Compare commits

...

5 Commits

Author SHA1 Message Date
En Yi 510428d152 Refactor render function 2024-06-30 15:24:22 +08:00
En Yi c5a30bee6d Use scene idx instead of pointer 2024-06-29 13:26:48 +08:00
En Yi 921b59d2ab 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
2024-06-27 21:36:55 +08:00
En Yi 0653cbd0a2 Continue plan data struct for scene management 2024-06-20 21:41:14 +08:00
En Yi f98e242378 Add struct for scene management 2024-06-19 21:14:48 +08:00
13 changed files with 453 additions and 32 deletions

View File

@ -155,6 +155,19 @@ target_link_libraries(particle_test
${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)
find_package(cmocka 1.1.0 REQUIRED)
add_subdirectory(tests)

View File

@ -27,6 +27,7 @@ target_link_libraries(lib_engine
zstd
raylib
sc_queue
sc_heap
sc_map
sc_array
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 "mempool.h"
#include <math.h>
void init_engine(GameEngine_t* engine, Vector2 starting_win_size)
{
InitAudioDevice();
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;
memset(engine->sfx_list.sfx, 0, engine->sfx_list.n_sfx * sizeof(SFX_t));
init_memory_pools();
@ -18,6 +22,8 @@ void deinit_engine(GameEngine_t* engine)
term_assets(&engine->assets);
free_memory_pools();
sc_queue_term(&engine->key_buffer);
sc_queue_term(&engine->scene_stack);
sc_heap_term(&engine->scenes_render_order);
CloseAudioDevice();
CloseWindow();
}
@ -85,9 +91,8 @@ void process_inputs(GameEngine_t* engine, Scene_t* scene)
void change_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;
// Backwards compat
change_active_scene(engine, idx);
}
bool load_sfx(GameEngine_t* engine, const char* snd_name, uint32_t tag_idx)
@ -157,7 +162,7 @@ void init_scene(Scene_t* scene, action_func_t action_func)
scene->bg_colour = WHITE;
scene->action_function = action_func;
scene->state = SCENE_ENDED;
scene->state = SCENE_COMPLETE_ACTIVE;
scene->time_scale = 1.0f;
}
@ -194,10 +199,19 @@ inline void update_scene(Scene_t* scene, float delta_time)
update_particle_system(&scene->part_sys, scene->delta_time);
}
inline void render_scene(Scene_t* scene)
static void _internal_render_scene(Scene_t* scene)
{
BeginDrawing();
ClearBackground(scene->bg_colour);
if ((scene->state & SCENE_RENDER_BIT) == 0) return;
if (scene->parent_scene == NULL)
{
ClearBackground(scene->bg_colour);
}
else
{
ClearBackground((Color){255,255,255,0});
}
for (uint8_t i = 0; i < scene->layers.n_layers; ++i)
{
RenderLayer_t* layer = scene->layers.render_layers + i;
@ -213,10 +227,148 @@ inline void render_scene(Scene_t* scene)
WHITE
);
}
}
inline void render_scene(Scene_t* scene)
{
BeginDrawing();
_internal_render_scene(scene);
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);
}
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);
if ((scene->state & SCENE_ACTIVE_BIT) == 0) continue;
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;
_internal_render_scene(scene);
}
EndDrawing();
}
void add_child_scene(GameEngine_t* engine, unsigned int child_idx, unsigned int parent_idx)
{
if (
child_idx >= engine->max_scenes
|| parent_idx >= engine->max_scenes
) return;
Scene_t* child = engine->scenes[child_idx];
Scene_t* parent = engine->scenes[parent_idx];
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(GameEngine_t* engine, unsigned int idx)
{
if (idx >= engine->max_scenes) return;
Scene_t* child = engine->scenes[idx];
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 = 0;
engine->curr_scene = idx;
engine->scenes[engine->curr_scene]->state = SCENE_COMPLETE_ACTIVE;
}
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 "collisions.h"
#include "sc/array/sc_array.h"
#include "sc/heap/sc_heap.h"
#include "assets.h"
#include "particle_sys.h"
@ -16,34 +17,39 @@ typedef struct SFXList
uint32_t played_sfx;
} SFXList_t;
typedef struct SceneNode {
Scene_t* scene;
Scene_t* next;
} SceneNode_t;
typedef struct GameEngine {
Scene_t **scenes;
unsigned int max_scenes;
unsigned int curr_scene;
unsigned int curr_scene; // Current root scene
Assets_t assets;
SFXList_t sfx_list;
// 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_ptr scene_stack;
struct sc_heap scenes_render_order;
// This is the original size of the window.
// This is in case of window scaling, where there needs to be
// an absolute reference
Vector2 intended_window_size;
} GameEngine_t;
//typedef enum SceneType {
// LEVEL_SCENE = 0,
// MENU_SCENE,
//}SceneType_t;
#define SCENE_ACTIVE_BIT (1 << 0) // Systems Active
#define SCENE_RENDER_BIT (1 << 1) // Whether to render
#define SCENE_COMPLETE_ACTIVE (SCENE_ACTIVE_BIT | SCENE_RENDER_BIT)
typedef enum SceneState {
SCENE_PLAYING = 0,
SCENE_SUSPENDED,
SCENE_ENDED,
}SceneState_t;
typedef enum ActionResult {
ACTION_PROPAGATE = 0,
ACTION_CONSUMED,
} ActionResult;
typedef void(*render_func_t)(Scene_t*);
typedef void(*system_func_t)(Scene_t*);
typedef void(*action_func_t)(Scene_t*, ActionType_t, bool);
typedef ActionResult(*action_func_t)(Scene_t*, ActionType_t, bool);
sc_array_def(system_func_t, systems);
typedef struct RenderLayer {
@ -57,19 +63,23 @@ typedef struct SceneRenderLayers {
} SceneRenderLayers_t;
struct Scene {
// Not all scene needs an entity manager
// but too late to change this
EntityManager_t ent_manager;
Scene_t* parent_scene;
struct sc_map_64 action_map; // key -> actions
struct sc_array_systems systems;
SceneRenderLayers_t layers;
Color bg_colour;
action_func_t action_function;
EntityManager_t ent_manager;
float delta_time;
float time_scale;
Vector2 mouse_pos;
//SceneType_t scene_type;
SceneState_t state;
uint8_t state;
ParticleSystem_t part_sys;
GameEngine_t *engine;
int8_t depth_index;
SceneNode_t child_scene; // Intrusive Linked List for children scene
};
@ -77,7 +87,13 @@ void init_engine(GameEngine_t* engine, Vector2 starting_win_size);
void deinit_engine(GameEngine_t* engine);
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_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);
void play_sfx(GameEngine_t* engine, unsigned int tag_idx);
void play_sfx_pitched(GameEngine_t* engine, unsigned int tag_idx, float pitch);
@ -86,10 +102,12 @@ void update_sfx_list(GameEngine_t* engine);
// Inline functions, for convenience
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);
extern ActionResult do_action(Scene_t* scene, ActionType_t action, bool pressed);
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);
void free_scene(Scene_t* scene);
void add_child_scene(GameEngine_t* engine, unsigned int child_idx, unsigned int parent_idx);
void remove_child_scene(GameEngine_t* engine, unsigned int idx);
#endif // __ENGINE_H

View File

@ -1,6 +1,7 @@
#ifndef _ENGINE_CONF_H
#define _ENGINE_CONF_H
#define MAX_SCENES_TO_RENDER 16
#define MAX_RENDER_LAYERS 4
#define MAX_ENTITIES 2048
#define MAX_TEXTURES 16

4
main.c
View File

@ -86,7 +86,7 @@ int main(void)
// appear in the polling of raylib
Scene_t* curr_scene = engine.scenes[engine.curr_scene];
if (curr_scene->state == SCENE_ENDED && engine.curr_scene == 0)
if (curr_scene->state == 0 && engine.curr_scene == 0)
{
break;
}
@ -103,7 +103,7 @@ int main(void)
render_scene(curr_scene);
update_sfx_list(&engine);
if (curr_scene->state != SCENE_PLAYING)
if (curr_scene->state != 0)
{
sc_queue_clear(&key_buffer);
}

188
scene_man_test.c 100644
View File

@ -0,0 +1,188 @@
#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;
Vector2 text_pos;
};
static void level_scene_render_func(Scene_t* scene)
{
struct DummyScene* data = CONTAINER_OF(scene, struct DummyScene, scene);
char text[32];
sprintf(text, "Scene %u", data->number);
BeginTextureMode(scene->layers.render_layers[0].layer_tex);
DrawText(text, 32 * data->number, 32 * data->number, 12, BLACK);
EndTextureMode();
}
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);
// TODO: Add render function
// Add a way to switch focused scene
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}
);
dummy_scenes[i].scene.bg_colour = WHITE;
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(&engine, 1, 0);
add_child_scene(&engine, 2, 0);
add_child_scene(&engine, 3, 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(&engine, 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(&engine, 4, 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(&engine, 2, 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

@ -34,4 +34,10 @@ typedef enum SFXTag {
BUBBLE_SFX,
COIN_SFX,
} SFXTag_t;
//typedef enum SceneType {
// LEVEL_SCENE = 0,
// MENU_SCENE,
//}SceneType_t;
#endif

View File

@ -797,7 +797,7 @@ static void restart_editor_level(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)
{
LevelSceneData_t* data = &(CONTAINER_OF(scene, LevelScene_t, scene)->data);
Entity_t* p_player;
@ -1002,6 +1002,7 @@ static void level_do_action(Scene_t* scene, ActionType_t action, bool pressed)
break;
}
}
return ACTION_PROPAGATE;
}
void init_sandbox_scene(LevelScene_t* scene)

View File

@ -45,7 +45,7 @@ static void level_scene_render_func(Scene_t* scene)
EndTextureMode();
}
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;
sc_map_foreach_value(&scene->ent_manager.component_map[CPLAYERSTATE_T], p_playerstate)
@ -96,6 +96,7 @@ static void level_do_action(Scene_t* scene, ActionType_t action, bool pressed)
break;
}
}
return ACTION_PROPAGATE;
}
static void render_regular_game_scene(Scene_t* scene)

View File

@ -27,14 +27,14 @@ static void exec_component_function(Scene_t* scene, int sel)
change_scene(scene->engine, 2);
break;
case 3:
scene->state = SCENE_ENDED;
scene->state = 0;
break;
default:
break;
}
}
static void menu_do_action(Scene_t* scene, ActionType_t action, bool pressed)
static ActionResult menu_do_action(Scene_t* scene, ActionType_t action, bool pressed)
{
MenuSceneData_t* data = &(CONTAINER_OF(scene, MenuScene_t, scene)->data);
unsigned int new_selection = data->selected_comp;
@ -83,6 +83,7 @@ static void menu_do_action(Scene_t* scene, ActionType_t action, bool pressed)
exec_component_function(scene, data->selected_comp);
}
}
return ACTION_PROPAGATE;
}
static void gui_loop(Scene_t* scene)

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;
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:
break;
}
return ACTION_PROPAGATE;
}
static void player_simple_movement_system(Scene_t* scene)