This commit is contained in:
2025-11-03 19:50:23 -06:00
parent f3d985ecbc
commit d4a2e059d7
16 changed files with 329 additions and 220 deletions

View File

@@ -35,7 +35,6 @@ add_subdirectory(engine)
add_subdirectory(error) add_subdirectory(error)
add_subdirectory(input) add_subdirectory(input)
# add_subdirectory(locale) # add_subdirectory(locale)
add_subdirectory(physics)
add_subdirectory(rpg) add_subdirectory(rpg)
add_subdirectory(scene) add_subdirectory(scene)
add_subdirectory(thread) add_subdirectory(thread)

View File

@@ -7,7 +7,8 @@
target_sources(${DUSK_TARGET_NAME} target_sources(${DUSK_TARGET_NAME}
PRIVATE PRIVATE
entity.c entity.c
entityanim.c
npc.c npc.c
player.c player.c
direction.c entitydir.c
) )

View File

@@ -1,48 +0,0 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "direction.h"
#include "assert/assert.h"
float_t directionToAngle(const direction_t dir) {
switch(dir) {
case DIRECTION_NORTH: return (M_PI_2);
case DIRECTION_SOUTH: return -(M_PI_2);
case DIRECTION_EAST: return 0;
case DIRECTION_WEST: return (M_PI);
default: return 0; // Should never happen
}
}
void directionGetVec2(const direction_t dir, vec2 out) {
assertNotNull(out, "Output vector cannot be NULL");
switch(dir) {
case DIRECTION_NORTH:
out[0] = 0.0f;
out[1] = 1.0f;
break;
case DIRECTION_SOUTH:
out[0] = 0.0f;
out[1] = -1.0f;
break;
case DIRECTION_EAST:
out[0] = 1.0f;
out[1] = 0.0f;
break;
case DIRECTION_WEST:
out[0] = -1.0f;
out[1] = 0.0f;
break;
default:
assertUnreachable("Invalid direction");
}
}

View File

@@ -1,37 +0,0 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "dusk.h"
typedef enum {
DIRECTION_SOUTH = 0,
DIRECTION_EAST = 1,
DIRECTION_WEST = 2,
DIRECTION_NORTH = 3,
DIRECTION_UP = DIRECTION_NORTH,
DIRECTION_DOWN = DIRECTION_SOUTH,
DIRECTION_LEFT = DIRECTION_WEST,
DIRECTION_RIGHT = DIRECTION_EAST,
} direction_t;
/**
* Converts a direction to an angle in float_t format.
*
* @param dir The direction to convert.
* @return The angle corresponding to the direction.
*/
float_t directionToAngle(const direction_t dir);
/**
* Converts a direction to a vec2 unit vector.
*
* @param dir The direction to convert.
* @param out Pointer to the vec2 array to populate.
*/
void directionGetVec2(const direction_t dir, vec2 out);

View File

@@ -46,8 +46,66 @@ void entityUpdate(entity_t *entity) {
assertTrue(entity->type < ENTITY_TYPE_COUNT, "Invalid entity type"); assertTrue(entity->type < ENTITY_TYPE_COUNT, "Invalid entity type");
assertTrue(entity->type != ENTITY_TYPE_NULL, "Cannot have NULL entity type"); assertTrue(entity->type != ENTITY_TYPE_NULL, "Cannot have NULL entity type");
// What state is the entity in?
if(entity->animation != ENTITY_ANIM_IDLE) {
// Entity is mid animation, tick it (down).
entity->animFrame--;
if(entity->animFrame == 0) entity->animation = ENTITY_ANIM_IDLE;
return;
}
// Movement code. // Movement code.
if(entity->type == ENTITY_TYPE_PLAYER) { if(entity->type == ENTITY_TYPE_PLAYER) {
playerMovement(entity); playerMovement(entity);
} }
} }
void entityTurn(entity_t *entity, const entitydir_t direction) {
entity->direction = direction;
entity->animation = ENTITY_ANIM_TURN;
entity->animFrame = ENTITY_ANIM_TURN_DURATION;
}
void entityWalk(entity_t *entity, const entitydir_t direction) {
// TODO: Animation, delay, etc.
entity->direction = direction;
// Where are we moving?
uint8_t newX, newY;
newX = entity->position.x;
newY = entity->position.y;
{
int8_t relX, relY;
entityDirGetRelative(direction, &relX, &relY);
newX += relX;
newY += relY;
}
// TODO: Tile in way?
// TODO: Map bounds in way?
// Entity in way?
// entity_t *start = GAME.overworld.map.entities;
// entity_t *end = start + MAP_ENTITY_COUNT;
// while(start < end) {
// if(
// start == entity ||
// entity->type == ENTITY_TYPE_NULL ||
// start->position.x != newX ||
// start->position.y != newY
// ) {
// start++;
// continue;
// }
// return;// Blocked
// }
// Move.
entity->position.x = newX;
entity->position.y = newY;
entity->animation = ENTITY_ANIM_WALK;
entity->animFrame = ENTITY_ANIM_WALK_DURATION;// TODO: Running vs walking
}

View File

@@ -6,9 +6,11 @@
*/ */
#pragma once #pragma once
#include "direction.h" #include "entitydir.h"
#include "entityanim.h"
#include "rpg/entity/player.h" #include "rpg/entity/player.h"
#include "npc.h" #include "npc.h"
#include "rpg/world/worldpos.h"
#define ENTITY_COUNT 256 #define ENTITY_COUNT 256
@@ -18,15 +20,19 @@ typedef enum {
ENTITY_TYPE_NULL, ENTITY_TYPE_NULL,
ENTITY_TYPE_PLAYER, ENTITY_TYPE_PLAYER,
ENTITY_TYPE_NPC, ENTITY_TYPE_NPC,
ENTITY_TYPE_COUNT ENTITY_TYPE_COUNT
} entitytype_t; } entitytype_t;
typedef struct entity_s { typedef struct entity_s {
uint8_t id; uint8_t id;
entitytype_t type; entitytype_t type;
direction_t direction;
uint32_t position;// Tile index // Movement
entitydir_t direction;
worldpos_t position;
entityanim_t animation;
uint8_t animFrame;
union { union {
player_t player; player_t player;
@@ -50,3 +56,19 @@ void entityInit(entity_t *entity, const entitytype_t type);
* @param entity Pointer to the entity structure to update. * @param entity Pointer to the entity structure to update.
*/ */
void entityUpdate(entity_t *entity); void entityUpdate(entity_t *entity);
/**
* Turn an entity to face a new direction.
*
* @param entity Pointer to the entity to turn.
* @param direction The direction to face.
*/
void entityTurn(entity_t *entity, const entitydir_t direction);
/**
* Make an entity walk in a direction.
*
* @param entity Pointer to the entity to make walk.
* @param direction The direction to walk in.
*/
void entityWalk(entity_t *entity, const entitydir_t direction);

View File

@@ -0,0 +1,9 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "entityanim.h"

View File

@@ -0,0 +1,18 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "dusk.h"
#define ENTITY_ANIM_TURN_DURATION 1
#define ENTITY_ANIM_WALK_DURATION 10
typedef enum {
ENTITY_ANIM_IDLE,
ENTITY_ANIM_TURN,
ENTITY_ANIM_WALK,
} entityanim_t;

View File

@@ -0,0 +1,37 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "entitydir.h"
#include "assert/assert.h"
void entityDirGetRelative(const entitydir_t from, int8_t *outX, int8_t *outY) {
assertValidEntityDir(from, "Invalid direction provided");
assertNotNull(outX, "Output X pointer cannot be NULL");
assertNotNull(outY, "Output Y pointer cannot be NULL");
switch(from) {
case ENTITY_DIR_NORTH:
*outX = 0;
*outY = -1;
break;
case ENTITY_DIR_EAST:
*outX = 1;
*outY = 0;
break;
case ENTITY_DIR_SOUTH:
*outX = 0;
*outY = 1;
break;
case ENTITY_DIR_WEST:
*outX = -1;
*outY = 0;
break;
}
}

View File

@@ -0,0 +1,45 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "dusk.h"
typedef enum {
ENTITY_DIR_SOUTH = 0,
ENTITY_DIR_EAST = 1,
ENTITY_DIR_WEST = 2,
ENTITY_DIR_NORTH = 3,
ENTITY_DIR_UP = ENTITY_DIR_NORTH,
ENTITY_DIR_DOWN = ENTITY_DIR_SOUTH,
ENTITY_DIR_LEFT = ENTITY_DIR_WEST,
ENTITY_DIR_RIGHT = ENTITY_DIR_EAST,
} entitydir_t;
/**
* Asserts a given direction is valid.
*
* @param dir The direction to validate.
* @param msg The message to display if the assertion fails.
*/
#define assertValidEntityDir(dir, msg) \
assertTrue( \
(dir) == ENTITY_DIR_NORTH || \
(dir) == ENTITY_DIR_EAST || \
(dir) == ENTITY_DIR_SOUTH || \
(dir) == ENTITY_DIR_WEST, \
msg \
)
/**
* Gets the relative x and y offsets for a given direction.
*
* @param dir The direction to get offsets for.
* @param relX Pointer to store the relative x offset.
* @param relY Pointer to store the relative y offset.
*/
void entityDirGetRelative(const entitydir_t dir, int8_t *relX, int8_t *relY);

View File

@@ -12,6 +12,20 @@
#include "util/memory.h" #include "util/memory.h"
#include "time/time.h" #include "time/time.h"
typedef struct {
inputaction_t action;
entitydir_t direction;
} playerinputdirmap_t;
static const playerinputdirmap_t PLAYER_INPUT_DIR_MAP[] = {
{ INPUT_ACTION_UP, ENTITY_DIR_NORTH },
{ INPUT_ACTION_DOWN, ENTITY_DIR_SOUTH },
{ INPUT_ACTION_LEFT, ENTITY_DIR_WEST },
{ INPUT_ACTION_RIGHT, ENTITY_DIR_EAST },
{ 0xFF, 0xFF }
};
void playerInit(entity_t *entity) { void playerInit(entity_t *entity) {
assertNotNull(entity, "Entity pointer cannot be NULL"); assertNotNull(entity, "Entity pointer cannot be NULL");
} }
@@ -19,47 +33,23 @@ void playerInit(entity_t *entity) {
void playerMovement(entity_t *entity) { void playerMovement(entity_t *entity) {
assertNotNull(entity, "Entity pointer cannot be NULL"); assertNotNull(entity, "Entity pointer cannot be NULL");
// Get movement angle as 0-> normalized vector. // Turn
vec2 dir = { const playerinputdirmap_t *dirMap = PLAYER_INPUT_DIR_MAP;
inputAxis(INPUT_ACTION_LEFT, INPUT_ACTION_RIGHT), do {
inputAxis(INPUT_ACTION_UP, INPUT_ACTION_DOWN) if(!inputPressed(dirMap->action)) continue;
}; if(entity->direction == dirMap->direction) continue;
if(dir[0] == 0 && dir[1] == 0) return; return entityTurn(entity, dirMap->direction);
glm_vec2_normalize(dir); } while((++dirMap)->action != 0xFF);
entity->velocity[0] += PLAYER_SPEED * dir[0] * TIME.fixedDelta; // Walk
entity->velocity[1] += PLAYER_SPEED * dir[1] * TIME.fixedDelta; dirMap = PLAYER_INPUT_DIR_MAP;
do {
if(!inputIsDown(dirMap->action)) continue;
if(entity->direction != dirMap->direction) continue;
return entityWalk(entity, dirMap->direction);
} while((++dirMap)->action != 0xFF);
// Update direction. // Interaction
if(dir[0] > 0) {
if(entity->direction == DIRECTION_RIGHT) {
entity->direction = DIRECTION_RIGHT;
} else {
if(dir[1] < 0) {
entity->direction = DIRECTION_UP;
} else if(dir[1] > 0) {
entity->direction = DIRECTION_DOWN;
} else {
entity->direction = DIRECTION_RIGHT;
}
}
} else if(dir[0] < 0) {
if(entity->direction == DIRECTION_LEFT) {
entity->direction = DIRECTION_LEFT;
} else {
if(dir[1] < 0) {
entity->direction = DIRECTION_UP;
} else if(dir[1] > 0) {
entity->direction = DIRECTION_DOWN;
} else {
entity->direction = DIRECTION_LEFT;
}
}
} else if(dir[1] < 0) {
entity->direction = DIRECTION_UP;
} else if(dir[1] > 0) {
entity->direction = DIRECTION_DOWN;
}
} }
void playerInteraction(entity_t *entity) { void playerInteraction(entity_t *entity) {
@@ -67,50 +57,4 @@ void playerInteraction(entity_t *entity) {
if(!inputPressed(INPUT_ACTION_ACCEPT)) return; if(!inputPressed(INPUT_ACTION_ACCEPT)) return;
physicsbox_t interactBox;
// Get direction vector
directionGetVec2(entity->direction, interactBox.min);
// Scale by interact range
glm_vec2_scale(interactBox.min, PLAYER_INTERACTION_RANGE, interactBox.min);
// Add entity position, this makes the center of the box.
glm_vec2_add(interactBox.min, entity->position, interactBox.min);
// Copy to max
glm_vec2_copy(interactBox.min, interactBox.max);
// // Size of the hitbox
// vec2 halfSize = {
// 1 * PLAYER_INTERACTION_SIZE * 0.5f,
// 1 * PLAYER_INTERACTION_SIZE * 0.5f
// };
// // Subtract from min, add to max.
// glm_vec2_sub(interactBox.min, halfSize, interactBox.min);
// glm_vec2_add(interactBox.max, halfSize, interactBox.max);
// // For each entity
// entity_t *start = ENTITIES;
// entity_t *end = &ENTITIES[ENTITY_COUNT];
// vec2 otherSize = { 1, 1 };
// physicsbox_t otherBox;
// physicsboxboxresult_t result;
// do {
// if(start->type != ENTITY_TYPE_NPC) continue;
// // Setup other box.
// glm_vec2_copy(start->position, otherBox.min);
// glm_vec2_copy(start->position, otherBox.max);
// glm_vec2_sub(otherBox.min, otherSize, otherBox.min);
// glm_vec2_add(otherBox.min, otherSize, otherBox.max);
// physicsBoxCheckBox(interactBox, otherBox, &result);
// if(!result.hit) continue;
// printf("Interacted with entity at (%.2f, %.2f)\n", start->position[0], start->position[1]);
// break;
// } while(++start != end);
} }

View File

@@ -27,11 +27,11 @@ errorret_t rpgInit(void) {
entityInit(ent, ENTITY_TYPE_PLAYER); entityInit(ent, ENTITY_TYPE_PLAYER);
RPG_CAMERA.mode = RPG_CAMERA_MODE_FOLLOW_ENTITY; RPG_CAMERA.mode = RPG_CAMERA_MODE_FOLLOW_ENTITY;
RPG_CAMERA.followEntity.followEntityId = ent->id; RPG_CAMERA.followEntity.followEntityId = ent->id;
ent->position[0] = 4, ent->position[1] = 4; ent->position.x = 4, ent->position.y = 4;
ent = &ENTITIES[1]; ent = &ENTITIES[1];
entityInit(ent, ENTITY_TYPE_NPC); entityInit(ent, ENTITY_TYPE_NPC);
ent->position[0] = 6, ent->position[1] = 6; ent->position.x = 6, ent->position.y = 6;
// All Good! // All Good!
errorOk(); errorOk();
@@ -51,9 +51,6 @@ void rpgUpdate(void) {
if(ent->type == ENTITY_TYPE_NULL) continue; if(ent->type == ENTITY_TYPE_NULL) continue;
entityUpdate(ent); entityUpdate(ent);
} while(++ent < &ENTITIES[ENTITY_COUNT]); } while(++ent < &ENTITIES[ENTITY_COUNT]);
// Update the camera.
rpgCameraUpdate();
} }
void rpgDispose(void) { void rpgDispose(void) {

View File

@@ -14,19 +14,3 @@ rpgcamera_t RPG_CAMERA;
void rpgCameraInit(void) { void rpgCameraInit(void) {
memoryZero(&RPG_CAMERA, sizeof(rpgcamera_t)); memoryZero(&RPG_CAMERA, sizeof(rpgcamera_t));
} }
void rpgCameraUpdate(void) {
switch(RPG_CAMERA.mode) {
case RPG_CAMERA_MODE_FOLLOW_ENTITY:
if(RPG_CAMERA.followEntity.followEntityId >= ENTITY_COUNT) break;
entity_t *ent = &ENTITIES[RPG_CAMERA.followEntity.followEntityId];
if(ent->type == ENTITY_TYPE_NULL) break;
memoryCopy(
&RPG_CAMERA.position, ent->position, sizeof(RPG_CAMERA.position)
);
break;
default:
break;
}
}

View File

@@ -6,7 +6,7 @@
*/ */
#pragma once #pragma once
#include "dusk.h" #include "rpg/world/worldpos.h"
typedef enum { typedef enum {
RPG_CAMERA_MODE_FREE, RPG_CAMERA_MODE_FREE,
@@ -14,10 +14,10 @@ typedef enum {
} rpgcameramode_t; } rpgcameramode_t;
typedef struct { typedef struct {
vec3 position;
rpgcameramode_t mode; rpgcameramode_t mode;
union { union {
worldpos_t free;
struct { struct {
uint8_t followEntityId; uint8_t followEntityId;
} followEntity; } followEntity;
@@ -30,8 +30,3 @@ extern rpgcamera_t RPG_CAMERA;
* Initializes the RPG camera. * Initializes the RPG camera.
*/ */
void rpgCameraInit(void); void rpgCameraInit(void);
/**
* Updates the RPG camera.
*/
void rpgCameraUpdate(void);

13
src/rpg/world/worldpos.h Normal file
View File

@@ -0,0 +1,13 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "dusk.h"
typedef struct worldpos_s {
uint32_t x, y, z;
} worldpos_t;

View File

@@ -40,18 +40,68 @@ void sceneMapUpdate(scenedata_t *data) {
} }
void sceneMapGetWorldPosition(const worldpos_t pos, vec3 outPosition) {
assertNotNull(outPosition, "Output position cannot be NULL");
outPosition[0] = pos.x * TILE_SIZE;
outPosition[1] = pos.y * TILE_SIZE;
outPosition[2] = pos.z * TILE_SIZE;
}
void sceneMapEntityGetPosition(const entity_t *entity, vec3 outPosition) {
assertNotNull(entity, "Entity cannot be NULL");
assertNotNull(outPosition, "Output position cannot be NULL");
sceneMapGetWorldPosition(entity->position, outPosition);
// Add animation offset(s)
switch(entity->animation) {
case ENTITY_ANIM_WALK:
float_t animPercentage = (
(float_t)entity->animFrame / (float_t)ENTITY_ANIM_WALK_DURATION
);
// Get facing rel, we know we moved from the inverse direction.
int8_t x, y;
entityDirGetRelative(entity->direction, &x, &y);
x = -x, y = -y;
// Add tile size times percentage to posMin/max
vec3 offset = {
x * TILE_SIZE * animPercentage,
y * TILE_SIZE * animPercentage,
0.0f
};
glm_vec3_add(outPosition, offset, outPosition);
break;
default:
break;
}
}
void sceneMapRender(scenedata_t *data) { void sceneMapRender(scenedata_t *data) {
// Look at target. // Look at target.
glm_vec3_scale( vec3 cameraTarget;
RPG_CAMERA.position, switch(RPG_CAMERA.mode) {
TILE_SIZE, case RPG_CAMERA_MODE_FREE:
data->sceneMap.camera.lookatPixelPerfect.target sceneMapGetWorldPosition(RPG_CAMERA.free, cameraTarget);
); break;
// Center within tile case RPG_CAMERA_MODE_FOLLOW_ENTITY: {
glm_vec3_add( const entity_t *ent = &ENTITIES[RPG_CAMERA.followEntity.followEntityId];
data->sceneMap.camera.lookatPixelPerfect.target, sceneMapEntityGetPosition(ent, cameraTarget);
(vec3){TILE_SIZE / 2.0f, TILE_SIZE / 2.0f, TILE_SIZE / 2.0f }, break;
}
default:
glm_vec3_zero(cameraTarget);
break;
}
glm_vec3_copy(
cameraTarget,
data->sceneMap.camera.lookatPixelPerfect.target data->sceneMap.camera.lookatPixelPerfect.target
); );
@@ -74,14 +124,36 @@ void sceneMapRenderEntity(entity_t *entity) {
if(entity->type == ENTITY_TYPE_NULL) return; if(entity->type == ENTITY_TYPE_NULL) return;
vec3 posMin, posMax; vec3 posCenter, posMin, posMax;
glm_vec3_scale(entity->position, TILE_SIZE, posMin); vec3 halfSize = { TILE_SIZE / 2.0f, TILE_SIZE / 2.0f, TILE_SIZE / 2.0f };
glm_vec3_add(posMin, (vec3){TILE_SIZE, TILE_SIZE, TILE_SIZE }, posMax); sceneMapEntityGetPosition(entity, posCenter);
glm_vec3_sub(posCenter, halfSize, posMin);
glm_vec3_add(posCenter, halfSize, posMax);
// TEST: Change color depending on dir.
color_t testColor;
switch(entity->direction) {
case ENTITY_DIR_NORTH:
testColor = COLOR_BLUE;
break;
case ENTITY_DIR_EAST:
testColor = COLOR_GREEN;
break;
case ENTITY_DIR_SOUTH:
testColor = COLOR_CYAN;
break;
case ENTITY_DIR_WEST:
testColor = COLOR_YELLOW;
break;
default:
testColor = COLOR_WHITE;
break;
}
vec2 uv0 = { 0.0f, 0.0f }; vec2 uv0 = { 0.0f, 0.0f };
vec2 uv1 = { 1.0f, 1.0f }; vec2 uv1 = { 1.0f, 1.0f };
spriteBatchPush3D(NULL, posMin, posMax, COLOR_RED, uv0, uv1); spriteBatchPush3D(NULL, posMin, posMax, testColor, uv0, uv1);
} }
void sceneMapDispose(scenedata_t *data) { void sceneMapDispose(scenedata_t *data) {