Compare commits

...

4 Commits

Author SHA1 Message Date
En Yi cb9508a4b6 Update README 2024-08-24 14:43:52 +08:00
En Yi 10d48c1d70 Allow optional inits for scene
Particle system and entity management are now optional during scene
init.
2024-08-24 14:42:11 +08:00
En Yi 8645bbd963 Use heaptrack for memory profiling on main 2024-08-24 14:41:10 +08:00
En Yi add592afd2 Refactor out entity removal function
This allows a singular entity removal method for the game
2024-08-24 12:43:49 +08:00
14 changed files with 88 additions and 48 deletions

1
.gitignore vendored
View File

@ -4,3 +4,4 @@ release/
web/
compile_commands.json
.gdb_history
heaptrack.*

View File

@ -34,10 +34,9 @@ add_executable(${PROJECT_NAME}
main.c
)
if (NOT EMSCRIPTEN)
target_compile_options(${PROJECT_NAME} PRIVATE -fsanitize=address -gdwarf-4)
target_link_options(${PROJECT_NAME} PRIVATE -fsanitize=address -gdwarf-4)
endif ()
# Use Heaptrack to profile the main application
# Do not compile in ASAN
target_include_directories(${PROJECT_NAME}
PRIVATE
${CMAKE_CURRENT_LIST_DIR}

View File

@ -11,13 +11,13 @@ The goal of the project is to make a game similar to Hannah and The Pirate Caves
## Implementation Notes
As mentioned, this is mostly a learning experience, so certain things are implemented for the sake of learning. However, there are certain things that I didn't bother with because dealing with them will open up another can of worms. Either that or I find it too large of a feature to implement on my own without hating my existance. For these, I will use libraries.
As this point of time (23/11/2023), these are the things I won't bother for now:
As this point of time (24/08/2024), these are the things I won't bother for now:
- Game rendering: I remember dealing with OpenGL for a little bit and it being a not-so-pleasant experience. This is also a large topic on its own too.
- Camera System: This is an interesting one. I would really like to implement this. However, this is sort of tied to game rendering, so no.
- Windows and raw input handling: Not keen on this.
- GUI: I'm not about to roll my own GUI library for this.
- GUI: I'm not about to roll my own GUI library for this. I'll do a lite version, but that's about it.
- Data structures: Will only do it for specific reasons. Otherwise, use a library.
- Level editor: ... no. This is more towards GUI design which I'm not currently keen on.
- Level editor: ... maybe. I already have a sandbox, so maybe I can turn that into an editor lite??? Won't think too much about it for now.
Libraries/Tools used:
- _raylib_ \[[link](https://github.com/raysan5/raylib)\] + _raygui_ \[[link](https://github.com/raysan5/raygui)\]: MVP of this project. Basically the backbone of the game. I've use it for some past projects and it's always a pleasant experience. Can recommend!
@ -29,14 +29,16 @@ Libraries/Tools used:
- _LDtk_ \[[link](https://ldtk.io/)\]: A nice level editor. I haven't use it to its fullest extent, but definitely having a good experience so far.
- _Aseprite_ \[[link](https://www.aseprite.org/)\]: Used it to create sprites. Good tool!
- _Emscripten_ \[[link](https://emscripten.org/)\]: For web build. It's really a marvel of technology!
- _heaptrack_ \[[link](https://github.com/KDE/heaptrack)\]: For heap profiling. Simple and straightforward to use.
## Progress
The engine features:
- An Entity-Component framework with an Entity Manager + memory pool that is specific for this project
- AABB collision system + Grid-based Broad phase collision detection
- Scene management and transition
- Scene tree management and transition
- Assets management and sprite transition
- Simple level loading
- Simple Particle effects management
Current progress:
- Simple main menu + sandbox scene
@ -47,7 +49,6 @@ Current progress:
- Chest and Level Ending
- Arrows and dynamites
- Water filling
- Simple Particle Effects
- Sound Effects
- Simple Camera Update System
- Demo level pack loading
@ -68,5 +69,12 @@ You may also turn off `BUILD_TESTING` to avoid building the tests.
There are also other binaries generated for testing purposes. Feel free to try them out if you manage to build them.
## Debugging && Profiling
All binaries except the _main_ one are built with ASAN, which helps to detect memory leakage.
_Heaptrack_ is used to do so for the main program to keep for memory leakage + heap usage. From my experience, it doesn't work with ASAN.
I'm looking for runtime profiler. Current candidates are: _Orbit_ and _tracey_. For small-ish gameplay, the program still runs fine.
## Note on assets
This repository will not contain the assets used in the game, such as fonts, art, and sfx. However, the program should still run without those. Assets are placed in the _res/_ directory.

View File

@ -150,13 +150,20 @@ void update_sfx_list(GameEngine_t* engine)
engine->sfx_list.played_sfx = 0;
}
void init_scene(Scene_t* scene, action_func_t action_func)
void init_scene(Scene_t* scene, action_func_t action_func, uint32_t subsystem_init)
{
sc_map_init_64(&scene->action_map, 32, 0);
sc_array_init(&scene->systems);
init_entity_manager(&scene->ent_manager);
init_particle_system(&scene->part_sys);
if (subsystem_init & ENABLE_ENTITY_MANAGEMENT_SYSTEM)
{
init_entity_manager(&scene->ent_manager);
}
if (subsystem_init & ENABLE_PARTICLE_SYSTEM)
{
init_particle_system(&scene->part_sys);
}
scene->subsystem_init = subsystem_init;
//scene->scene_type = scene_type;
scene->layers.n_layers = 0;
scene->bg_colour = WHITE;
@ -184,8 +191,16 @@ void free_scene(Scene_t* scene)
{
UnloadRenderTexture(scene->layers.render_layers[i].layer_tex);
}
free_entity_manager(&scene->ent_manager);
deinit_particle_system(&scene->part_sys);
if (scene->subsystem_init & ENABLE_ENTITY_MANAGEMENT_SYSTEM)
{
free_entity_manager(&scene->ent_manager);
}
if (scene->subsystem_init & ENABLE_PARTICLE_SYSTEM)
{
deinit_particle_system(&scene->part_sys);
}
}
inline void update_scene(Scene_t* scene, float delta_time)
@ -198,7 +213,10 @@ inline void update_scene(Scene_t* scene, float delta_time)
{
sys(scene);
}
update_particle_system(&scene->part_sys, scene->delta_time);
if (scene->subsystem_init & ENABLE_PARTICLE_SYSTEM)
{
update_particle_system(&scene->part_sys, scene->delta_time);
}
}
static void _internal_render_scene(Scene_t* scene)

View File

@ -60,6 +60,7 @@ typedef struct SceneRenderLayers {
struct Scene {
// Not all scene needs an entity manager
// but too late to change this
uint32_t subsystem_init;
EntityManager_t ent_manager;
Scene_t* parent_scene;
struct sc_map_64 action_map; // key -> actions
@ -99,7 +100,10 @@ extern void update_scene(Scene_t* scene, float delta_time);
extern void render_scene(Scene_t* scene);
extern void do_action(Scene_t* scene, ActionType_t action, bool pressed);
void init_scene(Scene_t* scene, action_func_t action_func);
//void init_scene(Scene_t* scene, action_func_t action_func);
#define ENABLE_ENTITY_MANAGEMENT_SYSTEM (1)
#define ENABLE_PARTICLE_SYSTEM (1 << 1)
void init_scene(Scene_t* scene, action_func_t action_func, uint32_t subsystem_init);
bool add_scene_layer(Scene_t* scene, int width, int height, Rectangle render_area);
void free_scene(Scene_t* scene);
void add_child_scene(GameEngine_t* engine, unsigned int child_idx, unsigned int parent_idx);

View File

@ -109,7 +109,7 @@ int main(void)
for (uint8_t i = 0; i < 6; ++i)
{
scenes[i] = &dummy_scenes[i].scene;
init_scene(&dummy_scenes[i].scene, &level_do_action);
init_scene(&dummy_scenes[i].scene, &level_do_action, 0);
dummy_scenes[i].scene.engine = &engine;
dummy_scenes[i].number = i;
add_scene_layer(

View File

@ -1146,7 +1146,7 @@ static void at_level_complete(Scene_t* scene)
void init_sandbox_scene(LevelScene_t* scene)
{
init_scene(&scene->scene, &level_do_action);
init_scene(&scene->scene, &level_do_action, ENABLE_ENTITY_MANAGEMENT_SYSTEM | ENABLE_PARTICLE_SYSTEM);
init_entity_tag_map(&scene->scene.ent_manager, PLAYER_ENT_TAG, 4);
init_entity_tag_map(&scene->scene.ent_manager, BOULDER_ENT_TAG, MAX_COMP_POOL_SIZE);
init_entity_tag_map(&scene->scene.ent_manager, LEVEL_END_TAG, 16);

View File

@ -478,7 +478,7 @@ static void at_level_complete(Scene_t* scene)
void init_game_scene(LevelScene_t* scene)
{
init_scene(&scene->scene, &level_do_action);
init_scene(&scene->scene, &level_do_action, ENABLE_ENTITY_MANAGEMENT_SYSTEM | ENABLE_PARTICLE_SYSTEM);
init_entity_tag_map(&scene->scene.ent_manager, PLAYER_ENT_TAG, 4);
init_entity_tag_map(&scene->scene.ent_manager, BOULDER_ENT_TAG, MAX_COMP_POOL_SIZE);
init_entity_tag_map(&scene->scene.ent_manager, LEVEL_END_TAG, 16);

View File

@ -1,8 +1,12 @@
#include "game_systems.h"
#include "particle_sys.h"
#include "scene_impl.h"
#include "ent_impl.h"
#include "particle_sys.h"
#include "AABB.h"
#include "EC.h"
#include "constants.h"
#include <stdio.h>
@ -48,8 +52,8 @@ static inline void destroy_tile(LevelSceneData_t* lvl_data, unsigned int tile_id
.spr = spr,
.config = get_emitter_conf(&scene->engine->assets, "pe_burst"),
.position = {
.x = tile_idx % tilemap.width * tilemap.tile_size + tilemap.tile_size / 2,
.y = tile_idx / tilemap.width * tilemap.tile_size + tilemap.tile_size / 2,
.x = tile_idx % tilemap.width * tilemap.tile_size + (tilemap.tile_size >> 1),
.y = tile_idx / tilemap.width * tilemap.tile_size + (tilemap.tile_size >> 1),
},
.n_particles = 5,
.user_data = CONTAINER_OF(lvl_data, LevelScene_t, data),
@ -71,6 +75,7 @@ static bool check_collision_and_move(
SolidType_t other_solid
)
{
// TODO: Need some NULL checks to be more robust
CTransform_t* p_ct = get_component(ent, CTRANSFORM_COMP_T);
CBBox_t* p_bbox = get_component(ent, CBBOX_COMP_T);
Vector2 overlap = {0,0};
@ -149,24 +154,14 @@ collision_end:
return overlap_mode > 0;
}
void destroy_entity(Scene_t* scene, TileGrid_t* tilemap, Entity_t* p_ent)
static void destroy_entity(Scene_t* scene, TileGrid_t* tilemap, Entity_t* p_ent)
{
/* Use the helper function to remove any entity
* This is because some components may have deinit steps
* This function will also take care of the tilemap collision handling
* */
Vector2 half_size = {0,0};
CBBox_t* p_bbox = get_component(p_ent, CBBOX_COMP_T);
if (p_bbox != NULL)
{
half_size = p_bbox->half_size;
}
CEmitter_t* p_emitter = get_component(p_ent, CEMITTER_T);
if (p_emitter != NULL)
{
unload_emitter_handle(&scene->part_sys, p_emitter->handle);
}
if (p_ent->m_tag == BOULDER_ENT_TAG)
{
@ -236,7 +231,7 @@ void destroy_entity(Scene_t* scene, TileGrid_t* tilemap, Entity_t* p_ent)
play_sfx(scene->engine, ARROW_DESTROY_SFX);
}
remove_entity_from_tilemap(&scene->ent_manager, tilemap, p_ent);
clear_an_entity(scene, tilemap, p_ent);
}
void check_player_dead_system(Scene_t* scene)
@ -564,7 +559,6 @@ void player_crushing_system(Scene_t* scene)
Entity_t* p_player;
sc_map_foreach_value(&scene->ent_manager.entities_map[PLAYER_ENT_TAG], p_player)
{
// TODO: Use BBox for crushing check only if not noclip
CBBox_t* p_bbox = get_component(p_player, CBBOX_COMP_T);
uint8_t edges = check_bbox_edges(

View File

@ -104,7 +104,7 @@ static void level_select_do_action(Scene_t* scene, ActionType_t action, bool pre
#define SCROLL_TOTAL_HEIGHT 800
void init_level_select_scene(LevelSelectScene_t* scene)
{
init_scene(&scene->scene, &level_select_do_action);
init_scene(&scene->scene, &level_select_do_action, 0);
add_scene_layer(
&scene->scene, 400, 800,
(Rectangle){START_X, START_Y, 400, 800}

View File

@ -138,7 +138,7 @@ static void gui_loop(Scene_t* scene)
void init_menu_scene(MenuScene_t* scene)
{
init_scene(&scene->scene, &menu_do_action);
init_scene(&scene->scene, &menu_do_action, 0);
sc_array_add(&scene->scene.systems, &gui_loop);
sc_array_add(&scene->scene.systems, &menu_scene_render_func);

View File

@ -94,6 +94,7 @@ void free_game_scene(LevelScene_t* scene);
void init_sandbox_scene(LevelScene_t* scene);
void free_sandbox_scene(LevelScene_t* scene);
void init_level_scene_data(LevelSceneData_t* data, uint32_t max_tiles, Tile_t* tiles, Rectangle view_zone);
void clear_an_entity(Scene_t* scene, TileGrid_t* tilemap, Entity_t* p_ent);
void clear_all_game_entities(LevelScene_t* scene);
void term_level_scene_data(LevelSceneData_t* data);
void reload_level_tilemap(LevelScene_t* scene);

View File

@ -53,22 +53,37 @@ void term_level_scene_data(LevelSceneData_t* data)
}
}
void clear_an_entity(Scene_t* scene, TileGrid_t* tilemap, Entity_t* p_ent)
{
/* Use the helper function to remove any entity
* This is because some components may have deinit steps
* This function will also take care of the tilemap collision handling
* */
CEmitter_t* p_emitter = get_component(p_ent, CEMITTER_T);
if (p_emitter != NULL)
{
unload_emitter_handle(&scene->part_sys, p_emitter->handle);
}
if (get_component(p_ent, CWATERRUNNER_T)!= NULL)
{
free_water_runner(p_ent, &scene->ent_manager);
}
remove_entity_from_tilemap(&scene->ent_manager, tilemap, p_ent);
}
void clear_all_game_entities(LevelScene_t* scene)
{
Entity_t* ent;
sc_map_foreach_value(&scene->scene.ent_manager.entities, ent)
{
CWaterRunner_t* p_crunner = get_component(ent, CWATERRUNNER_T);
if (p_crunner != NULL)
{
free_water_runner(ent, &scene->scene.ent_manager);
}
CEmitter_t* p_emitter = get_component(ent, CEMITTER_T);
if (p_emitter != NULL)
{
unload_emitter_handle(&scene->scene.part_sys, p_emitter->handle);
}
clear_an_entity(&scene->scene, &scene->data.tilemap, ent);
}
// This is unnecessary as the first pass should clear everything
// For now, leave it in. This is not expected to call all the time
// so not too bad
clear_entity_manager(&scene->scene.ent_manager);
for (size_t i = 0; i < scene->data.tilemap.n_tiles;i++)
{

View File

@ -400,7 +400,7 @@ int main(void)
LevelScene_t scene;
scene.scene.engine = &engine;
init_scene(&scene.scene, &level_do_action);
init_scene(&scene.scene, &level_do_action, ENABLE_ENTITY_MANAGEMENT_SYSTEM);
init_entity_tag_map(&scene.scene.ent_manager, PLAYER_ENT_TAG, 4);
init_entity_tag_map(&scene.scene.ent_manager, DYNMEM_ENT_TAG, 16);
init_level_scene_data(