From 8825b9e01f8014b64480f5eb516100e6ecca1aca Mon Sep 17 00:00:00 2001 From: Klemen Plestenjak Date: Tue, 14 Nov 2023 12:01:28 +0100 Subject: [PATCH] Path following --- CMakeLists.txt | 4 +- assets/maps/test.tmj | 4 +- game/components.c | 30 +++++++++++ game/components.h | 27 +++++++++- game/game_state.h | 3 ++ game/main.c | 24 +++++++-- game/map_init.c | 11 +--- game/systems/animations.c | 20 ------- game/systems/entity_renderer.c | 17 ------ game/systems/entity_systems.c | 95 ++++++++++++++++++++++++++++++++++ game/systems/systems.h | 37 ++----------- game/utils/pathfinding.c | 40 ++++++++++---- game/utils/pathfinding.h | 1 + tiled/test.tmx | 2 +- 14 files changed, 213 insertions(+), 102 deletions(-) create mode 100644 game/components.c delete mode 100644 game/systems/animations.c delete mode 100644 game/systems/entity_renderer.c create mode 100644 game/systems/entity_systems.c diff --git a/CMakeLists.txt b/CMakeLists.txt index f13922e..4390225 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -9,8 +9,7 @@ add_subdirectory(engine/) add_executable(PixelDefense - game/systems/animations.c - game/systems/entity_renderer.c + game/systems/entity_systems.c game/systems/systems.h game/utils/building_types.h @@ -19,6 +18,7 @@ add_executable(PixelDefense game/buildings.c game/buildings.h + game/components.c game/components.h game/entrypoint.c game/main.c diff --git a/assets/maps/test.tmj b/assets/maps/test.tmj index 8adee82..ec4ee4f 100644 --- a/assets/maps/test.tmj +++ b/assets/maps/test.tmj @@ -384,8 +384,8 @@ "type":"", "visible":true, "width":10, - "x":1155.52, - "y":473.417 + "x":914.186666666667, + "y":396.083666666667 }], "opacity":1, "type":"objectgroup", diff --git a/game/components.c b/game/components.c new file mode 100644 index 0000000..0c7831c --- /dev/null +++ b/game/components.c @@ -0,0 +1,30 @@ +#include "components.h" + + +ECS_COMPONENT_DECLARE(TilePosition); +ECS_COMPONENT_DECLARE(TileSize); +ECS_COMPONENT_DECLARE(Owner); +ECS_COMPONENT_DECLARE(Position); +ECS_COMPONENT_DECLARE(Size); +ECS_COMPONENT_DECLARE(TargetPosition); +ECS_COMPONENT_DECLARE(Rotation); +ECS_COMPONENT_DECLARE(Health); +ECS_COMPONENT_DECLARE(TextureRegion); +ECS_COMPONENT_DECLARE(AnimationType); +ECS_COMPONENT_DECLARE(Animation); +ECS_COMPONENT_DECLARE(Path); + +void initComponentIDs(ecs_world_t *ecs) { + ECS_COMPONENT_DEFINE(ecs, TilePosition); + ECS_COMPONENT_DEFINE(ecs, TileSize); + ECS_COMPONENT_DEFINE(ecs, Owner); + ECS_COMPONENT_DEFINE(ecs, Position); + ECS_COMPONENT_DEFINE(ecs, Size); + ECS_COMPONENT_DEFINE(ecs, TargetPosition); + ECS_COMPONENT_DEFINE(ecs, Rotation); + ECS_COMPONENT_DEFINE(ecs, Health); + ECS_COMPONENT_DEFINE(ecs, TextureRegion); + ECS_COMPONENT_DEFINE(ecs, AnimationType); + ECS_COMPONENT_DEFINE(ecs, Animation); + ECS_COMPONENT_DEFINE(ecs, Path); +} diff --git a/game/components.h b/game/components.h index 1fd79dd..8c73c9b 100644 --- a/game/components.h +++ b/game/components.h @@ -2,28 +2,40 @@ #define PIXELDEFENSE_COMPONENTS_H #include +#include #include "utils/building_types.h" + +#define ecs_set_p(world, e, T, ...) ecs_set_id(world, e, ecs_id(T), sizeof(T), (T*)__VA_ARGS__) + typedef struct TilePosition { BzTile x; BzTile y; } TilePosition; +extern ECS_COMPONENT_DECLARE(TilePosition); typedef struct TileSize { BzTile sizeX; BzTile sizeY; } TileSize; +extern ECS_COMPONENT_DECLARE(TileSize); typedef struct Owner { BuildingType playerID; } Owner; +extern ECS_COMPONENT_DECLARE(Owner); -typedef Vector2 Position, Size; +typedef Vector2 Position, Size, TargetPosition; +extern ECS_COMPONENT_DECLARE(Position); +extern ECS_COMPONENT_DECLARE(Size); +extern ECS_COMPONENT_DECLARE(TargetPosition); typedef f32 Rotation; +extern ECS_COMPONENT_DECLARE(Rotation); typedef f32 Health; +extern ECS_COMPONENT_DECLARE(Health); typedef struct TextureRegion { Texture2D texture; @@ -31,11 +43,13 @@ typedef struct TextureRegion { bool flipX; bool flipY; } TextureRegion; +extern ECS_COMPONENT_DECLARE(TextureRegion); typedef enum AnimationType { ANIMATION_IDLE, ANIMATION_WALK, } AnimationType; +extern ECS_COMPONENT_DECLARE(AnimationType); typedef struct Animation { TextureRegion firstFrame; @@ -45,7 +59,18 @@ typedef struct Animation { f32 frameDuration; f32 elapsed; } Animation; +extern ECS_COMPONENT_DECLARE(Animation); +typedef struct Path { + Position *waypoints; + i32 maxWaypoints; + i32 numWaypoints; + i32 curWaypoint; +} Path; +extern ECS_COMPONENT_DECLARE(Path); + + +void initComponentIDs(ecs_world_t *ecs); #endif //PIXELDEFENSE_COMPONENTS_H diff --git a/game/game_state.h b/game/game_state.h index cce6212..a6bb26c 100644 --- a/game/game_state.h +++ b/game/game_state.h @@ -14,6 +14,9 @@ typedef struct Game { ecs_entity_t *entityMap; f32 frameDuration; Vector2 targetPos; + ecs_entity_t entity; + Path path; + Position waypoints[128]; } Game; extern Game *GAME; diff --git a/game/main.c b/game/main.c index c3fab9b..b3bfcab 100644 --- a/game/main.c +++ b/game/main.c @@ -40,6 +40,8 @@ bool bzMain(BzAppDesc *appDesc, int argc, const char **argv) { } bool init(Game *game) { + initComponentIDs(ECS); + int screenWidth = 1280; int screenHeight = 720; @@ -48,6 +50,7 @@ bool init(Game *game) { game->camera.offset = (Vector2) {screenWidth / 2.0f, screenHeight / 2.0f}; game->camera.rotation = 0.0f; game->camera.zoom = 1.0f; + game->frameDuration = 0.16f; game->terrainTileset = bzTilesetCreate( &(BzTilesetDesc) { .path="assets/terrain.tsj", @@ -86,8 +89,9 @@ bool init(Game *game) { ECS_SYSTEM(ECS, updateAnimations, EcsOnUpdate, Animation, TextureRegion); ECS_SYSTEM(ECS, renderEntities, EcsOnUpdate, Position, Size, Rotation, TextureRegion); - ECS_SYSTEM(ECS, updatePos, EcsOnUpdate, Position, TextureRegion); - + ECS_SYSTEM(ECS, updatePos, EcsOnUpdate, Position, TargetPosition, TextureRegion); + ECS_OBSERVER(ECS, targetFinish, EcsOnRemove, TargetPosition); + ECS_OBSERVER(ECS, startPath, EcsOnSet, Path); return true; } void deinit(Game *game) { @@ -121,8 +125,6 @@ void update(float dt, Game *game) { game->camera.zoom += ((float) GetMouseWheelMove() * 0.05f); Vector2 worldPos = GetScreenToWorld2D(GetMousePosition(), game->camera); - if (IsMouseButtonPressed(MOUSE_BUTTON_LEFT)) - game->targetPos = worldPos; int tileX = (int) worldPos.x / 16; int tileY = (int) worldPos.y / 16; @@ -162,12 +164,26 @@ void render(float dt, Game *game) { static PathNode *heap = NULL; if (!heap) heap = bzHeapNew(PathNode, game->map.width * game->map.height); + game->path.waypoints = game->waypoints; + game->path.maxWaypoints = 128; findPath(&(PathfindingDesc) { .start=(TilePosition){57, 24}, .target=(TilePosition){tileX, tileY}, .map=&game->map, .heap=heap, + .outPath=&game->path }); + for (i32 i = 0; i < game->path.numWaypoints; i++) { + Position pos = game->path.waypoints[i]; + DrawCircle(pos.x, pos.y, 3.0f, RED); + } + if (game->path.numWaypoints > 0 && IsMouseButtonPressed(MOUSE_BUTTON_LEFT)) { + ecs_entity_t e = game->entity; + bzLogInfo("%d", ecs_is_alive(ECS, e)); + Position pos = worldPos; + //ecs_set_ptr(ECS, e, Position, &pos); + ecs_set_ptr(ECS, e, Path, &game->path); + } ecs_progress(ECS, dt); diff --git a/game/map_init.c b/game/map_init.c index d2f7a9d..6c42114 100644 --- a/game/map_init.c +++ b/game/map_init.c @@ -18,18 +18,13 @@ bool initGameObjectsLayer(BzTileMap *map, BzTileObjectGroup *objectGroup) { } bool initEntityObjectsLayer(BzTileMap *map, BzTileObjectGroup *objectGroup) { - ECS_COMPONENT(ECS, Position); - ECS_COMPONENT(ECS, Size); - ECS_COMPONENT(ECS, Rotation); - ECS_COMPONENT(ECS, TextureRegion); - ECS_COMPONENT(ECS, Animation); - BzTileset *objectTileset = bzTileObjectGroupGetTileset(&GAME->map, objectGroup); if (!objectTileset) return true; for (i32 i = 0; i < objectGroup->objectCount; i++) { BzTileObject object = objectGroup->objects[i]; ecs_entity_t e = ecs_new_id(ECS); + GAME->entity = e; ecs_set(ECS, e, Position, {object.shape.x, object.shape.y}); ecs_set(ECS, e, Size, {object.shape.sizeX, object.shape.sizeY}); ecs_set(ECS, e, Rotation, {0.0f}); @@ -50,10 +45,6 @@ bool initEntityObjectsLayer(BzTileMap *map, BzTileObjectGroup *objectGroup) { } bool initBuildingsLayer(BzTileMap *map, BzTileLayer *layer) { - ECS_COMPONENT(ECS, TilePosition); - ECS_COMPONENT(ECS, TileSize); - ECS_COMPONENT(ECS, Owner); - GAME->entityMap = bzCalloc(sizeof(*GAME->entityMap), layer->width * layer->height); BzTileLayer *ownershipLayer = layer; diff --git a/game/systems/animations.c b/game/systems/animations.c deleted file mode 100644 index 8436b5b..0000000 --- a/game/systems/animations.c +++ /dev/null @@ -1,20 +0,0 @@ -#include "systems.h" - -#include "../game_state.h" - -void updateAnimations(ecs_iter_t *it) { - Animation *anim = ecs_field(it, Animation, 1); - TextureRegion *t = ecs_field(it, TextureRegion, 2); - - float dt = GetFrameTime(); - - for (i32 i = 0; i < it->count; i++) { - anim[i].frameDuration = GAME->frameDuration; - anim[i].elapsed += dt; - if (anim[i].elapsed < anim[i].frameDuration) continue; - - anim[i].currFrame = (anim[i].currFrame + 1) % anim[i].frameCount; - anim[i].elapsed = 0.0f; - t[i].rec.x = anim[i].firstFrame.rec.x + anim[i].currFrame * t[i].rec.width; - } -} diff --git a/game/systems/entity_renderer.c b/game/systems/entity_renderer.c deleted file mode 100644 index 2cd2bda..0000000 --- a/game/systems/entity_renderer.c +++ /dev/null @@ -1,17 +0,0 @@ -#include "systems.h" - -void renderEntities(ecs_iter_t *it) { - Position *p = ecs_field(it, Position, 1); - Size *s = ecs_field(it, Size, 2); - Rotation *r = ecs_field(it, Rotation, 3); - TextureRegion *t = ecs_field(it, TextureRegion, 4); - - for (i32 i = 0; i < it->count; i++) { - Rectangle dst = {p[i].x, p[i].y, s[i].x, s[i].y}; - Vector2 origin = {dst.width * 0.5f, dst.height * 0.5f}; - Rectangle src = t[i].rec; - if (t[i].flipX) src.width *= -1.0f; - if (t[i].flipY) src.height *= -1.0f; - DrawTexturePro(t[i].texture, src, dst, origin, r[i], WHITE); - } -} diff --git a/game/systems/entity_systems.c b/game/systems/entity_systems.c new file mode 100644 index 0000000..feba150 --- /dev/null +++ b/game/systems/entity_systems.c @@ -0,0 +1,95 @@ +#include "systems.h" + + +#include "../game_state.h" +#include +#include + + +void updateAnimations(ecs_iter_t *it) { + Animation *anim = ecs_field(it, Animation, 1); + TextureRegion *t = ecs_field(it, TextureRegion, 2); + + float dt = GetFrameTime(); + + for (i32 i = 0; i < it->count; i++) { + anim[i].frameDuration = GAME->frameDuration; + anim[i].elapsed += dt; + if (anim[i].elapsed < anim[i].frameDuration) continue; + + anim[i].currFrame = (anim[i].currFrame + 1) % anim[i].frameCount; + anim[i].elapsed = 0.0f; + t[i].rec.x = anim[i].firstFrame.rec.x + anim[i].currFrame * t[i].rec.width; + } +} + +void renderEntities(ecs_iter_t *it) { + Position *p = ecs_field(it, Position, 1); + Size *s = ecs_field(it, Size, 2); + Rotation *r = ecs_field(it, Rotation, 3); + TextureRegion *t = ecs_field(it, TextureRegion, 4); + + for (i32 i = 0; i < it->count; i++) { + Rectangle dst = {p[i].x, p[i].y, s[i].x, s[i].y}; + Vector2 origin = {dst.width * 0.5f, dst.height * 0.5f}; + Rectangle src = t[i].rec; + if (t[i].flipX) src.width *= -1.0f; + if (t[i].flipY) src.height *= -1.0f; + DrawTexturePro(t[i].texture, src, dst, origin, r[i], WHITE); + } +} + +void updatePos(ecs_iter_t *it) { + Position *pos = ecs_field(it, Position, 1); + TargetPosition *target = ecs_field(it, TargetPosition, 2); + TextureRegion *t = ecs_field(it, TextureRegion, 3); + + for (i32 i = 0; i < it->count; i++) { + Vector2 d = Vector2Subtract(target[i], pos[i]); + if (Vector2LengthSqr(d) < 1) { + bzLogInfo("Done"); + ecs_remove(ECS, it->entities[i], TargetPosition); + } + + Vector2 dN = Vector2Normalize(d); + + dN = Vector2Scale(dN, 20); + t[i].flipX = dN.x < 0; + + + pos[i].x += dN.x * it->delta_time; + pos[i].y += dN.y * it->delta_time; + + } +} +#include +void targetFinish(ecs_iter_t *it) { + for (i32 i = 0; i < it->count; i++) { + ecs_entity_t e = it->entities[i]; + Path *path = ecs_get(it->world, e, Path); + if (!path) continue; + path->curWaypoint++; + if (path->curWaypoint >= path->numWaypoints) { + // Finished + ecs_remove(it->world, e, Path); + continue; + } + TargetPosition target = path->waypoints[path->curWaypoint - 1]; + target.x += rand() % (4 + 1 + 2) -2; + ecs_set(it->world, e, TargetPosition, {target.x, target.y}); + } + +} +void startPath(ecs_iter_t *it) { + Path *path = ecs_field(it, Path, 1); + + for (i32 i = 0; i < it->count; i++) { + ecs_entity_t e = it->entities[i]; + if (path->numWaypoints == 0) { + ecs_remove(it->world, e, Path); + continue; + } + ecs_set(it->world, e, TargetPosition, {path[i].waypoints[0].x, path[i].waypoints[0].y}); + path[i].curWaypoint++; + } +} diff --git a/game/systems/systems.h b/game/systems/systems.h index aa0563f..16b2e3a 100644 --- a/game/systems/systems.h +++ b/game/systems/systems.h @@ -7,39 +7,8 @@ void renderEntities(ecs_iter_t *it); void updateAnimations(ecs_iter_t *it); - -#include "../game_state.h" -#include -#include -static void updatePos(ecs_iter_t *it) { - Position *pos = ecs_field(it, Position, 1); - TextureRegion *t = ecs_field(it, TextureRegion, 2); - - Vector2 target = GAME->targetPos; - if (target.x == 0 && target.y == 0) return; - - for (i32 i = 0; i < it->count; i++) { - target = Vector2Subtract(target, pos[i]); - float dX = 0, dY = 0; - if (target.x > 0) dX = 1; - else if (target.x < 0) dX = -1; - if (target.y > 0) dY = 1; - else if (target.y < 0) dY = -1; - - dX *= 20; - dY *= 20; - - if (Vector2Length(target) < 1) continue; - - pos[i].x += dX * it->delta_time; - pos[i].y += dY * it->delta_time; - - f32 ddx = GAME->targetPos.x - pos[i].x; - if (ddx < 0) ddx *= -1.0f; - - if (ddx > 5) - t[i].flipX = dX < 0; - } -} +void updatePos(ecs_iter_t *it); +void targetFinish(ecs_iter_t *it); +void startPath(ecs_iter_t *it); #endif //PIXELDEFENSE_SYSTEMS_H diff --git a/game/utils/pathfinding.c b/game/utils/pathfinding.c index fe0d434..f111485 100644 --- a/game/utils/pathfinding.c +++ b/game/utils/pathfinding.c @@ -78,28 +78,46 @@ bool findPath(const PathfindingDesc *desc) { } } - DrawRectangle(desc->start.x * 16, desc->start.y * 16, 16, 16, BLUE); - Color color = RED; - if (foundPath) { - color = GREEN; + i32 pathLen = 0; + if (foundPath && desc->outPath) { TilePosition pos = desc->target; - int count = 0; while (pos.x != desc->start.x || pos.y != desc->start.y) { + Visited *visit = &visited[pos.y * map->width + pos.x]; + BZ_ASSERT(visit->x != 0 && visit->y != 0); + pos.x -= visit->x; + pos.y -= visit->y; + pathLen++; + } + Path *out = desc->outPath; + out->curWaypoint = 0; + pos = desc->target; + i32 len = pathLen; + // Skip positions + while (len >= out->maxWaypoints) { Visited visit = visited[pos.y * map->width + pos.x]; - BZ_ASSERT(visit.x != 0 && visit.y != 0); pos.x -= visit.x; pos.y -= visit.y; - DrawRectangle(pos.x * 16, pos.y * 16, 16, 16, GREEN); - count++; + len--; } - bzLogInfo("Path length: %d", count); + // Write path + for (i32 i = 0; i < len; i++) { + out->waypoints[len - i - 1] = (Position){ + pos.x * map->tileWidth + map->tileWidth * 0.5f, + pos.y * map->tileHeight + map->tileHeight * 0.5f + }; + out->numWaypoints++; + Visited visit = visited[pos.y * map->width + pos.x]; + pos.x -= visit.x; + pos.y -= visit.y; + } + BZ_ASSERT(len == out->maxWaypoints); + out->numWaypoints = len; } - DrawRectangle(desc->target.x * 16, desc->target.y * 16, 16, 16, color); if (!desc->heap) { bzHeapFree(heap); heap = NULL; } - return foundPath; + return foundPath ? pathLen : -1; } \ No newline at end of file diff --git a/game/utils/pathfinding.h b/game/utils/pathfinding.h index 7c0813f..1ff055e 100644 --- a/game/utils/pathfinding.h +++ b/game/utils/pathfinding.h @@ -22,6 +22,7 @@ typedef struct PathfindingDesc { TilePosition target; BzTileMap *map; PathNode *heap; + Path *outPath; } PathfindingDesc; bool findPath(const PathfindingDesc *desc); diff --git a/tiled/test.tmx b/tiled/test.tmx index 8bde69b..fab587b 100644 --- a/tiled/test.tmx +++ b/tiled/test.tmx @@ -331,7 +331,7 @@ - +