Compare commits

...

4 Commits

Author SHA1 Message Date
En Yi 7af75b8366 Implement runner movement to lowest position
Changelog:
- Add new state for when runner reaches target position
- Skip movement if already on target position
- Add extra handling for spawning on solid tile
- Fix tile toggling bug on the same tile
2023-07-19 23:32:53 +08:00
En Yi a0f6cf3471 Visualise path from BFS + Improving BFS
Changelog:
- Avoid continuous BFS by changing state after BFS completion
- Reset to BFS state on tile change
2023-07-19 22:44:49 +08:00
En Yi 5b3cbd1bba Implement BFS into water runner 2023-07-19 19:16:35 +08:00
En Yi 917fdeba9b Fix incorrect entity deletion process
Changelog:
- Components of an entity are now freed on the spot
2023-07-19 19:15:16 +08:00
5 changed files with 203 additions and 7 deletions

View File

@ -141,9 +141,20 @@ typedef struct _BFSTileMap {
uint32_t len; uint32_t len;
}BFSTileMap_t; }BFSTileMap_t;
typedef enum _WaterRunnerState
{
LOWEST_POINT_SEARCH = 0,
LOWEST_POINT_MOVEMENT,
SCANLINE_FILL,
}WaterRunerState_t;
typedef struct _CWaterRunner { typedef struct _CWaterRunner {
int32_t current_tile;
BFSTileMap_t bfs_tilemap; BFSTileMap_t bfs_tilemap;
int32_t current_tile;
int32_t target_tile;
struct sc_queue_32 bfs_queue;
bool* visited;
WaterRunerState_t state;
}CWaterRunner_t; }CWaterRunner_t;
// Credits to bedroomcoders.co.uk for this // Credits to bedroomcoders.co.uk for this

View File

@ -37,7 +37,10 @@ void update_entity_manager(EntityManager_t* p_manager)
if (!p_entity) continue; if (!p_entity) continue;
for (size_t i = 0; i < N_COMPONENTS; ++i) for (size_t i = 0; i < N_COMPONENTS; ++i)
{ {
remove_component(p_entity, i); if (p_entity->components[i] == MAX_COMP_POOL_SIZE) continue;
free_component_to_mempool(i, p_entity->components[i]);
sc_map_del_64v(&p_manager->component_map[i], e_idx);
p_entity->components[i] = MAX_COMP_POOL_SIZE;
} }
sc_map_del_64v(&p_manager->entities_map[p_entity->m_tag], e_idx); sc_map_del_64v(&p_manager->entities_map[p_entity->m_tag], e_idx);
free_entity_to_mempool(e_idx); free_entity_to_mempool(e_idx);

View File

@ -15,6 +15,7 @@ typedef struct Tile {
SolidType_t solid; SolidType_t solid;
uint8_t def; uint8_t def;
unsigned int water_level; unsigned int water_level;
unsigned int max_water_level;
struct sc_map_64v entities_set; struct sc_map_64v entities_set;
Vector2 offset; Vector2 offset;
Vector2 size; Vector2 size;

View File

@ -1,6 +1,6 @@
#include "water_flow.h" #include "water_flow.h"
#include "sc/queue/sc_queue.h"
#include <stdio.h>
Entity_t* create_water_runner(EntityManager_t* ent_manager, int32_t width, int32_t height, int32_t start_tile) Entity_t* create_water_runner(EntityManager_t* ent_manager, int32_t width, int32_t height, int32_t start_tile)
{ {
Entity_t* p_filler = add_entity(ent_manager, DYNMEM_ENT_TAG); Entity_t* p_filler = add_entity(ent_manager, DYNMEM_ENT_TAG);
@ -22,7 +22,11 @@ Entity_t* create_water_runner(EntityManager_t* ent_manager, int32_t width, int32
p_crunner->bfs_tilemap.height = height; p_crunner->bfs_tilemap.height = height;
p_crunner->bfs_tilemap.len = total; p_crunner->bfs_tilemap.len = total;
sc_queue_init(&p_crunner->bfs_queue);
p_crunner->visited = calloc(total, sizeof(bool));
p_crunner->current_tile = start_tile; p_crunner->current_tile = start_tile;
p_crunner->target_tile = total;
CTransform_t* p_ct = add_component(p_filler, CTRANSFORM_COMP_T); CTransform_t* p_ct = add_component(p_filler, CTRANSFORM_COMP_T);
p_ct->movement_mode = KINEMATIC_MOVEMENT; p_ct->movement_mode = KINEMATIC_MOVEMENT;
@ -34,10 +38,11 @@ void free_water_runner(Entity_t* ent, EntityManager_t* ent_manager)
{ {
CWaterRunner_t* p_crunner = get_component(ent, CWATERRUNNER_T); CWaterRunner_t* p_crunner = get_component(ent, CWATERRUNNER_T);
free(p_crunner->bfs_tilemap.tilemap); free(p_crunner->bfs_tilemap.tilemap);
free(p_crunner->visited);
sc_queue_term(&p_crunner->bfs_queue);
remove_entity(ent_manager, ent->m_id); remove_entity(ent_manager, ent->m_id);
} }
void update_water_runner_system(Scene_t* scene) void update_water_runner_system(Scene_t* scene)
{ {
// The core of the water runner is to: // The core of the water runner is to:
@ -45,17 +50,158 @@ void update_water_runner_system(Scene_t* scene)
// - Scanline fill // - Scanline fill
// A runner is given an amount of movement cost // A runner is given an amount of movement cost
// Within the movement cost, do the following logic // Within the movement cost, do the following logic
// Perform a modified DFS to find the lowest point: // Perform a modified BFS to find the lowest point:
// - Solid tiles are not reachable // - Solid tiles are not reachable
// - If bottom tile is non-solid, that is the only reachable tile, // - If bottom tile is non-solid, that is the only reachable tile,
// - If bottom tile is filled with water fully, down+left+right are reachable // - If bottom tile is filled with water fully, down+left+right are reachable
// - If bottom tile is solid, left+right are reachable // - If bottom tile is solid, left+right are reachable
// - If bottom tile is OOB, terminate // - If bottom tile is OOB, terminate
// Use a LIFO to deal with this. // Use a FIFO to deal with this.
// On DFS completion, find the path to the lowest point. Keep track of this // On DFS completion, find the path to the lowest point. Keep track of this
// The DFS should have figured out all reachable tiles, start scanline filling at the lowest point. // The DFS should have figured out all reachable tiles, start scanline filling at the lowest point.
// On completion, move up update tile reachability by DFS on the current level. (repeat first step) // On completion, move up update tile reachability by DFS on the current level. (repeat first step)
// - No need to recheck already reachable tiles // - No need to recheck already reachable tiles
// - If current tile is solid, scan left and right for reachable tile and start from there // - If current tile is solid, scan left and right for reachable tile and start from there
// Repeat scanline fill // Repeat scanline fill
LevelSceneData_t* data = &(CONTAINER_OF(scene, LevelScene_t, scene)->data);
TileGrid_t tilemap = data->tilemap;
CWaterRunner_t* p_crunner;
unsigned int ent_idx;
sc_map_foreach(&scene->ent_manager.component_map[CWATERRUNNER_T], ent_idx, p_crunner)
{
Entity_t* ent = get_entity(&scene->ent_manager, ent_idx);
if (tilemap.tiles[p_crunner->current_tile].solid == SOLID)
{
CTileCoord_t* p_tilecoord = get_component(
ent, CTILECOORD_COMP_T
);
for (size_t i = 0;i < p_tilecoord->n_tiles; ++i)
{
// Use previously store tile position
// Clear from those positions
unsigned int tile_idx = p_tilecoord->tiles[i];
sc_map_del_64v(&(tilemap.tiles[tile_idx].entities_set), ent_idx);
}
free_water_runner(ent, &scene->ent_manager);
continue;
}
static uint8_t delay = 5;
switch (p_crunner->state)
{
case LOWEST_POINT_SEARCH:
{
for (size_t i = 0; i < p_crunner->bfs_tilemap.len; ++i)
{
p_crunner->bfs_tilemap.tilemap[i].from = -1;
p_crunner->bfs_tilemap.tilemap[i].reachable = false;
}
memset(p_crunner->visited, 0, p_crunner->bfs_tilemap.len * sizeof(bool));
int32_t lowest_tile = p_crunner->current_tile;
sc_queue_add_last(&p_crunner->bfs_queue, p_crunner->current_tile);
while (!sc_queue_empty(&p_crunner->bfs_queue))
{
unsigned int curr_idx = sc_queue_peek_first(&p_crunner->bfs_queue);
sc_queue_del_first(&p_crunner->bfs_queue);
unsigned int curr_height = curr_idx / p_crunner->bfs_tilemap.width;
unsigned int curr_low = lowest_tile / p_crunner->bfs_tilemap.width;
if (curr_height > curr_low)
{
lowest_tile = curr_idx;
}
p_crunner->visited[curr_idx] = true;
p_crunner->bfs_tilemap.tilemap[curr_idx].reachable = true;
// Possible optimisation to avoid repeated BFS, dunno how possible
unsigned int next = curr_idx + p_crunner->bfs_tilemap.width;
if (next >= p_crunner->bfs_tilemap.len) continue;
Tile_t* next_tile = tilemap.tiles + next;
if (
next_tile->solid != SOLID
&& next_tile->water_level < next_tile->max_water_level
&& !p_crunner->visited[next]
)
{
sc_queue_add_last(&p_crunner->bfs_queue, next);
p_crunner->bfs_tilemap.tilemap[next].from = curr_idx;
}
else
{
next = curr_idx - 1;
if (next % p_crunner->bfs_tilemap.width != 0)
{
next_tile = tilemap.tiles + next;
if (
next_tile->solid != SOLID
&& next_tile->water_level < next_tile->max_water_level
&& !p_crunner->visited[next]
)
{
sc_queue_add_last(&p_crunner->bfs_queue, next);
p_crunner->bfs_tilemap.tilemap[next].from = curr_idx;
}
}
next = curr_idx + 1;
if (next % p_crunner->bfs_tilemap.width != 0)
{
next_tile = tilemap.tiles + next;
if (
next_tile->solid != SOLID
&& next_tile->water_level < next_tile->max_water_level
&& !p_crunner->visited[next]
)
{
sc_queue_add_last(&p_crunner->bfs_queue, next);
p_crunner->bfs_tilemap.tilemap[next].from = curr_idx;
}
}
}
}
p_crunner->target_tile = lowest_tile;
if (p_crunner->target_tile == p_crunner->current_tile)
{
p_crunner->state = SCANLINE_FILL;
break;
}
// Trace path from lowest_tile
unsigned int prev_idx = lowest_tile;
unsigned int curr_idx = p_crunner->bfs_tilemap.tilemap[prev_idx].from;
while (p_crunner->bfs_tilemap.tilemap[prev_idx].from >= 0)
{
p_crunner->bfs_tilemap.tilemap[curr_idx].to = prev_idx;
prev_idx = curr_idx;
curr_idx = p_crunner->bfs_tilemap.tilemap[prev_idx].from;
}
delay = 5;
p_crunner->state = LOWEST_POINT_MOVEMENT;
}
break;
case LOWEST_POINT_MOVEMENT:
delay--;
if (delay == 0)
{
p_crunner->current_tile = p_crunner->bfs_tilemap.tilemap[p_crunner->current_tile].to;
CTransform_t* p_ct = get_component(ent, CTRANSFORM_COMP_T);
p_ct->position.x = (p_crunner->current_tile % tilemap.width) * tilemap.tile_size;
p_ct->position.y = (p_crunner->current_tile / tilemap.width) * tilemap.tile_size;
if (p_crunner->current_tile == p_crunner->target_tile)
{
p_crunner->state = SCANLINE_FILL;
}
delay = 5;
}
break;
default:
break;
}
}
}
void init_water_runner_system(void)
{
} }

View File

@ -85,6 +85,28 @@ static void level_scene_render_func(Scene_t* scene)
unsigned int x = ((p_runner->current_tile) % tilemap.width) * tilemap.tile_size; unsigned int x = ((p_runner->current_tile) % tilemap.width) * tilemap.tile_size;
unsigned int y = ((p_runner->current_tile) / tilemap.width) * tilemap.tile_size; unsigned int y = ((p_runner->current_tile) / tilemap.width) * tilemap.tile_size;
DrawCircle(x+16, y+16, 8, ColorAlpha(BLACK, 0.2)); DrawCircle(x+16, y+16, 8, ColorAlpha(BLACK, 0.2));
if (p_runner->target_tile < p_runner->bfs_tilemap.len)
{
unsigned int x = ((p_runner->target_tile) % tilemap.width) * tilemap.tile_size;
unsigned int y = ((p_runner->target_tile) / tilemap.width) * tilemap.tile_size;
DrawCircle(x+16, y+16, 8, ColorAlpha(BLUE, 0.2));
}
if (p_runner->state == LOWEST_POINT_MOVEMENT)
{
unsigned int curr_idx = p_runner->current_tile;
unsigned int next_idx = p_runner->bfs_tilemap.tilemap[curr_idx].to;
while(curr_idx != p_runner->target_tile)
{
unsigned int x1 = (curr_idx % tilemap.width) * tilemap.tile_size + tilemap.tile_size / 2;
unsigned int y1 = (curr_idx / tilemap.width) * tilemap.tile_size + tilemap.tile_size / 2;
unsigned int x2 = (next_idx % tilemap.width) * tilemap.tile_size + tilemap.tile_size / 2;
unsigned int y2 = (next_idx / tilemap.width) * tilemap.tile_size + tilemap.tile_size / 2;
DrawLine(x1, y1, x2, y2, BLACK);
curr_idx = next_idx;
next_idx = p_runner->bfs_tilemap.tilemap[curr_idx].to;
}
}
} }
char buffer[64] = {0}; char buffer[64] = {0};
@ -173,6 +195,10 @@ static void toggle_block_system(Scene_t* scene)
Vector2 raw_mouse_pos = {GetMouseX(), GetMouseY()}; Vector2 raw_mouse_pos = {GetMouseX(), GetMouseY()};
raw_mouse_pos = Vector2Subtract(raw_mouse_pos, (Vector2){data->game_rec.x, data->game_rec.y}); raw_mouse_pos = Vector2Subtract(raw_mouse_pos, (Vector2){data->game_rec.x, data->game_rec.y});
if (IsMouseButtonReleased(MOUSE_LEFT_BUTTON))
{
last_tile_idx = MAX_N_TILES;
}
if ( if (
raw_mouse_pos.x < data->game_rec.width raw_mouse_pos.x < data->game_rec.width
&& raw_mouse_pos.y < data->game_rec.height && raw_mouse_pos.y < data->game_rec.height
@ -189,6 +215,11 @@ static void toggle_block_system(Scene_t* scene)
if (new_type == SOLID_TILE) tilemap.tiles[tile_idx].water_level = 0; if (new_type == SOLID_TILE) tilemap.tiles[tile_idx].water_level = 0;
change_a_tile(&tilemap, tile_idx, new_type); change_a_tile(&tilemap, tile_idx, new_type);
last_tile_idx = tile_idx; last_tile_idx = tile_idx;
CWaterRunner_t* p_crunner;
sc_map_foreach_value(&scene->ent_manager.component_map[CWATERRUNNER_T], p_crunner)
{
p_crunner->state = LOWEST_POINT_SEARCH;
}
} }
else if (IsMouseButtonReleased(MOUSE_RIGHT_BUTTON)) else if (IsMouseButtonReleased(MOUSE_RIGHT_BUTTON))
{ {
@ -309,6 +340,7 @@ int main(void)
all_tiles[i].solid = NOT_SOLID; all_tiles[i].solid = NOT_SOLID;
all_tiles[i].tile_type = EMPTY_TILE; all_tiles[i].tile_type = EMPTY_TILE;
all_tiles[i].moveable = true; all_tiles[i].moveable = true;
all_tiles[i].max_water_level = 1;
sc_map_init_64v(&all_tiles[i].entities_set, 16, 0); sc_map_init_64v(&all_tiles[i].entities_set, 16, 0);
all_tiles[i].size = (Vector2){TILE_SIZE, TILE_SIZE}; all_tiles[i].size = (Vector2){TILE_SIZE, TILE_SIZE};
} }
@ -330,9 +362,12 @@ int main(void)
sc_array_add(&scene.scene.systems, &simple_friction_system); sc_array_add(&scene.scene.systems, &simple_friction_system);
sc_array_add(&scene.scene.systems, &movement_update_system); sc_array_add(&scene.scene.systems, &movement_update_system);
sc_array_add(&scene.scene.systems, &update_tilemap_system); sc_array_add(&scene.scene.systems, &update_tilemap_system);
sc_array_add(&scene.scene.systems, &update_water_runner_system);
sc_array_add(&scene.scene.systems, &toggle_block_system); sc_array_add(&scene.scene.systems, &toggle_block_system);
sc_array_add(&scene.scene.systems, &camera_update_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, &player_dir_reset_system);
sc_map_put_64(&scene.scene.action_map, KEY_R, ACTION_RESTART); 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_UP, ACTION_UP);
sc_map_put_64(&scene.scene.action_map, KEY_DOWN, ACTION_DOWN); sc_map_put_64(&scene.scene.action_map, KEY_DOWN, ACTION_DOWN);