From 8543cc7b432b7ab69268815e5bb3fc2d4a7c8435 Mon Sep 17 00:00:00 2001 From: Klemen Plestenjak Date: Thu, 7 Dec 2023 14:19:10 +0100 Subject: [PATCH] Rework input --- game/components.c | 40 +++++++++---- game/components.h | 100 +++++++++++++++++++++++-------- game/game_state.h | 19 +++--- game/main.c | 37 +++++++++--- game/map_init.c | 6 ++ game/systems_input.c | 140 ++++++++++++++++++++++--------------------- 6 files changed, 220 insertions(+), 122 deletions(-) diff --git a/game/components.c b/game/components.c index 1b28e9a..1247216 100644 --- a/game/components.c +++ b/game/components.c @@ -4,8 +4,6 @@ ECS_TAG_DECLARE(TextureTerrain); ECS_TAG_DECLARE(TextureBuildings); ECS_TAG_DECLARE(TextureEntities); -ECS_TAG_DECLARE(UnitSelected); - ECS_COMPONENT_DECLARE(Resource); ECS_COMPONENT_DECLARE(TilePosition); @@ -13,26 +11,34 @@ ECS_COMPONENT_DECLARE(TileSize); ECS_COMPONENT_DECLARE(Owner); ECS_COMPONENT_DECLARE(SpatialGridID); - ECS_COMPONENT_DECLARE(Position); ECS_COMPONENT_DECLARE(Size); ECS_COMPONENT_DECLARE(Velocity); -ECS_COMPONENT_DECLARE(TargetPosition); -ECS_COMPONENT_DECLARE(Steering); ECS_COMPONENT_DECLARE(Rotation); +ECS_COMPONENT_DECLARE(Steering); +ECS_COMPONENT_DECLARE(TargetPosition); +ECS_COMPONENT_DECLARE(Path); ECS_COMPONENT_DECLARE(TextureRegion); + ECS_COMPONENT_DECLARE(Animation); ECS_COMPONENT_DECLARE(AnimationType); -ECS_COMPONENT_DECLARE(Path); + +ECS_TAG_DECLARE(Selectable); +ECS_TAG_DECLARE(Selected); + +ECS_TAG_DECLARE(Unit); +ECS_TAG_DECLARE(Worker); +ECS_TAG_DECLARE(Harvestable); +ECS_TAG_DECLARE(Buildable); +ECS_TAG_DECLARE(Workable); +ECS_TAG_DECLARE(Attackable); void initComponentIDs(ecs_world_t *ecs) { ECS_TAG_DEFINE(ecs, TextureTerrain); ECS_TAG_DEFINE(ecs, TextureBuildings); ECS_TAG_DEFINE(ecs, TextureEntities); - ECS_TAG_DEFINE(ecs, UnitSelected); - ECS_COMPONENT_DEFINE(ecs, Resource); ECS_COMPONENT_DEFINE(ecs, TilePosition); @@ -40,16 +46,26 @@ void initComponentIDs(ecs_world_t *ecs) { ECS_COMPONENT_DEFINE(ecs, Owner); ECS_COMPONENT_DEFINE(ecs, SpatialGridID); - ECS_COMPONENT_DEFINE(ecs, Position); ECS_COMPONENT_DEFINE(ecs, Size); ECS_COMPONENT_DEFINE(ecs, Velocity); - ECS_COMPONENT_DEFINE(ecs, TargetPosition); - ECS_COMPONENT_DEFINE(ecs, Steering); ECS_COMPONENT_DEFINE(ecs, Rotation); + ECS_COMPONENT_DEFINE(ecs, Steering); + ECS_COMPONENT_DEFINE(ecs, TargetPosition); + ECS_COMPONENT_DEFINE(ecs, Path); ECS_COMPONENT_DEFINE(ecs, TextureRegion); + ECS_COMPONENT_DEFINE(ecs, Animation); ECS_COMPONENT_DEFINE(ecs, AnimationType); - ECS_COMPONENT_DEFINE(ecs, Path); + + ECS_TAG_DEFINE(ecs, Selectable); + ECS_TAG_DEFINE(ecs, Selected); + + ECS_TAG_DEFINE(ecs, Unit); + ECS_TAG_DEFINE(ecs, Worker); + ECS_TAG_DEFINE(ecs, Harvestable); + ECS_TAG_DEFINE(ecs, Buildable); + ECS_TAG_DEFINE(ecs, Workable); + ECS_TAG_DEFINE(ecs, Attackable); } diff --git a/game/components.h b/game/components.h index 9b7efec..434e324 100644 --- a/game/components.h +++ b/game/components.h @@ -11,7 +11,6 @@ extern ECS_TAG_DECLARE(TextureTerrain); extern ECS_TAG_DECLARE(TextureBuildings); extern ECS_TAG_DECLARE(TextureEntities); -extern ECS_TAG_DECLARE(UnitSelected); typedef enum ResourceType { RES_IRON, @@ -21,6 +20,16 @@ typedef enum ResourceType { RES_COUNT, } ResourceType; +static const char *getResourceTypePrettyName(ResourceType type) { + switch (type) { + case RES_IRON: return "Iron"; + case RES_WOOD: return "Wood"; + case RES_GOLD: return "Gold"; + case RES_FOOD: return "Food"; + default: return "Invalid"; + } +} + typedef struct Resource { ResourceType type; i32 amount; @@ -44,39 +53,24 @@ typedef struct Owner { } Owner; extern ECS_COMPONENT_DECLARE(Owner); + + +/********************************************************** + * Movement components + *********************************************************/ + typedef BzSpatialGridID SpatialGridID; extern ECS_COMPONENT_DECLARE(SpatialGridID); typedef Vector2 Position, Size, Velocity, TargetPosition, Steering; +typedef f32 Rotation; + extern ECS_COMPONENT_DECLARE(Position); extern ECS_COMPONENT_DECLARE(Size); extern ECS_COMPONENT_DECLARE(Velocity); -extern ECS_COMPONENT_DECLARE(TargetPosition); -extern ECS_COMPONENT_DECLARE(Steering); -typedef f32 Rotation; extern ECS_COMPONENT_DECLARE(Rotation); - -typedef struct TextureRegion { - Texture2D texture; - Rectangle rec; - f32 rotation; - bool flipX : 1; - bool flipY : 1; -} TextureRegion; -extern ECS_COMPONENT_DECLARE(TextureRegion); - -typedef struct Animation { - 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); +extern ECS_COMPONENT_DECLARE(Steering); +extern ECS_COMPONENT_DECLARE(TargetPosition); #define PATH_DATA_SIZE 8 typedef struct PathData { @@ -91,12 +85,66 @@ typedef struct Path { } Path; extern ECS_COMPONENT_DECLARE(Path); +/********************************************************** + * Render components + *********************************************************/ + +typedef struct TextureRegion { + Texture2D texture; + Rectangle rec; + f32 rotation; + bool flipX : 1; + bool flipY : 1; +} TextureRegion; +extern ECS_COMPONENT_DECLARE(TextureRegion); + +/********************************************************** + * Animation components + *********************************************************/ + +typedef struct Animation { + 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); + + + typedef struct EntityArms { ecs_entity_t left; ecs_entity_t right; } EntityArms; //extern ECS_COMPONENT_DECLARE(EntityArms); +/********************************************************** + * Gameplay components + *********************************************************/ + +extern ECS_TAG_DECLARE(Selectable); +extern ECS_TAG_DECLARE(Selected); + +// Unit can: +// - Attack +extern ECS_TAG_DECLARE(Unit); + +// Worker can: +// - Harvest +// - Build +// - Work +// - Attack (since it is also a unit) +extern ECS_TAG_DECLARE(Worker); + +extern ECS_TAG_DECLARE(Harvestable); +extern ECS_TAG_DECLARE(Buildable); +extern ECS_TAG_DECLARE(Workable); +extern ECS_TAG_DECLARE(Attackable); void initComponentIDs(ecs_world_t *ecs); diff --git a/game/game_state.h b/game/game_state.h index 9a911fd..8bd776a 100644 --- a/game/game_state.h +++ b/game/game_state.h @@ -29,13 +29,18 @@ typedef struct InputState { bool buildingCanPlace; TilePosition buildingPos; TileSize buildingSize; - // SELECTED_UNITS - /* - * 1: Position - * 2: Size - * 3: UnitSelected - */ - ecs_query_t *unitSelectedQuery; + // Units + Rectangle pickArea; + + struct { + /* Selected units + * 1: Position + * 2: Size + * 3: UnitSelected + */ + ecs_query_t *selected; + //ecs_query_t *selectedBuilding; + } queries; Position *unitPositions; // SELECTED_OBJECT // SELECTED_BUILDING diff --git a/game/main.c b/game/main.c index 3b48d86..89e27c4 100644 --- a/game/main.c +++ b/game/main.c @@ -71,11 +71,13 @@ bool init(void *userData) { input->ESC = KEY_ESCAPE; input->CLICK_LIMIT = 0.2f; - input->unitSelectedQuery = ecs_query(ECS, { + // Create queries + input->queries.selected = ecs_query(ECS, { .filter.terms = { - { ecs_id(Position) }, { ecs_id(Size) }, { ecs_id(UnitSelected) } + { ecs_id(Position) }, { ecs_id(Size) }, { ecs_id(Selected) } } }); + input->unitPositions = bzArrayCreate(Position, 16); @@ -94,7 +96,7 @@ bool init(void *userData) { game->camera.target = (Vector2) {0, 0}; game->camera.offset = (Vector2) {screenWidth / 2.0f, screenHeight / 2.0f}; game->camera.rotation = 0.0f; - game->camera.zoom = 1.0f; + game->camera.zoom = 3.0f; game->frameDuration = 0.16f; game->terrainTileset = bzTilesetCreate( &(BzTilesetDesc) { @@ -188,6 +190,9 @@ void deinit(void *userData) { Game gameCopy = *game; InputState inputCopy = *input; + // Destroy queries + ecs_query_fini(inputCopy.queries.selected); + ecs_fini(ECS); ECS = NULL; @@ -265,19 +270,33 @@ void imguiRender(float dt, void *userData) { igText("Input state: %s", inputState); if (igCollapsingHeader_TreeNodeFlags("Selection", 0)) { switch (input->state) { - case INPUT_SELECTED_UNITS: + case INPUT_SELECTED_UNITS: { igText("Selected units:"); - ecs_iter_t it = ecs_query_iter(ECS, input->unitSelectedQuery); + ecs_iter_t it = ecs_query_iter(ECS, input->queries.selected); while (ecs_iter_next(&it)) { for (i32 i = 0; i < it.count; i++) { - igText("\t%llu", it.entities[i]); + ecs_entity_t entity = it.entities[i]; + igText("\tEntity %llu", entity); } } break; - case INPUT_SELECTED_OBJECT: - break; - case INPUT_SELECTED_BUILDING: + } + case INPUT_SELECTED_OBJECT: { + igText("Selected objects:"); + ecs_iter_t it = ecs_query_iter(ECS, input->queries.selected); + while (ecs_iter_next(&it)) { + for (i32 i = 0; i < it.count; i++) { + ecs_entity_t entity = it.entities[i]; + if (ecs_has(ECS, entity, Harvestable) && + ecs_has(ECS, entity, Resource)) { + Resource res = *ecs_get(ECS, entity, Resource); + const char *resName = getResourceTypePrettyName(res.type); + igText("\tEntity %llu: %d %s", entity, res.amount, resName); + } + } + } break; + } default: igText("NONE"); break; diff --git a/game/map_init.c b/game/map_init.c index 38cc254..d553f10 100644 --- a/game/map_init.c +++ b/game/map_init.c @@ -47,6 +47,9 @@ bool initEntityObjectsLayer(BzTileMap *map, BzTileObjectGroup *objectGroup) { .elapsed=i * 0.1f, }); ecs_set(ECS, e, AnimationType, {ANIM_IDLE}); + ecs_add_id(ECS, e, Selectable); + ecs_add_id(ECS, e, Unit); + ecs_add_id(ECS, e, Worker); //EntityArms arms = { // .left=ecs_new_id(ECS), // .right=ecs_new_id(ECS), @@ -84,6 +87,7 @@ bool initBuildingsLayer(BzTileMap *map, BzTileLayer *layer) { ownerTile = bzTilesetGetTile(buildingTileset, ownerTile); ownerTile = getTileBuilding(ownerTile); ecs_set(ECS, e, Owner, {.playerID=ownerTile}); + ecs_add_id(ECS, e, Selectable); //bzTileMapUpdateCollider(&GAME.map, x, y); } @@ -118,6 +122,8 @@ bool initTreesLayer(BzTileMap *map, BzTileLayer *layer) { ecs_set(ECS, e, Rotation, {0}); ecs_set(ECS, e, TextureRegion, {tileset->tiles, bzTilesetGetTileRegion(tileset, layerTile)}); ecs_set(ECS, e, Resource, {RES_WOOD, 20}); + ecs_add_id(ECS, e, Selectable); + ecs_add_id(ECS, e, Harvestable); } } diff --git a/game/systems_input.c b/game/systems_input.c index 820ce4b..58e568d 100644 --- a/game/systems_input.c +++ b/game/systems_input.c @@ -8,8 +8,8 @@ Rectangle calculateEntityBounds(Position pos, Size size); bool getEntityBounds(ecs_entity_t entity, Position *outPos, Size *outSize, Rectangle *outBounds); -void pickEntity(BzSpatialGrid *entityGrid, Vector2 point); -void pickEntities(BzSpatialGrid *entityGrid, Rectangle area); +bool pickEntity(BzSpatialGrid *entityGrid, Vector2 point, ecs_entity_t tag); +void pickUnits(BzSpatialGrid *entityGrid, Rectangle area); static void iterateSelectedUnits(ecs_query_t *query, void (*fn)(ecs_entity_t entity, Position *pos, Size *size)); static void iterRemovePaths(ecs_entity_t entity, Position *pos, Size *size); @@ -23,7 +23,7 @@ static bool wasInputDragged(InputState *input) { return IsMouseButtonReleased(input->LMB) && input->mouseDownElapsed > input->CLICK_LIMIT; } static bool wasInputClicked(InputState *input) { - return IsMouseButtonReleased(input->LMB); + return IsMouseButtonReleased(input->LMB) && input->mouseDownElapsed <= input->CLICK_LIMIT; } void updatePlayerInput() { @@ -34,10 +34,10 @@ void updatePlayerInput() { Game *game = ecs_singleton_get_mut(ECS, Game); InputState *input = ecs_singleton_get_mut(ECS, InputState); - if (IsKeyDown(KEY_W)) game->camera.target.y -= 20; - if (IsKeyDown(KEY_S)) game->camera.target.y += 20; - if (IsKeyDown(KEY_A)) game->camera.target.x -= 20; - if (IsKeyDown(KEY_D)) game->camera.target.x += 20; + if (IsKeyDown(KEY_W)) game->camera.target.y -= 5; + if (IsKeyDown(KEY_S)) game->camera.target.y += 5; + if (IsKeyDown(KEY_A)) game->camera.target.x -= 5; + if (IsKeyDown(KEY_D)) game->camera.target.x += 5; if (IsKeyDown(KEY_Q)) game->camera.rotation--; if (IsKeyDown(KEY_E)) game->camera.rotation++; @@ -57,30 +57,39 @@ void updatePlayerInput() { Vector2 worldPos = GetScreenToWorld2D(GetMousePosition(), game->camera); BzTile tileX = 0, tileY = 0; bzTileMapPosToTile(map, worldPos, &tileX, &tileY); - i32 selectedUnitCount = ecs_query_entity_count(input->unitSelectedQuery); switch (input->state) { - case INPUT_NONE: - if (wasInputDragged(input)) { - if (selectedUnitCount > 0) { - input->state = INPUT_SELECTED_UNITS; - break; + case INPUT_NONE: { + if (isInputDragged(input)) { + Vector2 start = input->mouseDownWorld; + Vector2 end = worldPos; + if (start.x > end.x) { + f32 tmp = start.x; + start.x = end.x; + end.x = tmp; } - - } else if (wasInputClicked(input)) { - // Click - - // 1. Entity - if (selectedUnitCount > 0) { - input->state = INPUT_SELECTED_UNITS; - break; + if (start.y > end.y) { + f32 tmp = start.y; + start.y = end.y; + end.y = tmp; } - - // 2. Object - - // 3. Building + input->pickArea = (Rectangle) {start.x, start.y, end.x - start.x, end.y - start.y}; + pickUnits(game->entityGrid, input->pickArea); + } + i32 selectedCount = ecs_query_entity_count(input->queries.selected); + if (wasInputClicked(input)) { + if (pickEntity(game->entityGrid, input->mouseDownWorld, Unit)) { + input->state = INPUT_SELECTED_UNITS; + } else if (pickEntity(game->entityGrid, input->mouseDownWorld, Harvestable)) { + input->state = INPUT_SELECTED_OBJECT; + } else if (pickEntity(game->entityGrid, input->mouseDownWorld, Buildable)) { + input->state = INPUT_SELECTED_BUILDING; + } + } else if (wasInputDragged(input) && selectedCount > 0) { + input->state = INPUT_SELECTED_UNITS; } break; - case INPUT_BUILDING: + } + case INPUT_BUILDING: { if (IsKeyPressed(input->ESC) || IsMouseButtonPressed(input->RMB) || input->building == 0) { @@ -99,15 +108,17 @@ void updatePlayerInput() { input->buildingPos = (TilePosition) {tileX, tileY}; input->buildingSize = (TileSize) {sizeX, sizeY}; break; - case INPUT_SELECTED_UNITS: + } + case INPUT_SELECTED_UNITS: { if (IsKeyPressed(input->ESC) || IsMouseButtonPressed(input->RMB)) { - ecs_remove_all(ECS, UnitSelected); + ecs_remove_all(ECS, Selected); input->state = INPUT_NONE; break; } - if (selectedUnitCount > 1 && wasInputDragged(input)) { + i32 selectedCount = ecs_query_entity_count(input->queries.selected); + if (selectedCount > 1 && wasInputDragged(input)) { // TODO: For click it should just move them - i32 numUnits = selectedUnitCount; + i32 numUnits = selectedCount; f32 unitSpacing = 3.5f; bzArrayClear(input->unitPositions); placeUnits(numUnits, unitSpacing, input->mouseDownWorld, worldPos, @@ -117,10 +128,10 @@ void updatePlayerInput() { i32 unitPosIdx = 0; ecs_defer_begin(ECS); - iterateSelectedUnits(input->unitSelectedQuery, iterRemovePaths); + iterateSelectedUnits(input->queries.selected, iterRemovePaths); ecs_defer_end(ECS); - ecs_iter_t it = ecs_query_iter(ECS, input->unitSelectedQuery); + ecs_iter_t it = ecs_query_iter(ECS, input->queries.selected); ecs_defer_begin(ECS); while (ecs_iter_next(&it)) { Position *pos = ecs_field(&it, Position, 1); @@ -149,10 +160,10 @@ void updatePlayerInput() { ecs_defer_end(ECS); } else if (wasInputClicked(input)) { ecs_defer_begin(ECS); - iterateSelectedUnits(input->unitSelectedQuery, iterRemovePaths); + iterateSelectedUnits(input->queries.selected, iterRemovePaths); ecs_defer_end(ECS); - ecs_iter_t it = ecs_query_iter(ECS, input->unitSelectedQuery); + ecs_iter_t it = ecs_query_iter(ECS, input->queries.selected); ecs_defer_begin(ECS); while (ecs_iter_next(&it)) { @@ -163,12 +174,12 @@ void updatePlayerInput() { Path path = {NULL, 0}; pathfindAStar(&(PathfindingDesc) { - .start=pos[i], - .target=worldPos, - .map=map, - .outPath=&path, - .pool=game->pools.pathData, - .alloc=&game->stackAlloc + .start=pos[i], + .target=worldPos, + .map=map, + .outPath=&path, + .pool=game->pools.pathData, + .alloc=&game->stackAlloc }); if (!path.paths) continue; ecs_set_ptr(ECS, entity, Path, &path); @@ -178,10 +189,16 @@ void updatePlayerInput() { ecs_defer_end(ECS); } break; + } case INPUT_SELECTED_OBJECT: - break; case INPUT_SELECTED_BUILDING: + if (IsKeyPressed(input->ESC) || IsMouseButtonPressed(input->RMB)) { + ecs_remove_all(ECS, Selected); + input->state = INPUT_NONE; + break; + } break; + } } @@ -191,26 +208,12 @@ void drawPlayerInputUI() { BzTileMap *map = &game->map; Vector2 worldPos = GetScreenToWorld2D(GetMousePosition(), game->camera); - i32 selectedUnitCount = ecs_count_id(ECS, UnitSelected); + i32 selectedUnitCount = ecs_count_id(ECS, Selected); switch (input->state) { case INPUT_NONE: if (isInputDragged(input)) { - Vector2 start = input->mouseDownWorld; - Vector2 end = worldPos; - if (start.x > end.x) { - f32 tmp = start.x; - start.x = end.x; - end.x = tmp; - } - if (start.y > end.y) { - f32 tmp = start.y; - start.y = end.y; - end.y = tmp; - } - Rectangle area = {start.x, start.y, end.x - start.x, end.y - start.y}; + Rectangle area = input->pickArea; DrawRectangleLines(area.x, area.y, area.width, area.height, RED); - - pickEntities(game->entityGrid, area); } break; case INPUT_BUILDING: { @@ -243,12 +246,8 @@ void drawPlayerInputUI() { break; } - case INPUT_SELECTED_OBJECT: - break; - case INPUT_SELECTED_BUILDING: - break; } - ecs_iter_t it = ecs_query_iter(ECS, input->unitSelectedQuery); + ecs_iter_t it = ecs_query_iter(ECS, input->queries.selected); rlSetLineWidth(2.0f); while (ecs_query_next(&it)) { Position *pos = ecs_field(&it, Position, 1); @@ -298,13 +297,15 @@ bool getEntityBounds(ecs_entity_t entity, Position *outPos, Size *outSize, Recta return true; } -void pickEntity(BzSpatialGrid *entityGrid, Vector2 point) { - ecs_remove_all(ECS, UnitSelected); +bool pickEntity(BzSpatialGrid *entityGrid, Vector2 point, ecs_entity_t tag) { + ecs_remove_all(ECS, Selected); BzSpatialGridIter it = bzSpatialGridIter(entityGrid, point.x, point.y, 0.0f, 0.0f); f32 closestDst = INFINITY; ecs_entity_t closest = 0; while (bzSpatialGridQueryNext(&it)) { ecs_entity_t entity = *(ecs_entity_t *) it.data; + if (!ecs_has_id(ECS, entity, Selectable)) continue; + if (!ecs_has_id(ECS, entity, tag)) continue; Vector2 pos; Rectangle bounds; if (!getEntityBounds(entity, &pos, NULL, &bounds)) continue; @@ -318,20 +319,23 @@ void pickEntity(BzSpatialGrid *entityGrid, Vector2 point) { } } if (closest) { - ecs_add(ECS, closest, UnitSelected); + ecs_add(ECS, closest, Selected); + return true; } + return false; } -void pickEntities(BzSpatialGrid *entityGrid, Rectangle area) { - ecs_remove_all(ECS, UnitSelected); +void pickUnits(BzSpatialGrid *entityGrid, Rectangle area) { + ecs_remove_all(ECS, Selected); BzSpatialGridIter it = bzSpatialGridIter(entityGrid, area.x, area.y, area.width, area.height); while (bzSpatialGridQueryNext(&it)) { ecs_entity_t entity = *(ecs_entity_t *) it.data; + if (!ecs_has_id(ECS, entity, Unit)) continue; Rectangle bounds; if (!getEntityBounds(entity, NULL, NULL, &bounds)) continue; if (!CheckCollisionRecs(area, bounds)) continue; - ecs_add(ECS, entity, UnitSelected); + ecs_add(ECS, entity, Selected); } }