Add scene hierachy feature

This allows scene to have children but can only have one parent

There is only one focused scene to get input

Update and Render order is child first and next
scene_man
En Yi 2024-06-27 21:36:55 +08:00
parent 0653cbd0a2
commit 921b59d2ab
7 changed files with 451 additions and 6 deletions

View File

@ -155,6 +155,19 @@ target_link_libraries(particle_test
${GAME_LIBS} ${GAME_LIBS}
) )
add_executable(scene_man_test
scene_man_test.c
)
target_include_directories(scene_man_test
PRIVATE
${CMAKE_CURRENT_LIST_DIR}
)
target_compile_options(scene_man_test PRIVATE -fsanitize=address -gdwarf-4)
target_link_options(scene_man_test PRIVATE -fsanitize=address -gdwarf-4)
target_link_libraries(scene_man_test
${GAME_LIBS}
)
if (BUILD_TESTING) if (BUILD_TESTING)
find_package(cmocka 1.1.0 REQUIRED) find_package(cmocka 1.1.0 REQUIRED)
add_subdirectory(tests) add_subdirectory(tests)

View File

@ -27,6 +27,7 @@ target_link_libraries(lib_engine
zstd zstd
raylib raylib
sc_queue sc_queue
sc_heap
sc_map sc_map
sc_array sc_array
m m

38
engine/README.md 100644
View File

@ -0,0 +1,38 @@
I suppose need to write these down to remind myself
# Scene Management
The engine provides a generic Scene struct that should be embedded in a another struct, which provide scene-specific data.
As such, the engine will manage pointers to Scene struct. When using the engine, it is expected to provide the a pointer to the embedded Scene struct to the engine for scene management. All scenes should be declared upfront and an array of the Scene pointers should be provided.
## What is a scene?
A scene is a data struct containing these major fields:
1. Entity Manager
2. Input-Action Map
3. Systems
4. Particle System
5. plus other fields
These fields exists to perform the scene update loop of:
1. Input handling
2. Scene Update
3. Scene Rendering
## Scene State
In this implementation, there will always be a 'root' scene at any given time. The program ends if there is no 'root' scene. A scene change/transition occurs when this 'root' scene is changed to another scene.
A scene has an active state and a render state.
- Active: Should it run its scene update procedures?
- Render: Should it be render?
Hence, it is possible to have a scene updated but hidden, or a paused scene (not updated but rendered).
There is also a 'focused' scene. This is the scene to intercept inputs and run its action update procedure. There can only be at most one such scene. Two or more scenes are not allowed to receive inputs in this implementation. It is possible to have zero focused scene.
# Scene Hierachy
A scene can have multiple children scenes but can have only one parent scene. This implemented as a intrusive linked-list.
The implementation assumes a single-threaded environment for simplicity. The scene update travesal logic is as such:
1. The current scene first
2. If the scene has a child scene, traverse that child first.
3. If the scene has a next scene, traverse there then.
4. Repeat from (1) until no possible scene is traversable

View File

@ -1,10 +1,14 @@
#include "engine.h" #include "engine.h"
#include "mempool.h" #include "mempool.h"
#include <math.h>
void init_engine(GameEngine_t* engine, Vector2 starting_win_size) void init_engine(GameEngine_t* engine, Vector2 starting_win_size)
{ {
InitAudioDevice(); InitAudioDevice();
sc_queue_init(&engine->key_buffer); sc_queue_init(&engine->key_buffer);
sc_queue_init(&engine->scene_stack);
sc_heap_init(&engine->scenes_render_order, 0);
engine->sfx_list.n_sfx = N_SFX; engine->sfx_list.n_sfx = N_SFX;
memset(engine->sfx_list.sfx, 0, engine->sfx_list.n_sfx * sizeof(SFX_t)); memset(engine->sfx_list.sfx, 0, engine->sfx_list.n_sfx * sizeof(SFX_t));
init_memory_pools(); init_memory_pools();
@ -18,6 +22,8 @@ void deinit_engine(GameEngine_t* engine)
term_assets(&engine->assets); term_assets(&engine->assets);
free_memory_pools(); free_memory_pools();
sc_queue_term(&engine->key_buffer); sc_queue_term(&engine->key_buffer);
sc_queue_term(&engine->scene_stack);
sc_heap_term(&engine->scenes_render_order);
CloseAudioDevice(); CloseAudioDevice();
CloseWindow(); CloseWindow();
} }
@ -216,7 +222,203 @@ inline void render_scene(Scene_t* scene)
EndDrawing(); EndDrawing();
} }
inline void do_action(Scene_t* scene, ActionType_t action, bool pressed) inline ActionResult do_action(Scene_t* scene, ActionType_t action, bool pressed)
{ {
scene->action_function(scene, action, pressed); return scene->action_function(scene, action, pressed);
}
static void process_scene_key_input(GameEngine_t* engine, int button, bool pressed)
{
if (engine->curr_scene == engine->max_scenes) return;
sc_queue_clear(&engine->scene_stack);
sc_queue_add_first(&engine->scene_stack, engine->scenes[engine->curr_scene]);
while (!sc_queue_empty(&engine->scene_stack))
{
Scene_t* scene = sc_queue_del_first(&engine->scene_stack);
ActionType_t action = sc_map_get_64(&scene->action_map, button);
ActionResult res = ACTION_PROPAGATE;
if (sc_map_found(&scene->action_map))
{
res = do_action(scene, action, pressed);
}
if (scene->child_scene.next != NULL)
{
sc_queue_add_first(&engine->scene_stack, scene->child_scene.next);
}
if (scene->child_scene.scene != NULL && res != ACTION_CONSUMED)
{
sc_queue_add_first(&engine->scene_stack, scene->child_scene.scene);
}
}
}
static void process_scene_mouse_input(GameEngine_t* engine, Vector2 pos, int button)
{
if (engine->curr_scene == engine->max_scenes) return;
sc_queue_clear(&engine->scene_stack);
sc_queue_add_first(&engine->scene_stack, engine->scenes[engine->curr_scene]);
while (!sc_queue_empty(&engine->scene_stack))
{
Scene_t* scene = sc_queue_del_first(&engine->scene_stack);
scene->mouse_pos = pos;
ActionResult res = ACTION_PROPAGATE;
ActionType_t action = sc_map_get_64(&scene->action_map, button);
if (sc_map_found(&scene->action_map))
{
if (IsMouseButtonDown(button))
{
res = do_action(scene, action, true);
}
else if (IsMouseButtonReleased(button))
{
res = do_action(scene, action, false);
}
}
if (scene->child_scene.next != NULL)
{
sc_queue_add_first(&engine->scene_stack, scene->child_scene.next);
}
if (scene->child_scene.scene != NULL && res != ACTION_CONSUMED)
{
sc_queue_add_first(&engine->scene_stack, scene->child_scene.scene);
}
}
}
void process_active_scene_inputs(GameEngine_t* engine)
{
if (engine->focused_scene == NULL) return;
process_inputs(engine, engine->focused_scene);
}
void update_curr_scene(GameEngine_t* engine)
{
if (engine->curr_scene == engine->max_scenes) return;
const float DT = 1.0f/60.0f;
float frame_time = GetFrameTime();
float delta_time = fminf(frame_time, DT);
sc_queue_clear(&engine->scene_stack);
sc_heap_clear(&engine->scenes_render_order);
sc_queue_add_first(&engine->scene_stack, engine->scenes[engine->curr_scene]);
while (!sc_queue_empty(&engine->scene_stack))
{
Scene_t* scene = sc_queue_del_first(&engine->scene_stack);
update_scene(scene, delta_time);
if (scene->child_scene.next != NULL)
{
sc_queue_add_first(&engine->scene_stack, scene->child_scene.next);
}
if (scene->child_scene.scene != NULL)
{
sc_queue_add_first(&engine->scene_stack, scene->child_scene.scene);
}
sc_heap_add(&engine->scenes_render_order, scene->depth_index, scene);
}
}
void render_curr_scene(GameEngine_t* engine)
{
struct sc_heap_data *elem;
BeginDrawing();
while ((elem = sc_heap_pop(&engine->scenes_render_order)) != NULL)
{
Scene_t* scene = elem->data;
for (uint8_t i = 0; i < scene->layers.n_layers; ++i)
{
RenderLayer_t* layer = scene->layers.render_layers + i;
Rectangle draw_rec = layer->render_area;
Vector2 draw_pos = {draw_rec.x, draw_rec.y};
draw_rec.x = 0;
draw_rec.y = 0;
draw_rec.height *= -1;
DrawTextureRec(
layer->layer_tex.texture,
draw_rec,
draw_pos,
WHITE
);
}
}
EndDrawing();
}
void add_child_scene(Scene_t* child, Scene_t* parent)
{
if (parent == NULL) return;
if (parent->child_scene.scene == NULL)
{
parent->child_scene.scene = child;
}
else
{
Scene_t* curr = parent->child_scene.scene;
while (curr->child_scene.next != NULL)
{
curr = curr->child_scene.next;
}
curr->child_scene.next = child;
}
child->parent_scene = parent;
}
void remove_child_scene(Scene_t* child)
{
if (child == NULL) return;
if (child->parent_scene == NULL) return;
Scene_t* parent = child->parent_scene;
if (parent->child_scene.scene == NULL) return;
Scene_t* prev = NULL;
Scene_t* curr = parent->child_scene.scene;
while (curr != NULL)
{
if (curr == child)
{
if (prev != NULL)
{
prev->child_scene.next = curr->child_scene.next;
}
else
{
parent->child_scene.scene = curr->child_scene.next;
}
break;
}
prev = curr;
curr = curr->child_scene.next;
}
child->parent_scene = NULL;
}
void change_active_scene(GameEngine_t* engine, unsigned int idx)
{
engine->scenes[engine->curr_scene]->state = SCENE_ENDED;
engine->curr_scene = idx;
engine->scenes[engine->curr_scene]->state = SCENE_PLAYING;
}
void change_focused_scene(GameEngine_t* engine, unsigned int idx)
{
engine->focused_scene = engine->scenes[idx];
} }

View File

@ -3,6 +3,7 @@
#include "actions.h" #include "actions.h"
#include "collisions.h" #include "collisions.h"
#include "sc/array/sc_array.h" #include "sc/array/sc_array.h"
#include "sc/heap/sc_heap.h"
#include "assets.h" #include "assets.h"
#include "particle_sys.h" #include "particle_sys.h"
@ -24,12 +25,14 @@ typedef struct SceneNode {
typedef struct GameEngine { typedef struct GameEngine {
Scene_t **scenes; Scene_t **scenes;
unsigned int max_scenes; unsigned int max_scenes;
unsigned int curr_scene; unsigned int curr_scene; // Current root scene
Scene_t* scenes_render_order[MAX_SCENES_TO_RENDER];
Assets_t assets; Assets_t assets;
SFXList_t sfx_list; SFXList_t sfx_list;
// Maintain own queue to handle key presses // Maintain own queue to handle key presses
Scene_t* focused_scene; // The one scene to receive key inputs
struct sc_queue_32 key_buffer; struct sc_queue_32 key_buffer;
struct sc_queue_ptr scene_stack;
struct sc_heap scenes_render_order;
// This is the original size of the window. // This is the original size of the window.
// This is in case of window scaling, where there needs to be // This is in case of window scaling, where there needs to be
// an absolute reference // an absolute reference
@ -89,7 +92,13 @@ void init_engine(GameEngine_t* engine, Vector2 starting_win_size);
void deinit_engine(GameEngine_t* engine); void deinit_engine(GameEngine_t* engine);
void process_inputs(GameEngine_t* engine, Scene_t* scene); void process_inputs(GameEngine_t* engine, Scene_t* scene);
void process_active_scene_inputs(GameEngine_t* engine);
void update_curr_scene(GameEngine_t* engine);
void render_curr_scene(GameEngine_t* engine);
void change_scene(GameEngine_t* engine, unsigned int idx); void change_scene(GameEngine_t* engine, unsigned int idx);
void change_active_scene(GameEngine_t* engine, unsigned int idx);
void change_focused_scene(GameEngine_t* engine, unsigned int idx);
bool load_sfx(GameEngine_t* engine, const char* snd_name, uint32_t tag_idx); bool load_sfx(GameEngine_t* engine, const char* snd_name, uint32_t tag_idx);
void play_sfx(GameEngine_t* engine, unsigned int tag_idx); void play_sfx(GameEngine_t* engine, unsigned int tag_idx);
void play_sfx_pitched(GameEngine_t* engine, unsigned int tag_idx, float pitch); void play_sfx_pitched(GameEngine_t* engine, unsigned int tag_idx, float pitch);
@ -98,10 +107,12 @@ void update_sfx_list(GameEngine_t* engine);
// Inline functions, for convenience // Inline functions, for convenience
extern void update_scene(Scene_t* scene, float delta_time); extern void update_scene(Scene_t* scene, float delta_time);
extern void render_scene(Scene_t* scene); extern void render_scene(Scene_t* scene);
extern void do_action(Scene_t* scene, ActionType_t action, bool pressed); extern ActionResult 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);
bool add_scene_layer(Scene_t* scene, int width, int height, Rectangle render_area); bool add_scene_layer(Scene_t* scene, int width, int height, Rectangle render_area);
void free_scene(Scene_t* scene); void free_scene(Scene_t* scene);
void add_child_scene(Scene_t* child, Scene_t* parent);
void remove_child_scene(Scene_t* child);
#endif // __ENGINE_H #endif // __ENGINE_H

179
scene_man_test.c 100644
View File

@ -0,0 +1,179 @@
#include "constants.h"
#include "scene_impl.h"
#include "ent_impl.h"
#include "water_flow.h"
#include "game_systems.h"
#include "assets_loader.h"
#include "raymath.h"
#include <stdio.h>
#include <unistd.h>
static Tile_t all_tiles[MAX_N_TILES] = {0};
// Maintain own queue to handle key presses
struct sc_queue_32 key_buffer;
Scene_t* scenes[6];
static GameEngine_t engine =
{
.scenes = scenes,
.max_scenes = 6,
.curr_scene = 0,
.assets = {0}
};
#define GAME_LAYER 0
struct DummyScene {
Scene_t scene;
unsigned int number;
float elapsed;
};
static void level_scene_render_func(Scene_t* scene)
{
}
static inline unsigned int get_tile_idx(int x, int y, const TileGrid_t* tilemap)
{
unsigned int tile_x = x / TILE_SIZE;
unsigned int tile_y = y / TILE_SIZE;
if (tile_x < tilemap->width && tile_y < tilemap->height)
{
return tile_y * tilemap->width + tile_x;
}
return MAX_N_TILES;
}
static void print_number_sys(Scene_t* scene)
{
struct DummyScene* data = CONTAINER_OF(scene, struct DummyScene, scene);
data->elapsed += scene->delta_time;
if (data->elapsed > 1.0f)
{
printf("Data: %u\n", data->number);
data->elapsed -= 1.0f;
}
}
static ActionResult level_do_action(Scene_t* scene, ActionType_t action, bool pressed)
{
CPlayerState_t* p_playerstate;
sc_map_foreach_value(&scene->ent_manager.component_map[CPLAYERSTATE_T], p_playerstate)
{
switch(action)
{
case ACTION_UP:
p_playerstate->player_dir.y = (pressed)? -1 : 0;
break;
case ACTION_DOWN:
p_playerstate->player_dir.y = (pressed)? 1 : 0;
break;
case ACTION_LEFT:
p_playerstate->player_dir.x = (pressed)? -1 : 0;
break;
case ACTION_RIGHT:
p_playerstate->player_dir.x = (pressed)? 1 : 0;
break;
default:
break;
}
}
switch (action)
{
case ACTION_RESTART:
puts("Restarting!");
break;
default:
break;
}
return ACTION_PROPAGATE;
}
int main(void)
{
init_engine(&engine, (Vector2){1280,640});
SetTargetFPS(60);
static struct DummyScene dummy_scenes[6];
for (uint8_t i = 0; i < 6; ++i)
{
scenes[i] = &dummy_scenes[i].scene;
init_scene(&dummy_scenes[i].scene, &level_do_action);
dummy_scenes[i].scene.engine = &engine;
dummy_scenes[i].number = i;
add_scene_layer(
&dummy_scenes[i].scene, 1280, 640, (Rectangle){0,0,1280,640}
);
sc_array_add(&dummy_scenes[i].scene.systems, &print_number_sys);
sc_array_add(&dummy_scenes[i].scene.systems, &level_scene_render_func);
//sc_map_put_64(&scene.scene.action_map, KEY_R, ACTION_RESTART);
//sc_map_put_64(&scene.scene.action_map, KEY_UP, ACTION_UP);
//sc_map_put_64(&scene.scene.action_map, KEY_DOWN, ACTION_DOWN);
//sc_map_put_64(&scene.scene.action_map, KEY_LEFT, ACTION_LEFT);
//sc_map_put_64(&scene.scene.action_map, KEY_RIGHT, ACTION_RIGHT);
//sc_map_put_64(&scene.scene.action_map, KEY_P, ACTION_METAL_TOGGLE);
}
change_active_scene(&engine, 0);
add_child_scene(scenes[1], scenes[0]);
add_child_scene(scenes[2], scenes[0]);
add_child_scene(scenes[3], scenes[0]);
float timer = 0;
while(timer < 1.2f)
{
timer += GetFrameTime();
process_active_scene_inputs(&engine);
update_curr_scene(&engine);
// This is needed to advance time delta
render_curr_scene(&engine);
if (WindowShouldClose()) break;
}
remove_child_scene(scenes[2]);
timer = 0;
while(timer < 1.2f)
{
timer += GetFrameTime();
process_active_scene_inputs(&engine);
update_curr_scene(&engine);
// This is needed to advance time delta
render_curr_scene(&engine);
if (WindowShouldClose()) break;
}
add_child_scene(scenes[4], scenes[0]);
timer = 0;
while(timer < 1.2f)
{
timer += GetFrameTime();
process_active_scene_inputs(&engine);
update_curr_scene(&engine);
// This is needed to advance time delta
render_curr_scene(&engine);
if (WindowShouldClose()) break;
}
add_child_scene(scenes[2], scenes[1]);
timer = 0;
while(timer < 1.2f)
{
timer += GetFrameTime();
process_active_scene_inputs(&engine);
update_curr_scene(&engine);
// This is needed to advance time delta
render_curr_scene(&engine);
if (WindowShouldClose()) break;
}
for (uint8_t i = 0; i < 6; ++i)
{
free_scene(&dummy_scenes[i].scene);
}
deinit_engine(&engine);
}

View File

@ -343,7 +343,7 @@ static void toggle_block_system(Scene_t* scene)
} }
} }
static void level_do_action(Scene_t* scene, ActionType_t action, bool pressed) static ActionResult level_do_action(Scene_t* scene, ActionType_t action, bool pressed)
{ {
CPlayerState_t* p_playerstate; CPlayerState_t* p_playerstate;
sc_map_foreach_value(&scene->ent_manager.component_map[CPLAYERSTATE_T], p_playerstate) sc_map_foreach_value(&scene->ent_manager.component_map[CPLAYERSTATE_T], p_playerstate)
@ -377,6 +377,7 @@ static void level_do_action(Scene_t* scene, ActionType_t action, bool pressed)
default: default:
break; break;
} }
return ACTION_PROPAGATE;
} }
static void player_simple_movement_system(Scene_t* scene) static void player_simple_movement_system(Scene_t* scene)