diff --git a/CMakeLists.txt b/CMakeLists.txt index 9def1d0..80ee3bc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -17,6 +17,8 @@ add_executable(PixelDefense game/components.c game/components.h game/entrypoint.c + game/game_state.h + game/input.h game/main.c game/map_init.c game/map_init.h diff --git a/engine/tests/CMakeLists.txt b/engine/tests/CMakeLists.txt index 4e7b52c..06f2770 100644 --- a/engine/tests/CMakeLists.txt +++ b/engine/tests/CMakeLists.txt @@ -16,3 +16,6 @@ target_link_libraries(heap_test LINK_PRIVATE Breeze) add_executable(array_test array_test.c) target_link_libraries(array_test LINK_PRIVATE Breeze) + +add_executable(pan_test pan_test.c) +target_link_libraries(pan_test LINK_PRIVATE Breeze) diff --git a/game/game_state.h b/game/game_state.h index 8bd776a..4f2234d 100644 --- a/game/game_state.h +++ b/game/game_state.h @@ -4,48 +4,6 @@ #include #include -typedef enum InputType { - INPUT_NONE, - INPUT_BUILDING, - INPUT_SELECTED_UNITS, - INPUT_SELECTED_OBJECT, - INPUT_SELECTED_BUILDING, -} InputType; - -typedef struct InputState { - InputType state; - // Input mapping - MouseButton LMB; - MouseButton RMB; - MouseButton MMB; - KeyboardKey ESC; - f32 CLICK_LIMIT; - // Common - Vector2 mouseDown; - Vector2 mouseDownWorld; - f32 mouseDownElapsed; - // INPUT_BUILDING - int building; - bool buildingCanPlace; - TilePosition buildingPos; - TileSize buildingSize; - // 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 -} InputState; - typedef struct Game { Camera2D camera; BzTileset terrainTileset; @@ -77,7 +35,6 @@ typedef struct Game { extern ecs_world_t *ECS; -extern ECS_COMPONENT_DECLARE(Game); -extern ECS_COMPONENT_DECLARE(InputState); +extern ECS_COMPONENT_DECLARE(Game); // defined in main.c #endif //PIXELDEFENSE_GAME_STATE_H diff --git a/game/main.c b/game/main.c index 89e27c4..e4b7a85 100644 --- a/game/main.c +++ b/game/main.c @@ -4,6 +4,7 @@ #include "utils/building_types.h" #include "components.h" #include "game_state.h" +#include "input.h" #include "map_init.h" #include "map_layers.h" #include "buildings.h" @@ -65,11 +66,7 @@ bool init(void *userData) { ecs_singleton_set(ECS, InputState, {}); InputState *input = ecs_singleton_get_mut(ECS, InputState); - input->LMB = MOUSE_LEFT_BUTTON; - input->RMB = MOUSE_RIGHT_BUTTON; - input->MMB = MOUSE_MIDDLE_BUTTON; - input->ESC = KEY_ESCAPE; - input->CLICK_LIMIT = 0.2f; + input->mapping = inputDefaultMapping(); // Create queries input->queries.selected = ecs_query(ECS, { @@ -210,11 +207,14 @@ void update(float dt, void *userData) { snprintf(titleBuf, sizeof(titleBuf), "FPS: %d | %.2f ms", GetFPS(), GetFrameTime() * 1000); SetWindowTitle(titleBuf); - Game *game = ecs_singleton_get_mut(ECS, Game); + Game *game = ecs_singleton_get_mut(ECS, Game); + InputState *input = ecs_singleton_get_mut(ECS, InputState); BZ_ASSERT(game->stackAlloc.allocated == 0); bzStackAllocReset(&game->stackAlloc); + updateInputState(input, game->camera, dt); + updatePlayerInput(); } diff --git a/game/systems_entity.c b/game/systems_entity.c index 45fde7f..994f947 100644 --- a/game/systems_entity.c +++ b/game/systems_entity.c @@ -2,6 +2,8 @@ #include "game_state.h" +#include "input.h" + #include #include diff --git a/game/systems_input.c b/game/systems_input.c index 58e568d..115f178 100644 --- a/game/systems_input.c +++ b/game/systems_input.c @@ -1,5 +1,6 @@ #include "systems.h" #include "game_state.h" +#include "input.h" #include "buildings.h" #include "pathfinding.h" #include @@ -8,6 +9,9 @@ Rectangle calculateEntityBounds(Position pos, Size size); bool getEntityBounds(ecs_entity_t entity, Position *outPos, Size *outSize, Rectangle *outBounds); + +ecs_entity_t queryEntity(BzSpatialGrid *entityGrid, Vector2 point, ecs_entity_t tag); + bool pickEntity(BzSpatialGrid *entityGrid, Vector2 point, ecs_entity_t tag); void pickUnits(BzSpatialGrid *entityGrid, Rectangle area); @@ -16,14 +20,43 @@ static void iterRemovePaths(ecs_entity_t entity, Position *pos, Size *size); void placeUnits(i32 numUnits, f32 unitSpacing, Vector2 start, Vector2 end, BzTileMap *map, Vector2 **outPlaces); -static bool isInputDragged(InputState *input) { - return IsMouseButtonDown(input->LMB && input->mouseDownElapsed > input->CLICK_LIMIT); +void inputPrimaryAction(Game *game, InputState *input) { + const MouseButton primaryBtn = input->mapping.primaryBtn; + if (isInputBtnDragged(input, primaryBtn)) { + Vector2 start = input->mouseDownWorld; + Vector2 end = input->mouseWorld; + 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; + } + 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 (isInputBtnJustDragged(input, primaryBtn) && selectedCount > 0) { + input->state = INPUT_SELECTED_UNITS; + } else if (isInputBtnJustUp(input, primaryBtn)) { + 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; + } + selectedCount = ecs_query_entity_count(input->queries.selected); + } + + if (selectedCount == 0) + input->state = INPUT_NONE; } -static bool wasInputDragged(InputState *input) { - return IsMouseButtonReleased(input->LMB) && input->mouseDownElapsed > input->CLICK_LIMIT; -} -static bool wasInputClicked(InputState *input) { - return IsMouseButtonReleased(input->LMB) && input->mouseDownElapsed <= input->CLICK_LIMIT; +void inputUnitAction(Game *game, InputState *input) { + } void updatePlayerInput() { @@ -43,65 +76,33 @@ void updatePlayerInput() { if (IsKeyDown(KEY_E)) game->camera.rotation++; - game->camera.zoom += ((float) GetMouseWheelMove() * 0.05f); + game->camera.zoom += GetMouseWheelMove() * 0.05f; BzTileMap *map = &game->map; - if (IsMouseButtonPressed(input->LMB)) { - input->mouseDown = GetMousePosition(); - input->mouseDownWorld = GetScreenToWorld2D(input->mouseDown, game->camera); - input->mouseDownElapsed = 0; - } else if (IsMouseButtonDown(input->LMB)) { - input->mouseDownElapsed += dt; + BzTile tileX = 0, tileY = 0; + bzTileMapPosToTile(map, input->mouseWorld, &tileX, &tileY); + + if (IsKeyPressed(input->mapping.backBtn)) { + ecs_remove_all(ECS, Selected); + input->state = INPUT_NONE; + input->building = 0; + return; } - Vector2 worldPos = GetScreenToWorld2D(GetMousePosition(), game->camera); - BzTile tileX = 0, tileY = 0; - bzTileMapPosToTile(map, worldPos, &tileX, &tileY); + const MouseButton primaryBtn = input->mapping.primaryBtn; + const MouseButton secondaryBtn = input->mapping.secondaryBtn; + 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; - } - 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; - } + inputPrimaryAction(game, input); break; } case INPUT_BUILDING: { - if (IsKeyPressed(input->ESC) || - IsMouseButtonPressed(input->RMB) || - input->building == 0) { - input->state = INPUT_NONE; - input->building = 0; - break; - } BZ_ASSERT(input->building); BzTile sizeX = 0, sizeY = 0; getBuildingSize(input->building, &sizeX, &sizeY); bool canPlace = canPlaceBuilding(&game->map, input->building, tileX, tileY); - if (canPlace && IsMouseButtonDown(input->LMB)) { + if (canPlace && isInputBtnDown(input, primaryBtn)) { placeBuilding(&game->map, input->building, tileX, tileY); } input->buildingCanPlace = canPlace; @@ -110,19 +111,17 @@ void updatePlayerInput() { break; } case INPUT_SELECTED_UNITS: { - if (IsKeyPressed(input->ESC) || IsMouseButtonPressed(input->RMB)) { - ecs_remove_all(ECS, Selected); - input->state = INPUT_NONE; - break; - } + inputPrimaryAction(game, input); + if (input->state != INPUT_SELECTED_UNITS) break; i32 selectedCount = ecs_query_entity_count(input->queries.selected); - if (selectedCount > 1 && wasInputDragged(input)) { + if (selectedCount > 1 && isInputBtnJustDragged(input, secondaryBtn)) { // TODO: For click it should just move them i32 numUnits = selectedCount; f32 unitSpacing = 3.5f; bzArrayClear(input->unitPositions); - placeUnits(numUnits, unitSpacing, input->mouseDownWorld, worldPos, - map, &input->unitPositions); + placeUnits(numUnits, unitSpacing, + input->mouseDownWorld, input->mouseWorld, + map, &input->unitPositions); BZ_ASSERT(bzArraySize(input->unitPositions) == numUnits); i32 unitPosIdx = 0; @@ -138,8 +137,6 @@ void updatePlayerInput() { for (i32 i = 0; i < it.count; i++) { ecs_entity_t entity = it.entities[i]; - fflush(stdout); - Position target = bzArrayGet(input->unitPositions, unitPosIdx); unitPosIdx++; @@ -158,7 +155,7 @@ void updatePlayerInput() { } } ecs_defer_end(ECS); - } else if (wasInputClicked(input)) { + } else if (isInputBtnJustDown(input, secondaryBtn)) { ecs_defer_begin(ECS); iterateSelectedUnits(input->queries.selected, iterRemovePaths); ecs_defer_end(ECS); @@ -175,7 +172,7 @@ void updatePlayerInput() { Path path = {NULL, 0}; pathfindAStar(&(PathfindingDesc) { .start=pos[i], - .target=worldPos, + .target=input->mouseWorld, .map=map, .outPath=&path, .pool=game->pools.pathData, @@ -190,14 +187,16 @@ void updatePlayerInput() { } break; } - case INPUT_SELECTED_OBJECT: - case INPUT_SELECTED_BUILDING: - if (IsKeyPressed(input->ESC) || IsMouseButtonPressed(input->RMB)) { - ecs_remove_all(ECS, Selected); - input->state = INPUT_NONE; - break; - } + case INPUT_SELECTED_OBJECT: { + inputPrimaryAction(game, input); + if (input->state != INPUT_SELECTED_OBJECT) break; break; + } + case INPUT_SELECTED_BUILDING: { + inputPrimaryAction(game, input); + if (input->state != INPUT_SELECTED_BUILDING) break; + break; + } } } @@ -206,12 +205,14 @@ void drawPlayerInputUI() { Game *game = ecs_singleton_get_mut(ECS, Game); InputState *input = ecs_singleton_get_mut(ECS, InputState); BzTileMap *map = &game->map; - Vector2 worldPos = GetScreenToWorld2D(GetMousePosition(), game->camera); + + const MouseButton primaryBtn = input->mapping.primaryBtn; + const MouseButton secondaryBtn = input->mapping.secondaryBtn; i32 selectedUnitCount = ecs_count_id(ECS, Selected); switch (input->state) { case INPUT_NONE: - if (isInputDragged(input)) { + if (isInputBtnDragged(input, primaryBtn)) { Rectangle area = input->pickArea; DrawRectangleLines(area.x, area.y, area.width, area.height, RED); } @@ -230,11 +231,12 @@ void drawPlayerInputUI() { break; } case INPUT_SELECTED_UNITS: { - if (selectedUnitCount > 1 && isInputDragged(input)) { + BZ_ASSERT(selectedUnitCount); + if (selectedUnitCount > 1 && isInputBtnDragged(input, primaryBtn)) { i32 numUnits = selectedUnitCount; f32 unitSpacing = 3.5f; bzArrayClear(input->unitPositions); - placeUnits(numUnits, unitSpacing, input->mouseDownWorld, worldPos, + placeUnits(numUnits, unitSpacing, input->mouseDownWorld, input->mouseWorld, map, &input->unitPositions); BZ_ASSERT(bzArraySize(input->unitPositions) == numUnits); @@ -243,6 +245,22 @@ void drawPlayerInputUI() { DrawCircle(pos.x, pos.y, 2.0f, ORANGE); } } + Rectangle area = {input->mouseWorld.x, input->mouseWorld.y, 1, 1}; + DrawCircle(area.x, area.y, 4.0f, BLUE); + + BzSpatialGridIter it = bzSpatialGridIter(game->entityGrid, area.x, area.y, area.width, area.height); + while (bzSpatialGridQueryNext(&it)) { + ecs_entity_t entity = 0; + + BzSpatialGrid *grid = game->entityGrid; + Vector2 point = input->mouseWorld; + + + if ((entity = queryEntity(grid, point, Harvestable))) { + DrawText("Collect resources", point.x, point.y, 10.0f, RED); + } + + } break; } @@ -297,8 +315,7 @@ bool getEntityBounds(ecs_entity_t entity, Position *outPos, Size *outSize, Recta return true; } -bool pickEntity(BzSpatialGrid *entityGrid, Vector2 point, ecs_entity_t tag) { - ecs_remove_all(ECS, Selected); +ecs_entity_t queryEntity(BzSpatialGrid *entityGrid, Vector2 point, ecs_entity_t tag) { BzSpatialGridIter it = bzSpatialGridIter(entityGrid, point.x, point.y, 0.0f, 0.0f); f32 closestDst = INFINITY; ecs_entity_t closest = 0; @@ -318,8 +335,14 @@ bool pickEntity(BzSpatialGrid *entityGrid, Vector2 point, ecs_entity_t tag) { closest = entity; } } - if (closest) { - ecs_add(ECS, closest, Selected); + return closest; +} + +bool pickEntity(BzSpatialGrid *entityGrid, Vector2 point, ecs_entity_t tag) { + ecs_remove_all(ECS, Selected); + const ecs_entity_t entity = queryEntity(entityGrid, point, tag); + if (entity) { + ecs_add(ECS, entity, Selected); return true; } return false;