Scene prefabs

This commit is contained in:
2026-07-20 15:11:38 -05:00
parent 38c2f6b6f9
commit 7ceb9e571d
18 changed files with 349 additions and 52 deletions
+1 -38
View File
@@ -1,48 +1,11 @@
{
"extend": "OVERWORLDSCENE",
"entities": [
{
"name": "camera",
"components": [
{
"type": "POSITION",
"x": 10, "y": 10, "z": 10,
"lookAt": { "x": 0, "y": 0, "z": 0 }
},
{
"type": "CAMERA",
"projType": "PERSPECTIVE",
"nearClip": 0.1, "farClip": 1000,
"fov": 0.9
}
]
},
{
"components": [
{ "type": "POSITION", "x": 0, "y": 1, "z": 0, "parent": "camera" }
]
},
{
"name": "player",
"extend": "PLAYER"
},
{
"components": [
{
"type": "POSITION",
"x": 0, "y": 0, "z": 0.85,
"parent": "player",
"scale": { "x": 0.7, "y": 0.7, "z": 0.7 }
},
{
"type": "RENDERABLE",
"renderType": "SHADER_MATERIAL",
"priority": 0,
"shaderType": "UNLIT",
"color": { "r": 80, "g": 160, "b": 255, "a": 255 },
"displayState": { "cull": false, "depthTest": true, "blend": false }
}
]
},
{
"name": "testArea",
"components": [
+1
View File
@@ -6,4 +6,5 @@
target_sources(${DUSK_LIBRARY_TARGET_NAME}
PUBLIC
scene.c
sceneprefab.c
)
+11
View File
@@ -15,6 +15,7 @@
#include "entity/component/display/entityposition.h"
#include "entity/component/display/entityrenderable.h"
#include "physics/triggersystem.h"
#include "sceneprefab.h"
#include "ui/ui.h"
#include "console/console.h"
@@ -251,6 +252,16 @@ errorret_t sceneDeserialize(
entitymanager_t *mgr = &SCENE_MANAGER.scenes[id].entities;
yyjson_val *extendVal = yyjson_obj_get(json, "extend");
if(extendVal) {
errorChain(scenePrefabResolveAndApply(id, yyjson_get_str(extendVal)));
}
yyjson_val *prefabVal = yyjson_obj_get(json, "prefab");
if(prefabVal) {
errorChain(scenePrefabResolveAndApply(id, yyjson_get_str(prefabVal)));
}
yyjson_val *entities = yyjson_obj_get(json, "entities");
if(!entities) errorOk();
+20 -5
View File
@@ -141,14 +141,29 @@ errorret_t sceneSerialize(
);
/**
* Deserializes entities from the given JSON object's "entities" array into
* a scene, creating one new entity per array entry (see
* entityDeserialize()), then resolves every entity's POSITION "parent"
* Deserializes a scene's optional "extend"/"prefab" and its entities from
* the given JSON object.
*
* If present, "extend" and then "prefab" are each resolved and applied as
* a scene prefab (see scenePrefabResolveAndApply()) before "entities" is
* processed -- both keys do the same thing (resolve a named scene
* prefab, C-coded first then a "scenes/<name>.json" asset, and apply it
* to this scene); "extend" is the conventional key when this JSON is
* itself a scene prefab definition inheriting from a parent, "prefab" is
* the conventional key when a plain scene wants to be defined by a
* prefab. Since a scene prefab just spawns more entities, there's no
* collision risk the way there is for entity components -- this scene's
* own "entities" are simply added on top of whatever the prefab spawned.
*
* Then creates one new entity per "entities" array entry (see
* entityDeserialize()), and resolves every entity's POSITION "parent"
* name reference (see sceneDeserializeResolveParent()) now that all
* entities exist and are named. No-op if json has no "entities" array.
* entities exist and are named. No-op for "entities" if json has no such
* array.
*
* @param id The ID of the scene to deserialize into.
* @param json The JSON object to read the scene's "entities" key from.
* @param json The JSON object to read the scene's "extend"/"prefab"/
* "entities" keys from.
* @return Error state.
*/
errorret_t sceneDeserialize(
+68
View File
@@ -0,0 +1,68 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "sceneprefab.h"
#include "scene.h"
#include "asset/asset.h"
#include "util/string.h"
#include "assert/assert.h"
#include "yyjson.h"
// Pulls in every scene prefab's real header (sceneprefablist.h's one-time
// #include lines) exactly once -- each header's #pragma once then makes
// the array-building pass below a no-op for those lines, leaving only
// bare X(...) invocations to expand.
#define X(enumName, extends, apply) // do nothing
#include "sceneprefablist.h"
#undef X
sceneprefab_t SCENE_PREFABS[] = {
#define X(enm, extendsName, applyFn) \
{ .name = #enm, .extends = extendsName, .apply = applyFn },
#include "sceneprefablist.h"
#undef X
// Sentinel: name[0] == '\0' marks the end for callers iterating without
// a separate count.
{ 0 }
};
errorret_t scenePrefabInit(
const sceneid_t sceneId,
const sceneprefab_t prefab
) {
assertNotNull(prefab.apply, "Prefab apply function cannot be null");
if(prefab.extends[0] != '\0') {
errorChain(scenePrefabResolveAndApply(sceneId, prefab.extends));
}
errorChain(prefab.apply(sceneId));
errorOk();
}
errorret_t scenePrefabResolveAndApply(
const sceneid_t sceneId,
const char_t *name
) {
for(size_t i = 0; SCENE_PREFABS[i].name[0] != '\0'; i++) {
if(!stringEquals(SCENE_PREFABS[i].name, name)) continue;
errorChain(scenePrefabInit(sceneId, SCENE_PREFABS[i]));
errorOk();
}
char_t assetPath[ASSET_FILE_NAME_MAX];
stringFormat(assetPath, ASSET_FILE_NAME_MAX - 1, "scenes/%s.json", name);
assetentry_t *entry = assetLock(assetPath, ASSET_LOADER_TYPE_JSON, NULL);
errorret_t ret = assetRequireLoaded(entry);
if(errorIsOk(ret)) {
ret = sceneDeserialize(sceneId, yyjson_doc_get_root(entry->data.json));
}
assetUnlockEntry(entry);
errorChain(ret);
errorOk();
}
+67
View File
@@ -0,0 +1,67 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "scenebase.h"
#include "error/error.h"
#define SCENE_PREFAB_NAME_MAX 32
/**
* Applies a prefab to a scene, e.g. spawning and configuring whatever
* entities make up the scene.
*
* @param sceneId The ID of the scene to apply the prefab to.
* @return Error state.
*/
typedef errorret_t (*sceneprefabapply_t)(const sceneid_t sceneId);
typedef struct {
char_t name[SCENE_PREFAB_NAME_MAX];
char_t extends[SCENE_PREFAB_NAME_MAX];
sceneprefabapply_t apply;
} sceneprefab_t;
/**
* The C-coded scene prefab registry, built from sceneprefablist.h.
* Terminated by a sentinel entry whose name[0] is '\0' -- iterate with a
* `for(...; SCENE_PREFABS[i].name[0] != '\0'; ...)` style loop rather
* than a fixed count.
*/
extern sceneprefab_t SCENE_PREFABS[];
/**
* Initializes a scene from a prefab, running its apply function. If the
* prefab declares a parent (prefab.extends is non-empty), that parent is
* resolved and applied first (see scenePrefabResolveAndApply()), so this
* prefab's own apply function runs on top of/after it.
*
* @param sceneId The ID of the scene to apply the prefab to.
* @param prefab The prefab to apply.
* @return Error state.
*/
errorret_t scenePrefabInit(
const sceneid_t sceneId,
const sceneprefab_t prefab
);
/**
* Resolves a scene prefab by name and applies it to the given scene.
* First searches the C-coded SCENE_PREFABS[] registry (see
* sceneprefablist.h) for a matching name; if none match, falls back to
* loading "scenes/<name>.json" as a JSON asset and applying it the same
* way a scene's own JSON definition is applied (see sceneDeserialize()
* and its "prefab"/"extend" keys).
*
* @param sceneId The ID of the scene to apply the prefab to.
* @param name The scene prefab name to resolve.
* @return Error state.
*/
errorret_t scenePrefabResolveAndApply(
const sceneid_t sceneId,
const char_t *name
);
+13
View File
@@ -0,0 +1,13 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
// Name (Uppercase)
// Extends (name of another scene prefab to apply first, or "" for none)
// Apply function
// Game-specific scene prefabs
#include "scene/gameprefablist.h"
@@ -11,10 +11,13 @@
#include "entity/component/display/entitycamera.h"
#include "entity/component/display/entityposition.h"
#include "input/input.h"
#include "time/time.h"
#include "util/memory.h"
#include "util/math.h"
#define ENTITY_PLAYER_MOVE_SPEED_DEFAULT 4.0f
#define ENTITY_PLAYER_JUMP_IMPULSE_DEFAULT 6.0f
#define ENTITY_PLAYER_TURN_SPEED_DEFAULT 10.0f
// Below this squared magnitude, movement input is treated as "not moving"
// and the player keeps facing whichever way it last faced, rather than
@@ -32,6 +35,7 @@ void entityPlayerInit(
player->moveSpeed = ENTITY_PLAYER_MOVE_SPEED_DEFAULT;
player->jumpImpulse = ENTITY_PLAYER_JUMP_IMPULSE_DEFAULT;
player->turnSpeed = ENTITY_PLAYER_TURN_SPEED_DEFAULT;
entityUpdateAdd(mgr, entityId, entityPlayerUpdate, componentId, NULL);
}
@@ -109,7 +113,16 @@ void entityPlayerUpdate(
// faces the opposite way), which for a pure yaw rotation works out to
// (sin(yaw), 0, cos(yaw)) -- invert that to solve for the yaw facing
// moveDir.
rotation[1] = atan2f(moveDir[0], moveDir[1]);
float_t targetYaw = atan2f(moveDir[0], moveDir[1]);
// Turn towards targetYaw at turnSpeed rad/s instead of snapping to it:
// wrap the shortest signed delta into [-PI, PI), then clamp the step.
float_t yawDelta = mathModFloat(
targetYaw - rotation[1] + (float_t)MATH_PI, 2.0f * (float_t)MATH_PI
) - (float_t)MATH_PI;
float_t maxStep = player->turnSpeed * TIME.delta;
rotation[1] += mathClamp(yawDelta, -maxStep, maxStep);
entityPositionSetLocalRotation(mgr, entityId, posComp, rotation);
}
@@ -124,6 +137,7 @@ errorret_t entityPlayerSerialize(
yyjson_mut_obj_add_real(doc, json, "moveSpeed", player->moveSpeed);
yyjson_mut_obj_add_real(doc, json, "jumpImpulse", player->jumpImpulse);
yyjson_mut_obj_add_real(doc, json, "turnSpeed", player->turnSpeed);
errorOk();
}
@@ -143,6 +157,9 @@ errorret_t entityPlayerDeserialize(
if((v = yyjson_obj_get(json, "jumpImpulse"))) {
player->jumpImpulse = (float_t)yyjson_get_num(v);
}
if((v = yyjson_obj_get(json, "turnSpeed"))) {
player->turnSpeed = (float_t)yyjson_get_num(v);
}
errorOk();
}
@@ -13,11 +13,14 @@
typedef struct {
float_t moveSpeed;
float_t jumpImpulse;
/** Yaw turn rate, in radians/second, when facing the movement direction. */
float_t turnSpeed;
} entityplayer_t;
/**
* Initializes the player component: sets default move speed and jump
* impulse.
* Initializes the player component: sets default move speed, jump
* impulse, and turn speed.
*
* @param mgr The entity manager that owns the entity.
* @param entityId The entity ID.
@@ -53,7 +56,8 @@ entityplayer_t *entityPlayerGet(
* axes. No-op if the entity has no physics component.
*
* Also yaws the entity's position component (if any) to face the
* direction of movement. Facing is only updated while there's actual
* direction of movement, turning at most turnSpeed radians/second rather
* than snapping instantly. Facing is only updated while there's actual
* movement input -- releasing input leaves the entity facing whichever
* way it last moved, rather than snapping to a default orientation.
*
@@ -72,8 +76,8 @@ void entityPlayerUpdate(
);
/**
* Serializes the player's "moveSpeed" and "jumpImpulse" into the given
* JSON object.
* Serializes the player's "moveSpeed", "jumpImpulse", and "turnSpeed"
* into the given JSON object.
*
* @param mgr The entity manager that owns the entity.
* @param entityId The entity ID.
@@ -91,9 +95,9 @@ errorret_t entityPlayerSerialize(
);
/**
* Reads "moveSpeed" and "jumpImpulse" (see entityPlayerSerialize()) from
* the given JSON object and applies whichever are present, leaving the
* rest at their current values.
* Reads "moveSpeed", "jumpImpulse", and "turnSpeed" (see
* entityPlayerSerialize()) from the given JSON object and applies
* whichever are present, leaving the rest at their current values.
*
* @param mgr The entity manager that owns the entity.
* @param entityId The entity ID.
+2
View File
@@ -8,9 +8,11 @@
// Game-specific prefabs, appended after the engine's inbuilt prefabs in
// entity/entityprefablist.h.
#include "entity/prefab/entityprefabplayer.h"
#include "entity/prefab/entityprefaboverworldcamera.h"
// Name (Uppercase)
// Extends (name of another prefab to apply first, or "" for none)
// Apply function
X(PLAYER, "", entityPrefabPlayerApply)
X(OVERWORLD_CAMERA, "", entityPrefabOverworldCameraApply)
+1
View File
@@ -6,4 +6,5 @@
target_sources(${DUSK_LIBRARY_TARGET_NAME}
PUBLIC
entityprefabplayer.c
entityprefaboverworldcamera.c
)
@@ -0,0 +1,37 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "entityprefaboverworldcamera.h"
#include "entity/entitymanager.h"
#include "entity/component/display/entityposition.h"
#include "entity/component/display/entitycamera.h"
errorret_t entityPrefabOverworldCameraApply(
entitymanager_t *mgr,
const entityid_t entityId
) {
componentid_t posComp = entityAddComponent(
mgr, entityId, COMPONENT_TYPE_POSITION
);
vec3 eye = { 10.0f, 10.0f, 10.0f };
vec3 target = { 0.0f, 0.0f, 0.0f };
vec3 up = { 0.0f, 1.0f, 0.0f };
entityPositionLookAt(mgr, entityId, posComp, eye, target, up);
componentid_t camComp = entityAddComponent(
mgr, entityId, COMPONENT_TYPE_CAMERA
);
entitycamera_t *cam = componentGetData(
mgr, entityId, camComp, COMPONENT_TYPE_CAMERA
);
cam->projType = ENTITY_CAMERA_PROJECTION_TYPE_PERSPECTIVE;
cam->nearClip = 0.1f;
cam->farClip = 1000.0f;
cam->perspective.fov = 0.9f;
errorOk();
}
@@ -0,0 +1,25 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "entity/entitybase.h"
#include "error/error.h"
/**
* Applies the OVERWORLD_CAMERA prefab: a POSITION at (10, 10, 10) looking
* at the world origin, and a perspective CAMERA (0.1/1000 near/far clip,
* 0.9 fov). C-coded equivalent of the hand-authored "camera" entity
* previously in assets/scenes/test.json.
*
* @param mgr The entity manager that owns the entity.
* @param entityId The ID of the entity to apply the prefab to.
* @return Error state.
*/
errorret_t entityPrefabOverworldCameraApply(
entitymanager_t *mgr,
const entityid_t entityId
);
+2
View File
@@ -7,3 +7,5 @@ target_sources(${DUSK_LIBRARY_TARGET_NAME}
PUBLIC
overworldscene.c
)
add_subdirectory(prefab)
+16
View File
@@ -0,0 +1,16 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
// Game-specific scene prefabs, appended after the engine's inbuilt scene
// prefabs in scene/sceneprefablist.h.
#include "scene/prefab/sceneprefaboverworldscene.h"
// Name (Uppercase)
// Extends (name of another prefab to apply first, or "" for none)
// Apply function
X(OVERWORLDSCENE, "", scenePrefabOverworldSceneApply)
+9
View File
@@ -0,0 +1,9 @@
# Copyright (c) 2026 Dominic Masters
#
# This software is released under the MIT License.
# https://opensource.org/licenses/MIT
target_sources(${DUSK_LIBRARY_TARGET_NAME}
PUBLIC
sceneprefaboverworldscene.c
)
@@ -0,0 +1,26 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "sceneprefaboverworldscene.h"
#include "scene/scene.h"
#include "entity/entityprefab.h"
errorret_t scenePrefabOverworldSceneApply(const sceneid_t sceneId) {
entitymanager_t *mgr = sceneGetEntities(sceneId);
entityid_t cameraEntity = entityManagerAdd(mgr);
entitySetName(mgr, cameraEntity, "camera");
errorChain(
entityPrefabResolveAndApply(mgr, cameraEntity, "OVERWORLD_CAMERA")
);
entityid_t playerEntity = entityManagerAdd(mgr);
entitySetName(mgr, playerEntity, "player");
errorChain(entityPrefabResolveAndApply(mgr, playerEntity, "PLAYER"));
errorOk();
}
@@ -0,0 +1,20 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "scene/scenebase.h"
#include "error/error.h"
/**
* Applies the OVERWORLDSCENE prefab: spawns a "camera" entity (see
* entityPrefabOverworldCameraApply()) and a "player" entity (see
* entityPrefabPlayerApply()) into the scene.
*
* @param sceneId The ID of the scene to apply the prefab to.
* @return Error state.
*/
errorret_t scenePrefabOverworldSceneApply(const sceneid_t sceneId);