Add prefab support
This commit is contained in:
+1
-13
@@ -23,19 +23,7 @@
|
||||
},
|
||||
{
|
||||
"name": "player",
|
||||
"components": [
|
||||
{ "type": "POSITION", "x": 0, "y": 5, "z": 0 },
|
||||
{
|
||||
"type": "RENDERABLE",
|
||||
"renderType": "SHADER_MATERIAL",
|
||||
"priority": 0,
|
||||
"shaderType": "UNLIT",
|
||||
"color": { "r": 255, "g": 80, "b": 80, "a": 255 },
|
||||
"displayState": { "cull": false, "depthTest": true, "blend": false }
|
||||
},
|
||||
{ "type": "PLAYER", "moveSpeed": 4, "jumpImpulse": 6 },
|
||||
{ "type": "PHYSICS", "bodyType": "DYNAMIC", "collideMask": 3 }
|
||||
]
|
||||
"extend": "PLAYER"
|
||||
},
|
||||
{
|
||||
"components": [
|
||||
|
||||
@@ -9,6 +9,7 @@ target_sources(${DUSK_LIBRARY_TARGET_NAME}
|
||||
entity.c
|
||||
entitymanager.c
|
||||
component.c
|
||||
entityprefab.c
|
||||
)
|
||||
|
||||
# Subdirs
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
|
||||
#include "entitymanager.h"
|
||||
#include "component/display/entityposition.h"
|
||||
#include "entityprefab.h"
|
||||
#include "util/memory.h"
|
||||
#include "util/string.h"
|
||||
#include "assert/assert.h"
|
||||
@@ -261,6 +262,20 @@ errorret_t entityDeserialize(
|
||||
yyjson_val *nameVal = yyjson_obj_get(json, "name");
|
||||
if(nameVal) entitySetName(mgr, entityId, yyjson_get_str(nameVal));
|
||||
|
||||
yyjson_val *extendVal = yyjson_obj_get(json, "extend");
|
||||
if(extendVal) {
|
||||
errorChain(entityPrefabResolveAndApply(
|
||||
mgr, entityId, yyjson_get_str(extendVal)
|
||||
));
|
||||
}
|
||||
|
||||
yyjson_val *prefabVal = yyjson_obj_get(json, "prefab");
|
||||
if(prefabVal) {
|
||||
errorChain(entityPrefabResolveAndApply(
|
||||
mgr, entityId, yyjson_get_str(prefabVal)
|
||||
));
|
||||
}
|
||||
|
||||
yyjson_val *components = yyjson_obj_get(json, "components");
|
||||
if(!components) errorOk();
|
||||
|
||||
|
||||
@@ -226,18 +226,32 @@ errorret_t entitySerialize(
|
||||
);
|
||||
|
||||
/**
|
||||
* Deserializes an entity's optional "name" and its components from the
|
||||
* given JSON object's "components" array. For each component entry,
|
||||
* adds a component of the type named by its "type" field (matched
|
||||
* against COMPONENT_DEFINITIONS[].enumName) and hands the entry to that
|
||||
* component type's deserialize callback (see componentDeserialize()).
|
||||
* No-op for "components" if json has no such array.
|
||||
* Deserializes an entity's optional "name", "extend"/"prefab", and its
|
||||
* components from the given JSON object's "components" array.
|
||||
*
|
||||
* If present, "extend" and then "prefab" are each resolved and applied
|
||||
* as a prefab (see entityPrefabResolveAndApply()) before "components" is
|
||||
* processed -- both keys do the same thing (resolve a named prefab,
|
||||
* C-coded first then a "prefabs/<name>.json" asset, and apply it to this
|
||||
* entity); "extend" is the conventional key when this JSON is itself a
|
||||
* prefab definition inheriting from a parent, "prefab" is the
|
||||
* conventional key when a plain entity wants to be defined by a prefab.
|
||||
* A prefab-added component still counts as already added, so listing the
|
||||
* same component type again in "components" throws (see
|
||||
* entityAddComponent()).
|
||||
*
|
||||
* For each "components" entry, adds a component of the type named by its
|
||||
* "type" field (matched against COMPONENT_DEFINITIONS[].enumName) and
|
||||
* hands the entry to that component type's deserialize callback (see
|
||||
* componentDeserialize()). No-op for "components" if json has no such
|
||||
* array.
|
||||
*
|
||||
* @param mgr The entity manager that owns the entity.
|
||||
* @param entityId The ID of the entity to deserialize into. Must not
|
||||
* already have any of the components named in json.
|
||||
* @param json The JSON object to read the entity's "name"/"components"
|
||||
* keys from.
|
||||
* already have any of the components named in json (including any a
|
||||
* resolved "extend"/"prefab" prefab already added).
|
||||
* @param json The JSON object to read the entity's "name"/"extend"/
|
||||
* "prefab"/"components" keys from.
|
||||
* @return Error state.
|
||||
*/
|
||||
errorret_t entityDeserialize(
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "entityprefab.h"
|
||||
#include "entity.h"
|
||||
#include "asset/asset.h"
|
||||
#include "util/string.h"
|
||||
#include "assert/assert.h"
|
||||
#include "yyjson.h"
|
||||
|
||||
// Pulls in every prefab's real header (entityprefablist.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 "entityprefablist.h"
|
||||
#undef X
|
||||
|
||||
entityprefab_t ENTITY_PREFABS[] = {
|
||||
#define X(enm, extendsName, applyFn) \
|
||||
{ .name = #enm, .extends = extendsName, .apply = applyFn },
|
||||
#include "entityprefablist.h"
|
||||
#undef X
|
||||
|
||||
// Sentinel: name[0] == '\0' marks the end for callers iterating without
|
||||
// a separate count.
|
||||
{ 0 }
|
||||
};
|
||||
|
||||
errorret_t entityPrefabInit(
|
||||
entitymanager_t *mgr,
|
||||
const entityid_t entityId,
|
||||
const entityprefab_t prefab
|
||||
) {
|
||||
assertNotNull(prefab.apply, "Prefab apply function cannot be null");
|
||||
if(prefab.extends[0] != '\0') {
|
||||
errorChain(entityPrefabResolveAndApply(mgr, entityId, prefab.extends));
|
||||
}
|
||||
errorChain(prefab.apply(mgr, entityId));
|
||||
errorOk();
|
||||
}
|
||||
|
||||
errorret_t entityPrefabResolveAndApply(
|
||||
entitymanager_t *mgr,
|
||||
const entityid_t entityId,
|
||||
const char_t *name
|
||||
) {
|
||||
for(size_t i = 0; ENTITY_PREFABS[i].name[0] != '\0'; i++) {
|
||||
if(!stringEquals(ENTITY_PREFABS[i].name, name)) continue;
|
||||
errorChain(entityPrefabInit(mgr, entityId, ENTITY_PREFABS[i]));
|
||||
errorOk();
|
||||
}
|
||||
|
||||
char_t assetPath[ASSET_FILE_NAME_MAX];
|
||||
stringFormat(assetPath, ASSET_FILE_NAME_MAX - 1, "prefabs/%s.json", name);
|
||||
|
||||
assetentry_t *entry = assetLock(assetPath, ASSET_LOADER_TYPE_JSON, NULL);
|
||||
errorret_t ret = assetRequireLoaded(entry);
|
||||
if(errorIsOk(ret)) {
|
||||
ret = entityDeserialize(
|
||||
mgr, entityId, yyjson_doc_get_root(entry->data.json)
|
||||
);
|
||||
}
|
||||
assetUnlockEntry(entry);
|
||||
errorChain(ret);
|
||||
|
||||
errorOk();
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "entitybase.h"
|
||||
#include "error/error.h"
|
||||
|
||||
#define ENTITY_PREFAB_NAME_MAX 32
|
||||
|
||||
/**
|
||||
* Applies a prefab to an entity, e.g. adding and configuring whatever
|
||||
* components make up the prefab.
|
||||
*
|
||||
* @param mgr The entity manager that owns the entity.
|
||||
* @param entityId The ID of the entity to apply the prefab to.
|
||||
* @return Error state.
|
||||
*/
|
||||
typedef errorret_t (*entityprefabapply_t)(
|
||||
entitymanager_t *mgr,
|
||||
const entityid_t entityId
|
||||
);
|
||||
|
||||
typedef struct {
|
||||
char_t name[ENTITY_PREFAB_NAME_MAX];
|
||||
char_t extends[ENTITY_PREFAB_NAME_MAX];
|
||||
entityprefabapply_t apply;
|
||||
} entityprefab_t;
|
||||
|
||||
/**
|
||||
* The C-coded prefab registry, built from entityprefablist.h. Terminated
|
||||
* by a sentinel entry whose name[0] is '\0' -- iterate with a
|
||||
* `for(...; ENTITY_PREFABS[i].name[0] != '\0'; ...)` style loop rather
|
||||
* than a fixed count.
|
||||
*/
|
||||
extern entityprefab_t ENTITY_PREFABS[];
|
||||
|
||||
/**
|
||||
* Initializes an entity 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 entityPrefabResolveAndApply()), so this
|
||||
* prefab's own apply function runs on top of/after it.
|
||||
*
|
||||
* @param mgr The entity manager that owns the entity.
|
||||
* @param entityId The ID of the entity to apply the prefab to.
|
||||
* @param prefab The prefab to apply.
|
||||
* @return Error state.
|
||||
*/
|
||||
errorret_t entityPrefabInit(
|
||||
entitymanager_t *mgr,
|
||||
const entityid_t entityId,
|
||||
const entityprefab_t prefab
|
||||
);
|
||||
|
||||
/**
|
||||
* Resolves a prefab by name and applies it to the given entity. First
|
||||
* searches the C-coded ENTITY_PREFABS[] registry (see
|
||||
* entityprefablist.h) for a matching name; if none match, falls back to
|
||||
* loading "prefabs/<name>.json" as a JSON asset and applying it the same
|
||||
* way an entity's own JSON definition is applied (see entityDeserialize()
|
||||
* and its "prefab"/"extend" keys).
|
||||
*
|
||||
* @param mgr The entity manager that owns the entity.
|
||||
* @param entityId The ID of the entity to apply the prefab to.
|
||||
* @param name The prefab name to resolve.
|
||||
* @return Error state.
|
||||
*/
|
||||
errorret_t entityPrefabResolveAndApply(
|
||||
entitymanager_t *mgr,
|
||||
const entityid_t entityId,
|
||||
const char_t *name
|
||||
);
|
||||
@@ -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 prefab to apply first, or "" for none)
|
||||
// Apply function
|
||||
|
||||
// Game-specific prefabs
|
||||
#include "entity/gameprefablist.h"
|
||||
@@ -7,7 +7,6 @@
|
||||
|
||||
#pragma once
|
||||
#include "error/error.h"
|
||||
#include "entity/entitybase.h"
|
||||
|
||||
/**
|
||||
* Initializes the game. Called once, after the engine and all of its
|
||||
@@ -31,24 +30,3 @@ errorret_t gameUpdate(void);
|
||||
* @return An error code indicating success or failure.
|
||||
*/
|
||||
errorret_t gameDispose(void);
|
||||
|
||||
/**
|
||||
* Test-only TRIGGER onEnter callback, subscribed by gameInit() to the
|
||||
* "testArea" entity in the test scene (see assets/scenes/test.json).
|
||||
* Prints a debug message identifying whichever entity just entered.
|
||||
*
|
||||
* @param mgr The entity manager that owns both entities.
|
||||
* @param triggerEntityId The entity ID of the "testArea" trigger volume.
|
||||
* @param triggerComponentId The TRIGGER component ID.
|
||||
* @param otherEntityId The entity ID that entered the trigger volume.
|
||||
* @param otherComponentId The other entity's POSITION component ID.
|
||||
* @param user Unused.
|
||||
*/
|
||||
void gameTestAreaOnEnter(
|
||||
entitymanager_t *mgr,
|
||||
const entityid_t triggerEntityId,
|
||||
const componentid_t triggerComponentId,
|
||||
const entityid_t otherEntityId,
|
||||
const componentid_t otherComponentId,
|
||||
void *user
|
||||
);
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
# This software is released under the MIT License.
|
||||
# https://opensource.org/licenses/MIT
|
||||
|
||||
# Header-only: gamecomponentlist.h is an X-macro list, no implementation.
|
||||
# gameprefablist.h is an X-macro list, no implementation of its own.
|
||||
|
||||
add_subdirectory(component)
|
||||
add_subdirectory(prefab)
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
// Game-specific prefabs, appended after the engine's inbuilt prefabs in
|
||||
// entity/entityprefablist.h.
|
||||
#include "entity/prefab/entityprefabplayer.h"
|
||||
|
||||
// Name (Uppercase)
|
||||
// Extends (name of another prefab to apply first, or "" for none)
|
||||
// Apply function
|
||||
|
||||
X(PLAYER, "", entityPrefabPlayerApply)
|
||||
@@ -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
|
||||
entityprefabplayer.c
|
||||
)
|
||||
@@ -0,0 +1,41 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "entityprefabplayer.h"
|
||||
#include "entity/entitymanager.h"
|
||||
#include "entity/component/display/entityposition.h"
|
||||
#include "entity/component/display/entityrenderable.h"
|
||||
#include "entity/component/physics/entityphysics.h"
|
||||
#include "display/color.h"
|
||||
|
||||
errorret_t entityPrefabPlayerApply(
|
||||
entitymanager_t *mgr,
|
||||
const entityid_t entityId
|
||||
) {
|
||||
componentid_t posComp = entityAddComponent(
|
||||
mgr, entityId, COMPONENT_TYPE_POSITION
|
||||
);
|
||||
vec3 startPos = { 0.0f, 5.0f, 0.0f };
|
||||
entityPositionSetLocalPosition(mgr, entityId, posComp, startPos);
|
||||
|
||||
componentid_t renderComp = entityAddComponent(
|
||||
mgr, entityId, COMPONENT_TYPE_RENDERABLE
|
||||
);
|
||||
entityRenderableSetColor(
|
||||
mgr, entityId, renderComp, color4b(255, 80, 80, 255)
|
||||
);
|
||||
|
||||
entityAddComponent(mgr, entityId, COMPONENT_TYPE_PLAYER);
|
||||
|
||||
componentid_t physComp = entityAddComponent(
|
||||
mgr, entityId, COMPONENT_TYPE_PHYSICS
|
||||
);
|
||||
entityPhysicsSetBodyType(mgr, entityId, physComp, PHYSICS_BODY_DYNAMIC);
|
||||
entityPhysicsSetCollideMask(mgr, entityId, physComp, 0x3);
|
||||
|
||||
errorOk();
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
/**
|
||||
* 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 PLAYER prefab: a POSITION 5 units up, a red
|
||||
* SHADER_MATERIAL RENDERABLE, a PLAYER component, and a DYNAMIC PHYSICS
|
||||
* body tagged with collideMask 0x3 (world + player layers). C-coded
|
||||
* equivalent of the hand-authored "player" entity 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 entityPrefabPlayerApply(
|
||||
entitymanager_t *mgr,
|
||||
const entityid_t entityId
|
||||
);
|
||||
@@ -34,19 +34,6 @@ errorret_t gameInit(void) {
|
||||
assetUnlockEntry(sceneEntry);
|
||||
errorChain(ret);
|
||||
|
||||
// Test-only: print a debug message whenever an entity enters "testArea"
|
||||
// (see assets/scenes/test.json), exercising the TRIGGER component.
|
||||
entitymanager_t *mgr = sceneGetEntities(testSceneId);
|
||||
entityid_t testAreaEntity = entityFindByName(mgr, "testArea");
|
||||
if(testAreaEntity != ENTITY_ID_INVALID) {
|
||||
componentid_t testAreaTrig = entityGetComponent(
|
||||
mgr, testAreaEntity, COMPONENT_TYPE_TRIGGER
|
||||
);
|
||||
entityTriggerOnEnterAdd(
|
||||
mgr, testAreaEntity, testAreaTrig, gameTestAreaOnEnter, NULL
|
||||
);
|
||||
}
|
||||
|
||||
sceneSetActive(testSceneId);
|
||||
errorOk();
|
||||
}
|
||||
@@ -58,14 +45,3 @@ errorret_t gameUpdate(void) {
|
||||
errorret_t gameDispose(void) {
|
||||
errorOk();
|
||||
}
|
||||
|
||||
void gameTestAreaOnEnter(
|
||||
entitymanager_t *mgr,
|
||||
const entityid_t triggerEntityId,
|
||||
const componentid_t triggerComponentId,
|
||||
const entityid_t otherEntityId,
|
||||
const componentid_t otherComponentId,
|
||||
void *user
|
||||
) {
|
||||
logDebug("testArea: entity %d entered", otherEntityId);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user