Refator pass 1

This commit is contained in:
2025-10-06 19:14:52 -05:00
parent 85434b4edb
commit fc52afdb00
113 changed files with 96 additions and 726 deletions

View File

@@ -0,0 +1,14 @@
# Copyright (c) 2025 Dominic Masters
#
# This software is released under the MIT License.
# https://opensource.org/licenses/MIT
# Sources
target_sources(${DUSK_TARGET_NAME}
PRIVATE
rpg.c
)
# Subdirs
add_subdirectory(entity)
add_subdirectory(world)

View File

@@ -0,0 +1,13 @@
# Copyright (c) 2025 Dominic Masters
#
# This software is released under the MIT License.
# https://opensource.org/licenses/MIT
# Sources
target_sources(${DUSK_TARGET_NAME}
PRIVATE
entity.c
npc.c
player.c
direction.c
)

View File

@@ -0,0 +1,48 @@
/**
* 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

@@ -0,0 +1,37 @@
/**
* 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);

107
archive/rpg/entity/entity.c Normal file
View File

@@ -0,0 +1,107 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "entity.h"
#include "rpg/world/map.h"
#include "assert/assert.h"
#include "util/memory.h"
#include "display/tileset/tileset_entities.h"
#include "time/time.h"
#include "util/math.h"
void entityInit(entity_t *entity, const entitytype_t type, map_t *map) {
assertNotNull(entity, "Entity pointer cannot be NULL");
assertNotNull(map, "Map pointer cannot be NULL");
assertTrue(type < ENTITY_TYPE_COUNT, "Invalid entity type");
assertTrue(type != ENTITY_TYPE_NULL, "Cannot have NULL entity type");
memoryZero(entity, sizeof(entity_t));
entity->type = type;
entity->map = map;
// Init. I did use a callback struct but it was not flexible enough.
switch(type) {
case ENTITY_TYPE_PLAYER:
playerInit(entity);
break;
case ENTITY_TYPE_NPC:
npcInit(entity);
break;
default:
break;
}
}
void entityUpdate(entity_t *entity) {
assertNotNull(entity, "Entity pointer cannot be NULL");
assertTrue(entity->type < ENTITY_TYPE_COUNT, "Invalid entity type");
assertTrue(entity->type != ENTITY_TYPE_NULL, "Cannot have NULL entity type");
// Handle movement logic
switch(entity->type) {
case ENTITY_TYPE_PLAYER:
playerMovement(entity);
break;
case ENTITY_TYPE_NPC:
npcUpdate(entity);
break;
default:
break;
}
// Apply velocity
if(entity->velocity[0] != 0.0f || entity->velocity[1] != 0.0f) {
entity->position[0] += entity->velocity[0] * TIME.delta;
entity->position[1] += entity->velocity[1] * TIME.delta;
// Hit test on other entities.
entity_t *start = entity->map->entities;
entity_t *end = &entity->map->entities[entity->map->entityCount];
// Our hitbox
physicscircle_t self;
glm_vec2_copy(entity->position, self.position);
self.radius = TILESET_ENTITIES.tileWidth / 2.0f;
physicscircle_t other;
other.radius = self.radius;
// TODO: what if multiple collisions?
do {
if(start == entity) continue;
if(start->type == ENTITY_TYPE_NULL) continue;
glm_vec2_copy(start->position, other.position);
physicscirclecircleresult_t result;
physicsCircleCheckCircle(self, other, &result);
if(result.hit) {
entity->position[0] -= result.normal[0] * result.depth;
entity->position[1] -= result.normal[1] * result.depth;
break;
}
} while((start++) != end);
// Friction (and dampening)
entity->velocity[0] *= ENTITY_FRICTION * TIME.delta;
entity->velocity[1] *= ENTITY_FRICTION * TIME.delta;
if(mathAbs(entity->velocity[0]) < ENTITY_MIN_VELOCITY) {
entity->velocity[0] = 0.0f;
}
if(mathAbs(entity->velocity[1]) < ENTITY_MIN_VELOCITY) {
entity->velocity[1] = 0.0f;
}
}
if(entity->type == ENTITY_TYPE_PLAYER) {
playerInteraction(entity);
}
}

View File

@@ -0,0 +1,55 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "direction.h"
#include "rpg/entity/player.h"
#include "npc.h"
#include "physics/physics.h"
#define ENTITY_FRICTION 0.9f
#define ENTITY_MIN_VELOCITY 0.05f
typedef struct map_s map_t;
typedef enum {
ENTITY_TYPE_NULL,
ENTITY_TYPE_PLAYER,
ENTITY_TYPE_NPC,
ENTITY_TYPE_COUNT
} entitytype_t;
typedef struct entity_s {
map_t *map;
entitytype_t type;
direction_t direction;
vec2 position;
vec2 velocity;
union {
player_t player;
npc_t npc;
};
} entity_t;
/**
* Initializes an entity structure.
*
* @param entity Pointer to the entity structure to initialize.
* @param type The type of the entity.
* @param map Pointer to the map the entity belongs to.
*/
void entityInit(entity_t *entity, const entitytype_t type, map_t *map);
/**
* Updates an entity.
*
* @param entity Pointer to the entity structure to update.
*/
void entityUpdate(entity_t *entity);

17
archive/rpg/entity/npc.c Normal file
View File

@@ -0,0 +1,17 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "entity.h"
#include "assert/assert.h"
void npcInit(entity_t *entity) {
assertNotNull(entity, "Entity pointer cannot be NULL");
}
void npcUpdate(entity_t *entity) {
assertNotNull(entity, "Entity pointer cannot be NULL");
}

29
archive/rpg/entity/npc.h Normal file
View File

@@ -0,0 +1,29 @@
/**
* 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 entity_s entity_t;
typedef struct {
void *nothing;
} npc_t;
/**
* Initializes an NPC entity.
*
* @param entity Pointer to the entity structure to initialize.
*/
void npcInit(entity_t *entity);
/**
* Updates an NPC entity.
*
* @param entity Pointer to the entity structure to update.
*/
void npcUpdate(entity_t *entity);

115
archive/rpg/entity/player.c Normal file
View File

@@ -0,0 +1,115 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "entity.h"
#include "assert/assert.h"
#include "input/input.h"
#include "display/scene/overworld/sceneoverworld.h"
#include "display/tileset/tileset_entities.h"
void playerInit(entity_t *entity) {
assertNotNull(entity, "Entity pointer cannot be NULL");
}
void playerMovement(entity_t *entity) {
assertNotNull(entity, "Entity pointer cannot be NULL");
// Update velocity.
vec2 dir = {
inputAxis(INPUT_ACTION_LEFT, INPUT_ACTION_RIGHT),
inputAxis(INPUT_ACTION_DOWN, INPUT_ACTION_UP)
};
if(dir[0] == 0 && dir[1] == 0) return;
glm_vec2_normalize(dir);
entity->velocity[0] += PLAYER_SPEED * dir[0];
entity->velocity[1] += PLAYER_SPEED * dir[1];
// Update direction.
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) {
assertNotNull(entity, "Entity pointer cannot be NULL");
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 = {
TILESET_ENTITIES.tileWidth * PLAYER_INTERACTION_SIZE * 0.5f,
TILESET_ENTITIES.tileHeight * 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 = entity->map->entities;
entity_t *end = &entity->map->entities[entity->map->entityCount];
vec2 otherSize = { TILESET_ENTITIES.tileWidth, TILESET_ENTITIES.tileHeight };
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

@@ -0,0 +1,40 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "dusk.h"
#define PLAYER_SPEED 64.0f
#define PLAYER_INTERACTION_RANGE 1.0f
#define PLAYER_INTERACTION_SIZE 0.5f
typedef struct entity_s entity_t;
typedef struct {
void *nothing;
} player_t;
/**
* Initializes a player entity.
*
* @param entity Pointer to the entity structure to initialize.
*/
void playerInit(entity_t *entity);
/**
* Handles movement logic for the player entity.
*
* @param entity Pointer to the player entity structure.
*/
void playerMovement(entity_t *entity);
/**
* Handles interaction logic for the player entity.
*
* @param entity Pointer to the player entity structure.
*/
void playerInteraction(entity_t *entity);

21
archive/rpg/game.c Normal file
View File

@@ -0,0 +1,21 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "game/game.h"
errorret_t gameInit(void) {
printf("Init RPG\n");
errorOk();
}
void gameUpdate(void) {
}
void gameDispose(void) {
}

View File

@@ -0,0 +1,10 @@
# Copyright (c) 2025 Dominic Masters
#
# This software is released under the MIT License.
# https://opensource.org/licenses/MIT
# Sources
target_sources(${DUSK_TARGET_NAME}
PRIVATE
sceneoverworld.c
)

View File

@@ -0,0 +1,146 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "sceneoverworld.h"
#include "rpg/entity/entity.h"
#include "display/spritebatch.h"
#include "display/framebuffer.h"
#include "display/scene/scenemanager.h"
#include "display/mesh/quad.h"
#include "asset/assetmanager.h"
#include "assert/assert.h"
#include "display/tileset/tileset_entities.h"
sceneoverworld_t SCENE_OVERWORLD;
asset_t *testAsset;
ref_t testAssetRef;
errorret_t sceneOverworldInit(void) {
cameraInit(&SCENE_OVERWORLD.camera);
glm_vec3_copy((vec3){ 0.0f, 1.0f, 0.0f }, SCENE_OVERWORLD.camera.lookat.up);
SCENE_OVERWORLD.camera.perspective.fov = 45;
SCENE_OVERWORLD.camera.farClip = 10000.0f;
scene_t *scene = &SCENE_MANAGER_SCENES[SCENE_TYPE_OVERWORLD];
scene->flags |= SCENE_FLAG_ACTIVE | SCENE_FLAG_VISIBLE;
errorChain(assetManagerLoadAsset(
TILESET_ENTITIES.image, &testAsset, &testAssetRef
));
errorOk();
}
void sceneOverworldUpdate(void) {
if(RPG.map == NULL) return;
// Move camera to player.
const entity_t *start = &RPG.map->entities[0];
const entity_t *end = &RPG.map->entities[RPG.map->entityCount];
while(start < end) {
if(start->type == ENTITY_TYPE_PLAYER) {
SCENE_OVERWORLD.camera.lookat.target[0] = start->position[0];
SCENE_OVERWORLD.camera.lookat.target[1] = start->position[1];
break;
}
start++;
}
}
void sceneOverworldRender(void) {
const float_t camOffset = 12.0f;
const float_t fbWidth = frameBufferGetWidth(FRAMEBUFFER_BOUND);
const float_t fbHeight = frameBufferGetHeight(FRAMEBUFFER_BOUND);
const float_t aspect = fbWidth / fbHeight;
const float_t pixelPerfectOffset = tanf(
(glm_rad(180) - SCENE_OVERWORLD.camera.perspective.fov) / 2.0f
) * (fbHeight/ 2.0f);
// glm_vec3_copy((vec3){
// -100.0f, -100.0f, 0.0f
// }, SCENE_OVERWORLD.camera.lookat.target);
glm_vec3_copy((vec3){
SCENE_OVERWORLD.camera.lookat.target[0],
SCENE_OVERWORLD.camera.lookat.target[1] + camOffset,
SCENE_OVERWORLD.camera.lookat.target[2] + pixelPerfectOffset
}, SCENE_OVERWORLD.camera.lookat.position);
cameraPushMatrix(&SCENE_OVERWORLD.camera);
if(RPG.map != NULL) sceneOverworldRenderMap(RPG.map);
spriteBatchFlush();
cameraPopMatrix();
}
void sceneOverworldRenderMap(const map_t *map) {
assertNotNull(map, "Map pointer cannot be NULL");
// Draw base layer
sceneOverworldRenderMapLayer(map, &map->base);
// Draw entities
const entity_t *start = &map->entities[0];
const entity_t *end = &map->entities[map->entityCount];
while(start < end) {
// Render entity here.
sceneOverworldRenderEntity(start);
start++;
}
spriteBatchFlush();
// Draw overlay layer.
sceneOverworldRenderMapLayer(map, &map->overlay);
}
void sceneOverworldRenderEntity(const entity_t *entity) {
assertNotNull(entity, "Entity pointer cannot be NULL");
assertTrue(entity->type < ENTITY_TYPE_COUNT, "Invalid entity type");
assertTrue(entity->type != ENTITY_TYPE_NULL, "Cannot have NULL entity type");
vec4 uv;
tilesetPositionGetUV(&TILESET_ENTITIES, entity->direction, 0, uv);
// For now, just draw a placeholder quad.
spriteBatchPush(
&testAsset->paletteImage.texture,
entity->position[0], entity->position[1],
entity->position[0] + TILESET_ENTITIES.tileWidth,
entity->position[1] + TILESET_ENTITIES.tileHeight,
COLOR_WHITE,
uv[0], uv[1], uv[2], uv[3]
);
}
void sceneOverworldRenderMapLayer(const map_t *map, const maplayer_t *layer) {
assertNotNull(layer, "Map layer pointer cannot be NULL");
for(uint32_t y = 0; y < map->height; y++) {
for(uint32_t x = 0; x < map->width; x++) {
const tile_t *tile = &layer->tiles[y * map->width + x];
if(tile->id == 0) continue;
spriteBatchPush(
NULL,
x * TILESET_ENTITIES.tileWidth,
y * TILESET_ENTITIES.tileHeight,
(x + 1) * TILESET_ENTITIES.tileWidth,
(y + 1) * TILESET_ENTITIES.tileHeight,
COLOR_RED,
0, 0, 1, 1
);
}
}
spriteBatchFlush();
}
void sceneOverworldDispose(void) {
// Dispose of the overworld scene.
if(testAsset) assetUnlock(testAsset, testAssetRef);
}

View File

@@ -0,0 +1,59 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "display/camera.h"
#include "rpg/rpg.h"
#include "error/error.h"
typedef struct {
camera_t camera;
} sceneoverworld_t;
extern sceneoverworld_t SCENE_OVERWORLD;
/**
* Initialize the overworld scene.
*/
errorret_t sceneOverworldInit(void);
/**
* Update the overworld scene.
*/
void sceneOverworldUpdate(void);
/**
* Render the overworld scene.
*/
void sceneOverworldRender(void);
/**
* Render a map in the overworld scene.
*
* @param map Pointer to the map to render.
*/
void sceneOverworldRenderMap(const map_t *map);
/**
* Render an entity in the overworld scene.
*
* @param entity Pointer to the entity to render.
*/
void sceneOverworldRenderEntity(const entity_t *entity);
/**
* Render a map layer in the overworld scene.
*
* @param map Pointer to the map the layer belongs to.
* @param layer Pointer to the map layer to render.
*/
void sceneOverworldRenderMapLayer(const map_t *map, const maplayer_t *layer);
/**
* Dispose of the overworld scene.
*/
void sceneOverworldDispose(void);

49
archive/rpg/rpg.c Normal file
View File

@@ -0,0 +1,49 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "rpg.h"
#include "rpg/world/map.h"
#include "util/memory.h"
#include "asset/assetmanager.h"
rpg_t RPG;
asset_t *asset;
ref_t assetRef;
void rpgInit() {
memoryZero(&RPG, sizeof(RPG));
errorret_t ret = assetManagerLoadAsset("map/untitled.drm", &asset, &assetRef);
if(ret.code != ERROR_OK) {
errorPrint(ret);
errorCatch(ret);
return;
}
RPG.map = &asset->rpgMap.map;
// mapInit(&testMap);
// testMap.width = 2;
// testMap.height = 2;
// for(uint32_t i = 0; i < testMap.width * testMap.height; i++) {
// testMap.base.tiles[i].id = 1;
// }
// entity_t *ent = mapEntityAdd(&testMap);
// entityInit(ent, ENTITY_TYPE_PLAYER, &testMap);
// entity_t *npc = mapEntityAdd(&testMap);
// entityInit(npc, ENTITY_TYPE_NPC, &testMap);
// npc->position[0] = 32.0f;
// npc->position[1] = 32.0f;
}
void rpgUpdate() {
if(RPG.map != NULL) {
mapUpdate(RPG.map);
}
}

25
archive/rpg/rpg.h Normal file
View File

@@ -0,0 +1,25 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "rpg/world/map.h"
typedef struct {
map_t *map;
} rpg_t;
extern rpg_t RPG;
/**
* Initializes the RPG subsystem.
*/
void rpgInit();
/**
* Updates the RPG subsystem.
*/
void rpgUpdate();

112
archive/rpg/screen.c Normal file
View File

@@ -0,0 +1,112 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "screen.h"
#include "assert/assert.h"
#include "display/spritebatch.h"
screen_t SCREEN;
void screenInit(void) {
// Virtual backbuffer for dynamic resolution scaling
#if DISPLAY_SIZE_DYNAMIC == 1
frameBufferInit(&SCREEN.frameBuffer, DISPLAY_WIDTH, DISPLAY_HEIGHT);
cameraInit(&SCREEN.frameBufferCamera);
SCREEN.frameBufferCamera.projType = CAMERA_PROJECTION_TYPE_ORTHOGRAPHIC;
SCREEN.frameBufferCamera.viewType = CAMERA_VIEW_TYPE_MATRIX;
glm_lookat(
(vec3){0.0f, 0.0f, 1.0f},
(vec3){0.0f, 0.0f, 0.0f},
(vec3){0.0f, 1.0f, 0.0f},
SCREEN.frameBufferCamera.view
);
SCREEN.frameBufferCamera.nearClip = -1.0f;
SCREEN.frameBufferCamera.farClip = 1.0f;
#endif
}
void screenBind(void) {
#if DISPLAY_SIZE_DYNAMIC == 1
frameBufferBind(&SCREEN.frameBuffer);
#else
frameBufferBind(NULL);
#endif
frameBufferClear(
FRAMEBUFFER_CLEAR_COLOR | FRAMEBUFFER_CLEAR_DEPTH,
COLOR_CORNFLOWER_BLUE
);
}
void screenUnbindAndRender(void) {
assertTrue(SPRITEBATCH.spriteCount == 0, "Sprite batch not flushed");
// Render to real backbuffer
#if DISPLAY_SIZE_DYNAMIC == 1
frameBufferBind(NULL);
frameBufferClear(
FRAMEBUFFER_CLEAR_COLOR | FRAMEBUFFER_CLEAR_DEPTH,
COLOR_BLACK
);
SCREEN.frameBufferCamera.orthographic.left = 0;
SCREEN.frameBufferCamera.orthographic.right = frameBufferGetWidth(
FRAMEBUFFER_BOUND
);
SCREEN.frameBufferCamera.orthographic.bottom = frameBufferGetHeight(
FRAMEBUFFER_BOUND
);
SCREEN.frameBufferCamera.orthographic.top = 0;
cameraPushMatrix(&SCREEN.frameBufferCamera);
vec2 backbuffer = {
(float_t)frameBufferGetWidth(FRAMEBUFFER_BOUND),
(float_t)frameBufferGetHeight(FRAMEBUFFER_BOUND)
};
vec2 virtual = {
(float_t)frameBufferGetWidth(&SCREEN.frameBuffer),
(float_t)frameBufferGetHeight(&SCREEN.frameBuffer)
};
// Compare aspect ratios.
vec4 viewport;
float_t backbufferAspect = backbuffer[0] / backbuffer[1];
float_t virtualAspect = virtual[0] / virtual[1];
if (backbufferAspect > virtualAspect) {
// Backbuffer is wider: pillarbox
float_t scale = backbuffer[1] / virtual[1];
float_t width = virtual[0] * scale;
viewport[0] = (backbuffer[0] - width) * 0.5f;
viewport[1] = 0;
viewport[2] = width;
viewport[3] = backbuffer[1];
} else {
// Backbuffer is taller: letterbox
float_t scale = backbuffer[0] / virtual[0];
float_t height = virtual[1] * scale;
viewport[0] = 0;
viewport[1] = (backbuffer[1] - height) * 0.5f;
viewport[2] = backbuffer[0];
viewport[3] = height;
}
spriteBatchPush(
&SCREEN.frameBuffer.texture,
viewport[0], viewport[1],
viewport[0] + viewport[2], viewport[1] + viewport[3],
COLOR_WHITE,
0.0f, 1.0f, 1.0f, 0.0f
);
spriteBatchFlush();
cameraPopMatrix();
#endif
}
void screenDispose(void) {
frameBufferDispose(&SCREEN.frameBuffer);
}

41
archive/rpg/screen.h Normal file
View File

@@ -0,0 +1,41 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "display/camera.h"
#include "display/framebuffer.h"
typedef struct {
#if DISPLAY_SIZE_DYNAMIC == 1
framebuffer_t frameBuffer;
camera_t frameBufferCamera;
#else
void *empty;
#endif
} screen_t;
extern screen_t SCREEN;
/**
* Initializes the screen.
*/
void screenInit(void);
/**
* Binds the screen for rendering.
*/
void screenBind(void);
/**
* Unbinds the screen and renders it.
*/
void screenUnbindAndRender(void);
/**
* Disposes of the screen.
*/
void screenDispose(void);

View File

@@ -0,0 +1,10 @@
# Copyright (c) 2025 Dominic Masters
#
# This software is released under the MIT License.
# https://opensource.org/licenses/MIT
# Sources
target_sources(${DUSK_TARGET_NAME}
PRIVATE
scenetest.c
)

View File

@@ -0,0 +1,48 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "scenetest.h"
#include "display/scene/scenemanager.h"
#include "display/spritebatch.h"
scenetest_t SCENE_TEST;
errorret_t sceneTestInit(void) {
cameraInit(&SCENE_TEST.camera);
SCENE_TEST.camera.projType = CAMERA_PROJECTION_TYPE_ORTHOGRAPHIC;
SCENE_TEST.camera.nearClip = -1.0f;
SCENE_TEST.camera.farClip = 2.0f;
SCENE_TEST.camera.viewType = CAMERA_VIEW_TYPE_2D;
SCENE_TEST.camera._2d.zoom = 2.0f;
SCENE_TEST.camera._2d.position[0] = -150.0f;
SCENE_TEST.camera._2d.position[1] = -50.0f;
scene_t *scene = &SCENE_MANAGER_SCENES[SCENE_TYPE_TEST];
scene->flags |= SCENE_FLAG_ACTIVE | SCENE_FLAG_VISIBLE;
errorOk();
}
void sceneTestUpdate(void) {
}
void sceneTestRender(void) {
SCENE_TEST.camera.orthographic.left = 0.0f;
SCENE_TEST.camera.orthographic.right = frameBufferGetWidth(FRAMEBUFFER_BOUND);
SCENE_TEST.camera.orthographic.top = frameBufferGetHeight(FRAMEBUFFER_BOUND);
SCENE_TEST.camera.orthographic.bottom = 0.0f;
cameraPushMatrix(&SCENE_TEST.camera);
spriteBatchClear();
spriteBatchFlush();
cameraPopMatrix();
}
void sceneTestDispose(void) {
}

View File

@@ -0,0 +1,36 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "display/camera.h"
#include "error/error.h"
typedef struct {
camera_t camera;
} scenetest_t;
extern scenetest_t SCENE_TEST;
/**
* Initialize the test scene.
*/
errorret_t sceneTestInit(void);
/**
* Update the test scene.
*/
void sceneTestUpdate(void);
/**
* Render the test scene.
*/
void sceneTestRender(void);
/**
* Dispose of the test scene.
*/
void sceneTestDispose(void);

View File

@@ -0,0 +1,10 @@
# Copyright (c) 2025 Dominic Masters
#
# This software is released under the MIT License.
# https://opensource.org/licenses/MIT
# Sources
target_sources(${DUSK_TARGET_NAME}
PRIVATE
map.c
)

33
archive/rpg/world/map.c Normal file
View File

@@ -0,0 +1,33 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "map.h"
#include "util/memory.h"
#include "assert/assert.h"
void mapInit(map_t *map) {
assertNotNull(map, "Map cannot be NULL");
memoryZero(map, sizeof(map_t));
}
void mapUpdate(map_t *map) {
assertNotNull(map, "Map cannot be NULL");
entity_t *start = &map->entities[0];
entity_t *end = &map->entities[map->entityCount];
while(start < end) {
entityUpdate(start++);
}
}
entity_t * mapEntityAdd(map_t *map) {
assertNotNull(map, "Map cannot be NULL");
assertTrue(map->entityCount < MAP_ENTITY_COUNT_MAX, "Map entities full");
entity_t *entity = &map->entities[map->entityCount++];
return entity;
}

57
archive/rpg/world/map.h Normal file
View File

@@ -0,0 +1,57 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "rpg/entity/entity.h"
#define MAP_ENTITY_COUNT_MAX 32
#define MAP_WIDTH_MAX 64
#define MAP_HEIGHT_MAX 64
#define MAP_TILE_COUNT_MAX (MAP_WIDTH_MAX * MAP_HEIGHT_MAX)
#define MAP_LAYER_COUNT_MAX 2
typedef struct {
uint8_t id;
} tile_t;
typedef struct {
tile_t tiles[MAP_TILE_COUNT_MAX];
} maplayer_t;
typedef struct map_s {
entity_t entities[MAP_ENTITY_COUNT_MAX];
uint8_t entityCount;
uint8_t width, height;
maplayer_t base;
maplayer_t overlay;
} map_t;
extern map_t testMap;
/**
* Initializes a map structure.
*
* @param map Pointer to the map structure to initialize.
*/
void mapInit(map_t *map);
/**
* Updates the map and its entities.
*
* @param map Pointer to the map structure to update.
*/
void mapUpdate(map_t *map);
/**
* Adds (but does not initialize) an entity on the map.
*
* @param map Pointer to the map structure.
* @return Pointer to the added entity.
*/
entity_t * mapEntityAdd(map_t *map);

16
archive/rpg/world/tile.h Normal file
View File

@@ -0,0 +1,16 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
typedef enum {
TILE_TYPE_NULL,
} tiletype_t;
typedef struct {
tiletype_t type;
} tile_t;