RPG stuff

This commit is contained in:
2026-06-11 12:18:13 -05:00
parent 5be21a21d5
commit aa246eff94
9 changed files with 274 additions and 106 deletions
+1
View File
@@ -51,6 +51,7 @@ errorret_t engineInit(const int32_t argc, const char_t **argv) {
consolePrint("Engine initialized"); consolePrint("Engine initialized");
sceneSet(SCENE_TYPE_OVERWORLD); sceneSet(SCENE_TYPE_OVERWORLD);
errorOk(); errorOk();
} }
+1
View File
@@ -20,6 +20,7 @@ typedef struct chunk_s {
tile_t tiles[CHUNK_TILE_COUNT]; tile_t tiles[CHUNK_TILE_COUNT];
spritebatchsprite_t sprites[CHUNK_TILE_COUNT]; spritebatchsprite_t sprites[CHUNK_TILE_COUNT];
color_t testColor;
uint32_t spriteCount; uint32_t spriteCount;
// uint8_t meshCount; // uint8_t meshCount;
+133 -57
View File
@@ -16,68 +16,86 @@ map_t MAP;
errorret_t mapInit() { errorret_t mapInit() {
memoryZero(&MAP, sizeof(map_t)); memoryZero(&MAP, sizeof(map_t));
errorOk();
}
bool_t mapIsLoaded() {
return MAP.filePath[0] != '\0';
}
errorret_t mapLoad(const char_t *path, const chunkpos_t position) {
assertStrLenMin(path, 1, "Map file path cannot be empty");
assertStrLenMax(path, MAP_FILE_PATH_MAX - 1, "Map file path too long");
if(stringCompare(MAP.filePath, path) == 0) {
// Same map, no need to reload
errorOk();
}
chunkindex_t i;
// Unload all loaded chunks
if(mapIsLoaded()) {
for(i = 0; i < MAP_CHUNK_COUNT; i++) {
mapChunkUnload(&MAP.chunks[i]);
}
}
// Store the map file path
stringCopy(MAP.filePath, path, MAP_FILE_PATH_MAX);
// Determine directory path (it is dirname)
stringCopy(MAP.dirPath, path, MAP_FILE_PATH_MAX);
char_t *last = stringFindLastChar(MAP.dirPath, '/');
if(last == NULL) errorThrow("Invalid map file path");
// Store filename, sans extension
stringCopy(MAP.fileName, last + 1, MAP_FILE_PATH_MAX);
*last = '\0'; // Terminate to get directory path
last = stringFindLastChar(MAP.fileName, '.');
if(last == NULL) errorThrow("Map file name has no extension");
*last = '\0'; // Terminate to remove extension
// Reset map position
MAP.chunkPosition = position;
// Perform "initial load" // Perform "initial load"
i = 0; MAP.loaded = true;
int32_t i = 0;
for(chunkunit_t z = 0; z < MAP_CHUNK_DEPTH; z++) { for(chunkunit_t z = 0; z < MAP_CHUNK_DEPTH; z++) {
for(chunkunit_t y = 0; y < MAP_CHUNK_HEIGHT; y++) { for(chunkunit_t y = 0; y < MAP_CHUNK_HEIGHT; y++) {
for(chunkunit_t x = 0; x < MAP_CHUNK_WIDTH; x++) { for(chunkunit_t x = 0; x < MAP_CHUNK_WIDTH; x++) {
chunk_t *chunk = &MAP.chunks[i]; chunk_t *chunk = &MAP.chunks[i];
chunk->position.x = x + position.x; chunk->position.x = x + MAP.chunkPosition.x;
chunk->position.y = y + position.y; chunk->position.y = y + MAP.chunkPosition.y;
chunk->position.z = z + position.z; chunk->position.z = z + MAP.chunkPosition.z;
MAP.chunkOrder[i] = chunk; MAP.chunkOrder[i] = chunk;
errorChain(mapChunkLoad(chunk)); errorChain(mapChunkLoad(chunk));
i++; i++;
} }
} }
} }
errorOk(); errorOk();
} }
bool_t mapIsLoaded() {
return MAP.loaded;
}
// errorret_t mapLoad(const char_t *path, const chunkpos_t position) {
// assertStrLenMin(path, 1, "Map file path cannot be empty");
// assertStrLenMax(path, MAP_FILE_PATH_MAX - 1, "Map file path too long");
// if(stringCompare(MAP.filePath, path) == 0) {
// // Same map, no need to reload
// errorOk();
// }
// chunkindex_t i;
// // Unload all loaded chunks
// if(mapIsLoaded()) {
// for(i = 0; i < MAP_CHUNK_COUNT; i++) {
// mapChunkUnload(&MAP.chunks[i]);
// }
// }
// // Store the map file path
// stringCopy(MAP.filePath, path, MAP_FILE_PATH_MAX);
// // Determine directory path (it is dirname)
// stringCopy(MAP.dirPath, path, MAP_FILE_PATH_MAX);
// char_t *last = stringFindLastChar(MAP.dirPath, '/');
// if(last == NULL) errorThrow("Invalid map file path");
// // Store filename, sans extension
// stringCopy(MAP.fileName, last + 1, MAP_FILE_PATH_MAX);
// *last = '\0'; // Terminate to get directory path
// last = stringFindLastChar(MAP.fileName, '.');
// if(last == NULL) errorThrow("Map file name has no extension");
// *last = '\0'; // Terminate to remove extension
// // Reset map position
// MAP.chunkPosition = position;
// // Perform "initial load"
// i = 0;
// for(chunkunit_t z = 0; z < MAP_CHUNK_DEPTH; z++) {
// for(chunkunit_t y = 0; y < MAP_CHUNK_HEIGHT; y++) {
// for(chunkunit_t x = 0; x < MAP_CHUNK_WIDTH; x++) {
// chunk_t *chunk = &MAP.chunks[i];
// chunk->position.x = x + position.x;
// chunk->position.y = y + position.y;
// chunk->position.z = z + position.z;
// MAP.chunkOrder[i] = chunk;
// errorChain(mapChunkLoad(chunk));
// i++;
// }
// }
// }
// errorOk();
// }
errorret_t mapPositionSet(const chunkpos_t newPos) { errorret_t mapPositionSet(const chunkpos_t newPos) {
if(!mapIsLoaded()) errorThrow("No map loaded"); if(!mapIsLoaded()) errorThrow("No map loaded");
@@ -177,21 +195,79 @@ void mapChunkUnload(chunk_t* chunk) {
entity->type = ENTITY_TYPE_NULL; entity->type = ENTITY_TYPE_NULL;
} }
for(uint8_t i = 0; i < chunk->meshCount; i++) { // for(uint8_t i = 0; i < chunk->meshCount; i++) {
if(chunk->meshes[i].vertexCount == 0) continue; // if(chunk->meshes[i].vertexCount == 0) continue;
meshDispose(&chunk->meshes[i]); // meshDispose(&chunk->meshes[i]);
} // }
} }
errorret_t mapChunkLoad(chunk_t* chunk) { errorret_t mapChunkLoad(chunk_t* chunk) {
if(!mapIsLoaded()) errorThrow("No map loaded"); if(!mapIsLoaded()) errorThrow("No map loaded");
char_t buffer[64]; color_t color = COLOR_WHITE;
if(chunk->position.y % 2 == 0) {
// TODO: Can probably move this to asset load logic? if(chunk->position.x % 2 == 0) {
chunk->meshCount = 0; color = COLOR_BLACK;
memoryZero(chunk->meshes, sizeof(chunk->meshes)); } else {
color = COLOR_WHITE;
}
} else {
if(chunk->position.x % 2 == 0) {
color = COLOR_WHITE;
} else {
color = COLOR_BLACK;
}
}
// if(chunk->position.x == 0 && chunk->position.y == 0 && chunk->position.z == 0) {
// color = COLOR_RED;
// }
chunk->testColor = color;
memorySet(chunk->tiles, TILE_SHAPE_GROUND, sizeof(chunk->tiles));
memoryZero(chunk->sprites, sizeof(chunk->sprites));
memorySet(chunk->entities, 0xFF, sizeof(chunk->entities)); memorySet(chunk->entities, 0xFF, sizeof(chunk->entities));
chunk->spriteCount = 0;
if(chunk->position.z != 0) {
errorOk();
}
// Set Chunk sprites.
uint32_t i = 0;
vec3 spriteMin = {
chunk->position.x * CHUNK_WIDTH,
chunk->position.y * CHUNK_HEIGHT,
chunk->position.z * CHUNK_DEPTH
};
for(uint8_t x = 0; x < CHUNK_WIDTH; x++) {
for(uint8_t y = 0; y < CHUNK_HEIGHT; y++) {
glm_vec3_copy(spriteMin, chunk->sprites[i].min);
glm_vec3_add(
chunk->sprites[i].min,
(vec3){ x, y, 0 },
chunk->sprites[i].min
);
glm_vec3_copy(chunk->sprites[i].min, chunk->sprites[i].max);
glm_vec3_add(
chunk->sprites[i].max,
(vec3){ 1, 1, 0 },
chunk->sprites[i].max
);
glm_vec2_copy((vec2){ 0, 0 }, chunk->sprites[i].uvMin);
glm_vec2_copy((vec2){ 1, 1 }, chunk->sprites[i].uvMax);
chunk->spriteCount++;
i++;
}
}
// char_t buffer[64];
// TODO: Can probably move this to asset load logic?
// chunk->meshCount = 0;
// memoryZero(chunk->meshes, sizeof(chunk->meshes));
// memorySet(chunk->entities, 0xFF, sizeof(chunk->entities));
// Load. // Load.
errorOk(); errorOk();
+8 -7
View File
@@ -11,9 +11,10 @@
#define MAP_FILE_PATH_MAX 128 #define MAP_FILE_PATH_MAX 128
typedef struct map_s { typedef struct map_s {
char_t filePath[MAP_FILE_PATH_MAX]; // char_t filePath[MAP_FILE_PATH_MAX];
char_t dirPath[MAP_FILE_PATH_MAX]; // char_t dirPath[MAP_FILE_PATH_MAX];
char_t fileName[MAP_FILE_PATH_MAX]; // char_t fileName[MAP_FILE_PATH_MAX];
bool_t loaded;
chunk_t chunks[MAP_CHUNK_COUNT]; chunk_t chunks[MAP_CHUNK_COUNT];
chunk_t *chunkOrder[MAP_CHUNK_COUNT]; chunk_t *chunkOrder[MAP_CHUNK_COUNT];
@@ -43,10 +44,10 @@ bool_t mapIsLoaded();
* @param position The initial chunk position. * @param position The initial chunk position.
* @return An error code. * @return An error code.
*/ */
errorret_t mapLoad( // errorret_t mapLoad(
const char_t *path, // const char_t *path,
const chunkpos_t position // const chunkpos_t position
); // );
/** /**
* Updates the map. * Updates the map.
+1 -1
View File
@@ -12,7 +12,7 @@
typedef enum { typedef enum {
TILE_SHAPE_NULL, TILE_SHAPE_NULL,
TILE_SHAPE_SOLID, TILE_SHAPE_GROUND,
TILE_SHAPE_RAMP_NORTH, TILE_SHAPE_RAMP_NORTH,
TILE_SHAPE_RAMP_SOUTH, TILE_SHAPE_RAMP_SOUTH,
TILE_SHAPE_RAMP_EAST, TILE_SHAPE_RAMP_EAST,
+5 -3
View File
@@ -26,14 +26,16 @@ errorret_t rpgInit(void) {
rpgCameraInit(); rpgCameraInit();
rpgTextboxInit(); rpgTextboxInit();
// Init world
errorChain(mapPositionSet((chunkpos_t){ 0, 0, 0 }));
// TEST: Create some entities. // TEST: Create some entities.
uint8_t entIndex = entityGetAvailable(); uint8_t entIndex = entityGetAvailable();
assertTrue(entIndex != 0xFF, "No available entity slots!."); assertTrue(entIndex != 0xFF, "No available entity slots!.");
entity_t *ent = &ENTITIES[entIndex]; entity_t *ent = &ENTITIES[entIndex];
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.x = 2, ent->position.y = 2;
// All Good! // All Good!
errorOk(); errorOk();
+14 -20
View File
@@ -11,44 +11,38 @@
#include "rpg/overworld/map.h" #include "rpg/overworld/map.h"
#include "assert/assert.h" #include "assert/assert.h"
#include "display/screen/screen.h"
rpgcamera_t RPG_CAMERA; rpgcamera_t RPG_CAMERA;
void rpgCameraInit(void) { void rpgCameraInit(void) {
memoryZero(&RPG_CAMERA, sizeof(rpgcamera_t)); memoryZero(&RPG_CAMERA, sizeof(rpgcamera_t));
} }
errorret_t rpgCameraUpdate(void) { worldpos_t rpgCameraGetPosition(void) {
glm_lookat(
(vec3){ 3, 3, 3 },
(vec3){ 0, 0, 0 }, // center
(vec3){ 0, 1, 0 }, // up
RPG_CAMERA.eye
);
if(!mapIsLoaded()) errorOk();
chunkpos_t chunkPos;
switch(RPG_CAMERA.mode) { switch(RPG_CAMERA.mode) {
case RPG_CAMERA_MODE_FREE: case RPG_CAMERA_MODE_FREE:
worldPosToChunkPos(&RPG_CAMERA.free, &chunkPos); return RPG_CAMERA.free;
break;
case RPG_CAMERA_MODE_FOLLOW_ENTITY: { case RPG_CAMERA_MODE_FOLLOW_ENTITY: {
entity_t *entity = &ENTITIES[RPG_CAMERA.followEntity.followEntityId]; entity_t *entity = &ENTITIES[RPG_CAMERA.followEntity.followEntityId];
if(entity->type == ENTITY_TYPE_NULL) { if(entity->type == ENTITY_TYPE_NULL) {
errorOk(); return (worldpos_t){ 0, 0, 0 };
} }
return entity->position;
// Update map position to match camera. By default map wants to know the
// top left but we want to set the center, so we need to sub half map size
worldPosToChunkPos(&entity->position, &chunkPos);
break;
} }
default: default:
assertUnreachable("Invalid RPG camera mode"); assertUnreachable("Invalid RPG camera mode");
} }
}
errorret_t rpgCameraUpdate(void) {
if(!mapIsLoaded()) errorOk();
chunkpos_t chunkPos;
worldpos_t worldPos = rpgCameraGetPosition();
worldPosToChunkPos(&worldPos, &chunkPos);
errorChain(mapPositionSet((chunkpos_t){ errorChain(mapPositionSet((chunkpos_t){
.x = chunkPos.x - (MAP_CHUNK_WIDTH / 2), .x = chunkPos.x - (MAP_CHUNK_WIDTH / 2),
+8
View File
@@ -19,6 +19,7 @@ typedef struct {
union { union {
worldpos_t free; worldpos_t free;
struct { struct {
uint8_t followEntityId; uint8_t followEntityId;
} followEntity; } followEntity;
@@ -34,6 +35,13 @@ extern rpgcamera_t RPG_CAMERA;
*/ */
void rpgCameraInit(void); void rpgCameraInit(void);
/**
* Gets the RPG camera's position.
*
* @return The RPG camera's position.
*/
worldpos_t rpgCameraGetPosition(void);
/** /**
* Updates the RPG camera. * Updates the RPG camera.
* *
+103 -18
View File
@@ -14,6 +14,8 @@
#include "display/shader/shaderunlit.h" #include "display/shader/shaderunlit.h"
#include "display/spritebatch/spritebatch.h" #include "display/spritebatch/spritebatch.h"
#include "rpg/overworld/map.h"
#include "rpg/entity/entity.h"
#include "rpg/rpgcamera.h" #include "rpg/rpgcamera.h"
errorret_t sceneOverworldInit(scenedata_t *sceneData) { errorret_t sceneOverworldInit(scenedata_t *sceneData) {
@@ -39,38 +41,121 @@ errorret_t sceneOverworldRender(scenedata_t *sceneData) {
glm_mat4_identity(model); glm_mat4_identity(model);
glm_perspective(
glm_rad(45),
(float)SCREEN.width / (float)SCREEN.height,
0.1f,
100.0f,
proj
);
errorChain(shaderBind(&SHADER_UNLIT)); errorChain(shaderBind(&SHADER_UNLIT));
errorChain(shaderSetTexture(&SHADER_UNLIT, SHADER_UNLIT_TEXTURE, &TEXTURE_TEST)); errorChain(shaderSetTexture(&SHADER_UNLIT, SHADER_UNLIT_TEXTURE, &TEXTURE_TEST));
errorChain(shaderSetColor(&SHADER_UNLIT, SHADER_UNLIT_COLOR, COLOR_WHITE)); errorChain(shaderSetColor(&SHADER_UNLIT, SHADER_UNLIT_COLOR, COLOR_WHITE));
errorChain(shaderSetMatrix(&SHADER_UNLIT, SHADER_UNLIT_MODEL, model)); errorChain(shaderSetMatrix(&SHADER_UNLIT, SHADER_UNLIT_MODEL, model));
errorChain(shaderSetMatrix(&SHADER_UNLIT, SHADER_UNLIT_VIEW, RPG_CAMERA.eye));
// Camera projection
float_t fov = glm_rad(45.0f);
glm_perspective(
fov,
(float_t)SCREEN.width / (float_t)SCREEN.height,
0.1f,
100.0f,
proj
);
errorChain(shaderSetMatrix(&SHADER_UNLIT, SHADER_UNLIT_PROJECTION, proj)); errorChain(shaderSetMatrix(&SHADER_UNLIT, SHADER_UNLIT_PROJECTION, proj));
// Camera Eye
float_t pixelsPerUnit = 16.0f;
float_t worldH = (float)SCREEN.height / pixelsPerUnit;
float_t z = (worldH * 0.5f) / tanf(fov * 0.5f);
worldpos_t worldPos = rpgCameraGetPosition();
float_t offset = 16.0f;
vec3 worldPosVec = {
worldPos.x,
worldPos.y,
worldPos.z
};
glm_vec3_add(worldPosVec, (vec3){ 0.5f, 0.5f, 0.5f }, worldPosVec);
glm_lookat(
(vec3){
worldPosVec[0],
worldPosVec[1] + offset,
worldPosVec[2] + z
},
worldPosVec,
(vec3){ 0, -1, 0 }, // up
RPG_CAMERA.eye
);
errorChain(shaderSetMatrix(&SHADER_UNLIT, SHADER_UNLIT_VIEW, RPG_CAMERA.eye));
// Chunk Data
{ {
uint8_t spriteCount = 1; shadermaterial_t chunkMaterial = {
spritebatchsprite_t sprites[spriteCount]; .unlit = {
.color = COLOR_WHITE
}
};
glm_vec3_copy((vec3){ -0.5f, -0.5f, 0 }, sprites[0].min); uint32_t i = 0;
glm_vec3_copy((vec3){ 0.5f, 0.5f, 0 }, sprites[0].max); for(uint8_t x = 0; x < MAP_CHUNK_WIDTH; x++) {
glm_vec2_copy((vec2){ 0, 0 }, sprites[0].uvMin); for(uint8_t y = 0; y < MAP_CHUNK_HEIGHT; y++) {
glm_vec2_copy((vec2){ 1, 1 }, sprites[0].uvMax); for(uint8_t z = 0; z < MAP_CHUNK_DEPTH; z++) {
chunk_t *chunk = &MAP.chunks[i];
if(chunk->spriteCount == 0) {
i++;
continue;
}
shadermaterial_t material; chunkMaterial.unlit.color = chunk->testColor;
material.unlit.color = COLOR_WHITE;
material.unlit.texture = &TEXTURE_TEST; spriteBatchBuffer(
chunk->sprites, chunk->spriteCount, &SHADER_UNLIT, chunkMaterial
);
i++;
}
}
}
spriteBatchBuffer(sprites, spriteCount, &SHADER_UNLIT, material);
spriteBatchFlush(); spriteBatchFlush();
} }
// Entities
{
uint8_t spriteCount = 0;
spritebatchsprite_t sprites[ENTITY_COUNT];
for(uint8_t i = 0; i < ENTITY_COUNT; i++) {
entity_t *ent = &ENTITIES[i];
if(ent->type == ENTITY_TYPE_NULL) continue;
vec3 position = {
ent->position.x,
ent->position.y,
((float_t)ent->position.z) + 0.01f
};
glm_vec3_copy(position, sprites[spriteCount].min);
glm_vec3_copy(position, sprites[spriteCount].max);
glm_vec3_add(
sprites[spriteCount].max,
(vec3){ 1, 1, 0 },
sprites[spriteCount].max
);
glm_vec2_copy((vec2){ 0, 0 }, sprites[spriteCount].uvMin);
glm_vec2_copy((vec2){ 1, 1 }, sprites[spriteCount].uvMax);
spriteCount++;
}
if(spriteCount) {
shadermaterial_t material = {
.unlit = {
.color = COLOR_CYAN,
.texture = NULL
}
};
// material.unlit.texture = &TEXTURE_TEST;
spriteBatchBuffer(sprites, spriteCount, &SHADER_UNLIT, material);
spriteBatchFlush();
}
}
errorOk(); errorOk();
} }