diff --git a/External/tely b/External/tely index 82dffbc..30b2bb7 160000 --- a/External/tely +++ b/External/tely @@ -1 +1 @@ -Subproject commit 82dffbc84d4119c0cc5b8d72972f0f4eaef83fa4 +Subproject commit 30b2bb7f10bd8a53785d78a8bdbe919881ffbe2d diff --git a/feely_pona.cpp b/feely_pona.cpp index b87716c..4ef9902 100644 --- a/feely_pona.cpp +++ b/feely_pona.cpp @@ -478,29 +478,31 @@ void TELY_DLL_Init(void *user_data) // NOTE: Hero { - FP_GameEntity *hero = FP_Game_MakeEntityPointerF(game, "Hero"); - hero->local_pos = Dqn_V2_InitNx2(100.f, 100.f); - hero->size_scale = Dqn_V2_InitNx1(4); - hero->sprite_sheet = &game->hero_sprite_sheet; - hero->sprite_anims = game->hero_sprite_anims; - hero->local_hit_box_size = Dqn_V2_InitV2I(game->hero_sprite_sheet.sprite_size); - hero->flags |= FP_GameEntityFlag_Clickable; - hero->flags |= FP_GameEntityFlag_MoveByKeyboard; - hero->flags |= FP_GameEntityFlag_MoveByMouse; - game->clicked_entity = hero->handle; + FP_GameEntity *entity = FP_Game_MakeEntityPointerF(game, "Hero"); + entity->local_pos = Dqn_V2_InitNx2(100.f, 100.f); + entity->size_scale = Dqn_V2_InitNx1(4); + entity->sprite_sheet = &game->hero_sprite_sheet; + entity->sprite_anims = game->hero_sprite_anims; + entity->local_hit_box_size = Dqn_V2_InitV2I(game->hero_sprite_sheet.sprite_size); + entity->flags |= FP_GameEntityFlag_Clickable; + entity->flags |= FP_GameEntityFlag_MoveByKeyboard; + entity->flags |= FP_GameEntityFlag_MoveByMouse; + entity->flags |= FP_GameEntityFlag_NonTraversable; + game->clicked_entity = entity->handle; } // NOTE: Enemy { - FP_GameEntity *enemy = FP_Game_MakeEntityPointerF(game, "Enemy"); - enemy->local_pos = Dqn_V2_InitNx2(300.f, 300.f); - enemy->size_scale = Dqn_V2_InitNx1(4); - enemy->sprite_sheet = &game->hero_sprite_sheet; - enemy->sprite_anims = game->hero_sprite_anims; - enemy->local_hit_box_size = Dqn_V2_InitV2I(game->hero_sprite_sheet.sprite_size); - enemy->flags |= FP_GameEntityFlag_Clickable; - enemy->flags |= FP_GameEntityFlag_MoveByKeyboard; - enemy->flags |= FP_GameEntityFlag_MoveByMouse; + FP_GameEntity *entity = FP_Game_MakeEntityPointerF(game, "Enemy"); + entity->local_pos = Dqn_V2_InitNx2(300.f, 300.f); + entity->size_scale = Dqn_V2_InitNx1(4); + entity->sprite_sheet = &game->hero_sprite_sheet; + entity->sprite_anims = game->hero_sprite_anims; + entity->local_hit_box_size = Dqn_V2_InitV2I(game->hero_sprite_sheet.sprite_size); + entity->flags |= FP_GameEntityFlag_Clickable; + entity->flags |= FP_GameEntityFlag_MoveByKeyboard; + entity->flags |= FP_GameEntityFlag_MoveByMouse; + entity->flags |= FP_GameEntityFlag_NonTraversable; } // NOTE: Wall @@ -511,6 +513,7 @@ void TELY_DLL_Init(void *user_data) entity->flags |= FP_GameEntityFlag_Clickable; entity->flags |= FP_GameEntityFlag_MoveByKeyboard; entity->flags |= FP_GameEntityFlag_MoveByMouse; + entity->flags |= FP_GameEntityFlag_NonTraversable; FP_GameShape *wall = Dqn_FArray_Make(&entity->shapes, Dqn_ZeroMem_Yes); 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->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 path_find = FP_Game_AStarPathFind(game, &platform->frame_arena, platform, entity_tile, stalk_tile); - + Dqn_Slice path_find = FP_Game_AStarPathFind(game, &platform->frame_arena, platform, entity->handle, stalk_tile); for (Dqn_usize index = path_find.size - 1; index < path_find.size; index--) { FP_GameWaypoint *waypoint = TELY_ChunkPool_New(game->chunk_pool, FP_GameWaypoint); waypoint->pos = path_find.data[index]; diff --git a/feely_pona_game.cpp b/feely_pona_game.cpp index 841e707..cf21ef4 100644 --- a/feely_pona_game.cpp +++ b/feely_pona_game.cpp @@ -450,8 +450,37 @@ static bool FP_Game_EntityActionHasFailed(FP_GameEntityAction const *action) return result; } -static Dqn_Slice 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 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 colliders = Dqn_List_Init(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 astar_info = Dqn_DSMap_Init(128); DQN_DEFER { Dqn_DSMap_Deinit(&astar_info); }; @@ -465,6 +494,10 @@ static Dqn_Slice 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); 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) { Dqn_V2I curr_tile = Dqn_FArray_PopFront(&frontier, 1); if (curr_tile == dest_tile) @@ -491,18 +524,47 @@ static Dqn_Slice FP_Game_AStarPathFind(FP_Game *game, Dqn_Arena *arena, Dqn_usize const curr_cost = Dqn_DSMap_FindKeyU64(&astar_info, curr_tile_u64).value->cost; for (Dqn_V2I next_tile : neighbours) { + // NOTE: Calculate cost to move to this neighbouring tile. 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); Dqn_DSMapResult 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) 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 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); next_cost_result.value->cost = new_cost; next_cost_result.value->came_from = curr_tile; 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 bool inserted = false; DQN_FOR_UINDEX(index, frontier.size) { @@ -525,14 +587,14 @@ static Dqn_Slice FP_Game_AStarPathFind(FP_Game *game, Dqn_Arena *arena, } 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); it = Dqn_DSMap_FindKeyU64(&astar_info, key_u64).value->came_from; } Dqn_Slice result = Dqn_Slice_Alloc(arena, slice_size, Dqn_ZeroMem_No); 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; 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; diff --git a/feely_pona_game.h b/feely_pona_game.h index 73b0c06..029ee0d 100644 --- a/feely_pona_game.h +++ b/feely_pona_game.h @@ -10,6 +10,7 @@ enum FP_GameEntityFlag FP_GameEntityFlag_MoveByMouse = 1 << 2, FP_GameEntityFlag_DrawHitBox = 1 << 3, FP_GameEntityFlag_DeriveHitBoxFromChildrenBoundingBox = 1 << 4, + FP_GameEntityFlag_NonTraversable = 1 << 5, }; enum FP_GameShapeType