Improve input processing

This commit is contained in:
2023-12-07 18:21:14 +01:00
parent 8543cc7b43
commit 56a73fe28c
6 changed files with 117 additions and 130 deletions

View File

@@ -17,6 +17,8 @@ add_executable(PixelDefense
game/components.c game/components.c
game/components.h game/components.h
game/entrypoint.c game/entrypoint.c
game/game_state.h
game/input.h
game/main.c game/main.c
game/map_init.c game/map_init.c
game/map_init.h game/map_init.h

View File

@@ -16,3 +16,6 @@ target_link_libraries(heap_test LINK_PRIVATE Breeze)
add_executable(array_test array_test.c) add_executable(array_test array_test.c)
target_link_libraries(array_test LINK_PRIVATE Breeze) target_link_libraries(array_test LINK_PRIVATE Breeze)
add_executable(pan_test pan_test.c)
target_link_libraries(pan_test LINK_PRIVATE Breeze)

View File

@@ -4,48 +4,6 @@
#include <breeze.h> #include <breeze.h>
#include <flecs.h> #include <flecs.h>
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 { typedef struct Game {
Camera2D camera; Camera2D camera;
BzTileset terrainTileset; BzTileset terrainTileset;
@@ -77,7 +35,6 @@ typedef struct Game {
extern ecs_world_t *ECS; extern ecs_world_t *ECS;
extern ECS_COMPONENT_DECLARE(Game); extern ECS_COMPONENT_DECLARE(Game); // defined in main.c
extern ECS_COMPONENT_DECLARE(InputState);
#endif //PIXELDEFENSE_GAME_STATE_H #endif //PIXELDEFENSE_GAME_STATE_H

View File

@@ -4,6 +4,7 @@
#include "utils/building_types.h" #include "utils/building_types.h"
#include "components.h" #include "components.h"
#include "game_state.h" #include "game_state.h"
#include "input.h"
#include "map_init.h" #include "map_init.h"
#include "map_layers.h" #include "map_layers.h"
#include "buildings.h" #include "buildings.h"
@@ -65,11 +66,7 @@ bool init(void *userData) {
ecs_singleton_set(ECS, InputState, {}); ecs_singleton_set(ECS, InputState, {});
InputState *input = ecs_singleton_get_mut(ECS, InputState); InputState *input = ecs_singleton_get_mut(ECS, InputState);
input->LMB = MOUSE_LEFT_BUTTON; input->mapping = inputDefaultMapping();
input->RMB = MOUSE_RIGHT_BUTTON;
input->MMB = MOUSE_MIDDLE_BUTTON;
input->ESC = KEY_ESCAPE;
input->CLICK_LIMIT = 0.2f;
// Create queries // Create queries
input->queries.selected = ecs_query(ECS, { 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); snprintf(titleBuf, sizeof(titleBuf), "FPS: %d | %.2f ms", GetFPS(), GetFrameTime() * 1000);
SetWindowTitle(titleBuf); 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); BZ_ASSERT(game->stackAlloc.allocated == 0);
bzStackAllocReset(&game->stackAlloc); bzStackAllocReset(&game->stackAlloc);
updateInputState(input, game->camera, dt);
updatePlayerInput(); updatePlayerInput();
} }

View File

@@ -2,6 +2,8 @@
#include "game_state.h" #include "game_state.h"
#include "input.h"
#include <math.h> #include <math.h>
#include <raymath.h> #include <raymath.h>

View File

@@ -1,5 +1,6 @@
#include "systems.h" #include "systems.h"
#include "game_state.h" #include "game_state.h"
#include "input.h"
#include "buildings.h" #include "buildings.h"
#include "pathfinding.h" #include "pathfinding.h"
#include <rlImGui.h> #include <rlImGui.h>
@@ -8,6 +9,9 @@
Rectangle calculateEntityBounds(Position pos, Size size); Rectangle calculateEntityBounds(Position pos, Size size);
bool getEntityBounds(ecs_entity_t entity, Position *outPos, Size *outSize, Rectangle *outBounds); 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); bool pickEntity(BzSpatialGrid *entityGrid, Vector2 point, ecs_entity_t tag);
void pickUnits(BzSpatialGrid *entityGrid, Rectangle area); 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); void placeUnits(i32 numUnits, f32 unitSpacing, Vector2 start, Vector2 end, BzTileMap *map, Vector2 **outPlaces);
static bool isInputDragged(InputState *input) { void inputPrimaryAction(Game *game, InputState *input) {
return IsMouseButtonDown(input->LMB && input->mouseDownElapsed > input->CLICK_LIMIT); 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) { void inputUnitAction(Game *game, 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 updatePlayerInput() { void updatePlayerInput() {
@@ -43,65 +76,33 @@ void updatePlayerInput() {
if (IsKeyDown(KEY_E)) game->camera.rotation++; if (IsKeyDown(KEY_E)) game->camera.rotation++;
game->camera.zoom += ((float) GetMouseWheelMove() * 0.05f); game->camera.zoom += GetMouseWheelMove() * 0.05f;
BzTileMap *map = &game->map; BzTileMap *map = &game->map;
if (IsMouseButtonPressed(input->LMB)) { BzTile tileX = 0, tileY = 0;
input->mouseDown = GetMousePosition(); bzTileMapPosToTile(map, input->mouseWorld, &tileX, &tileY);
input->mouseDownWorld = GetScreenToWorld2D(input->mouseDown, game->camera);
input->mouseDownElapsed = 0; if (IsKeyPressed(input->mapping.backBtn)) {
} else if (IsMouseButtonDown(input->LMB)) { ecs_remove_all(ECS, Selected);
input->mouseDownElapsed += dt; input->state = INPUT_NONE;
input->building = 0;
return;
} }
Vector2 worldPos = GetScreenToWorld2D(GetMousePosition(), game->camera); const MouseButton primaryBtn = input->mapping.primaryBtn;
BzTile tileX = 0, tileY = 0; const MouseButton secondaryBtn = input->mapping.secondaryBtn;
bzTileMapPosToTile(map, worldPos, &tileX, &tileY);
switch (input->state) { switch (input->state) {
case INPUT_NONE: { case INPUT_NONE: {
if (isInputDragged(input)) { inputPrimaryAction(game, 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;
}
break; break;
} }
case INPUT_BUILDING: { 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); BZ_ASSERT(input->building);
BzTile sizeX = 0, sizeY = 0; BzTile sizeX = 0, sizeY = 0;
getBuildingSize(input->building, &sizeX, &sizeY); getBuildingSize(input->building, &sizeX, &sizeY);
bool canPlace = canPlaceBuilding(&game->map, input->building, tileX, tileY); 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); placeBuilding(&game->map, input->building, tileX, tileY);
} }
input->buildingCanPlace = canPlace; input->buildingCanPlace = canPlace;
@@ -110,19 +111,17 @@ void updatePlayerInput() {
break; break;
} }
case INPUT_SELECTED_UNITS: { case INPUT_SELECTED_UNITS: {
if (IsKeyPressed(input->ESC) || IsMouseButtonPressed(input->RMB)) { inputPrimaryAction(game, input);
ecs_remove_all(ECS, Selected); if (input->state != INPUT_SELECTED_UNITS) break;
input->state = INPUT_NONE;
break;
}
i32 selectedCount = ecs_query_entity_count(input->queries.selected); 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 // TODO: For click it should just move them
i32 numUnits = selectedCount; i32 numUnits = selectedCount;
f32 unitSpacing = 3.5f; f32 unitSpacing = 3.5f;
bzArrayClear(input->unitPositions); bzArrayClear(input->unitPositions);
placeUnits(numUnits, unitSpacing, input->mouseDownWorld, worldPos, placeUnits(numUnits, unitSpacing,
map, &input->unitPositions); input->mouseDownWorld, input->mouseWorld,
map, &input->unitPositions);
BZ_ASSERT(bzArraySize(input->unitPositions) == numUnits); BZ_ASSERT(bzArraySize(input->unitPositions) == numUnits);
i32 unitPosIdx = 0; i32 unitPosIdx = 0;
@@ -138,8 +137,6 @@ void updatePlayerInput() {
for (i32 i = 0; i < it.count; i++) { for (i32 i = 0; i < it.count; i++) {
ecs_entity_t entity = it.entities[i]; ecs_entity_t entity = it.entities[i];
fflush(stdout);
Position target = bzArrayGet(input->unitPositions, unitPosIdx); Position target = bzArrayGet(input->unitPositions, unitPosIdx);
unitPosIdx++; unitPosIdx++;
@@ -158,7 +155,7 @@ void updatePlayerInput() {
} }
} }
ecs_defer_end(ECS); ecs_defer_end(ECS);
} else if (wasInputClicked(input)) { } else if (isInputBtnJustDown(input, secondaryBtn)) {
ecs_defer_begin(ECS); ecs_defer_begin(ECS);
iterateSelectedUnits(input->queries.selected, iterRemovePaths); iterateSelectedUnits(input->queries.selected, iterRemovePaths);
ecs_defer_end(ECS); ecs_defer_end(ECS);
@@ -175,7 +172,7 @@ void updatePlayerInput() {
Path path = {NULL, 0}; Path path = {NULL, 0};
pathfindAStar(&(PathfindingDesc) { pathfindAStar(&(PathfindingDesc) {
.start=pos[i], .start=pos[i],
.target=worldPos, .target=input->mouseWorld,
.map=map, .map=map,
.outPath=&path, .outPath=&path,
.pool=game->pools.pathData, .pool=game->pools.pathData,
@@ -190,14 +187,16 @@ void updatePlayerInput() {
} }
break; break;
} }
case INPUT_SELECTED_OBJECT: case INPUT_SELECTED_OBJECT: {
case INPUT_SELECTED_BUILDING: inputPrimaryAction(game, input);
if (IsKeyPressed(input->ESC) || IsMouseButtonPressed(input->RMB)) { if (input->state != INPUT_SELECTED_OBJECT) break;
ecs_remove_all(ECS, Selected);
input->state = INPUT_NONE;
break;
}
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); Game *game = ecs_singleton_get_mut(ECS, Game);
InputState *input = ecs_singleton_get_mut(ECS, InputState); InputState *input = ecs_singleton_get_mut(ECS, InputState);
BzTileMap *map = &game->map; 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); i32 selectedUnitCount = ecs_count_id(ECS, Selected);
switch (input->state) { switch (input->state) {
case INPUT_NONE: case INPUT_NONE:
if (isInputDragged(input)) { if (isInputBtnDragged(input, primaryBtn)) {
Rectangle area = input->pickArea; Rectangle area = input->pickArea;
DrawRectangleLines(area.x, area.y, area.width, area.height, RED); DrawRectangleLines(area.x, area.y, area.width, area.height, RED);
} }
@@ -230,11 +231,12 @@ void drawPlayerInputUI() {
break; break;
} }
case INPUT_SELECTED_UNITS: { case INPUT_SELECTED_UNITS: {
if (selectedUnitCount > 1 && isInputDragged(input)) { BZ_ASSERT(selectedUnitCount);
if (selectedUnitCount > 1 && isInputBtnDragged(input, primaryBtn)) {
i32 numUnits = selectedUnitCount; i32 numUnits = selectedUnitCount;
f32 unitSpacing = 3.5f; f32 unitSpacing = 3.5f;
bzArrayClear(input->unitPositions); bzArrayClear(input->unitPositions);
placeUnits(numUnits, unitSpacing, input->mouseDownWorld, worldPos, placeUnits(numUnits, unitSpacing, input->mouseDownWorld, input->mouseWorld,
map, &input->unitPositions); map, &input->unitPositions);
BZ_ASSERT(bzArraySize(input->unitPositions) == numUnits); BZ_ASSERT(bzArraySize(input->unitPositions) == numUnits);
@@ -243,6 +245,22 @@ void drawPlayerInputUI() {
DrawCircle(pos.x, pos.y, 2.0f, ORANGE); 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; break;
} }
@@ -297,8 +315,7 @@ bool getEntityBounds(ecs_entity_t entity, Position *outPos, Size *outSize, Recta
return true; return true;
} }
bool pickEntity(BzSpatialGrid *entityGrid, Vector2 point, ecs_entity_t tag) { ecs_entity_t queryEntity(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); BzSpatialGridIter it = bzSpatialGridIter(entityGrid, point.x, point.y, 0.0f, 0.0f);
f32 closestDst = INFINITY; f32 closestDst = INFINITY;
ecs_entity_t closest = 0; ecs_entity_t closest = 0;
@@ -318,8 +335,14 @@ bool pickEntity(BzSpatialGrid *entityGrid, Vector2 point, ecs_entity_t tag) {
closest = entity; closest = entity;
} }
} }
if (closest) { return closest;
ecs_add(ECS, closest, Selected); }
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 true;
} }
return false; return false;