Add basic input system
This commit is contained in:
@@ -24,6 +24,7 @@ add_executable(PixelDefense
|
||||
game/pathfinding.h
|
||||
game/systems.h
|
||||
game/systems_entity.c
|
||||
game/systems_input.c
|
||||
game/systems_ui.c
|
||||
)
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ typedef struct InputState {
|
||||
MouseButton RMB;
|
||||
MouseButton MMB;
|
||||
KeyboardKey ESC;
|
||||
f32 DRAG_LIMIT;
|
||||
f32 CLICK_LIMIT;
|
||||
// Common
|
||||
Vector2 mouseDown;
|
||||
Vector2 mouseDownWorld;
|
||||
@@ -30,7 +30,8 @@ typedef struct InputState {
|
||||
TilePosition buildingPos;
|
||||
TileSize buildingSize;
|
||||
// SELECTED_UNITS
|
||||
// SELECTED_OBJECTS
|
||||
ecs_entity_t *entities;
|
||||
// SELECTED_OBJECT
|
||||
// SELECTED_BUILDING
|
||||
} InputState;
|
||||
|
||||
@@ -43,7 +44,6 @@ typedef struct Game {
|
||||
BzSpatialGrid *entityGrid;
|
||||
f32 frameDuration;
|
||||
ecs_entity_t entity;
|
||||
InputState input;
|
||||
struct {
|
||||
i64 wood;
|
||||
i64 iron;
|
||||
@@ -66,5 +66,6 @@ typedef struct Game {
|
||||
extern ecs_world_t *ECS;
|
||||
|
||||
extern ECS_COMPONENT_DECLARE(Game);
|
||||
extern ECS_COMPONENT_DECLARE(InputState);
|
||||
|
||||
#endif //PIXELDEFENSE_GAME_STATE_H
|
||||
|
||||
139
game/main.c
139
game/main.c
@@ -14,6 +14,7 @@
|
||||
#include <raymath.h>
|
||||
|
||||
ECS_COMPONENT_DECLARE(Game);
|
||||
ECS_COMPONENT_DECLARE(InputState);
|
||||
|
||||
ecs_world_t *ECS = NULL;
|
||||
|
||||
@@ -50,21 +51,27 @@ bool init(void *userData) {
|
||||
|
||||
ECS = ecs_init();
|
||||
|
||||
ECS_COMPONENT_DEFINE(ECS, Game);
|
||||
initComponentIDs(ECS);
|
||||
|
||||
// For ecs explorer
|
||||
//ecs_singleton_set(ECS, EcsRest, {0});
|
||||
//ECS_IMPORT(ECS, FlecsMonitor);
|
||||
|
||||
ECS_COMPONENT_DEFINE(ECS, Game);
|
||||
ecs_singleton_set(ECS, Game, {});
|
||||
Game *game = ecs_singleton_get_mut(ECS, Game);
|
||||
|
||||
game->input.LMB = MOUSE_LEFT_BUTTON;
|
||||
game->input.RMB = MOUSE_RIGHT_BUTTON;
|
||||
game->input.MMB = MOUSE_MIDDLE_BUTTON;
|
||||
ECS_COMPONENT_DEFINE(ECS, InputState);
|
||||
ecs_singleton_set(ECS, InputState, {});
|
||||
InputState *input = ecs_singleton_get_mut(ECS, InputState);
|
||||
|
||||
game->input.ESC = KEY_ESCAPE;
|
||||
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->entities = bzArrayCreate(ecs_entity_t, 16);
|
||||
|
||||
|
||||
// init pools
|
||||
@@ -156,6 +163,7 @@ bool init(void *userData) {
|
||||
void deinit(void *userData) {
|
||||
BZ_UNUSED(userData);
|
||||
Game *game = ecs_singleton_get_mut(ECS, Game);
|
||||
InputState *input = ecs_singleton_get_mut(ECS, InputState);
|
||||
|
||||
bzTileMapDestroy(&game->map);
|
||||
bzTilesetDestroy(&game->terrainTileset);
|
||||
@@ -163,10 +171,12 @@ void deinit(void *userData) {
|
||||
bzTilesetDestroy(&game->entitiesTileset);
|
||||
|
||||
Game gameCopy = *game;
|
||||
InputState inputCopy = *input;
|
||||
|
||||
ecs_fini(ECS);
|
||||
ECS = NULL;
|
||||
|
||||
bzArrayDestroy(inputCopy.entities);
|
||||
bzObjectPoolDestroy(gameCopy.pools.pathData);
|
||||
bzSpatialGridDestroy(gameCopy.entityGrid);
|
||||
}
|
||||
@@ -174,74 +184,12 @@ void deinit(void *userData) {
|
||||
|
||||
void update(float dt, void *userData) {
|
||||
BZ_UNUSED(userData);
|
||||
Game *game = ecs_singleton_get_mut(ECS, Game);
|
||||
|
||||
char titleBuf[32];
|
||||
snprintf(titleBuf, sizeof(titleBuf), "FPS: %d | %.2f ms", GetFPS(), GetFrameTime() * 1000);
|
||||
SetWindowTitle(titleBuf);
|
||||
|
||||
ImGuiIO *io = igGetIO();
|
||||
if (io->WantCaptureMouse || io->WantCaptureKeyboard)
|
||||
return;
|
||||
|
||||
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_Q)) game->camera.rotation--;
|
||||
if (IsKeyDown(KEY_E)) game->camera.rotation++;
|
||||
|
||||
|
||||
game->camera.zoom += ((float) GetMouseWheelMove() * 0.05f);
|
||||
BzTileMap *map = &game->map;
|
||||
|
||||
InputState *input = &game->input;
|
||||
if (IsMouseButtonPressed(input->LMB)) {
|
||||
game->input.mouseDown = GetMousePosition();
|
||||
game->input.mouseDownWorld = GetScreenToWorld2D(game->input.mouseDown, game->camera);
|
||||
game->input.mouseDownElapsed = 0;
|
||||
} else if (IsMouseButtonDown(input->LMB)) {
|
||||
game->input.mouseDownElapsed += dt;
|
||||
}
|
||||
|
||||
switch (input->state) {
|
||||
case INPUT_NONE:
|
||||
if (IsMouseButtonUp(input->LMB)) {
|
||||
|
||||
}
|
||||
break;
|
||||
case INPUT_BUILDING:
|
||||
if (IsKeyPressed(input->ESC) ||
|
||||
IsMouseButtonPressed(input->RMB) ||
|
||||
input->building == 0) {
|
||||
input->state = INPUT_NONE;
|
||||
input->building = 0;
|
||||
break;
|
||||
}
|
||||
Vector2 worldPos = GetScreenToWorld2D(GetMousePosition(), game->camera);
|
||||
int tileX = (int) worldPos.x / map->tileWidth;
|
||||
int tileY = (int) worldPos.y / map->tileHeight;
|
||||
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)) {
|
||||
placeBuilding(&game->map, input->building, tileX, tileY);
|
||||
}
|
||||
input->buildingCanPlace = canPlace;
|
||||
input->buildingPos = (TilePosition) {tileX, tileY};
|
||||
input->buildingSize = (TileSize) {sizeX, sizeY};
|
||||
break;
|
||||
case INPUT_SELECTED_UNITS:
|
||||
break;
|
||||
case INPUT_SELECTED_OBJECT:
|
||||
break;
|
||||
case INPUT_SELECTED_BUILDING:
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
updatePlayerInput(NULL);
|
||||
}
|
||||
|
||||
static bool isUnitObstructed(f32 x, f32 y, BzTileMap *map) {
|
||||
@@ -299,27 +247,44 @@ static void placeUnits(i32 numUnits, f32 unitSpacing, Vector2 start, Vector2 end
|
||||
void render(float dt, void *userData) {
|
||||
BZ_UNUSED(userData);
|
||||
Game *game = ecs_singleton_get_mut(ECS, Game);
|
||||
InputState *input = ecs_singleton_get_mut(ECS, InputState);
|
||||
|
||||
ClearBackground(RAYWHITE);
|
||||
BeginMode2D(game->camera);
|
||||
|
||||
bzTileMapDraw(&game->map);
|
||||
|
||||
InputState *input = &game->input;
|
||||
Vector2 worldPos = GetScreenToWorld2D(GetMousePosition(), game->camera);
|
||||
switch (input->state) {
|
||||
case INPUT_NONE:
|
||||
if (IsMouseButtonDown(input->LMB) && input->mouseDownElapsed > input->CLICK_LIMIT) {
|
||||
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};
|
||||
DrawRectangleLines(area.x, area.y, area.width, area.height, RED);
|
||||
}
|
||||
break;
|
||||
case INPUT_BUILDING: {
|
||||
Color placeColor = game->input.buildingCanPlace ?
|
||||
Color placeColor = input->buildingCanPlace ?
|
||||
(Color) {0, 255, 0, 200} :
|
||||
(Color) {255, 0, 0, 200};
|
||||
|
||||
BzTile width = game->map.tileWidth;
|
||||
BzTile height = game->map.tileHeight;
|
||||
DrawRectangleLines(game->input.buildingPos.x * width,
|
||||
game->input.buildingPos.y * height,
|
||||
game->input.buildingSize.sizeX * width,
|
||||
game->input.buildingSize.sizeY * height, placeColor);
|
||||
DrawRectangleLines(input->buildingPos.x * width,
|
||||
input->buildingPos.y * height,
|
||||
input->buildingSize.sizeX * width,
|
||||
input->buildingSize.sizeY * height, placeColor);
|
||||
break;
|
||||
}
|
||||
case INPUT_SELECTED_UNITS:
|
||||
@@ -414,11 +379,12 @@ void render(float dt, void *userData) {
|
||||
void imguiRender(float dt, void *userData) {
|
||||
BZ_UNUSED(userData);
|
||||
Game *game = ecs_singleton_get_mut(ECS, Game);
|
||||
InputState *input = ecs_singleton_get_mut(ECS, InputState);
|
||||
|
||||
igSetNextWindowSize((ImVec2){300, 400}, ImGuiCond_FirstUseEver);
|
||||
igBegin("Debug Menu", NULL, 0);
|
||||
const char *inputState = "NONE";
|
||||
switch (game->input.state) {
|
||||
switch (input->state) {
|
||||
case INPUT_NONE:
|
||||
inputState = "NONE";
|
||||
break;
|
||||
@@ -437,6 +403,21 @@ void imguiRender(float dt, void *userData) {
|
||||
}
|
||||
igText("Input state: %s", inputState);
|
||||
if (igCollapsingHeader_TreeNodeFlags("Selection", 0)) {
|
||||
switch (input->state) {
|
||||
case INPUT_SELECTED_UNITS:
|
||||
igText("Selected units:");
|
||||
for (i32 i = 0; i < bzArraySize(input->entities); i++) {
|
||||
igText("\t%llu", bzArrayGet(input->entities, i));
|
||||
}
|
||||
break;
|
||||
case INPUT_SELECTED_OBJECT:
|
||||
break;
|
||||
case INPUT_SELECTED_BUILDING:
|
||||
break;
|
||||
default:
|
||||
igText("NONE");
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
if (igCollapsingHeader_TreeNodeFlags("Resources", 0)) {
|
||||
@@ -448,11 +429,11 @@ void imguiRender(float dt, void *userData) {
|
||||
}
|
||||
if (igCollapsingHeader_TreeNodeFlags("BuildMenu", 0)) {
|
||||
for (int i = 0; i < BUILDINGS_COUNT; i++) {
|
||||
if (igSelectable_Bool(getBuildingStr(i), game->input.building == i, 0, (ImVec2){0, 0}))
|
||||
game->input.building = i;
|
||||
if (igSelectable_Bool(getBuildingStr(i), input->building == i, 0, (ImVec2){0, 0}))
|
||||
input->building = i;
|
||||
}
|
||||
if (game->input.building)
|
||||
game->input.state = INPUT_BUILDING;
|
||||
if (input->building)
|
||||
input->state = INPUT_BUILDING;
|
||||
|
||||
}
|
||||
if (igCollapsingHeader_TreeNodeFlags("DebugDraw", 0)) {
|
||||
|
||||
@@ -70,4 +70,17 @@ void renderColliders(ecs_iter_t *it);
|
||||
*/
|
||||
void renderDebugPath(ecs_iter_t *it);
|
||||
|
||||
|
||||
|
||||
/**********************************
|
||||
* Entity Systems
|
||||
**********************************/
|
||||
|
||||
/*
|
||||
* Task:
|
||||
* 0: Game (singleton)
|
||||
* 0: InputState (singleton)
|
||||
*/
|
||||
void updatePlayerInput(ecs_iter_t *it);
|
||||
|
||||
#endif //PIXELDEFENSE_SYSTEMS_H
|
||||
|
||||
202
game/systems_input.c
Normal file
202
game/systems_input.c
Normal file
@@ -0,0 +1,202 @@
|
||||
#include "systems.h"
|
||||
#include "game_state.h"
|
||||
#include "buildings.h"
|
||||
#include "pathfinding.h"
|
||||
#include <rlImGui.h>
|
||||
#include <raymath.h>
|
||||
|
||||
void pickEntity(BzSpatialGrid *entityGrid, Vector2 point, ecs_entity_t **outEntities);
|
||||
void pickEntities(BzSpatialGrid *entityGrid, Rectangle area, ecs_entity_t **outEntities);
|
||||
|
||||
void updatePlayerInput(ecs_iter_t *it) {
|
||||
f32 dt = GetFrameTime();
|
||||
ImGuiIO *io = igGetIO();
|
||||
if (io->WantCaptureMouse || io->WantCaptureKeyboard)
|
||||
return;
|
||||
|
||||
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_Q)) game->camera.rotation--;
|
||||
if (IsKeyDown(KEY_E)) game->camera.rotation++;
|
||||
|
||||
|
||||
game->camera.zoom += ((float) 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;
|
||||
}
|
||||
|
||||
Vector2 worldPos = GetScreenToWorld2D(GetMousePosition(), game->camera);
|
||||
int tileX = (int) worldPos.x / map->tileWidth;
|
||||
int tileY = (int) worldPos.y / map->tileHeight;
|
||||
switch (input->state) {
|
||||
case INPUT_NONE:
|
||||
if (IsMouseButtonReleased(input->LMB) && input->mouseDownElapsed > input->CLICK_LIMIT) {
|
||||
// Dragging
|
||||
|
||||
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 pickArea = {start.x, start.y, end.x - start.x, end.y - start.y};
|
||||
|
||||
pickEntities(game->entityGrid, pickArea, &input->entities);
|
||||
if (bzArraySize(input->entities) > 0) {
|
||||
input->state = INPUT_SELECTED_UNITS;
|
||||
break;
|
||||
}
|
||||
|
||||
} else if (IsMouseButtonReleased(input->LMB)) {
|
||||
// Click
|
||||
|
||||
// 1. Entity
|
||||
pickEntity(game->entityGrid, worldPos, &input->entities);
|
||||
if (bzArraySize(input->entities) > 0) {
|
||||
input->state = INPUT_SELECTED_UNITS;
|
||||
break;
|
||||
}
|
||||
|
||||
// 2. Object
|
||||
|
||||
// 3. Building
|
||||
}
|
||||
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)) {
|
||||
placeBuilding(&game->map, input->building, tileX, tileY);
|
||||
}
|
||||
input->buildingCanPlace = canPlace;
|
||||
input->buildingPos = (TilePosition) {tileX, tileY};
|
||||
input->buildingSize = (TileSize) {sizeX, sizeY};
|
||||
break;
|
||||
case INPUT_SELECTED_UNITS:
|
||||
if (IsKeyPressed(input->ESC)) {
|
||||
input->state = INPUT_NONE;
|
||||
break;
|
||||
}
|
||||
if (IsMouseButtonPressed(input->LMB)) {
|
||||
bzArrayFor(input->entities, i) {
|
||||
ecs_entity_t entity = bzArrayGet(input->entities, i);
|
||||
ecs_remove(ECS, entity, Path);
|
||||
|
||||
const Position *start = ecs_get(ECS, entity, Position);
|
||||
Path path = {NULL, 0};
|
||||
findPath(&(PathfindingDesc) {
|
||||
.start=(TilePosition) {
|
||||
start->x / game->map.tileWidth,
|
||||
start->y / game->map.tileHeight,
|
||||
},
|
||||
.target=(TilePosition) {tileX, tileY},
|
||||
.map=&game->map,
|
||||
.outPath=&path,
|
||||
.pool=game->pools.pathData
|
||||
});
|
||||
if (!path.paths) continue;
|
||||
ecs_set_ptr(ECS, entity, Path, &path);
|
||||
input->state = INPUT_NONE;
|
||||
}
|
||||
|
||||
}
|
||||
break;
|
||||
case INPUT_SELECTED_OBJECT:
|
||||
break;
|
||||
case INPUT_SELECTED_BUILDING:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static bool getEntityBounds(ecs_entity_t entity, Position *outPos, Size *outSize,
|
||||
Rectangle *outBounds) {
|
||||
if (!ecs_is_alive(ECS, entity))
|
||||
return false;
|
||||
const Position *pos = ecs_get(ECS, entity, Position);
|
||||
if (!pos)
|
||||
return false;
|
||||
const Size *size = ecs_get(ECS, entity, Size);
|
||||
if (!size)
|
||||
return false;
|
||||
|
||||
if (outPos) {
|
||||
*outPos = *pos;
|
||||
}
|
||||
if (outSize) {
|
||||
*outSize = *size;
|
||||
}
|
||||
if (outBounds) {
|
||||
*outBounds = (Rectangle) {
|
||||
pos->x - size->x * 0.5f,
|
||||
pos->y - size->y * 0.5f,
|
||||
size->x, size->y
|
||||
};
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void pickEntity(BzSpatialGrid *entityGrid, Vector2 point, ecs_entity_t **outEntities) {
|
||||
BZ_ASSERT(*outEntities);
|
||||
bzArrayClear(*outEntities);
|
||||
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;
|
||||
Vector2 pos;
|
||||
Rectangle bounds;
|
||||
if (!getEntityBounds(entity, &pos, NULL, &bounds)) continue;
|
||||
|
||||
if (!CheckCollisionPointRec(point, bounds)) continue;
|
||||
|
||||
f32 curDst = Vector2Distance(point, pos);
|
||||
if (closestDst > curDst) {
|
||||
closestDst = curDst;
|
||||
closest = entity;
|
||||
}
|
||||
}
|
||||
if (closest) {
|
||||
bzArrayPush(*outEntities, closest);
|
||||
}
|
||||
}
|
||||
|
||||
void pickEntities(BzSpatialGrid *entityGrid, Rectangle area, ecs_entity_t **outEntities) {
|
||||
BZ_ASSERT(*outEntities);
|
||||
bzArrayClear(*outEntities);
|
||||
|
||||
BzSpatialGridIter it = bzSpatialGridIter(entityGrid, area.x, area.y, area.width, area.height);
|
||||
while (bzSpatialGridQueryNext(&it)) {
|
||||
ecs_entity_t entity = *(ecs_entity_t *) it.data;
|
||||
Rectangle bounds;
|
||||
if (!getEntityBounds(entity, NULL, NULL, &bounds)) continue;
|
||||
|
||||
if (!CheckCollisionRecs(area, bounds)) continue;
|
||||
bzArrayPush(*outEntities, entity);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user