diff --git a/.gitignore b/.gitignore index ed09431..962ad24 100644 --- a/.gitignore +++ b/.gitignore @@ -7,5 +7,6 @@ cmake-build-debug/ cmake-build-release/ tiled/PixelDefense.tiled-session venv/ +scripts/__pycache__/ imgui.ini diff --git a/CMakeLists.txt b/CMakeLists.txt index 217e8c9..9def1d0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -10,6 +10,7 @@ add_subdirectory(engine/) add_executable(PixelDefense game/utils/building_types.h + game/utils/entity_types.h game/buildings.c game/buildings.h diff --git a/assets/entities.tsj b/assets/entities.tsj index 6088bbb..e910145 100644 --- a/assets/entities.tsj +++ b/assets/entities.tsj @@ -49,6 +49,26 @@ }], "type":"worker" }, + { + "id":4, + "properties":[ + { + "name":"animation", + "type":"string", + "value":"walk_2" + }], + "type":"worker" + }, + { + "id":5, + "properties":[ + { + "name":"animation", + "type":"string", + "value":"walk_3" + }], + "type":"worker" + }, { "id":64, "type":"axe" diff --git a/engine/breeze/map/map.c b/engine/breeze/map/map.c index 8975c96..1848bbf 100644 --- a/engine/breeze/map/map.c +++ b/engine/breeze/map/map.c @@ -504,5 +504,5 @@ bool bzTileMapHasCollision(BzTileMap *map, i32 x, i32 y) { void bzTileMapUpdateCollisions(BzTileMap *map, i32 x, i32 y, i32 sizeX, i32 sizeY) { if (!map->collisionMap) return; - updateCollisionMap(map, x, y, sizeX, sizeY); + updateCollisionMap(map, x, y, x + sizeX, y + sizeY); } diff --git a/game/buildings.c b/game/buildings.c index 6fc21b8..e2ae4ad 100644 --- a/game/buildings.c +++ b/game/buildings.c @@ -65,7 +65,7 @@ ecs_entity_t placeBuilding(BzTileMap *map, BuildingType type, BzTile tileX, BzTi bzTileLayerSetTile(buildingLayer, layerTile, x, y, 1, 1); buildingTile++; - bzTileMapUpdateCollisions(map, x, y, map->tileWidth, map->tileHeight); + bzTileMapUpdateCollisions(map, x, y, 1, 1); } buildingTile += buildingTileset->width - sizeX; } diff --git a/game/components.c b/game/components.c index de2e78a..8c25636 100644 --- a/game/components.c +++ b/game/components.c @@ -22,8 +22,8 @@ ECS_COMPONENT_DECLARE(AngularVelocity); ECS_COMPONENT_DECLARE(SteeringOutput); ECS_COMPONENT_DECLARE(TextureRegion); -ECS_COMPONENT_DECLARE(AnimationType); ECS_COMPONENT_DECLARE(Animation); +ECS_COMPONENT_DECLARE(AnimationType); ECS_COMPONENT_DECLARE(Path); void initComponentIDs(ecs_world_t *ecs) { @@ -49,7 +49,7 @@ void initComponentIDs(ecs_world_t *ecs) { ECS_COMPONENT_DEFINE(ecs, SteeringOutput); ECS_COMPONENT_DEFINE(ecs, TextureRegion); - ECS_COMPONENT_DEFINE(ecs, AnimationType); ECS_COMPONENT_DEFINE(ecs, Animation); + ECS_COMPONENT_DEFINE(ecs, AnimationType); ECS_COMPONENT_DEFINE(ecs, Path); } diff --git a/game/components.h b/game/components.h index 561edb0..0d12be9 100644 --- a/game/components.h +++ b/game/components.h @@ -5,6 +5,7 @@ #include #include "utils/building_types.h" +#include "utils/entity_types.h" extern ECS_TAG_DECLARE(TextureTerrain); extern ECS_TAG_DECLARE(TextureBuildings); @@ -67,22 +68,19 @@ typedef struct TextureRegion { } TextureRegion; extern ECS_COMPONENT_DECLARE(TextureRegion); -typedef enum AnimationType { - ANIMATION_IDLE, - ANIMATION_WALK, -} AnimationType; -extern ECS_COMPONENT_DECLARE(AnimationType); - typedef struct Animation { - TextureRegion firstFrame; - AnimationType currAnimation; - i32 currFrame; - i32 frameCount; + EntityType entityType; + AnimationType animType; + AnimationSequence sequence; + BzTileset *tileset; + i32 curFrame; f32 frameDuration; f32 elapsed; } Animation; extern ECS_COMPONENT_DECLARE(Animation); +extern ECS_COMPONENT_DECLARE(AnimationType); + #define PATH_DATA_SIZE 8 typedef struct PathData { Position waypoints[PATH_DATA_SIZE]; diff --git a/game/main.c b/game/main.c index b9c4787..70f0580 100644 --- a/game/main.c +++ b/game/main.c @@ -134,6 +134,11 @@ bool init(void *userData) { .userDataSize=sizeof(ecs_entity_t) }); + ECS_OBSERVER(ECS, entitySpatialRemoved, EcsOnRemove, Position, SpatialGridID); + ECS_OBSERVER(ECS, entityPathRemoved, EcsOnRemove, Path); + + ECS_OBSERVER(ECS, entitySetAnimationState, EcsOnSet, Animation, AnimationType); + bzTileMapOverrideLayer(&game->map, LAYER_TREES, initTreesLayer); bzTileMapOverrideLayer(&game->map, LAYER_TREES2, initTreesLayer); @@ -142,13 +147,12 @@ bool init(void *userData) { bzTileMapOverrideObjectGroup(&game->map, OBJECTS_GAME, initGameObjectsLayer); bzTileMapOverrideObjectGroup(&game->map, OBJECTS_ENTITIES, initEntityObjectsLayer); - ECS_OBSERVER(ECS, entitySpatialRemoved, EcsOnRemove, Position, SpatialGridID); - ECS_OBSERVER(ECS, pathRemoved, EcsOnRemove, Path); - ECS_SYSTEM(ECS, entityUpdateSpatialID, EcsOnUpdate, Position, Size, Velocity, SpatialGridID); ECS_SYSTEM(ECS, entityUpdateKinematic, EcsOnUpdate, Position, Rotation, Velocity, AngularVelocity, SteeringOutput); ECS_SYSTEM(ECS, entityFollowPath, EcsOnUpdate, Position, Rotation, Velocity, AngularVelocity, SteeringOutput, Path); + ECS_SYSTEM(ECS, entityUpdateAnimation, EcsOnUpdate, Animation, TextureRegion); + ECS_SYSTEM(ECS, renderDebugPath, EcsOnUpdate, Path); ECS_SYSTEM(ECS, renderTerrain, EcsOnUpdate, Position, Size, Rotation, TextureRegion, TextureTerrain); diff --git a/game/map_init.c b/game/map_init.c index a85b0e6..2204a63 100644 --- a/game/map_init.c +++ b/game/map_init.c @@ -40,15 +40,13 @@ bool initEntityObjectsLayer(BzTileMap *map, BzTileObjectGroup *objectGroup) { ecs_set(ECS, e, SteeringOutput, {}); ecs_set(ECS, e, TextureRegion, {objectTileset->tiles, bzTilesetGetTileRegion(objectTileset, object.gid)}); ecs_set(ECS, e, Animation, { - .firstFrame=(TextureRegion) { - objectTileset->tiles, - bzTilesetGetTileRegion(objectTileset, object.gid + 1) - }, - .currAnimation=ANIMATION_IDLE, - .currFrame=0, - .frameCount=4, - .frameDuration=0.20f + .entityType=ENTITY_WORKER, + .tileset = objectTileset, + .curFrame=0, + .frameDuration=0.6f + (i % 10) * 0.05f, + .elapsed=i * 0.1f, }); + ecs_set(ECS, e, AnimationType, {ANIM_IDLE}); } return true; diff --git a/game/systems.h b/game/systems.h index 029c102..4f38c0e 100644 --- a/game/systems.h +++ b/game/systems.h @@ -21,7 +21,13 @@ void entitySpatialRemoved(ecs_iter_t *it); * 0: Game (singleton) for object pool * 1: Path */ -void pathRemoved(ecs_iter_t *it); +void entityPathRemoved(ecs_iter_t *it); + +/* Observer (for updating animation state) + * 1: Animation + * 2: EntityState + */ +void entitySetAnimationState(ecs_iter_t *it); /* * 0: Game (singleton) for entity map @@ -53,6 +59,13 @@ void entityUpdateKinematic(ecs_iter_t *it); */ void entityFollowPath(ecs_iter_t *it); +/* + * 0: + * 1: Animation + * 2: TextureRegion + */ +void entityUpdateAnimation(ecs_iter_t *it); + /* * 0: */ diff --git a/game/systems_entity.c b/game/systems_entity.c index da88f30..ba7e3b0 100644 --- a/game/systems_entity.c +++ b/game/systems_entity.c @@ -20,7 +20,7 @@ void entitySpatialRemoved(ecs_iter_t *it) { } -void pathRemoved(ecs_iter_t *it) { +void entityPathRemoved(ecs_iter_t *it) { Game *game = ecs_singleton_get_mut(ECS, Game); BzObjectPool *pool = game->pools.pathData; Path *path = ecs_field(it, Path, 1); @@ -34,6 +34,18 @@ void pathRemoved(ecs_iter_t *it) { } } +void entitySetAnimationState(ecs_iter_t *it) { + Animation *anim = ecs_field(it, Animation, 1); + AnimationType *animType = ecs_field(it, AnimationType , 2); + for (i32 i = 0; i < it->count; i++) { + EntityType entityType = anim[i].entityType; + AnimationType type = animType[i]; + BZ_ASSERT(entityHasAnimation(entityType, type)); + anim[i].animType = type; + anim[i].sequence = getEntityAnimation(entityType, type); + } +} + void entityUpdateSpatialID(ecs_iter_t *it) { Game *game = ecs_singleton_get_mut(ECS, Game); Position *position = ecs_field(it, Position, 1); @@ -109,7 +121,7 @@ void entityFollowPath(ecs_iter_t *it) { } } -void updateAnimations(ecs_iter_t *it) { +void entityUpdateAnimation(ecs_iter_t *it) { Game *game = ecs_singleton_get_mut(ECS, Game); Animation *anim = ecs_field(it, Animation, 1); TextureRegion *t = ecs_field(it, TextureRegion, 2); @@ -121,9 +133,11 @@ void updateAnimations(ecs_iter_t *it) { anim[i].elapsed += dt; if (anim[i].elapsed < anim[i].frameDuration) continue; - anim[i].currFrame = (anim[i].currFrame + 1) % anim[i].frameCount; + anim[i].curFrame = (anim[i].curFrame + 1) % anim[i].sequence.frameCount; + + BzTile tile = anim[i].sequence.startFrame + anim[i].curFrame + anim[i].tileset->startID; + t[i].rec = bzTilesetGetTileRegion(anim[i].tileset, tile); 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/utils/entity_types.h b/game/utils/entity_types.h new file mode 100644 index 0000000..2ee13d0 --- /dev/null +++ b/game/utils/entity_types.h @@ -0,0 +1,47 @@ +#ifndef ENTITY_TYPES +#define ENTITY_TYPES + +#include + +typedef enum EntityType { + ENTITY_WORKER = 0, + ENTITY_AXE = 64, +} EntityType; + +typedef enum AnimationType { + ANIM_WALK, + ANIM_IDLE, +} AnimationType; + +typedef struct AnimationSequence { + i32 startFrame; + i32 frameCount; +} AnimationSequence; + +static bool entityHasAnimation(EntityType entity, AnimationType anim) { + switch (entity) { + case ENTITY_WORKER: { + switch (anim) { + case ANIM_IDLE: + case ANIM_WALK: + return true; + } + break; + } + } + return false; +} + +static AnimationSequence getEntityAnimation(EntityType entity, AnimationType anim) { + switch (entity) { + case ENTITY_WORKER: { + switch (anim) { + case ANIM_IDLE: return (AnimationSequence) {0, 2}; + case ANIM_WALK: return (AnimationSequence) {2, 4}; + } + } + } + BZ_ASSERT(0); +} + +#endif // ENTITY_TYPES diff --git a/scripts/extract_common.py b/scripts/extract_common.py new file mode 100644 index 0000000..3f8c7f7 --- /dev/null +++ b/scripts/extract_common.py @@ -0,0 +1,64 @@ +class ExtractFileWriter: + indent_width = 4 + indent_level = 0 + indention = "" + + def __init__(self, name): + self.name = name.upper() + + def indent(self): + self.indent_level += self.indent_width + self.indention = " " * self.indent_level + + def unindent(self): + self.indent_level -= self.indent_width + self.indention = " " * self.indent_level + + def header_guard_start(self): + print(f"#ifndef {self.name}") + print(f"#define {self.name}") + print() + + def header_guard_stop(self): + print(f"#endif // {self.name}") + + def include(self, header): + print(f"#include {header}") + + def enum_start(self, name): + print(f"{self.indention}typedef enum {name} {{") + self.indent() + + def enum_stop(self, name): + self.unindent() + print(f"}} {name};") + print() + + def enum_list(self, name, enums): + self.enum_start(name) + for enum in enums: + print(f"{self.indention}{enum},") + self.enum_stop(name) + + def enum_dict(self, name, enums): + self.enum_start(name) + for enum, v in enums.items(): + print(f"{self.indention}{enum} = {v},") + self.enum_stop(name) + + def output(self, out): + print(f"{self.indention}{out}", end="") + + def output_line(self): + print() + + def only_output(self, out): + print(out, end="") + + def block_start(self): + print(f"{{") + self.indent() + + def block_end(self): + self.unindent() + print(f"{self.indention}}}") diff --git a/scripts/extract_entities.py b/scripts/extract_entities.py new file mode 100644 index 0000000..91a3ecd --- /dev/null +++ b/scripts/extract_entities.py @@ -0,0 +1,154 @@ +#!/usr/bin/python +""" +Utility script that extracts entities tileset with animations +""" + +import json +import sys +import os +from collections import defaultdict +from extract_common import * + +raw_entities = defaultdict(list) +entities = defaultdict(list) + +content = open("../assets/entities.tsj").read() +tiles = json.loads(content)["tiles"] + +for tile in tiles: + if "type" not in tile: + continue + raw_entities[tile["type"]].append(tile) + +entity_enum_prefix = "ENTITY_" +animation_enum_prefix = "ANIM_" + +entity_enums = {} +animations = set() +entity_animations = {} + + +def entity_enum(type): + return f"{entity_enum_prefix}{type.upper()}" + + +def animation_enum(value): + value = value.split("_")[0] + return f"{animation_enum_prefix}{value.upper()}" + + +def simple_entity(entity): + type = entity["type"] + id = entity["id"] + entity_enums[entity_enum(type)] = id + + +def animated_entity(entity): + simple_entity(entity[0]) + entity_name = entity_enum(entity[0]["type"]) + cur_animations = defaultdict(list) + for frame in entity: + assert entity_name == entity_enum(frame["type"]) + for prop in frame["properties"]: + if prop["name"] != "animation": + continue + animation_name = animation_enum(prop["value"]) + animations.add(animation_name) + cur_animations[animation_name].append(frame["id"]) + break + entity_animations[entity_name] = cur_animations + + +for k, v in raw_entities.items(): + num_frames = len(v) + if num_frames == 1: + simple_entity(v[0]) + else: + animated_entity(v) + + +writer = ExtractFileWriter("entity_types") +writer.header_guard_start() + +writer.include("") + +writer.output_line() + +# ============================ + +writer.enum_dict("EntityType", entity_enums) +writer.enum_list("AnimationType", animations) + +# ============================ + +writer.output("typedef struct AnimationSequence {\n") +writer.indent() +writer.output("i32 startFrame;\n") +writer.output("i32 frameCount;\n") +writer.unindent() +writer.output("} AnimationSequence;\n") +writer.output_line() + +# ============================ + +# ============================ + +writer.output("static bool entityHasAnimation(EntityType entity, AnimationType anim) ") +writer.block_start() + +writer.output("switch (entity) ") +writer.block_start() + +for entity, anims in entity_animations.items(): + writer.output(f"case {entity}: ") + writer.block_start() + + writer.output("switch (anim) ") + writer.block_start() + for anim in anims: + writer.output(f"case {anim}:") + writer.output_line() + writer.indent() + writer.output("return true;\n") + writer.unindent() + writer.block_end() + + writer.output("break;\n") + writer.block_end() + +writer.block_end() + +writer.output("return false;\n") +writer.block_end() + +writer.output_line() +# ============================ + +writer.output("static AnimationSequence getEntityAnimation(EntityType entity, AnimationType anim) ") +writer.block_start() +writer.output("switch (entity) ") +writer.block_start() +for entity, anims in entity_animations.items(): + writer.output(f"case {entity}: ") + writer.block_start() + + writer.output("switch (anim) ") + writer.block_start() + for anim in anims: + ids = sorted(anims[anim]) + for id0, id1 in zip(ids, ids[1:]): + assert id1 - id0 == 1 + writer.output(f"case {anim}: return (AnimationSequence) {{{min(ids)}, {len(ids)}}};") + writer.output_line() + writer.block_end() + + writer.block_end() +writer.block_end() +writer.output("BZ_ASSERT(0);\n") +writer.block_end() +writer.output_line() + +# ============================ + +writer.header_guard_stop() +