fp: Make path finding consider non-traversables
This commit is contained in:
parent
cc202cefac
commit
7d33baab4f
2
External/tely
vendored
2
External/tely
vendored
@ -1 +1 @@
|
|||||||
Subproject commit 82dffbc84d4119c0cc5b8d72972f0f4eaef83fa4
|
Subproject commit 30b2bb7f10bd8a53785d78a8bdbe919881ffbe2d
|
@ -478,29 +478,31 @@ void TELY_DLL_Init(void *user_data)
|
|||||||
|
|
||||||
// NOTE: Hero
|
// NOTE: Hero
|
||||||
{
|
{
|
||||||
FP_GameEntity *hero = FP_Game_MakeEntityPointerF(game, "Hero");
|
FP_GameEntity *entity = FP_Game_MakeEntityPointerF(game, "Hero");
|
||||||
hero->local_pos = Dqn_V2_InitNx2(100.f, 100.f);
|
entity->local_pos = Dqn_V2_InitNx2(100.f, 100.f);
|
||||||
hero->size_scale = Dqn_V2_InitNx1(4);
|
entity->size_scale = Dqn_V2_InitNx1(4);
|
||||||
hero->sprite_sheet = &game->hero_sprite_sheet;
|
entity->sprite_sheet = &game->hero_sprite_sheet;
|
||||||
hero->sprite_anims = game->hero_sprite_anims;
|
entity->sprite_anims = game->hero_sprite_anims;
|
||||||
hero->local_hit_box_size = Dqn_V2_InitV2I(game->hero_sprite_sheet.sprite_size);
|
entity->local_hit_box_size = Dqn_V2_InitV2I(game->hero_sprite_sheet.sprite_size);
|
||||||
hero->flags |= FP_GameEntityFlag_Clickable;
|
entity->flags |= FP_GameEntityFlag_Clickable;
|
||||||
hero->flags |= FP_GameEntityFlag_MoveByKeyboard;
|
entity->flags |= FP_GameEntityFlag_MoveByKeyboard;
|
||||||
hero->flags |= FP_GameEntityFlag_MoveByMouse;
|
entity->flags |= FP_GameEntityFlag_MoveByMouse;
|
||||||
game->clicked_entity = hero->handle;
|
entity->flags |= FP_GameEntityFlag_NonTraversable;
|
||||||
|
game->clicked_entity = entity->handle;
|
||||||
}
|
}
|
||||||
|
|
||||||
// NOTE: Enemy
|
// NOTE: Enemy
|
||||||
{
|
{
|
||||||
FP_GameEntity *enemy = FP_Game_MakeEntityPointerF(game, "Enemy");
|
FP_GameEntity *entity = FP_Game_MakeEntityPointerF(game, "Enemy");
|
||||||
enemy->local_pos = Dqn_V2_InitNx2(300.f, 300.f);
|
entity->local_pos = Dqn_V2_InitNx2(300.f, 300.f);
|
||||||
enemy->size_scale = Dqn_V2_InitNx1(4);
|
entity->size_scale = Dqn_V2_InitNx1(4);
|
||||||
enemy->sprite_sheet = &game->hero_sprite_sheet;
|
entity->sprite_sheet = &game->hero_sprite_sheet;
|
||||||
enemy->sprite_anims = game->hero_sprite_anims;
|
entity->sprite_anims = game->hero_sprite_anims;
|
||||||
enemy->local_hit_box_size = Dqn_V2_InitV2I(game->hero_sprite_sheet.sprite_size);
|
entity->local_hit_box_size = Dqn_V2_InitV2I(game->hero_sprite_sheet.sprite_size);
|
||||||
enemy->flags |= FP_GameEntityFlag_Clickable;
|
entity->flags |= FP_GameEntityFlag_Clickable;
|
||||||
enemy->flags |= FP_GameEntityFlag_MoveByKeyboard;
|
entity->flags |= FP_GameEntityFlag_MoveByKeyboard;
|
||||||
enemy->flags |= FP_GameEntityFlag_MoveByMouse;
|
entity->flags |= FP_GameEntityFlag_MoveByMouse;
|
||||||
|
entity->flags |= FP_GameEntityFlag_NonTraversable;
|
||||||
}
|
}
|
||||||
|
|
||||||
// NOTE: Wall
|
// NOTE: Wall
|
||||||
@ -511,6 +513,7 @@ void TELY_DLL_Init(void *user_data)
|
|||||||
entity->flags |= FP_GameEntityFlag_Clickable;
|
entity->flags |= FP_GameEntityFlag_Clickable;
|
||||||
entity->flags |= FP_GameEntityFlag_MoveByKeyboard;
|
entity->flags |= FP_GameEntityFlag_MoveByKeyboard;
|
||||||
entity->flags |= FP_GameEntityFlag_MoveByMouse;
|
entity->flags |= FP_GameEntityFlag_MoveByMouse;
|
||||||
|
entity->flags |= FP_GameEntityFlag_NonTraversable;
|
||||||
|
|
||||||
FP_GameShape *wall = Dqn_FArray_Make(&entity->shapes, Dqn_ZeroMem_Yes);
|
FP_GameShape *wall = Dqn_FArray_Make(&entity->shapes, Dqn_ZeroMem_Yes);
|
||||||
wall->type = FP_GameShapeType_Rect;
|
wall->type = FP_GameShapeType_Rect;
|
||||||
@ -593,9 +596,7 @@ void FP_Update(TELY_Platform *platform, FP_Game *game, TELY_Renderer *renderer,
|
|||||||
entity->waypoints->next = entity->waypoints;
|
entity->waypoints->next = entity->waypoints;
|
||||||
entity->waypoints->prev = entity->waypoints;
|
entity->waypoints->prev = entity->waypoints;
|
||||||
|
|
||||||
Dqn_V2I entity_tile = Dqn_V2I_InitNx2(entity_world_pos.x / game->tile_size, entity_world_pos.y / game->tile_size);
|
Dqn_Slice<Dqn_V2I> path_find = FP_Game_AStarPathFind(game, &platform->frame_arena, platform, entity->handle, stalk_tile);
|
||||||
Dqn_Slice<Dqn_V2I> path_find = FP_Game_AStarPathFind(game, &platform->frame_arena, platform, entity_tile, stalk_tile);
|
|
||||||
|
|
||||||
for (Dqn_usize index = path_find.size - 1; index < path_find.size; index--) {
|
for (Dqn_usize index = path_find.size - 1; index < path_find.size; index--) {
|
||||||
FP_GameWaypoint *waypoint = TELY_ChunkPool_New(game->chunk_pool, FP_GameWaypoint);
|
FP_GameWaypoint *waypoint = TELY_ChunkPool_New(game->chunk_pool, FP_GameWaypoint);
|
||||||
waypoint->pos = path_find.data[index];
|
waypoint->pos = path_find.data[index];
|
||||||
|
@ -450,8 +450,37 @@ static bool FP_Game_EntityActionHasFailed(FP_GameEntityAction const *action)
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
static Dqn_Slice<Dqn_V2I> FP_Game_AStarPathFind(FP_Game *game, Dqn_Arena *arena, TELY_Platform *platform, Dqn_V2I src_tile, Dqn_V2I dest_tile)
|
static Dqn_V2I FP_Game_WorldPosToTilePos(FP_Game *game, Dqn_V2 world_pos)
|
||||||
{
|
{
|
||||||
|
Dqn_V2I result = Dqn_V2I_InitNx2(world_pos.x / game->tile_size, world_pos.y / game->tile_size);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Dqn_Slice<Dqn_V2I> FP_Game_AStarPathFind(FP_Game *game,
|
||||||
|
Dqn_Arena *arena,
|
||||||
|
TELY_Platform *platform,
|
||||||
|
FP_GameEntityHandle entity,
|
||||||
|
Dqn_V2I dest_tile)
|
||||||
|
{
|
||||||
|
|
||||||
|
// NOTE: Enumerate the entities that are collidable ============================================
|
||||||
|
Dqn_ThreadScratch scratch = Dqn_Thread_GetScratch(arena);
|
||||||
|
Dqn_List<FP_GameEntity const *> colliders = Dqn_List_Init<FP_GameEntity const *>(scratch.arena, 128);
|
||||||
|
for (FP_GameEntityIterator it = {}; FP_Game_DFSPreOrderWalkEntityTree(game, &it, game->root_entity); ) {
|
||||||
|
FP_GameEntity const *walk_entity = it.entity;
|
||||||
|
if (entity == walk_entity->handle)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if ((walk_entity->flags & FP_GameEntityFlag_NonTraversable) == 0)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
Dqn_List_Add(&colliders, walk_entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOTE: Setup A* state ========================================================================
|
||||||
|
Dqn_V2 entity_world_pos = FP_Game_CalcEntityWorldPos(game, entity);
|
||||||
|
Dqn_V2I src_tile = FP_Game_WorldPosToTilePos(game, entity_world_pos);
|
||||||
|
|
||||||
Dqn_DSMap<FP_GameAStarNode> astar_info = Dqn_DSMap_Init<FP_GameAStarNode>(128);
|
Dqn_DSMap<FP_GameAStarNode> astar_info = Dqn_DSMap_Init<FP_GameAStarNode>(128);
|
||||||
DQN_DEFER { Dqn_DSMap_Deinit(&astar_info); };
|
DQN_DEFER { Dqn_DSMap_Deinit(&astar_info); };
|
||||||
|
|
||||||
@ -465,6 +494,10 @@ static Dqn_Slice<Dqn_V2I> FP_Game_AStarPathFind(FP_Game *game, Dqn_Arena *arena,
|
|||||||
uint64_t src_tile_u64 = (DQN_CAST(uint64_t)src_tile.y << 32) | (DQN_CAST(uint64_t)src_tile.x << 0);
|
uint64_t src_tile_u64 = (DQN_CAST(uint64_t)src_tile.y << 32) | (DQN_CAST(uint64_t)src_tile.x << 0);
|
||||||
Dqn_DSMap_MakeKeyU64(&astar_info, src_tile_u64);
|
Dqn_DSMap_MakeKeyU64(&astar_info, src_tile_u64);
|
||||||
|
|
||||||
|
// NOTE: Do the A* process =====================================================================
|
||||||
|
Dqn_usize last_successful_manhattan_dist = UINT64_MAX;
|
||||||
|
Dqn_V2I last_successful_tile = src_tile;
|
||||||
|
|
||||||
while (frontier.size) {
|
while (frontier.size) {
|
||||||
Dqn_V2I curr_tile = Dqn_FArray_PopFront(&frontier, 1);
|
Dqn_V2I curr_tile = Dqn_FArray_PopFront(&frontier, 1);
|
||||||
if (curr_tile == dest_tile)
|
if (curr_tile == dest_tile)
|
||||||
@ -491,18 +524,47 @@ static Dqn_Slice<Dqn_V2I> FP_Game_AStarPathFind(FP_Game *game, Dqn_Arena *arena,
|
|||||||
Dqn_usize const curr_cost = Dqn_DSMap_FindKeyU64(&astar_info, curr_tile_u64).value->cost;
|
Dqn_usize const curr_cost = Dqn_DSMap_FindKeyU64(&astar_info, curr_tile_u64).value->cost;
|
||||||
for (Dqn_V2I next_tile : neighbours) {
|
for (Dqn_V2I next_tile : neighbours) {
|
||||||
|
|
||||||
|
// NOTE: Calculate cost to move to this neighbouring tile.
|
||||||
Dqn_usize new_cost = curr_cost + 1;
|
Dqn_usize new_cost = curr_cost + 1;
|
||||||
uint64_t next_tile_u64 = (DQN_CAST(uint64_t)next_tile.y << 32) | (DQN_CAST(uint64_t)next_tile.x << 0);
|
uint64_t next_tile_u64 = (DQN_CAST(uint64_t)next_tile.y << 32) | (DQN_CAST(uint64_t)next_tile.x << 0);
|
||||||
Dqn_DSMapResult<FP_GameAStarNode> next_cost_result = Dqn_DSMap_MakeKeyU64(&astar_info, next_tile_u64);
|
Dqn_DSMapResult<FP_GameAStarNode> next_cost_result = Dqn_DSMap_MakeKeyU64(&astar_info, next_tile_u64);
|
||||||
|
|
||||||
|
// NOTE: If we have already visited this node before, we only keep the cost if it's cheaper
|
||||||
if (next_cost_result.found && new_cost >= next_cost_result.value->cost)
|
if (next_cost_result.found && new_cost >= next_cost_result.value->cost)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
|
// NOTE: Check if the neighbour is a non-traversable tile, if so we skip it
|
||||||
|
bool tile_is_non_traversable = false;
|
||||||
|
for (Dqn_ListIterator<FP_GameEntity const *> it = {}; Dqn_List_Iterate(&colliders, &it, 0); ) {
|
||||||
|
FP_GameEntity const *collider = *it.data;
|
||||||
|
Dqn_Rect bounding_box = FP_Game_CalcEntityWorldBoundingBox(game, collider->handle);
|
||||||
|
Dqn_RectMinMax min_max = Dqn_Rect_MinMax(bounding_box);
|
||||||
|
Dqn_V2I min_tile = FP_Game_WorldPosToTilePos(game, min_max.min);
|
||||||
|
Dqn_V2I max_tile = FP_Game_WorldPosToTilePos(game, min_max.max);
|
||||||
|
|
||||||
|
if ((next_tile.x >= min_tile.x && next_tile.x <= max_tile.x) &&
|
||||||
|
(next_tile.y >= min_tile.y && next_tile.y <= max_tile.y)) {
|
||||||
|
tile_is_non_traversable = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tile_is_non_traversable)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// NOTE: Update the node cost value and the heuristic (estimated cost to the end)
|
||||||
Dqn_usize manhattan_dist = DQN_ABS(dest_tile.x - next_tile.x) + DQN_ABS(dest_tile.y - next_tile.y);
|
Dqn_usize manhattan_dist = DQN_ABS(dest_tile.x - next_tile.x) + DQN_ABS(dest_tile.y - next_tile.y);
|
||||||
next_cost_result.value->cost = new_cost;
|
next_cost_result.value->cost = new_cost;
|
||||||
next_cost_result.value->came_from = curr_tile;
|
next_cost_result.value->came_from = curr_tile;
|
||||||
next_cost_result.value->heuristic = new_cost + manhattan_dist;
|
next_cost_result.value->heuristic = new_cost + manhattan_dist;
|
||||||
|
|
||||||
|
// NOTE: Store the last node we visited that had the best cost.
|
||||||
|
// We may end up with a partial path find that could only get
|
||||||
|
// part-way to the solution which this variable will track for us.
|
||||||
|
if (manhattan_dist < last_successful_manhattan_dist) {
|
||||||
|
last_successful_manhattan_dist = manhattan_dist;
|
||||||
|
last_successful_tile = next_tile;
|
||||||
|
}
|
||||||
|
|
||||||
// TODO(doyle): Find the insert location into the frontier
|
// TODO(doyle): Find the insert location into the frontier
|
||||||
bool inserted = false;
|
bool inserted = false;
|
||||||
DQN_FOR_UINDEX(index, frontier.size) {
|
DQN_FOR_UINDEX(index, frontier.size) {
|
||||||
@ -525,14 +587,14 @@ static Dqn_Slice<Dqn_V2I> FP_Game_AStarPathFind(FP_Game *game, Dqn_Arena *arena,
|
|||||||
}
|
}
|
||||||
|
|
||||||
Dqn_usize slice_size = 0;
|
Dqn_usize slice_size = 0;
|
||||||
for (Dqn_V2I it = dest_tile; it != src_tile; slice_size++) {
|
for (Dqn_V2I it = last_successful_tile; it != src_tile; slice_size++) {
|
||||||
uint64_t key_u64 = (DQN_CAST(uint64_t)it.y << 32) | (DQN_CAST(uint64_t)it.x << 0);
|
uint64_t key_u64 = (DQN_CAST(uint64_t)it.y << 32) | (DQN_CAST(uint64_t)it.x << 0);
|
||||||
it = Dqn_DSMap_FindKeyU64(&astar_info, key_u64).value->came_from;
|
it = Dqn_DSMap_FindKeyU64(&astar_info, key_u64).value->came_from;
|
||||||
}
|
}
|
||||||
|
|
||||||
Dqn_Slice<Dqn_V2I> result = Dqn_Slice_Alloc<Dqn_V2I>(arena, slice_size, Dqn_ZeroMem_No);
|
Dqn_Slice<Dqn_V2I> result = Dqn_Slice_Alloc<Dqn_V2I>(arena, slice_size, Dqn_ZeroMem_No);
|
||||||
slice_size = 0;
|
slice_size = 0;
|
||||||
for (Dqn_V2I it = dest_tile; it != src_tile; ) {
|
for (Dqn_V2I it = last_successful_tile; it != src_tile; ) {
|
||||||
result.data[slice_size++] = it;
|
result.data[slice_size++] = it;
|
||||||
uint64_t key_u64 = (DQN_CAST(uint64_t)it.y << 32) | (DQN_CAST(uint64_t)it.x << 0);
|
uint64_t key_u64 = (DQN_CAST(uint64_t)it.y << 32) | (DQN_CAST(uint64_t)it.x << 0);
|
||||||
it = Dqn_DSMap_FindKeyU64(&astar_info, key_u64).value->came_from;
|
it = Dqn_DSMap_FindKeyU64(&astar_info, key_u64).value->came_from;
|
||||||
|
@ -10,6 +10,7 @@ enum FP_GameEntityFlag
|
|||||||
FP_GameEntityFlag_MoveByMouse = 1 << 2,
|
FP_GameEntityFlag_MoveByMouse = 1 << 2,
|
||||||
FP_GameEntityFlag_DrawHitBox = 1 << 3,
|
FP_GameEntityFlag_DrawHitBox = 1 << 3,
|
||||||
FP_GameEntityFlag_DeriveHitBoxFromChildrenBoundingBox = 1 << 4,
|
FP_GameEntityFlag_DeriveHitBoxFromChildrenBoundingBox = 1 << 4,
|
||||||
|
FP_GameEntityFlag_NonTraversable = 1 << 5,
|
||||||
};
|
};
|
||||||
|
|
||||||
enum FP_GameShapeType
|
enum FP_GameShapeType
|
||||||
|
Loading…
Reference in New Issue
Block a user