Compare commits

...

7 Commits

Author SHA1 Message Date
En Yi b158bee1e8 Remove unused ActionResult
There is only one focused scene at a time as part of the design
limitation. Thus, it is no longer needed
2024-07-01 20:48:48 +08:00
En Yi e52855dfbd Fix incorrect check for scene active bit 2024-06-30 22:19:03 +08:00
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
10 changed files with 436 additions and 25 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;
}
@ -185,6 +190,8 @@ void free_scene(Scene_t* scene)
inline void update_scene(Scene_t* scene, float delta_time)
{
if ((scene->state & SCENE_ACTIVE_BIT) == 0) return;
scene->delta_time = delta_time * scene->time_scale;
system_func_t sys;
sc_array_foreach(&scene->systems, sys)
@ -194,10 +201,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,6 +229,12 @@ inline void render_scene(Scene_t* scene)
WHITE
);
}
}
inline void render_scene(Scene_t* scene)
{
BeginDrawing();
_internal_render_scene(scene);
EndDrawing();
}
@ -220,3 +242,133 @@ inline void do_action(Scene_t* scene, ActionType_t action, bool pressed)
{
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);
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,32 +17,32 @@ 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 void(*render_func_t)(Scene_t*);
typedef void(*system_func_t)(Scene_t*);
typedef void(*action_func_t)(Scene_t*, ActionType_t, bool);
sc_array_def(system_func_t, systems);
@ -57,19 +58,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 +82,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);
@ -91,5 +102,7 @@ extern void 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);
}

187
scene_man_test.c 100644
View File

@ -0,0 +1,187 @@
#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 void 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;
}
}
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

@ -27,7 +27,7 @@ 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;