Compare commits

...

5 Commits

Author SHA1 Message Date
En Yi 6e2ccfa875 Improve reachability search for scanline filling
Just do a second BFS from the current position, reset the current
scanline reachability.
2023-07-20 23:25:32 +08:00
En Yi 7607827420 Split out state for resetting and starting BFS 2023-07-20 22:58:09 +08:00
En Yi 34655d5c0a Visualine reachable scanline
Also tweak logic for lowest point
2023-07-20 22:51:15 +08:00
En Yi dc20a6b992 Fix lowest point check and movement delay
- This allows separte movement delay for each runner
- Lowest point has to be not in water
2023-07-20 22:25:57 +08:00
En Yi a080b5ee0a Add water checks in BFS
Also refactor the BFS logic to generalise the next tiles to go
after the tile checks
2023-07-20 21:23:43 +08:00
4 changed files with 175 additions and 80 deletions

View File

@ -143,18 +143,24 @@ typedef struct _BFSTileMap {
typedef enum _WaterRunnerState typedef enum _WaterRunnerState
{ {
LOWEST_POINT_SEARCH = 0, BFS_RESET = 0,
BFS_START,
LOWEST_POINT_SEARCH,
LOWEST_POINT_MOVEMENT, LOWEST_POINT_MOVEMENT,
REACHABILITY_SEARCH,
SCANLINE_FILL, SCANLINE_FILL,
}WaterRunerState_t; }WaterRunerState_t;
typedef struct _CWaterRunner { typedef struct _CWaterRunner {
BFSTileMap_t bfs_tilemap; BFSTileMap_t bfs_tilemap;
WaterRunerState_t state;
struct sc_queue_32 bfs_queue;
int32_t start_height;
int32_t current_tile; int32_t current_tile;
int32_t target_tile; int32_t target_tile;
struct sc_queue_32 bfs_queue;
bool* visited; bool* visited;
WaterRunerState_t state; uint8_t movement_delay;
uint8_t counter;
}CWaterRunner_t; }CWaterRunner_t;
// Credits to bedroomcoders.co.uk for this // Credits to bedroomcoders.co.uk for this

View File

@ -15,7 +15,6 @@ 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;
@ -28,6 +27,7 @@ typedef struct TileGrid
unsigned int height; unsigned int height;
unsigned int n_tiles; unsigned int n_tiles;
unsigned int tile_size; unsigned int tile_size;
unsigned int max_water_level;
Tile_t* tiles; Tile_t* tiles;
}TileGrid_t; }TileGrid_t;

View File

@ -21,11 +21,13 @@ Entity_t* create_water_runner(EntityManager_t* ent_manager, int32_t width, int32
p_crunner->bfs_tilemap.width = width; p_crunner->bfs_tilemap.width = width;
p_crunner->bfs_tilemap.height = height; p_crunner->bfs_tilemap.height = height;
p_crunner->bfs_tilemap.len = total; p_crunner->bfs_tilemap.len = total;
p_crunner->movement_delay = 5;
sc_queue_init(&p_crunner->bfs_queue); sc_queue_init(&p_crunner->bfs_queue);
p_crunner->visited = calloc(total, sizeof(bool)); p_crunner->visited = calloc(total, sizeof(bool));
p_crunner->current_tile = start_tile; p_crunner->current_tile = start_tile;
p_crunner->start_height = start_tile / width;
p_crunner->target_tile = total; 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);
@ -43,6 +45,84 @@ void free_water_runner(Entity_t* ent, EntityManager_t* ent_manager)
remove_entity(ent_manager, ent->m_id); remove_entity(ent_manager, ent->m_id);
} }
static void runner_BFS(const TileGrid_t* tilemap, CWaterRunner_t* p_crunner, int32_t* lowest_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;
// Possible optimisation to avoid repeated BFS, dunno how possible
bool to_go[4] = {false, false, false, false};
Tile_t* curr_tile = tilemap->tiles + curr_idx;
unsigned int next = curr_idx + p_crunner->bfs_tilemap.width;
Tile_t* next_tile = tilemap->tiles + next;
if (
curr_height > curr_low
&& curr_tile->water_level < tilemap->max_water_level
&& next_tile->water_level < tilemap->max_water_level
)
{
*lowest_tile = curr_idx;
}
if (next < p_crunner->bfs_tilemap.len)
{
to_go[0] = next_tile->solid != SOLID;
}
if (
next_tile->solid == SOLID
|| next_tile->water_level == tilemap->max_water_level
|| curr_tile->water_level == tilemap->max_water_level
)
{
if (curr_idx % p_crunner->bfs_tilemap.width != 0)
{
next = curr_idx - 1;
next_tile = tilemap->tiles + next;
to_go[1] = next_tile->solid != SOLID;
}
next = curr_idx + 1;
if (next % p_crunner->bfs_tilemap.width != 0)
{
next_tile = tilemap->tiles + next;
to_go[2] = next_tile->solid != SOLID;
}
}
if (curr_tile->water_level == tilemap->max_water_level)
{
next = curr_idx - p_crunner->bfs_tilemap.width;
if (next >= 0 && next / p_crunner->bfs_tilemap.width >= p_crunner->start_height)
{
next_tile = tilemap->tiles + next;
to_go[3] = next_tile->solid != SOLID;
}
}
const int8_t offsets[4] = {p_crunner->bfs_tilemap.width, -1, 1, -p_crunner->bfs_tilemap.width};
for (uint8_t i = 0; i < 4; ++i)
{
next = curr_idx + offsets[i];
if (to_go[i] && !p_crunner->visited[next])
{
sc_queue_add_last(&p_crunner->bfs_queue, next);
p_crunner->bfs_tilemap.tilemap[next].from = curr_idx;
p_crunner->visited[next] = true;
p_crunner->bfs_tilemap.tilemap[next].reachable = true;
}
}
}
}
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:
@ -87,80 +167,27 @@ void update_water_runner_system(Scene_t* scene)
continue; continue;
} }
static uint8_t delay = 5;
switch (p_crunner->state) switch (p_crunner->state)
{ {
case LOWEST_POINT_SEARCH: case BFS_RESET:
{
for (size_t i = 0; i < p_crunner->bfs_tilemap.len; ++i) 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].from = -1;
p_crunner->bfs_tilemap.tilemap[i].reachable = false; p_crunner->bfs_tilemap.tilemap[i].reachable = false;
} }
p_crunner->state = BFS_START;
// Want the fallthough
case BFS_START:
memset(p_crunner->visited, 0, p_crunner->bfs_tilemap.len * sizeof(bool)); memset(p_crunner->visited, 0, p_crunner->bfs_tilemap.len * sizeof(bool));
int32_t lowest_tile = p_crunner->current_tile; int32_t lowest_tile = p_crunner->current_tile;
p_crunner->visited[p_crunner->current_tile] = true;
p_crunner->bfs_tilemap.tilemap[p_crunner->current_tile].reachable = true;
sc_queue_add_last(&p_crunner->bfs_queue, p_crunner->current_tile); sc_queue_add_last(&p_crunner->bfs_queue, p_crunner->current_tile);
while (!sc_queue_empty(&p_crunner->bfs_queue)) p_crunner->state = LOWEST_POINT_SEARCH;
{ // Want the fallthough
unsigned int curr_idx = sc_queue_peek_first(&p_crunner->bfs_queue); case LOWEST_POINT_SEARCH:
sc_queue_del_first(&p_crunner->bfs_queue); {
runner_BFS(&tilemap, p_crunner, &lowest_tile);
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; p_crunner->target_tile = lowest_tile;
if (p_crunner->target_tile == p_crunner->current_tile) if (p_crunner->target_tile == p_crunner->current_tile)
@ -177,13 +204,13 @@ void update_water_runner_system(Scene_t* scene)
prev_idx = curr_idx; prev_idx = curr_idx;
curr_idx = p_crunner->bfs_tilemap.tilemap[prev_idx].from; curr_idx = p_crunner->bfs_tilemap.tilemap[prev_idx].from;
} }
delay = 5; p_crunner->counter = p_crunner->movement_delay;
p_crunner->state = LOWEST_POINT_MOVEMENT; p_crunner->state = LOWEST_POINT_MOVEMENT;
} }
break; break;
case LOWEST_POINT_MOVEMENT: case LOWEST_POINT_MOVEMENT:
delay--; p_crunner->counter--;
if (delay == 0) if (p_crunner->counter == 0)
{ {
p_crunner->current_tile = p_crunner->bfs_tilemap.tilemap[p_crunner->current_tile].to; p_crunner->current_tile = p_crunner->bfs_tilemap.tilemap[p_crunner->current_tile].to;
CTransform_t* p_ct = get_component(ent, CTRANSFORM_COMP_T); CTransform_t* p_ct = get_component(ent, CTRANSFORM_COMP_T);
@ -191,11 +218,30 @@ void update_water_runner_system(Scene_t* scene)
p_ct->position.y = (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) if (p_crunner->current_tile == p_crunner->target_tile)
{ {
p_crunner->state = SCANLINE_FILL; p_crunner->state = REACHABILITY_SEARCH;
} }
delay = 5; p_crunner->counter = p_crunner->movement_delay;
} }
break; break;
case REACHABILITY_SEARCH:
{
unsigned int start_tile =
(p_crunner->current_tile / p_crunner->bfs_tilemap.width) * p_crunner->bfs_tilemap.width;
for (size_t i = 0; i < p_crunner->bfs_tilemap.width; ++i)
{
p_crunner->bfs_tilemap.tilemap[start_tile + i].reachable = false;
}
memset(p_crunner->visited, 0, p_crunner->bfs_tilemap.len * sizeof(bool));
p_crunner->bfs_tilemap.tilemap[p_crunner->current_tile].reachable = true;
sc_queue_add_last(&p_crunner->bfs_queue, p_crunner->current_tile);
int32_t lowest_tile = p_crunner->current_tile;
runner_BFS(&tilemap, p_crunner, &lowest_tile);
p_crunner->state = SCANLINE_FILL;
}
break;
default: default:
break; break;
} }

View File

@ -24,6 +24,8 @@ static GameEngine_t engine =
.assets = {0} .assets = {0}
}; };
static bool water_toggle = false;
static void level_scene_render_func(Scene_t* scene) static void level_scene_render_func(Scene_t* scene)
{ {
LevelSceneData_t* data = &(CONTAINER_OF(scene, LevelScene_t, scene)->data); LevelSceneData_t* data = &(CONTAINER_OF(scene, LevelScene_t, scene)->data);
@ -107,6 +109,25 @@ static void level_scene_render_func(Scene_t* scene)
next_idx = p_runner->bfs_tilemap.tilemap[curr_idx].to; next_idx = p_runner->bfs_tilemap.tilemap[curr_idx].to;
} }
} }
else if (p_runner->state == SCANLINE_FILL)
{
unsigned int tile_y = ((p_runner->current_tile) / tilemap.width);
for (size_t i = 0; i < tilemap.width; ++i)
{
unsigned int tile_idx = i + tile_y * tilemap.width;
if (
p_runner->bfs_tilemap.tilemap[tile_idx].reachable
&& tilemap.tiles[tile_idx].water_level < tilemap.max_water_level
)
{
DrawRectangle(
i * tilemap.tile_size, tile_y * tilemap.tile_size,
tilemap.tile_size, tilemap.tile_size, ColorAlpha(GREEN, 0.4)
);
}
}
}
} }
char buffer[64] = {0}; char buffer[64] = {0};
@ -146,7 +167,7 @@ static void level_scene_render_func(Scene_t* scene)
draw_rec.y = 0; draw_rec.y = 0;
draw_rec.height *= -1; draw_rec.height *= -1;
BeginDrawing(); BeginDrawing();
ClearBackground(LIGHTGRAY); ClearBackground( water_toggle? ColorAlpha(BLUE, 0.2) : LIGHTGRAY);
DrawTextureRec( DrawTextureRec(
data->game_viewport.texture, data->game_viewport.texture,
draw_rec, draw_rec,
@ -210,15 +231,33 @@ static void toggle_block_system(Scene_t* scene)
if (tile_idx == last_tile_idx) return; if (tile_idx == last_tile_idx) return;
if (IsMouseButtonDown(MOUSE_LEFT_BUTTON)) if (IsMouseButtonDown(MOUSE_LEFT_BUTTON))
{ {
TileType_t new_type = EMPTY_TILE; if (!water_toggle)
new_type = (tilemap.tiles[tile_idx].tile_type == SOLID_TILE)? EMPTY_TILE : SOLID_TILE; {
if (new_type == SOLID_TILE) tilemap.tiles[tile_idx].water_level = 0; TileType_t new_type = EMPTY_TILE;
change_a_tile(&tilemap, tile_idx, new_type); new_type = (tilemap.tiles[tile_idx].tile_type == SOLID_TILE)? EMPTY_TILE : SOLID_TILE;
if (new_type == SOLID_TILE) tilemap.tiles[tile_idx].water_level = 0;
change_a_tile(&tilemap, tile_idx, new_type);
}
else
{
if (tilemap.tiles[tile_idx].tile_type != SOLID_TILE)
{
if (tilemap.tiles[tile_idx].water_level > 0)
{
tilemap.tiles[tile_idx].water_level = 0;
}
else
{
tilemap.tiles[tile_idx].water_level = tilemap.max_water_level;
}
}
}
last_tile_idx = tile_idx; last_tile_idx = tile_idx;
CWaterRunner_t* p_crunner; CWaterRunner_t* p_crunner;
sc_map_foreach_value(&scene->ent_manager.component_map[CWATERRUNNER_T], p_crunner) sc_map_foreach_value(&scene->ent_manager.component_map[CWATERRUNNER_T], p_crunner)
{ {
p_crunner->state = LOWEST_POINT_SEARCH; p_crunner->state = BFS_RESET;
} }
} }
else if (IsMouseButtonReleased(MOUSE_RIGHT_BUTTON)) else if (IsMouseButtonReleased(MOUSE_RIGHT_BUTTON))
@ -284,6 +323,9 @@ static void level_do_action(Scene_t* scene, ActionType_t action, bool pressed)
case ACTION_RIGHT: case ACTION_RIGHT:
p_playerstate->player_dir.x = (pressed)? 1 : 0; p_playerstate->player_dir.x = (pressed)? 1 : 0;
break; break;
case ACTION_METAL_TOGGLE:
if (!pressed) water_toggle = !water_toggle;
break;
default: default:
break; break;
} }
@ -331,6 +373,7 @@ int main(void)
scene.data.tilemap.width = DEFAULT_MAP_WIDTH; scene.data.tilemap.width = DEFAULT_MAP_WIDTH;
scene.data.tilemap.height = DEFAULT_MAP_HEIGHT; scene.data.tilemap.height = DEFAULT_MAP_HEIGHT;
scene.data.tilemap.tile_size = TILE_SIZE; scene.data.tilemap.tile_size = TILE_SIZE;
scene.data.tilemap.max_water_level = 1;
scene.data.tilemap.n_tiles = scene.data.tilemap.width * scene.data.tilemap.height; scene.data.tilemap.n_tiles = scene.data.tilemap.width * scene.data.tilemap.height;
assert(scene.data.tilemap.n_tiles <= MAX_N_TILES); assert(scene.data.tilemap.n_tiles <= MAX_N_TILES);
scene.data.tilemap.tiles = all_tiles; scene.data.tilemap.tiles = all_tiles;
@ -340,7 +383,6 @@ 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};
} }
@ -373,6 +415,7 @@ int main(void)
sc_map_put_64(&scene.scene.action_map, KEY_DOWN, ACTION_DOWN); 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_LEFT, ACTION_LEFT);
sc_map_put_64(&scene.scene.action_map, KEY_RIGHT, ACTION_RIGHT); 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);
while(true) while(true)