Rework input

This commit is contained in:
2023-12-07 14:19:10 +01:00
parent d3485303c9
commit 8543cc7b43
6 changed files with 220 additions and 122 deletions

View File

@@ -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);
}

View File

@@ -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);

View File

@@ -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

View File

@@ -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;

View File

@@ -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);
}
}

View File

@@ -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);
}
}