Implement basic level state management

Internal Changelog:
- Remove spawn position for entity
    - Not used. If spawning is needed, use a spawning entity
- Put in state machine callback function
    - Player spawning system is part of callback function
    - Remove player spawning system
- Incorporate state transition in systems
main
En Yi 2024-08-19 11:55:48 +08:00
parent f6f6d54ecf
commit 019f39f84c
8 changed files with 90 additions and 38 deletions

View File

@ -253,7 +253,6 @@ static inline void set_bbox(CBBox_t* p_bbox, unsigned int x, unsigned int y)
}
struct Entity {
Vector2 spawn_pos;
Vector2 position;
unsigned long m_id;
unsigned int m_tag;

View File

@ -115,7 +115,6 @@ Entity_t *add_entity(EntityManager_t* p_manager, unsigned int tag)
Entity_t* p_ent = new_entity_from_mempool(&e_idx);
if (p_ent == NULL) return NULL;
p_ent->spawn_pos = (Vector2){0, 0};
p_ent->m_tag = tag;
sc_queue_add_last(&p_manager->to_add, e_idx);
p_ent->manager = p_manager;

View File

@ -220,6 +220,12 @@ static void render_editor_game_scene(Scene_t* scene)
);
BeginMode2D(data->camera.cam);
if (point_in_AABB(data->player_spawn, (Rectangle){min.x * tilemap.tile_size, min.y * tilemap.tile_size, data->game_rec.width, data->game_rec.height}))
{
DrawCircleV(data->player_spawn, 6, PURPLE);
}
for (int tile_y = min.y; tile_y < max.y; tile_y++)
{
for (int tile_x = min.x; tile_x < max.x; tile_x++)
@ -248,12 +254,6 @@ static void render_editor_game_scene(Scene_t* scene)
{
CBBox_t* p_bbox = get_component(p_ent, CBBOX_COMP_T);
// Draw the spawn point
if (p_ent->m_tag == PLAYER_ENT_TAG)
{
DrawCircleV(p_ent->spawn_pos, 6, PURPLE);
}
// Entity culling
Vector2 box_size = {0};
if (p_bbox != NULL) box_size = p_bbox->size;
@ -887,6 +887,9 @@ static void level_do_action(Scene_t* scene, ActionType_t action, bool pressed)
p_playerstate->locked = !p_playerstate->locked;
}
break;
case ACTION_SET_SPAWNPOINT:
data->player_spawn = p_player->position;
break;
default:
break;
}
@ -1062,9 +1065,6 @@ static void level_do_action(Scene_t* scene, ActionType_t action, bool pressed)
data->show_grid = !data->show_grid;
}
break;
case ACTION_SET_SPAWNPOINT:
p_player->spawn_pos = p_player->position;
break;
case ACTION_TOGGLE_TIMESLOW:
if (!pressed)
{
@ -1103,6 +1103,27 @@ static void level_do_action(Scene_t* scene, ActionType_t action, bool pressed)
}
}
static void at_level_start(Scene_t* scene)
{
LevelSceneData_t* data = &(CONTAINER_OF(scene, LevelScene_t, scene)->data);
data->sm.state = LEVEL_STATE_RUNNING;
}
static void at_level_dead(Scene_t* scene)
{
LevelSceneData_t* data = &(CONTAINER_OF(scene, LevelScene_t, scene)->data);
Entity_t* p_player = create_player(&scene->ent_manager);
p_player->position = data->player_spawn;
data->sm.state = LEVEL_STATE_STARTING;
}
static void at_level_complete(Scene_t* scene)
{
LevelSceneData_t* data = &(CONTAINER_OF(scene, LevelScene_t, scene)->data);
do_action(scene, ACTION_NEXTLEVEL, true);
data->sm.state = LEVEL_STATE_STARTING;
}
void init_sandbox_scene(LevelScene_t* scene)
{
init_scene(&scene->scene, &level_do_action);
@ -1116,7 +1137,11 @@ void init_sandbox_scene(LevelScene_t* scene)
&scene->data, MAX_N_TILES, all_tiles,
(Rectangle){25, 25, VIEWABLE_EDITOR_MAP_WIDTH*TILE_SIZE, VIEWABLE_EDITOR_MAP_HEIGHT*TILE_SIZE}
);
scene->data.show_grid = true;
scene->data.sm.state_functions[LEVEL_STATE_STARTING] = at_level_start;
scene->data.sm.state_functions[LEVEL_STATE_RUNNING] = NULL;
scene->data.sm.state_functions[LEVEL_STATE_DEAD] = at_level_dead;
scene->data.sm.state_functions[LEVEL_STATE_COMPLETE] = at_level_complete;
scene->data.tile_sprites[ONEWAY_TILE] = get_sprite(&scene->scene.engine->assets, "tl_owp");
scene->data.tile_sprites[LADDER] = get_sprite(&scene->scene.engine->assets, "tl_ldr");
scene->data.tile_sprites[SPIKES] = get_sprite(&scene->scene.engine->assets, "d_spikes");
@ -1363,8 +1388,8 @@ void init_sandbox_scene(LevelScene_t* scene)
sc_array_add(&scene->scene.systems, &player_dir_reset_system);
sc_array_add(&scene->scene.systems, &update_water_runner_system);
sc_array_add(&scene->scene.systems, &check_player_dead_system);
sc_array_add(&scene->scene.systems, &player_respawn_system);
sc_array_add(&scene->scene.systems, &level_end_detection_system);
sc_array_add(&scene->scene.systems, &level_state_management_system);
sc_array_add(&scene->scene.systems, &render_editor_game_scene);
sc_array_add(&scene->scene.systems, &level_scene_render_func);

View File

@ -416,6 +416,19 @@ static void render_regular_game_scene(Scene_t* scene)
EndTextureMode();
}
static void at_level_start(Scene_t* scene)
{
LevelSceneData_t* data = &(CONTAINER_OF(scene, LevelScene_t, scene)->data);
data->sm.state = LEVEL_STATE_RUNNING;
}
static void at_level_complete(Scene_t* scene)
{
LevelSceneData_t* data = &(CONTAINER_OF(scene, LevelScene_t, scene)->data);
do_action(scene, ACTION_NEXTLEVEL, true);
data->sm.state = LEVEL_STATE_STARTING;
}
void init_game_scene(LevelScene_t* scene)
{
init_scene(&scene->scene, &level_do_action);
@ -433,6 +446,10 @@ void init_game_scene(LevelScene_t* scene)
VIEWABLE_MAP_WIDTH*TILE_SIZE, VIEWABLE_MAP_HEIGHT*TILE_SIZE
}
);
scene->data.sm.state_functions[LEVEL_STATE_STARTING] = at_level_start;
scene->data.sm.state_functions[LEVEL_STATE_RUNNING] = NULL;
scene->data.sm.state_functions[LEVEL_STATE_DEAD] = NULL;
scene->data.sm.state_functions[LEVEL_STATE_COMPLETE] = at_level_complete;
scene->scene.bg_colour = LIGHTGRAY;
add_scene_layer(
@ -480,9 +497,10 @@ void init_game_scene(LevelScene_t* scene)
sc_array_add(&scene->scene.systems, &sprite_animation_system);
sc_array_add(&scene->scene.systems, &camera_update_system);
sc_array_add(&scene->scene.systems, &player_dir_reset_system);
sc_array_add(&scene->scene.systems, &check_player_dead_system);
//sc_array_add(&scene->scene.systems, &player_respawn_system);
sc_array_add(&scene->scene.systems, &update_water_runner_system);
sc_array_add(&scene->scene.systems, &check_player_dead_system);
sc_array_add(&scene->scene.systems, &level_end_detection_system);
sc_array_add(&scene->scene.systems, &level_state_management_system);
sc_array_add(&scene->scene.systems, &render_regular_game_scene);
sc_array_add(&scene->scene.systems, &level_scene_render_func);
// This avoid graphical glitch, not essential

View File

@ -231,7 +231,6 @@ void destroy_entity(Scene_t* scene, TileGrid_t* tilemap, Entity_t* p_ent)
void check_player_dead_system(Scene_t* scene)
{
LevelSceneData_t* data = &(CONTAINER_OF(scene, LevelScene_t, scene)->data);
TileGrid_t tilemap = data->tilemap;
Entity_t* p_player;
// Cannot create player while looping though the players
// So have to create outside of the loop
@ -245,25 +244,7 @@ void check_player_dead_system(Scene_t* scene)
ent->position = p_player->position;
}
destroy_entity(scene, &data->tilemap, p_player);
}
}
}
void player_respawn_system(Scene_t* scene)
{
Entity_t* p_player;
// Cannot create player while looping though the players
// So have to create outside of the loop
sc_map_foreach_value(&scene->ent_manager.entities_map[PLAYER_ENT_TAG], p_player)
{
if (!p_player->m_alive)
{
Entity_t* new_player = create_player(&scene->ent_manager);
CTransform_t* p_ct = get_component(new_player, CTRANSFORM_COMP_T);
new_player->position = p_player->spawn_pos;
memset(&p_ct->velocity, 0, sizeof(p_ct->velocity));
memset(&p_ct->accel, 0, sizeof(p_ct->accel));
data->sm.state = LEVEL_STATE_DEAD;
}
}
}
@ -2038,13 +2019,24 @@ void level_end_detection_system(Scene_t* scene)
)
)
{
do_action(scene, ACTION_NEXTLEVEL, true);
lvl_scene->data.sm.state = LEVEL_STATE_COMPLETE;
//do_action(scene, ACTION_NEXTLEVEL, true);
}
}
}
}
void level_state_management_system(Scene_t* scene)
{
LevelSceneData_t* data = &(CONTAINER_OF(scene, LevelScene_t, scene)->data);
if (data->sm.state_functions[data->sm.state] != NULL)
{
data->sm.state_functions[data->sm.state](scene);
}
}
static inline bool is_point_in_water(Vector2 pos, TileGrid_t tilemap)
{
unsigned int tile_idx = get_tile_idx(pos.x, pos.y, tilemap);

View File

@ -22,11 +22,11 @@ void boulder_destroy_wooden_tile_system(Scene_t* scene);
void camera_update_system(Scene_t* scene);
void container_destroy_system(Scene_t* scene);
void player_dir_reset_system(Scene_t* scene);
void player_respawn_system(Scene_t* scene);
void check_player_dead_system(Scene_t* scene);
void lifetimer_update_system(Scene_t* scene);
void airtimer_update_system(Scene_t* scene);
void spike_collision_system(Scene_t* scene);
void level_end_detection_system(Scene_t* scene);
void level_state_management_system(Scene_t* scene);
#endif // __GAME_SYSTEMS_H

View File

@ -45,6 +45,22 @@ typedef struct LevelCamera {
float range_limit;
}LevelCamera_t;
typedef enum LevelSceneState {
LEVEL_STATE_STARTING = 0,
LEVEL_STATE_RUNNING,
LEVEL_STATE_DEAD,
LEVEL_STATE_COMPLETE,
} LevelSceneState_t;
typedef struct LevelSceneStateMachine {
LevelSceneState_t state;
system_func_t state_functions[4];
// Engine has no timeline support, so make a pseudo timeline implementation
float fractional;
uint32_t counter;
} LevelSceneStateMachine_t;
typedef struct LevelSceneData {
TileGrid_t tilemap;
// TODO: game_rec is actually obsolete since this is in the scene game layer
@ -57,6 +73,8 @@ typedef struct LevelSceneData {
unsigned int current_level;
CoinCounter_t coins;
bool show_grid;
Vector2 player_spawn;
LevelSceneStateMachine_t sm;
}LevelSceneData_t;
typedef struct LevelScene {

View File

@ -41,6 +41,8 @@ void init_level_scene_data(LevelSceneData_t* data, uint32_t max_tiles, Tile_t* t
}
memset(&data->coins, 0, sizeof(data->coins));
data->show_grid = false;
memset(&data->sm, 0, sizeof(data->sm));
}
void term_level_scene_data(LevelSceneData_t* data)
@ -145,7 +147,6 @@ bool load_level_tilemap(LevelScene_t* scene, unsigned int level_num)
Entity_t* ent = create_player(&scene->scene.ent_manager);
ent->position.x = (i % scene->data.tilemap.width) * scene->data.tilemap.tile_size;
ent->position.y = (i / scene->data.tilemap.width) * scene->data.tilemap.tile_size;
ent->spawn_pos = ent->position;
}
break;
case 23: