Entity refactoring

This commit is contained in:
2026-05-22 23:01:45 -05:00
parent 130fe4ca5d
commit 382c435bac
28 changed files with 828 additions and 93 deletions
+1 -5
View File
@@ -72,14 +72,10 @@ errorret_t engineUpdate(void) {
errorChain(uiTextboxUpdate());
physicsManagerUpdate();
errorChain(displayUpdate());
if(inputPressed(INPUT_ACTION_RAGEQUIT)) ENGINE.running = false;
// Scene update occurs last because only after rendering would we want to do
// scene switching, refer to sceneSet() for information.
errorChain(cutsceneUpdate());
errorChain(sceneUpdate());
if(inputPressed(INPUT_ACTION_RAGEQUIT)) ENGINE.running = false;
errorOk();
}
+1
View File
@@ -4,6 +4,7 @@
# https://opensource.org/licenses/MIT
add_subdirectory(display)
add_subdirectory(overworld)
add_subdirectory(physics)
add_subdirectory(script)
add_subdirectory(trigger)
@@ -11,7 +11,14 @@
#include "display/mesh/cube.h"
#include "display/spritebatch/spritebatch.h"
errorret_t entityRenderableDrawDefault() {
errorret_t entityRenderableDrawDefault(
const entityid_t entityId,
const componentid_t componentId,
void *user
) {
(void)entityId;
(void)componentId;
(void)user;
errorChain(shaderSetColor(&SHADER_UNLIT, SHADER_UNLIT_COLOR, COLOR_WHITE));
errorChain(shaderSetTexture(&SHADER_UNLIT, SHADER_UNLIT_TEXTURE, NULL));
return meshDraw(&CUBE_MESH_SIMPLE, 0, -1);
@@ -25,6 +32,7 @@ void entityRenderableInit(
entityId, componentId, COMPONENT_TYPE_RENDERABLE
);
r->draw = entityRenderableDrawDefault;
r->drawUser = NULL;
}
void entityRenderableDispose(
@@ -37,12 +45,18 @@ void entityRenderableDispose(
void entityRenderableSetDraw(
const entityid_t entityId,
const componentid_t componentId,
errorret_t (*draw)(void)
errorret_t (*draw)(
const entityid_t entityId,
const componentid_t componentId,
void *user
),
void *user
) {
entityrenderable_t *r = componentGetData(
entityId, componentId, COMPONENT_TYPE_RENDERABLE
);
r->draw = draw;
r->drawUser = user;
}
errorret_t entityRenderableDraw(
@@ -52,5 +66,5 @@ errorret_t entityRenderableDraw(
entityrenderable_t *r = componentGetData(
entityId, componentId, COMPONENT_TYPE_RENDERABLE
);
return r->draw();
}
return r->draw(entityId, componentId, r->drawUser);
}
@@ -12,7 +12,12 @@
#include "display/spritebatch/spritebatch.h"
typedef struct {
errorret_t (*draw)(void);
errorret_t (*draw)(
const entityid_t entityId,
const componentid_t componentId,
void *user
);
void *drawUser;
} entityrenderable_t;
/**
@@ -48,7 +53,12 @@ void entityRenderableDispose(
void entityRenderableSetDraw(
const entityid_t entityId,
const componentid_t componentId,
errorret_t (*draw)(void)
errorret_t (*draw)(
const entityid_t entityId,
const componentid_t componentId,
void *user
),
void *user
);
/**
@@ -0,0 +1,12 @@
# 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
entityinteractable.c
entityoverworld.c
entityoverworldcamera.c
entityplayer.c
)
@@ -0,0 +1,49 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "entityinteractable.h"
#include "entity/entitymanager.h"
void entityInteractableInit(
const entityid_t entityId,
const componentid_t componentId
) {
entityinteractable_t *inter = entityInteractableGet(entityId, componentId);
inter->onInteract = NULL;
inter->user = NULL;
}
entityinteractable_t * entityInteractableGet(
const entityid_t entityId,
const componentid_t componentId
) {
return componentGetData(entityId, componentId, COMPONENT_TYPE_INTERACTABLE);
}
void entityInteractableSetCallback(
const entityid_t entityId,
const componentid_t componentId,
void (*onInteract)(
const entityid_t entityId,
const componentid_t componentId,
void *user
),
void *user
) {
entityinteractable_t *inter = entityInteractableGet(entityId, componentId);
inter->onInteract = onInteract;
inter->user = user;
}
void entityInteractableTrigger(
const entityid_t entityId,
const componentid_t componentId
) {
entityinteractable_t *inter = entityInteractableGet(entityId, componentId);
if(inter->onInteract == NULL) return;
inter->onInteract(entityId, componentId, inter->user);
}
@@ -0,0 +1,71 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "entity/entitybase.h"
typedef struct {
void (*onInteract)(
const entityid_t entityId,
const componentid_t componentId,
void *user
);
void *user;
} entityinteractable_t;
/**
* Initializes the interactable component, clearing the callback and user pointer.
*
* @param entityId The owning entity.
* @param componentId This component's ID.
*/
void entityInteractableInit(
const entityid_t entityId,
const componentid_t componentId
);
/**
* Returns a pointer to the interactable component data.
*
* @param entityId The owning entity.
* @param componentId This component's ID.
* @return Pointer to the entityinteractable_t data.
*/
entityinteractable_t * entityInteractableGet(
const entityid_t entityId,
const componentid_t componentId
);
/**
* Sets the callback invoked when this interactable is triggered.
*
* @param entityId The owning entity.
* @param componentId This component's ID.
* @param onInteract Function called on interaction, or NULL to clear.
* @param user Arbitrary pointer forwarded to the callback.
*/
void entityInteractableSetCallback(
const entityid_t entityId,
const componentid_t componentId,
void (*onInteract)(
const entityid_t entityId,
const componentid_t componentId,
void *user
),
void *user
);
/**
* Fires the interactable's callback if one is set.
*
* @param entityId The owning entity.
* @param componentId This component's ID.
*/
void entityInteractableTrigger(
const entityid_t entityId,
const componentid_t componentId
);
@@ -0,0 +1,57 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "entityoverworld.h"
#include "entity/entitymanager.h"
#include "entity/component/display/entityrenderable.h"
#include "display/shader/shaderunlit.h"
#include "display/mesh/cube.h"
void entityOverworldInit(
const entityid_t entityId,
const componentid_t componentId
) {
entityoverworld_t *ow = entityOverworldGet(entityId, componentId);
ow->type = OVERWORLD_ENTITY_TYPE_NPC;
ow->facing = FACING_DIR_DOWN;
ow->renderCompId = entityGetComponent(entityId, COMPONENT_TYPE_RENDERABLE);
if(ow->renderCompId != COMPONENT_ID_INVALID) {
entityRenderableSetDraw(entityId, ow->renderCompId, entityOverworldDraw, NULL);
}
ow->physCompId = entityGetComponent(entityId, COMPONENT_TYPE_PHYSICS);
}
entityoverworld_t * entityOverworldGet(
const entityid_t entityId,
const componentid_t componentId
) {
return componentGetData(entityId, componentId, COMPONENT_TYPE_OVERWORLD);
}
void entityOverworldSetType(
const entityid_t entityId,
const componentid_t componentId,
const entityoverworldtype_t type
) {
entityOverworldGet(entityId, componentId)->type = type;
}
errorret_t entityOverworldDraw(
const entityid_t entityId,
const componentid_t componentId,
void *user
) {
(void)componentId; (void)user;
componentid_t owCompId = entityGetComponent(entityId, COMPONENT_TYPE_OVERWORLD);
entityoverworld_t *ow = entityOverworldGet(entityId, owCompId);
color_t col = ow->type == OVERWORLD_ENTITY_TYPE_PLAYER ? COLOR_WHITE : COLOR_BLUE;
errorChain(shaderSetColor(&SHADER_UNLIT, SHADER_UNLIT_COLOR, col));
errorChain(shaderSetTexture(&SHADER_UNLIT, SHADER_UNLIT_TEXTURE, NULL));
return meshDraw(&CUBE_MESH_SIMPLE, 0, -1);
}
@@ -0,0 +1,74 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "error/error.h"
#include "entity/entitybase.h"
#include "overworld/facingdir.h"
typedef enum {
OVERWORLD_ENTITY_TYPE_PLAYER = 0,
OVERWORLD_ENTITY_TYPE_NPC = 1,
} entityoverworldtype_t;
typedef struct {
entityoverworldtype_t type;
facingdir_t facing;
componentid_t renderCompId;
componentid_t physCompId;
} entityoverworld_t;
/**
* Initializes the overworld component, wiring up the draw callback if a
* renderable component is already present on the entity.
*
* @param entityId The owning entity.
* @param componentId This component's ID.
*/
void entityOverworldInit(
const entityid_t entityId,
const componentid_t componentId
);
/**
* Returns a pointer to the overworld component data.
*
* @param entityId The owning entity.
* @param componentId This component's ID.
* @return Pointer to the entityoverworld_t data.
*/
entityoverworld_t * entityOverworldGet(
const entityid_t entityId,
const componentid_t componentId
);
/**
* Sets the overworld entity type.
*
* @param entityId The owning entity.
* @param componentId This component's ID.
* @param type The type to assign.
*/
void entityOverworldSetType(
const entityid_t entityId,
const componentid_t componentId,
const entityoverworldtype_t type
);
/**
* Draw callback registered on the renderable component.
*
* @param entityId The owning entity.
* @param componentId The renderable component's ID.
* @param user Unused.
* @return Error result.
*/
errorret_t entityOverworldDraw(
const entityid_t entityId,
const componentid_t componentId,
void *user
);
@@ -0,0 +1,65 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "entityoverworldcamera.h"
#include "entity/entitymanager.h"
#include "entity/component/display/entityposition.h"
#include "entity/component/display/entitycamera.h"
void entityOverworldCameraInit(
const entityid_t entityId,
const componentid_t componentId
) {
entityoverworldcamera_t *cam = entityOverworldCameraGet(entityId, componentId);
cam->targetEntityId = ENTITY_ID_INVALID;
cam->targetPosCompId = COMPONENT_ID_INVALID;
glm_vec3_zero(cam->targetOffset);
glm_vec3_zero(cam->eyeOffset);
cam->scale = 1.0f;
}
entityoverworldcamera_t * entityOverworldCameraGet(
const entityid_t entityId,
const componentid_t componentId
) {
return componentGetData(entityId, componentId, COMPONENT_TYPE_OVERWORLD_CAMERA);
}
void entityOverworldCameraSetTarget(
const entityid_t entityId,
const componentid_t componentId,
const entityid_t targetEntityId,
const componentid_t targetPosCompId
) {
entityoverworldcamera_t *cam = entityOverworldCameraGet(entityId, componentId);
cam->targetEntityId = targetEntityId;
cam->targetPosCompId = targetPosCompId;
}
void entityOverworldCameraUpdate(
const entityid_t entityId,
const componentid_t componentId
) {
entityoverworldcamera_t *cam = entityOverworldCameraGet(entityId, componentId);
vec3 targetPos;
entityPositionGetWorldPosition(
cam->targetEntityId, cam->targetPosCompId, targetPos
);
vec3 center = {
targetPos[0] + cam->targetOffset[0],
targetPos[1] + cam->targetOffset[1],
targetPos[2] + cam->targetOffset[2]
};
componentid_t posComp = entityGetComponent(entityId, COMPONENT_TYPE_POSITION);
componentid_t camComp = entityGetComponent(entityId, COMPONENT_TYPE_CAMERA);
entityCameraLookAtPixelPerfect(
entityId, posComp, camComp, center, cam->eyeOffset, cam->scale
);
}
@@ -0,0 +1,66 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "entity/entitybase.h"
typedef struct {
entityid_t targetEntityId;
componentid_t targetPosCompId;
vec3 targetOffset;
vec3 eyeOffset;
float_t scale;
} entityoverworldcamera_t;
/**
* Initializes the overworld camera component.
*
* @param entityId The owning entity.
* @param componentId This component's ID.
*/
void entityOverworldCameraInit(
const entityid_t entityId,
const componentid_t componentId
);
/**
* Returns a pointer to the overworld camera component data.
*
* @param entityId The owning entity.
* @param componentId This component's ID.
* @return Pointer to the entityoverworldcamera_t data.
*/
entityoverworldcamera_t * entityOverworldCameraGet(
const entityid_t entityId,
const componentid_t componentId
);
/**
* Sets the entity and position component the camera will follow.
*
* @param entityId The owning entity.
* @param componentId This component's ID.
* @param targetEntityId Entity to follow.
* @param targetPosCompId Position component on the target entity.
*/
void entityOverworldCameraSetTarget(
const entityid_t entityId,
const componentid_t componentId,
const entityid_t targetEntityId,
const componentid_t targetPosCompId
);
/**
* Updates the camera position to track the target entity.
*
* @param entityId The owning entity.
* @param componentId This component's ID.
*/
void entityOverworldCameraUpdate(
const entityid_t entityId,
const componentid_t componentId
);
@@ -0,0 +1,103 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "entityplayer.h"
#include "entity/entitymanager.h"
#include "entity/component/display/entityposition.h"
#include "entity/component/physics/entityphysics.h"
#include "entity/component/overworld/entityoverworld.h"
#include "entity/component/overworld/entityinteractable.h"
#include "input/input.h"
void entityPlayerInit(
const entityid_t entityId,
const componentid_t componentId
) {
entityplayer_t *player = entityPlayerGet(entityId, componentId);
player->speed = ENTITY_PLAYER_SPEED;
player->runSpeed = ENTITY_PLAYER_RUN_SPEED;
}
entityplayer_t * entityPlayerGet(
const entityid_t entityId,
const componentid_t componentId
) {
return componentGetData(entityId, componentId, COMPONENT_TYPE_PLAYER);
}
void entityPlayerUpdate(
const entityid_t entityId,
const componentid_t componentId
) {
entityplayer_t *player = entityPlayerGet(entityId, componentId);
vec2 dir;
inputAngle2D(
INPUT_ACTION_LEFT, INPUT_ACTION_RIGHT,
INPUT_ACTION_UP, INPUT_ACTION_DOWN,
dir
);
float_t speed = (
inputIsDown(INPUT_ACTION_CANCEL) ? player->runSpeed : player->speed
);
componentid_t owCompId = entityGetComponent(entityId, COMPONENT_TYPE_OVERWORLD);
entityoverworld_t *ow = entityOverworldGet(entityId, owCompId);
if(ow && glm_vec2_norm(dir) > 0.0f) {
if(fabsf(dir[0]) >= fabsf(dir[1])) {
ow->facing = dir[0] > 0.0f ? FACING_DIR_RIGHT : FACING_DIR_LEFT;
} else {
ow->facing = dir[1] > 0.0f ? FACING_DIR_DOWN : FACING_DIR_UP;
}
}
vec3 vel;
entityPhysicsGetVelocity(entityId, ow->physCompId, vel);
vel[0] = dir[0] * speed;
vel[2] = dir[1] * speed;
entityPhysicsSetVelocity(entityId, ow->physCompId, vel);
if(!inputPressed(INPUT_ACTION_ACCEPT)) return;
vec3 playerPos;
componentid_t playerPosCompId = entityGetComponent(entityId, COMPONENT_TYPE_POSITION);
if(playerPosCompId == COMPONENT_ID_INVALID) return;
entityPositionGetWorldPosition(entityId, playerPosCompId, playerPos);
vec2 facingDir;
facingDirToVec2(ow ? ow->facing : FACING_DIR_DOWN, facingDir);
for(entityid_t i = 0; i < ENTITY_COUNT_MAX; i++) {
if((ENTITY_MANAGER.entities[i].state & ENTITY_STATE_ACTIVE) == 0) continue;
if(i == entityId) continue;
componentid_t interComp = entityGetComponent(i, COMPONENT_TYPE_INTERACTABLE);
if(interComp == COMPONENT_ID_INVALID) continue;
componentid_t posComp = entityGetComponent(i, COMPONENT_TYPE_POSITION);
if(posComp == COMPONENT_ID_INVALID) continue;
vec3 targetPos;
entityPositionGetWorldPosition(i, posComp, targetPos);
vec2 toTarget = {
targetPos[0] - playerPos[0],
targetPos[2] - playerPos[2],
};
float_t forward = glm_vec2_dot(facingDir, toTarget);
if(forward <= 0.0f || forward > ENTITY_PLAYER_INTERACT_RANGE) continue;
float_t lateral = fabsf(
facingDir[0] * toTarget[1] - facingDir[1] * toTarget[0]
);
if(lateral > ENTITY_PLAYER_INTERACT_LATERAL) continue;
entityInteractableTrigger(i, interComp);
}
}
@@ -0,0 +1,52 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "error/error.h"
#include "entity/entitybase.h"
#define ENTITY_PLAYER_SPEED 4.0f
#define ENTITY_PLAYER_RUN_SPEED 8.0f
#define ENTITY_PLAYER_INTERACT_RANGE 1.5f
#define ENTITY_PLAYER_INTERACT_LATERAL 0.6f
typedef struct {
float_t speed;
float_t runSpeed;
} entityplayer_t;
/**
* Initializes the player component.
*
* @param entityId The owning entity.
* @param componentId This component's ID.
*/
void entityPlayerInit(const entityid_t entityId, const componentid_t componentId);
/**
* Returns a pointer to the player component data.
*
* @param entityId The owning entity.
* @param componentId This component's ID.
* @return Pointer to the entityplayer_t data.
*/
entityplayer_t * entityPlayerGet(
const entityid_t entityId,
const componentid_t componentId
);
/**
* Reads input, moves the player, updates facing direction, and checks for
* interactable entities in front of the player when accept is pressed.
*
* @param entityId The owning entity.
* @param componentId This component's ID.
*/
void entityPlayerUpdate(
const entityid_t entityId,
const componentid_t componentId
);
+8
View File
@@ -10,6 +10,10 @@
#include "entity/component/display/entityrenderable.h"
#include "entity/component/physics/entityphysics.h"
#include "entity/component/trigger/entitytrigger.h"
#include "entity/component/overworld/entityoverworld.h"
#include "entity/component/overworld/entityplayer.h"
#include "entity/component/overworld/entityinteractable.h"
#include "entity/component/overworld/entityoverworldcamera.h"
// Name (Uppercase)
// Structure
@@ -22,3 +26,7 @@ X(CAMERA, entitycamera_t, camera, entityCameraInit, NULL)
X(RENDERABLE, entityrenderable_t, renderable, entityRenderableInit, entityRenderableDispose)
X(PHYSICS, entityphysics_t, physics, entityPhysicsInit, entityPhysicsDispose)
X(TRIGGER, entitytrigger_t, trigger, entityTriggerInit, NULL)
X(OVERWORLD, entityoverworld_t, overworld, entityOverworldInit, NULL)
X(PLAYER, entityplayer_t, player, entityPlayerInit, NULL)
X(INTERACTABLE, entityinteractable_t, interactable, entityInteractableInit, NULL)
X(OVERWORLD_CAMERA, entityoverworldcamera_t, overworldCamera, entityOverworldCameraInit, NULL)
+28 -8
View File
@@ -83,7 +83,7 @@ void entityDisposeDeep(const entityid_t entityId) {
void entityUpdate(const entityid_t entityId) {
entity_t *ent = &ENTITY_MANAGER.entities[entityId];
for(uint8_t i = 0; i < ent->updateCount; i++) {
ent->onUpdate[i](entityId);
ent->onUpdate[i](entityId, ent->updateComponentId[i], ent->updateUser[i]);
}
}
@@ -92,7 +92,7 @@ void entityDispose(const entityid_t entityId) {
entity_t *ent = &ENTITY_MANAGER.entities[entityId];
for(uint8_t i = 0; i < ent->disposeCount; i++) {
ent->onDispose[i](entityId);
ent->onDispose[i](entityId, ent->disposeComponentId[i], ent->disposeUser[i]);
}
for(componentid_t i = 0; i < ENTITY_COMPONENT_COUNT_MAX; i++) {
@@ -108,13 +108,21 @@ void entityDispose(const entityid_t entityId) {
ent->state = 0;
}
void entityUpdateAdd(const entityid_t entityId, const entitycallback_t callback) {
void entityUpdateAdd(
const entityid_t entityId,
const entitycallback_t callback,
const componentid_t componentId,
void *user
) {
entity_t *ent = &ENTITY_MANAGER.entities[entityId];
assertTrue(
ent->updateCount < ENTITY_UPDATE_CALLBACK_COUNT_MAX,
"Entity update callback slots full"
);
ent->onUpdate[ent->updateCount++] = callback;
ent->onUpdate[ent->updateCount] = callback;
ent->updateComponentId[ent->updateCount] = componentId;
ent->updateUser[ent->updateCount] = user;
ent->updateCount++;
}
void entityUpdateRemove(const entityid_t entityId, const entitycallback_t callback) {
@@ -123,19 +131,29 @@ void entityUpdateRemove(const entityid_t entityId, const entitycallback_t callba
if(ent->onUpdate[i] != callback) continue;
ent->updateCount--;
for(uint8_t j = i; j < ent->updateCount; j++) {
ent->onUpdate[j] = ent->onUpdate[j + 1];
ent->onUpdate[j] = ent->onUpdate[j + 1];
ent->updateComponentId[j] = ent->updateComponentId[j + 1];
ent->updateUser[j] = ent->updateUser[j + 1];
}
return;
}
}
void entityDisposeAdd(const entityid_t entityId, const entitycallback_t callback) {
void entityDisposeAdd(
const entityid_t entityId,
const entitycallback_t callback,
const componentid_t componentId,
void *user
) {
entity_t *ent = &ENTITY_MANAGER.entities[entityId];
assertTrue(
ent->disposeCount < ENTITY_DISPOSE_CALLBACK_COUNT_MAX,
"Entity dispose callback slots full"
);
ent->onDispose[ent->disposeCount++] = callback;
ent->onDispose[ent->disposeCount] = callback;
ent->disposeComponentId[ent->disposeCount] = componentId;
ent->disposeUser[ent->disposeCount] = user;
ent->disposeCount++;
}
void entityDisposeRemove(const entityid_t entityId, const entitycallback_t callback) {
@@ -144,7 +162,9 @@ void entityDisposeRemove(const entityid_t entityId, const entitycallback_t callb
if(ent->onDispose[i] != callback) continue;
ent->disposeCount--;
for(uint8_t j = i; j < ent->disposeCount; j++) {
ent->onDispose[j] = ent->onDispose[j + 1];
ent->onDispose[j] = ent->onDispose[j + 1];
ent->disposeComponentId[j] = ent->disposeComponentId[j + 1];
ent->disposeUser[j] = ent->disposeUser[j + 1];
}
return;
}
+21 -3
View File
@@ -13,14 +13,22 @@
#define ENTITY_UPDATE_CALLBACK_COUNT_MAX 5
#define ENTITY_DISPOSE_CALLBACK_COUNT_MAX 5
typedef void (*entitycallback_t)(const entityid_t entityId);
typedef void (*entitycallback_t)(
const entityid_t entityId,
const componentid_t componentId,
void *user
);
typedef struct {
uint8_t state;
uint8_t updateCount;
uint8_t disposeCount;
entitycallback_t onUpdate[ENTITY_UPDATE_CALLBACK_COUNT_MAX];
componentid_t updateComponentId[ENTITY_UPDATE_CALLBACK_COUNT_MAX];
void *updateUser[ENTITY_UPDATE_CALLBACK_COUNT_MAX];
entitycallback_t onDispose[ENTITY_DISPOSE_CALLBACK_COUNT_MAX];
componentid_t disposeComponentId[ENTITY_DISPOSE_CALLBACK_COUNT_MAX];
void *disposeUser[ENTITY_DISPOSE_CALLBACK_COUNT_MAX];
} entity_t;
/**
@@ -85,7 +93,12 @@ void entityDisposeDeep(const entityid_t entityId);
* @param entityId The entity to register on.
* @param callback The function to call.
*/
void entityUpdateAdd(const entityid_t entityId, const entitycallback_t callback);
void entityUpdateAdd(
const entityid_t entityId,
const entitycallback_t callback,
const componentid_t componentId,
void *user
);
/**
* Removes a previously registered update callback.
@@ -102,7 +115,12 @@ void entityUpdateRemove(const entityid_t entityId, const entitycallback_t callba
* @param entityId The entity to register on.
* @param callback The function to call.
*/
void entityDisposeAdd(const entityid_t entityId, const entitycallback_t callback);
void entityDisposeAdd(
const entityid_t entityId,
const entitycallback_t callback,
const componentid_t componentId,
void *user
);
/**
* Removes a previously registered dispose callback.
+4 -4
View File
@@ -8,11 +8,11 @@
#pragma once
#include "dusk.h"
#define ENTITY_COUNT_MAX 100
#define ENTITY_COMPONENT_COUNT_MAX 24
#define ENTITY_COUNT_MAX 64
#define ENTITY_COMPONENT_COUNT_MAX 16
#define ENTITY_ID_INVALID 0xFF
#define COMPONENT_ID_INVALID 0xFF
#define ENTITY_ID_INVALID 0xFF
#define COMPONENT_ID_INVALID 0xFF
typedef uint8_t entityid_t;
typedef uint8_t componentid_t;
+1
View File
@@ -5,6 +5,7 @@
target_sources(${DUSK_LIBRARY_TARGET_NAME}
PUBLIC
facingdir.c
maptypes.c
map.c
mapchunk.c
+17
View File
@@ -0,0 +1,17 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "facingdir.h"
void facingDirToVec2(facingdir_t facing, vec2 dest) {
switch(facing) {
case FACING_DIR_UP: dest[0] = 0.0f; dest[1] = -1.0f; return;
case FACING_DIR_LEFT: dest[0] = -1.0f; dest[1] = 0.0f; return;
case FACING_DIR_RIGHT: dest[0] = 1.0f; dest[1] = 0.0f; return;
default: dest[0] = 0.0f; dest[1] = 1.0f; return;
}
}
+28
View File
@@ -0,0 +1,28 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "dusk.h"
typedef enum {
FACING_DIR_DOWN = 0,
FACING_DIR_UP = 1,
FACING_DIR_LEFT = 2,
FACING_DIR_RIGHT = 3,
FACING_DIR_SOUTH = FACING_DIR_DOWN,
FACING_DIR_NORTH = FACING_DIR_UP,
FACING_DIR_WEST = FACING_DIR_LEFT,
FACING_DIR_EAST = FACING_DIR_RIGHT,
} facingdir_t;
/**
* Converts a facing direction to a normalized XZ vec2.
*
* @param facing The facing direction.
* @param dest Output vec2 — [0] is X, [1] is Z.
*/
void facingDirToVec2(facingdir_t facing, vec2 dest);
+1
View File
@@ -6,6 +6,7 @@
target_sources(${DUSK_LIBRARY_TARGET_NAME}
PUBLIC
overworldground.c
overworldnpc.c
overworldplayer.c
overworldscene.c
)
+9 -2
View File
@@ -14,7 +14,14 @@
#define OVERWORLD_GROUND_SIZE 20.0f
static errorret_t overworldGroundDraw(void) {
static errorret_t overworldGroundDraw(
const entityid_t entityId,
const componentid_t componentId,
void *user
) {
(void)entityId;
(void)componentId;
(void)user;
errorChain(shaderSetColor(&SHADER_UNLIT, SHADER_UNLIT_COLOR, COLOR_MAGENTA));
errorChain(shaderSetTexture(&SHADER_UNLIT, SHADER_UNLIT_TEXTURE, NULL));
return meshDraw(&PLANE_MESH_SIMPLE, 0, -1);
@@ -33,7 +40,7 @@ void overworldGroundAdd(overworldground_t *ground) {
vec3 scale = { OVERWORLD_GROUND_SIZE * 2.0f, 1.0f, OVERWORLD_GROUND_SIZE * 2.0f };
entityPositionSetLocalPosition(ground->entityId, ground->posCompId, pos);
entityPositionSetLocalScale(ground->entityId, ground->posCompId, scale);
entityRenderableSetDraw(ground->entityId, renderComp, overworldGroundDraw);
entityRenderableSetDraw(ground->entityId, renderComp, overworldGroundDraw, NULL);
// Separate physics entity centered on the finite ground area.
// The visual entity's position is its corner {-size, 0, -size}, not its
+47
View File
@@ -0,0 +1,47 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "overworldnpc.h"
#include "entity/entitymanager.h"
#include "entity/component/physics/entityphysics.h"
#include "entity/component/trigger/entitytrigger.h"
#include "entity/component/overworld/entityoverworld.h"
#include "entity/component/overworld/entityinteractable.h"
void overworldNpcAdd(overworldnpc_t *npc, vec3 position) {
npc->entityId = entityManagerAdd();
npc->posCompId = entityAddComponent(npc->entityId, COMPONENT_TYPE_POSITION);
(void)entityAddComponent(npc->entityId, COMPONENT_TYPE_RENDERABLE);
(void)entityAddComponent(npc->entityId, COMPONENT_TYPE_PHYSICS);
npc->overworldCompId = entityAddComponent(npc->entityId, COMPONENT_TYPE_OVERWORLD);
npc->triggerCompId = entityAddComponent(npc->entityId, COMPONENT_TYPE_TRIGGER);
npc->interactableCompId = entityAddComponent(
npc->entityId, COMPONENT_TYPE_INTERACTABLE
);
entityPositionSetLocalPosition(npc->entityId, npc->posCompId, position);
componentid_t physCompId = entityOverworldGet(npc->entityId, npc->overworldCompId)->physCompId;
entityPhysicsSetBodyType(npc->entityId, physCompId, PHYSICS_BODY_STATIC);
physicsshape_t shape = {
.type = PHYSICS_SHAPE_CAPSULE,
.data.capsule = { .radius = 0.4f, .halfHeight = 0.1f }
};
entityPhysicsSetShape(npc->entityId, physCompId, shape);
vec3 min = {
position[0] - OVERWORLD_NPC_INTERACT_RANGE,
position[1] - OVERWORLD_NPC_INTERACT_RANGE,
position[2] - OVERWORLD_NPC_INTERACT_RANGE
};
vec3 max = {
position[0] + OVERWORLD_NPC_INTERACT_RANGE,
position[1] + OVERWORLD_NPC_INTERACT_RANGE,
position[2] + OVERWORLD_NPC_INTERACT_RANGE
};
entityTriggerSetBounds(npc->entityId, npc->triggerCompId, min, max);
}
+27
View File
@@ -0,0 +1,27 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "entity/entitybase.h"
#define OVERWORLD_NPC_INTERACT_RANGE 1.5f
typedef struct {
entityid_t entityId;
componentid_t posCompId;
componentid_t overworldCompId;
componentid_t triggerCompId;
componentid_t interactableCompId;
} overworldnpc_t;
/**
* Creates the NPC entity at the given world position.
*
* @param npc The NPC state to initialize.
* @param position World-space position to spawn the NPC at.
*/
void overworldNpcAdd(overworldnpc_t *npc, vec3 position);
+11 -23
View File
@@ -8,43 +8,31 @@
#include "overworldplayer.h"
#include "entity/entitymanager.h"
#include "entity/component/physics/entityphysics.h"
#include "input/input.h"
#define OVERWORLD_PLAYER_SPEED 4.0f
#define OVERWORLD_PLAYER_RUN_SPEED 8.0f
#include "entity/component/overworld/entityoverworld.h"
#include "entity/component/overworld/entityplayer.h"
void overworldPlayerAdd(overworldplayer_t *player) {
player->entityId = entityManagerAdd();
player->posCompId = entityAddComponent(player->entityId, COMPONENT_TYPE_POSITION);
(void)entityAddComponent(player->entityId, COMPONENT_TYPE_RENDERABLE);
player->physCompId = entityAddComponent(player->entityId, COMPONENT_TYPE_PHYSICS);
(void)entityAddComponent(player->entityId, COMPONENT_TYPE_PHYSICS);
componentid_t owCompId = entityAddComponent(player->entityId, COMPONENT_TYPE_OVERWORLD);
entityOverworldSetType(player->entityId, owCompId, OVERWORLD_ENTITY_TYPE_PLAYER);
player->playerCompId = entityAddComponent(player->entityId, COMPONENT_TYPE_PLAYER);
vec3 pos = { 0.0f, 0.5f, 0.0f };
entityPositionSetLocalPosition(player->entityId, player->posCompId, pos);
entityPhysicsSetBodyType(player->entityId, player->physCompId, PHYSICS_BODY_DYNAMIC);
componentid_t physCompId = entityOverworldGet(player->entityId, owCompId)->physCompId;
entityPhysicsSetBodyType(player->entityId, physCompId, PHYSICS_BODY_DYNAMIC);
physicsshape_t shape = {
.type = PHYSICS_SHAPE_CAPSULE,
.data.capsule = { .radius = 0.4f, .halfHeight = 0.1f }
};
entityPhysicsSetShape(player->entityId, player->physCompId, shape);
entityPhysicsSetShape(player->entityId, physCompId, shape);
entityPhysicsGet(player->entityId, physCompId)->gravityScale = 1.0f;
}
void overworldPlayerUpdate(overworldplayer_t *player) {
vec2 dir;
inputAngle2D(
INPUT_ACTION_LEFT, INPUT_ACTION_RIGHT,
INPUT_ACTION_UP, INPUT_ACTION_DOWN,
dir
);
float_t speed = inputIsDown(INPUT_ACTION_CANCEL)
? OVERWORLD_PLAYER_RUN_SPEED
: OVERWORLD_PLAYER_SPEED;
vec3 vel;
entityPhysicsGetVelocity(player->entityId, player->physCompId, vel);
vel[0] = dir[0] * speed;
vel[2] = dir[1] * speed;
entityPhysicsSetVelocity(player->entityId, player->physCompId, vel);
entityPlayerUpdate(player->entityId, player->playerCompId);
}
+1 -1
View File
@@ -11,7 +11,7 @@
typedef struct {
entityid_t entityId;
componentid_t posCompId;
componentid_t physCompId;
componentid_t playerCompId;
} overworldplayer_t;
/**
+41 -38
View File
@@ -8,60 +8,60 @@
#include "overworldscene.h"
#include "overworldplayer.h"
#include "overworldground.h"
#include "overworldnpc.h"
#include "console/console.h"
#include "entity/entitymanager.h"
#include "entity/component/physics/entityphysics.h"
#include "entity/component/overworld/entityinteractable.h"
#include "entity/component/overworld/entityoverworldcamera.h"
#include "scene/scene.h"
#define OVERWORLD (SCENE.data.overworld)
static void overworldSceneNpcInteract(
const entityid_t entityId,
const componentid_t componentId,
void *user
) {
(void)entityId; (void)componentId; (void)user;
consolePrint("NPC interacted with!");
}
void overworldSceneInit(void) {
consolePrint("Overworld scene initialized");
OVERWORLD.cameraEntityId = entityManagerAdd();
OVERWORLD.cameraPosCompId = entityAddComponent(
OVERWORLD.cameraEntityId, COMPONENT_TYPE_POSITION
);
OVERWORLD.cameraCamCompId = entityAddComponent(
OVERWORLD.cameraEntityId, COMPONENT_TYPE_CAMERA
);
overworldGroundAdd(&OVERWORLD.ground);
overworldPlayerAdd(&OVERWORLD.player);
OVERWORLD.refCubeEntityId = entityManagerAdd();
componentid_t refPosComp = entityAddComponent(
OVERWORLD.refCubeEntityId, COMPONENT_TYPE_POSITION
);
(void)entityAddComponent(OVERWORLD.refCubeEntityId, COMPONENT_TYPE_RENDERABLE);
vec3 refPos = { 3.0f, 0.5f, 3.0f };
entityPositionSetLocalPosition(OVERWORLD.refCubeEntityId, refPosComp, refPos);
vec3 npcPos = { 3.0f, 0.5f, 3.0f };
overworldNpcAdd(&OVERWORLD.npc, npcPos);
componentid_t refPhysComp = entityAddComponent(
OVERWORLD.refCubeEntityId, COMPONENT_TYPE_PHYSICS
OVERWORLD.cameraEntityId = entityManagerAdd();
(void)entityAddComponent(OVERWORLD.cameraEntityId, COMPONENT_TYPE_POSITION);
(void)entityAddComponent(OVERWORLD.cameraEntityId, COMPONENT_TYPE_CAMERA);
OVERWORLD.cameraOverworldCompId = entityAddComponent(
OVERWORLD.cameraEntityId, COMPONENT_TYPE_OVERWORLD_CAMERA
);
entityOverworldCameraSetTarget(
OVERWORLD.cameraEntityId, OVERWORLD.cameraOverworldCompId,
OVERWORLD.player.entityId, OVERWORLD.player.posCompId
);
entityoverworldcamera_t *camData = entityOverworldCameraGet(
OVERWORLD.cameraEntityId, OVERWORLD.cameraOverworldCompId
);
glm_vec3_copy((vec3){ 0.5f, 0.5f, 0.5f }, camData->targetOffset);
glm_vec3_copy((vec3){ 0.0f, 0.0f, 5.0f }, camData->eyeOffset);
camData->scale = 32.0f;
entityInteractableSetCallback(
OVERWORLD.npc.entityId, OVERWORLD.npc.interactableCompId,
overworldSceneNpcInteract, NULL
);
entityPhysicsSetBodyType(OVERWORLD.refCubeEntityId, refPhysComp, PHYSICS_BODY_STATIC);
physicsshape_t refShape = {
.type = PHYSICS_SHAPE_CAPSULE,
.data.capsule = { .radius = 0.4f, .halfHeight = 0.1f }
};
entityPhysicsSetShape(OVERWORLD.refCubeEntityId, refPhysComp, refShape);
}
errorret_t overworldSceneUpdate(void) {
overworldPlayerUpdate(&OVERWORLD.player);
vec3 pos;
entityPositionGetLocalPosition(
OVERWORLD.player.entityId, OVERWORLD.player.posCompId, pos
);
vec3 center = { pos[0] + 0.5f, pos[1] + 0.5f, pos[2] + 0.5f };
vec3 eyeOffset = { 0.0f, 0.0f, 5.0f };
entityCameraLookAtPixelPerfect(
OVERWORLD.cameraEntityId,
OVERWORLD.cameraPosCompId,
OVERWORLD.cameraCamCompId,
center, eyeOffset, 32.0f
entityOverworldCameraUpdate(
OVERWORLD.cameraEntityId, OVERWORLD.cameraOverworldCompId
);
errorOk();
@@ -69,12 +69,15 @@ errorret_t overworldSceneUpdate(void) {
void overworldSceneDispose(void) {
OVERWORLD.cameraEntityId = ENTITY_ID_INVALID;
OVERWORLD.cameraPosCompId = COMPONENT_ID_INVALID;
OVERWORLD.cameraCamCompId = COMPONENT_ID_INVALID;
OVERWORLD.cameraOverworldCompId = COMPONENT_ID_INVALID;
OVERWORLD.ground.entityId = ENTITY_ID_INVALID;
OVERWORLD.ground.posCompId = COMPONENT_ID_INVALID;
OVERWORLD.ground.floorEntityId = ENTITY_ID_INVALID;
OVERWORLD.player.entityId = ENTITY_ID_INVALID;
OVERWORLD.player.posCompId = COMPONENT_ID_INVALID;
OVERWORLD.refCubeEntityId = ENTITY_ID_INVALID;
OVERWORLD.npc.entityId = ENTITY_ID_INVALID;
OVERWORLD.npc.posCompId = COMPONENT_ID_INVALID;
OVERWORLD.npc.overworldCompId = COMPONENT_ID_INVALID;
OVERWORLD.npc.triggerCompId = COMPONENT_ID_INVALID;
OVERWORLD.npc.interactableCompId = COMPONENT_ID_INVALID;
}
+3 -3
View File
@@ -10,14 +10,14 @@
#include "entity/entitybase.h"
#include "scene/overworld/overworldplayer.h"
#include "scene/overworld/overworldground.h"
#include "scene/overworld/overworldnpc.h"
typedef struct {
entityid_t cameraEntityId;
componentid_t cameraPosCompId;
componentid_t cameraCamCompId;
componentid_t cameraOverworldCompId;
overworldplayer_t player;
overworldground_t ground;
entityid_t refCubeEntityId;
overworldnpc_t npc;
} overworldscene_t;
void overworldSceneInit(void);