Fix and integrate BT

This commit is contained in:
2024-01-10 14:42:21 +01:00
parent a9d20cb7f9
commit 3ba5c8932b
16 changed files with 551 additions and 109 deletions

View File

@@ -18,6 +18,8 @@ add_executable(PixelDefense
game/systems/systems.c game/systems/systems.c
game/systems/systems.h game/systems/systems.h
game/ai_actions.c
game/ai_actions.h
game/buildings.c game/buildings.c
game/buildings.h game/buildings.h
game/components.c game/components.c
@@ -38,10 +40,6 @@ add_executable(PixelDefense
game/sounds.h game/sounds.h
game/ui_widgets.c game/ui_widgets.c
game/ui_widgets.h game/ui_widgets.h
game/unit_actions.c
game/unit_actions.h
game/unit_ai.c
game/unit_ai.h
) )

View File

@@ -211,6 +211,17 @@ bool bzBTNodeMatchesState(const BzBTNode *node, const BzBTNodeState *state) {
return state && state->node == node; return state && state->node == node;
} }
BzBTNode *bzBTCompStateGetRunningChild(const BzBTNodeState *state) {
BZ_ASSERT(state->node);
BzBTNodeType type = state->node->type;
bool isComposite = type == BZ_BT_COMP_SELECTOR ||
type == BZ_BT_COMP_PARALLEL_SELECTOR ||
type == BZ_BT_COMP_SEQUENCE ||
type == BZ_BT_COMP_PARALLEL_SEQUENCE;
BZ_ASSERT(isComposite);
return state->as.composite.running;
}
i32 bzBTRepeatStateGetIter(const BzBTNodeState *state) { i32 bzBTRepeatStateGetIter(const BzBTNodeState *state) {
BZ_ASSERT(state->node && state->node->type == BZ_BT_DECOR_REPEAT); BZ_ASSERT(state->node && state->node->type == BZ_BT_DECOR_REPEAT);
return state->as.repeat.iter; return state->as.repeat.iter;
@@ -226,14 +237,14 @@ BzBTState bzBTCreateState(const BzBTStateDesc *desc) {
BZ_ASSERT(desc->root); BZ_ASSERT(desc->root);
return (BzBTState) { return (BzBTState) {
.root = desc->root, .root = desc->root,
.first = NULL, ._first = NULL,
.last = NULL, ._last = NULL,
.nodeStatePool = desc->pool, .nodeStatePool = desc->pool,
.userData = desc->userData .userData = desc->userData
}; };
} }
void bzBTDestroyState(BzBTState *state) { void bzBTDestroyState(BzBTState *state) {
BzBTNodeState *pNodeState = state->first; BzBTNodeState *pNodeState = state->_first;
while (pNodeState) { while (pNodeState) {
BzBTNodeState *next = pNodeState->next; BzBTNodeState *next = pNodeState->next;
bzObjectPoolRelease(state->nodeStatePool, pNodeState); bzObjectPoolRelease(state->nodeStatePool, pNodeState);
@@ -244,16 +255,16 @@ void bzBTDestroyState(BzBTState *state) {
void bzBTStateAppend(BzBTState *state, BzBTNodeState *nodeState) { void bzBTStateAppend(BzBTState *state, BzBTNodeState *nodeState) {
nodeState->next = NULL; nodeState->next = NULL;
nodeState->prev = state->last; nodeState->prev = state->_last;
if (state->last) if (state->_last)
state->last->next = nodeState; state->_last->next = nodeState;
else else
state->first = nodeState; state->_first = nodeState;
state->last = nodeState; state->_last = nodeState;
} }
void bzBTStatePop(BzBTState *state, BzBTNodeState *nodeState) { void bzBTStatePop(BzBTState *state, BzBTNodeState *nodeState) {
if (state->first == nodeState) state->first = nodeState->next; if (state->_first == nodeState) state->_first = nodeState->next;
if (state->last == nodeState) state->last = nodeState->prev; if (state->_last == nodeState) state->_last = nodeState->prev;
BzBTNodeState *next = nodeState->next; BzBTNodeState *next = nodeState->next;
BzBTNodeState *prev = nodeState->prev; BzBTNodeState *prev = nodeState->prev;
if (nodeState->prev) if (nodeState->prev)
@@ -276,6 +287,7 @@ BzBTNodeState *bzBTStatePool(BzBTState *state, const BzBTNode *node) {
return nodeState; return nodeState;
} }
void bzBTStateRelease(BzBTState *state, BzBTNodeState *nodeState) { void bzBTStateRelease(BzBTState *state, BzBTNodeState *nodeState) {
bzBTStatePop(state, nodeState);
bzObjectPoolRelease(state->nodeStatePool, nodeState); bzObjectPoolRelease(state->nodeStatePool, nodeState);
} }
@@ -368,7 +380,7 @@ static inline BzBTStatus bzBTExecuteComposite(const BzBTNode *node, f32 dt,
} }
if (status == BZ_BT_ERROR) { if (status == BZ_BT_ERROR) {
bzBTStatePop(newState, nodeState); bzBTStateRelease(newState, nodeState);
return BZ_BT_ERROR; return BZ_BT_ERROR;
} }
@@ -376,7 +388,7 @@ static inline BzBTStatus bzBTExecuteComposite(const BzBTNode *node, f32 dt,
status == BZ_BT_FAIL; status == BZ_BT_FAIL;
if (finished) { if (finished) {
// Dummy state is no longer needed // Dummy state is no longer needed
bzBTStatePop(newState, nodeState); bzBTStateRelease(newState, nodeState);
} else { } else {
BZ_ASSERT(status == BZ_BT_RUNNING); BZ_ASSERT(status == BZ_BT_RUNNING);
nodeState->as.composite.running = child; nodeState->as.composite.running = child;
@@ -463,7 +475,7 @@ static inline BzBTStatus bzBTExecuteDecorator(const BzBTNode *node, f32 dt,
BZ_ASSERT(nodeState->node == node); BZ_ASSERT(nodeState->node == node);
nodeState->as.repeat.iter++; nodeState->as.repeat.iter++;
if (nodeState->as.repeat.iter >= node->as.repeat.n) { if (nodeState->as.repeat.iter >= node->as.repeat.n) {
bzBTStatePop(newState, nodeState); bzBTStateRelease(newState, nodeState);
status = inStatus; status = inStatus;
break; break;
} }
@@ -497,7 +509,7 @@ static inline BzBTStatus bzBTExecuteNode(const BzBTNode *node, f32 dt,
break; break;
case BZ_BT_ACTION: case BZ_BT_ACTION:
BZ_ASSERT(node->as.action.fn); BZ_ASSERT(node->as.action.fn);
return node->as.action.fn(oldState->userData); return node->as.action.fn(oldState->userData, dt);
} }
return status; return status;
} }
@@ -506,25 +518,43 @@ BzBTStatus bzBTExecute(BzBTState *state, f32 dt) {
BZ_ASSERT(state->nodeStatePool); BZ_ASSERT(state->nodeStatePool);
BZ_ASSERT(bzObjectPoolGetObjectSize(state->nodeStatePool) == bzBTGetNodeStateSize()); BZ_ASSERT(bzObjectPoolGetObjectSize(state->nodeStatePool) == bzBTGetNodeStateSize());
BZ_ASSERT(state); BZ_ASSERT(state);
BZ_ASSERT(state->root); if (state->root == NULL) {
return BZ_BT_FAIL;
}
BzBTState newState = { BzBTState newState = {
.first = NULL, ._first = NULL,
.last = NULL, ._last = NULL,
.nodeStatePool = state->nodeStatePool
}; };
BzBTNodeState *first = state->first; BzBTNodeState *first = state->_first;
const BzBTNode *firstNode = first ? first->node : state->root; const BzBTNode *firstNode = first ? first->node : state->root;
BzBTStatus status = bzBTExecuteNode(firstNode, dt, first, state, &newState); BzBTStatus status = bzBTExecuteNode(firstNode, dt, first, state, &newState);
// Release leftover states // Release leftover states
BzBTNodeState *pState = state->first; BzBTNodeState *pState = state->_first;
while (pState) { while (pState) {
BzBTNodeState *next = pState->next; BzBTNodeState *next = pState->next;
bzBTStateRelease(state, pState); bzBTStateRelease(state, pState);
pState = next; pState = next;
} }
state->first = newState.first; state->_first = newState._first;
state->last = newState.last; state->_last = newState._last;
switch (status) {
case BZ_BT_SUCCESS:
state->onSuccess && state->onSuccess(state->userData, dt);
break;
case BZ_BT_FAIL:
state->onFailure && state->onFailure(state->userData, dt);
break;
case BZ_BT_ERROR:
state->onError && state->onError(state->userData, dt);
break;
default:
break;
}
return status; return status;
} }

View File

@@ -12,7 +12,7 @@ typedef enum BzBTStatus {
BZ_BT_ERROR, BZ_BT_ERROR,
} BzBTStatus; } BzBTStatus;
typedef BzBTStatus(*BzBTActionFn)(void *data); typedef BzBTStatus(*BzBTActionFn)(void *data, f32 dt);
typedef enum BzBTNodeType { typedef enum BzBTNodeType {
// Composite // Composite
@@ -38,8 +38,12 @@ typedef struct BzBTNodeState BzBTNodeState;
typedef struct BzBTState { typedef struct BzBTState {
const BzBTNode *root; const BzBTNode *root;
BzBTNodeState *first; BzBTNodeState *_first;
BzBTNodeState *last; BzBTNodeState *_last;
BzBTActionFn onSuccess;
BzBTActionFn onFailure;
BzBTActionFn onError;
BzObjectPool *nodeStatePool; BzObjectPool *nodeStatePool;
void *userData; void *userData;
@@ -91,6 +95,8 @@ BzBTNode *bzBTNodeNext(const BzBTNode *node);
const BzBTNodeState *bzBTNodeStateNext(const BzBTNodeState *state); const BzBTNodeState *bzBTNodeStateNext(const BzBTNodeState *state);
bool bzBTNodeMatchesState(const BzBTNode *node, const BzBTNodeState *state); bool bzBTNodeMatchesState(const BzBTNode *node, const BzBTNodeState *state);
BzBTNode *bzBTCompStateGetRunningChild(const BzBTNodeState *state);
i32 bzBTRepeatStateGetIter(const BzBTNodeState *state); i32 bzBTRepeatStateGetIter(const BzBTNodeState *state);
f32 bzBTDelayStateGetElapsed(const BzBTNodeState *state); f32 bzBTDelayStateGetElapsed(const BzBTNodeState *state);

View File

@@ -7,7 +7,7 @@ BzObjectPool *nodeStatePool = NULL;
BzBTNode *printBT = NULL; BzBTNode *printBT = NULL;
BzBTState agentState; BzBTState agentState;
BzBTStatus printAction(void *data) { BzBTStatus printAction(void *data, f32 dt) {
bzLogInfo("Hello, world!"); bzLogInfo("Hello, world!");
return BZ_BT_SUCCESS; return BZ_BT_SUCCESS;
} }
@@ -48,7 +48,7 @@ void deinit(int *game) {
bzObjectPoolDestroy(nodeStatePool); bzObjectPoolDestroy(nodeStatePool);
} }
void igRenderBTNode(const BzBTNode *node, const BzBTNodeState *state, bool sameLine, i32 depth) { void igVisualizeBTState(const BzBTNode *node, const BzBTNodeState *state, bool sameLine, i32 depth) {
const BzBTNode *child = bzBTNodeChild(node); const BzBTNode *child = bzBTNodeChild(node);
BzBTNodeType type = bzBTGetNodeType(node); BzBTNodeType type = bzBTGetNodeType(node);
char extraInfo[128]; char extraInfo[128];
@@ -109,7 +109,7 @@ void igRenderBTNode(const BzBTNode *node, const BzBTNodeState *state, bool sameL
while (child) { while (child) {
if (hasSingleChild) igSameLine(0, 0); if (hasSingleChild) igSameLine(0, 0);
igRenderBTNode(child, state, hasSingleChild, depth); igVisualizeBTState(child, state, hasSingleChild, depth);
child = bzBTNodeNext(child); child = bzBTNodeNext(child);
} }
} }
@@ -117,7 +117,7 @@ void igRenderBTNode(const BzBTNode *node, const BzBTNodeState *state, bool sameL
void igRenderBT(BzBTState *state) { void igRenderBT(BzBTState *state) {
const BzBTNode *root = state->root; const BzBTNode *root = state->root;
if (igBegin("BehaviourTree", NULL, 0)) { if (igBegin("BehaviourTree", NULL, 0)) {
igRenderBTNode(root, state->first, false, 0); igVisualizeBTState(root, state->_first, false, 0);
} }
igEnd(); igEnd();
} }

200
game/ai_actions.c Normal file
View File

@@ -0,0 +1,200 @@
#include "ai_actions.h"
#include "game_state.h"
#include "components.h"
#include "systems/systems.h"
#include "buildings.h"
#include <raymath.h>
float shortestArc(float a, float b) {
if (fabs(b - a) < M_PI)
return b - a;
if (b > a)
return b - a - M_PI * 2.0f;
return b - a + M_PI * 2.0f;
}
BzBTStatus aiMoveTo(AIBlackboard *data, f32 dt) {
Game *game = ecs_singleton_get_mut(ECS, Game);
const Vector2 pos = *ecs_get(ECS, data->entity, Position);
const Vector2 target = data->moveToPos;
f32 dst = Vector2Distance(pos, target);
if (dst < data->proximity) {
ecs_remove(ECS, data->entity, Path);
return BZ_BT_SUCCESS;
}
if (!ecs_has(ECS, data->entity, Path)) {
entitySetPath(data->entity, target, game);
}
if (ecs_has(ECS, data->entity, Orientation)) {
Orientation *orientation = ecs_get_mut(ECS, data->entity, Orientation);
f32 currentAngle = *orientation;
f32 targetAngle = Vector2Angle(pos, target);
f32 dif = shortestArc(currentAngle, targetAngle);
dif = Clamp(dif, -1, 1) * dt * 10;
*orientation += dif;
*orientation = fmodf(*orientation + 180.0f, 360.0f) - 180.0f;
}
return BZ_BT_RUNNING;
}
BzBTStatus aiResetElapsed(AIBlackboard *data, f32 dt) {
data->elapsed = 0.0f;
return BZ_BT_SUCCESS;
}
BzBTStatus aiFindNextHarvestable(AIBlackboard *data, f32 dt) {
ecs_entity_t harvestTarget = data->as.worker.harvestTarget;
if (ecs_is_alive(ECS, harvestTarget)) {
BZ_ASSERT(ecs_has_id(ECS, harvestTarget, Harvestable));
// Target still alive, no need to find next harvestable
data->moveToPos = data->as.worker.harvestPos;
return BZ_BT_SUCCESS;
}
BZ_ASSERT(ecs_has(ECS, data->entity, Worker));
Worker *worker = ecs_get_mut(ECS, data->entity, Worker);
Game *game = ecs_singleton_get_mut(ECS, Game);
ResourceType harvestType = data->as.worker.harvestType;
// Perform spatial search
ecs_entity_t closest = 0;
f32 closestDst = INFINITY;
Vector2 closestPos = Vector2Zero();
const f32 range = 20.0f;
f32 hRange = range * 0.5f;
Vector2 pos = data->as.worker.harvestPos; // Last know harvest pos
Rectangle area = {pos.x - hRange, pos.y + hRange, hRange, hRange};
BzSpatialGridIter it = bzSpatialGridIter(game->entityGrid,
area.x, area.y,
area.width, area.height);
while (bzSpatialGridQueryNext(&it)) {
ecs_entity_t entity = *(ecs_entity_t *) it.data;
if (!ecs_is_alive(ECS, entity)) continue;
if (!ecs_has_id(ECS, entity, Harvestable) ||
!ecs_has(ECS, entity, Resource) ||
!ecs_has(ECS, entity, Position))
continue;
Resource resource = *ecs_get(ECS, entity, Resource);
Position resPos = *ecs_get(ECS, entity, Position);
if (resource.type != harvestType) continue;
f32 dst = Vector2Distance(pos, resPos);
if (dst < closestDst) {
closest = entity;
closestDst = dst;
closestPos = resPos;
}
}
if (closest) {
data->as.worker.harvestTarget = closest;
data->as.worker.harvestPos = closestPos;
data->moveToPos = closestPos;
return BZ_BT_SUCCESS;
}
return BZ_BT_FAIL;
}
BzBTStatus aiFindNearestStorage(AIBlackboard *data, f32 dt) {
ecs_filter_t *storageFilter = ecs_filter(ECS, {
.terms = {{ecs_id(Position)}, {ecs_id(Storage)}},
});
ecs_iter_t it = ecs_filter_iter(ECS, storageFilter);
const Vector2 pos = *ecs_get(ECS, data->entity, Position);
ecs_entity_t closest = 0;
f32 closestDst = INFINITY;
Position closestPos = {INFINITY, INFINITY};
while (ecs_filter_next(&it)) {
Position *storagePos = ecs_field(&it, Position, 1);
for (i32 i = 0; i < it.count; i++) {
f32 dst = Vector2Distance(pos, closestPos);
if (closestDst == INFINITY || dst < closestDst) {
closest = it.entities[i];
closestDst = dst;
closestPos = storagePos[i];
}
}
}
ecs_filter_fini(storageFilter);
if (!closest) {
return BZ_BT_FAIL;
}
data->as.worker.depositTarget = closest;
data->moveToPos = getPositionNearBuilding(closest, pos);
return BZ_BT_SUCCESS;
}
BzBTStatus aiHarvestRes(AIBlackboard *data, f32 dt) {
BZ_ASSERT(ecs_has(ECS, data->entity, Worker));
Worker *worker = ecs_get_mut(ECS, data->entity, Worker);
if (worker->carry >= worker->carryCapacity)
return BZ_BT_FAIL;
ecs_entity_t harvestTarget = data->as.worker.harvestTarget;
if (!ecs_is_alive(ECS, harvestTarget))
return BZ_BT_FAIL;
BZ_ASSERT(ecs_has_id(ECS, harvestTarget, Harvestable));
if (data->elapsed < worker->collectSpeed) {
data->elapsed += dt;
return BZ_BT_RUNNING;
}
data->elapsed = 0;
// Collect
i32 spareCapacity = worker->carryCapacity - worker->carry;
BZ_ASSERT(spareCapacity >= 0);
i32 collected = harvestEvent(harvestTarget, (HarvestEvent) {
.amount = BZ_MIN(1, spareCapacity)
});
worker->carry += collected;
return BZ_BT_SUCCESS;
}
BzBTStatus aiDepositRes(AIBlackboard *data, f32 dt) {
BZ_ASSERT(ecs_has(ECS, data->entity, Worker));
Worker *worker = ecs_get_mut(ECS, data->entity, Worker);
if (worker->carry == 0)
return BZ_BT_SUCCESS;
ecs_entity_t depositTarget = data->as.worker.depositTarget;
if (!ecs_is_alive(ECS, depositTarget))
return BZ_BT_FAIL;
if (data->elapsed < worker->depositSpeed) {
data->elapsed += dt;
return BZ_BT_RUNNING;
}
depositEvent(depositTarget, (DepositEvent) {
.amount = worker->carry
});
worker->carry = 0;
return BZ_BT_SUCCESS;
}
BzBTStatus aiCarryCapacityFull(AIBlackboard *data) {
BZ_ASSERT(ecs_has(ECS, data->entity, Worker));
Worker *worker = ecs_get_mut(ECS, data->entity, Worker);
if (worker->carry >= worker->carryCapacity)
return BZ_BT_SUCCESS;
return BZ_BT_FAIL;
}
BzBTStatus aiCarryCapacityEmpty(AIBlackboard *data) {
BZ_ASSERT(ecs_has(ECS, data->entity, Worker));
Worker *worker = ecs_get_mut(ECS, data->entity, Worker);
if (worker->carry == 0)
return BZ_BT_SUCCESS;
return BZ_BT_FAIL;
}

43
game/ai_actions.h Normal file
View File

@@ -0,0 +1,43 @@
#ifndef PIXELDEFENSE_AI_ACTIONS_H
#define PIXELDEFENSE_AI_ACTIONS_H
#include <breeze.h>
#include <flecs.h>
#include "components.h"
typedef struct AIBlackboard {
ecs_entity_t entity;
Vector2 moveToPos;
union {
struct {
ResourceType harvestType;
Vector2 harvestPos;
ecs_entity_t harvestTarget;
ecs_entity_t depositTarget;
} worker;
} as;
f32 proximity;
f32 elapsed;
} AIBlackboard;
BzBTStatus aiMoveTo(AIBlackboard *data, f32 dt);
BzBTStatus aiResetElapsed(AIBlackboard *data, f32 dt);
// Worker
BzBTStatus aiFindNextHarvestable(AIBlackboard *data, f32 dt);
BzBTStatus aiFindNearestStorage(AIBlackboard *data, f32 dt);
BzBTStatus aiHarvestRes(AIBlackboard *data, f32 dt);
BzBTStatus aiDepositRes(AIBlackboard *data, f32 dt);
BzBTStatus aiCarryCapacityFull(AIBlackboard *data);
BzBTStatus aiCarryCapacityEmpty(AIBlackboard *data);
//BzBTStatus aiIsTargetHarvestable(AIBlackboard *data);
//BzBTStatus aiIsTargetStorage(AIBlackboard *data);
#endif //PIXELDEFENSE_AI_ACTIONS_H

View File

@@ -1,7 +1,6 @@
#include "components.h" #include "components.h"
#include "unit_ai.h" #include "ai_actions.h"
#include "unit_actions.h"
ECS_TAG_DECLARE(GameEntity); ECS_TAG_DECLARE(GameEntity);
@@ -26,8 +25,8 @@ ECS_COMPONENT_DECLARE(Easing);
ECS_COMPONENT_DECLARE(Arms); ECS_COMPONENT_DECLARE(Arms);
ECS_COMPONENT_DECLARE(Arm); ECS_COMPONENT_DECLARE(Arm);
ECS_COMPONENT_DECLARE(UnitAI); ECS_COMPONENT_DECLARE(BzBTState);
ECS_COMPONENT_DECLARE(UnitAction); ECS_COMPONENT_DECLARE(AIBlackboard);
ECS_TAG_DECLARE(Selectable); ECS_TAG_DECLARE(Selectable);
ECS_TAG_DECLARE(Selected); ECS_TAG_DECLARE(Selected);
@@ -65,8 +64,8 @@ void initComponentIDs(ecs_world_t *ecs) {
ECS_COMPONENT_DEFINE(ecs, Arms); ECS_COMPONENT_DEFINE(ecs, Arms);
ECS_COMPONENT_DEFINE(ecs, Arm); ECS_COMPONENT_DEFINE(ecs, Arm);
ECS_COMPONENT_DEFINE(ecs, UnitAI); ECS_COMPONENT_DEFINE(ecs, BzBTState);
ECS_COMPONENT_DEFINE(ecs, UnitAction); ECS_COMPONENT_DEFINE(ecs, AIBlackboard);
ECS_TAG_DEFINE(ecs, Selectable); ECS_TAG_DEFINE(ecs, Selectable);
ECS_TAG_DEFINE(ecs, Selected); ECS_TAG_DEFINE(ecs, Selected);
@@ -191,14 +190,90 @@ void igArm(ecs_world_t *ecs,
} }
void igUnitAction(ecs_world_t *ecs, void igVisualizeBTState(const BzBTNode *node, const BzBTNodeState *state,
ecs_entity_t entity, ecs_entity_t comp) { bool isActive, bool sameLine, i32 depth) {
const BzBTNode *child = bzBTNodeChild(node);
BzBTNodeType type = bzBTGetNodeType(node);
char extraInfo[128];
extraInfo[0] = '\0';
bool hasState = bzBTNodeMatchesState(node, state);
isActive |= hasState;
switch (type) {
case BZ_BT_DECOR_REPEAT:
if (hasState) {
snprintf(extraInfo, sizeof(extraInfo), " (%d < %d)",
bzBTRepeatStateGetIter(state),
bzBTDecorGetRepeat(node));
} else {
snprintf(extraInfo, sizeof(extraInfo), " (%d)", bzBTDecorGetRepeat(node));
}
break;
case BZ_BT_DECOR_DELAY:
if (hasState) {
snprintf(extraInfo, sizeof(extraInfo), " (%.2f < %.2fms)",
bzBTDelayStateGetElapsed(state),
bzBTDecorGetDelay(node));
} else {
snprintf(extraInfo, sizeof(extraInfo), " (%.2fms)", bzBTDecorGetDelay(node));
}
break;
case BZ_BT_ACTION:
snprintf(extraInfo, sizeof(extraInfo), " (%s:%p)",
bzBTNodeGetName(node) ? bzBTNodeGetName(node) : "?",
bzBTActionGetFn(node));
break;
default:
break;
}
ImVec4 color = {1.0f, 1.0f, 1.0f, 1.0f};
if (isActive)
color = (ImVec4) {1.0f, 1.0f, 0.5f, 1.0f};
bool hasSingleChild = true;
if (child && bzBTNodeNext(child)) hasSingleChild = false;
const char *suffix = hasSingleChild ? " > " : ": ";
if (sameLine) {
igTextColored(color, "%s%s%s", bzBTNodeTypeToStr(type),
extraInfo, suffix);
} else {
igTextColored(color, "%*s%s %s",
depth * 2, "",
bzBTNodeTypeToStr(type), extraInfo, suffix);
depth++;
}
bool isComposite = type == BZ_BT_COMP_SELECTOR ||
type == BZ_BT_COMP_PARALLEL_SELECTOR ||
type == BZ_BT_COMP_SEQUENCE ||
type == BZ_BT_COMP_PARALLEL_SEQUENCE;
while (child) {
if (hasSingleChild) igSameLine(0, 0);
bool childActive = isActive && hasSingleChild;
if (hasState && isComposite && !childActive)
childActive = bzBTCompStateGetRunningChild(state) == child;
const BzBTNodeState *childState = state;
if (hasState)
childState = bzBTNodeStateNext(state);
igVisualizeBTState(child, childState, childActive, hasSingleChild, depth);
child = bzBTNodeNext(child);
}
}
void igBzBTState(ecs_world_t *ecs,
ecs_entity_t entity, ecs_entity_t comp) {
} }
void igUnitAI(ecs_world_t *ecs, void igAIBlackboard(ecs_world_t *ecs,
ecs_entity_t entity, ecs_entity_t comp) { ecs_entity_t entity, ecs_entity_t comp) {
} }
void igWorker(ecs_world_t *ecs, void igWorker(ecs_world_t *ecs,
ecs_entity_t entity, ecs_entity_t comp) { ecs_entity_t entity, ecs_entity_t comp) {

View File

@@ -159,8 +159,8 @@ typedef struct Arm {
} Arm; } Arm;
extern ECS_COMPONENT_DECLARE(Arm); extern ECS_COMPONENT_DECLARE(Arm);
extern ECS_COMPONENT_DECLARE(UnitAction); extern ECS_COMPONENT_DECLARE(BzBTState);
extern ECS_COMPONENT_DECLARE(UnitAI); extern ECS_COMPONENT_DECLARE(AIBlackboard);
extern ECS_TAG_DECLARE(Selectable); extern ECS_TAG_DECLARE(Selectable);
extern ECS_TAG_DECLARE(Selected); extern ECS_TAG_DECLARE(Selected);
@@ -235,10 +235,13 @@ void igArms(ecs_world_t *ecs,
void igArm(ecs_world_t *ecs, void igArm(ecs_world_t *ecs,
ecs_entity_t entity, ecs_entity_t comp); ecs_entity_t entity, ecs_entity_t comp);
void igUnitAction(ecs_world_t *ecs, void igVisualizeBTState(const BzBTNode *node, const BzBTNodeState *state,
ecs_entity_t entity, ecs_entity_t comp); bool isActive, bool sameLine, i32 depth);
void igUnitAI(ecs_world_t *ecs, void igBzBTState(ecs_world_t *ecs,
ecs_entity_t entity, ecs_entity_t comp); ecs_entity_t entity, ecs_entity_t comp);
void igAIBlackboard(ecs_world_t *ecs,
ecs_entity_t entity, ecs_entity_t comp);
void igWorker(ecs_world_t *ecs, void igWorker(ecs_world_t *ecs,
ecs_entity_t entity, ecs_entity_t comp); ecs_entity_t entity, ecs_entity_t comp);
void igUnit(ecs_world_t *ecs, void igUnit(ecs_world_t *ecs,

View File

@@ -1,5 +1,6 @@
#include "entity_factory.h" #include "entity_factory.h"
#include "unit_actions.h"
#include "ai_actions.h"
ecs_entity_t entityCreateEmpty() { ecs_entity_t entityCreateEmpty() {
ecs_entity_t e = ecs_new_id(ECS); ecs_entity_t e = ecs_new_id(ECS);
@@ -35,7 +36,11 @@ ecs_entity_t entityCreateWorker(const Position position, Game *game) {
.curFrame = 0, .curFrame = 0,
.elapsed = 0.0f, .elapsed = 0.0f,
}); });
ecs_set(ECS, e, UnitAction, { NULL, NULL }); ecs_set(ECS, e, BzBTState, {
.root = NULL,
.nodeStatePool = game->pools.btNodeState
});
ecs_set(ECS, e, AIBlackboard, {.entity = e});
ecs_add_id(ECS, e, Selectable); ecs_add_id(ECS, e, Selectable);
ecs_set(ECS, e, Unit, { ecs_set(ECS, e, Unit, {
.acceleration = 80.0f, .acceleration = 80.0f,

View File

@@ -37,9 +37,14 @@ typedef struct Game {
i64 pop; i64 pop;
} resources; } resources;
BzStackAlloc stackAlloc; BzStackAlloc stackAlloc;
struct {
BzBTNode *workerHarvest;
BzBTNode *moveTo;
} BTs;
struct { struct {
BzObjectPool *pathData; BzObjectPool *pathData;
BzObjectPool *actions; BzObjectPool *btNode;
BzObjectPool *btNodeState;
} pools; } pools;
struct { struct {
bool drawPath; bool drawPath;

View File

@@ -12,8 +12,6 @@
#include "map_layers.h" #include "map_layers.h"
#include "buildings.h" #include "buildings.h"
#include "ui_widgets.h" #include "ui_widgets.h"
#include "unit_ai.h"
#include "unit_actions.h"
#include "pathfinding.h" #include "pathfinding.h"
#include "sounds.h" #include "sounds.h"
@@ -215,10 +213,74 @@ bool init(void *userData) {
.objectSize = sizeof(PathData), .objectSize = sizeof(PathData),
.objectsPerPage = 512 .objectsPerPage = 512
}); });
game->pools.actions = bzObjectPoolCreate(&(BzObjectPoolDesc) { game->pools.btNode = bzObjectPoolCreate(&(BzObjectPoolDesc) {
.objectSize = sizeof(Action), .objectSize = bzBTGetNodeSize(),
.objectsPerPage = 64
});
game->pools.btNodeState = bzObjectPoolCreate(&(BzObjectPoolDesc) {
.objectSize = bzBTGetNodeStateSize(),
.objectsPerPage = 1024, .objectsPerPage = 1024,
}); });
BzObjectPool *nodePool = game->pools.btNode;
// moveTo
{
BzBTNode *root = NULL;
BzBTNode *node = NULL;
root = bzBTMakeRoot(nodePool);
game->BTs.moveTo = root;
// Just a single action for now
node = bzBTAction(nodePool, root, (BzBTActionFn) aiMoveTo);
bzBTNodeSetName(node, "moveTo");
}
// worker harvest
{
BzBTNode *root = NULL;
BzBTNode *node = NULL;
root = bzBTMakeRoot(nodePool);
game->BTs.workerHarvest = root;
//node = bzBTDecorUntilFail(nodePool, root);
BzBTNode *collectSeq = bzBTCompSequence(nodePool, root, false);
BzBTNode *untilFail = bzBTDecorUntilFail(nodePool, collectSeq);
{
BzBTNode *untilSeq = bzBTCompSequence(nodePool, untilFail, false);
node = bzBTAction(nodePool, untilSeq, (BzBTActionFn) aiFindNextHarvestable);
bzBTNodeSetName(node, "findNextHarvestable");
node = bzBTAction(nodePool, untilSeq, (BzBTActionFn) aiMoveTo);
bzBTNodeSetName(node, "moveTo");
node = bzBTAction(nodePool, untilSeq, (BzBTActionFn) aiResetElapsed);
bzBTNodeSetName(node, "resetElapsed");
node = bzBTAction(nodePool, untilSeq, (BzBTActionFn) aiHarvestRes);
bzBTNodeSetName(node, "harvestRes");
node = bzBTDecorInvert(nodePool, untilSeq);
node = bzBTAction(nodePool, node, (BzBTActionFn) aiCarryCapacityFull);
bzBTNodeSetName(node, "carryCapacityFull");
}
node = bzBTDecorInvert(nodePool, collectSeq);
node = bzBTAction(nodePool, node, (BzBTActionFn) aiCarryCapacityEmpty);
bzBTNodeSetName(node, "carryCapacityEmpty");
node = bzBTAction(nodePool, collectSeq, (BzBTActionFn) aiFindNearestStorage);
bzBTNodeSetName(node, "findNearestStorage");
node = bzBTAction(nodePool, collectSeq, (BzBTActionFn) aiMoveTo);
bzBTNodeSetName(node, "moveTo");
node = bzBTAction(nodePool, collectSeq, (BzBTActionFn) aiResetElapsed);
bzBTNodeSetName(node, "resetElapsed");
node = bzBTAction(nodePool, collectSeq, (BzBTActionFn) aiDepositRes);
bzBTNodeSetName(node, "depositRes");
node = bzBTDecorDelay(nodePool, collectSeq, 1.0f);
//node = bzBTAction(nodePool, collectSeq, NULL);
//bzBTNodeSetName(node, "harvest");
//node = bzBTAction(nodePool, collectSeq, NULL);
//bzBTNodeSetName(node, "moveTo");
//node = bzBTAction(nodePool, collectSeq, NULL);
//bzBTNodeSetName(node, "deposit");
}
game->frameDuration = 0.16f; game->frameDuration = 0.16f;
@@ -273,7 +335,8 @@ void deinit(void *userData) {
bzStackAllocDestroy(&game->stackAlloc); bzStackAllocDestroy(&game->stackAlloc);
bzObjectPoolDestroy(game->pools.pathData); bzObjectPoolDestroy(game->pools.pathData);
bzObjectPoolDestroy(game->pools.actions); bzObjectPoolDestroy(game->pools.btNode);
bzObjectPoolDestroy(game->pools.btNodeState);
bzArrayDestroy(game->drawData); bzArrayDestroy(game->drawData);
@@ -660,6 +723,14 @@ void igInspectWindow(ecs_entity_t entity, bool *open) {
igTagCheckbox("Workable", ECS, entity, Workable); igTagCheckbox("Workable", ECS, entity, Workable);
igTagCheckbox("Attackable", ECS, entity, Attackable); igTagCheckbox("Attackable", ECS, entity, Attackable);
} }
if (ecs_has(ECS, entity, BzBTState) &&
igCollapsingHeader_TreeNodeFlags("BehaviourTree", 0)) {
const BzBTState *state = ecs_get(ECS, entity, BzBTState);
if (state->root)
igVisualizeBTState(state->root, state->_first, true, false, 0);
else
igTextColored((ImVec4) {1, 0, 0, 1}, "NONE");
}
igInspectComp("Resource", entity, ecs_id(Resource), igResource); igInspectComp("Resource", entity, ecs_id(Resource), igResource);
igInspectComp("Owner", entity, ecs_id(Owner), igOwner); igInspectComp("Owner", entity, ecs_id(Owner), igOwner);
igInspectComp("SpatialGridID", entity, ecs_id(SpatialGridID), igSpatialGridID); igInspectComp("SpatialGridID", entity, ecs_id(SpatialGridID), igSpatialGridID);
@@ -675,8 +746,8 @@ void igInspectWindow(ecs_entity_t entity, bool *open) {
igInspectComp("Easing", entity, ecs_id(Easing), igEasing); igInspectComp("Easing", entity, ecs_id(Easing), igEasing);
igInspectComp("Arms", entity, ecs_id(Arms), igArms); igInspectComp("Arms", entity, ecs_id(Arms), igArms);
igInspectComp("Arm", entity, ecs_id(Arm), igArm); igInspectComp("Arm", entity, ecs_id(Arm), igArm);
igInspectComp("UnitAction", entity, ecs_id(UnitAction), igUnitAction); igInspectComp("BzBTState", entity, ecs_id(BzBTState), igBzBTState);
igInspectComp("UnitAI", entity, ecs_id(UnitAI), igUnitAI); igInspectComp("AIBlackboard", entity, ecs_id(AIBlackboard), igAIBlackboard);
igInspectComp("Worker", entity, ecs_id(Worker), igWorker); igInspectComp("Worker", entity, ecs_id(Worker), igWorker);
igInspectComp("Unit", entity, ecs_id(Unit), igUnit); igInspectComp("Unit", entity, ecs_id(Unit), igUnit);
} }
@@ -691,8 +762,9 @@ void imguiRender(float dt, void *userData) {
igSetNextWindowSize((ImVec2){300, 400}, ImGuiCond_FirstUseEver); igSetNextWindowSize((ImVec2){300, 400}, ImGuiCond_FirstUseEver);
igBegin("Debug Menu", NULL, 0); igBegin("Debug Menu", NULL, 0);
igText("PathData pool available: %llu", bzObjectPoolGetNumFree(game->pools.pathData)); igText("PathData pool available: %llu", bzObjectPoolGetNumFree(game->pools.pathData));
igText("Action pool available: %llu", bzObjectPoolGetNumFree(game->pools.actions)); igText("BTNode pool available: %llu", bzObjectPoolGetNumFree(game->pools.btNode));
igText("BTNodeState pool available: %llu", bzObjectPoolGetNumFree(game->pools.btNodeState));
const char *inputState = "NONE"; const char *inputState = "NONE";
switch (input->state) { switch (input->state) {
case INPUT_NONE: case INPUT_NONE:

View File

@@ -8,9 +8,6 @@
#include "game_state.h" #include "game_state.h"
#include "map_layers.h" #include "map_layers.h"
#include "unit_ai.h"
#include "unit_actions.h"
bool initGameObjectsLayer(BzTileMap *map, BzTileObjectGroup *objectGroup) { bool initGameObjectsLayer(BzTileMap *map, BzTileObjectGroup *objectGroup) {
Game *game = ecs_singleton_get_mut(ECS, Game); Game *game = ecs_singleton_get_mut(ECS, Game);
for (i32 i = 0; i < objectGroup->objectCount; i++) { for (i32 i = 0; i < objectGroup->objectCount; i++) {

View File

@@ -1,41 +1,47 @@
#include "systems.h" #include "systems.h"
#include "../game_state.h" #include "../game_state.h"
#include "../ai_actions.h"
#include "../unit_ai.h"
#include "../unit_actions.h"
void handleUnitActionsSystem(ecs_iter_t *it) { void updateAISystem(ecs_iter_t *it) {
Game *game = ecs_singleton_get_mut(ECS, Game); Game *game = ecs_singleton_get_mut(ECS, Game);
UnitAction *action = ecs_field(it, UnitAction, 1); BzBTState *state = ecs_field(it, BzBTState, 1);
f32 dt = GetFrameTime();
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];
handleAction(entity, &action[i], game); if (state[i].root == NULL) {
// No behaviour
continue;
}
if (ecs_has(ECS, entity, AIBlackboard)) {
AIBlackboard *blackboard = ecs_get_mut(ECS, entity, AIBlackboard);
blackboard->entity = entity;
state[i].userData = blackboard;
}
bzBTExecute(&state[i], dt);
} }
} }
void updateUnitAISystem(ecs_iter_t *it) { void setAIBehaviour(ecs_entity_t entity, const BzBTNode *root,
Game *game = ecs_singleton_get_mut(ECS, Game); const AIBlackboard *blackboard) {
UnitAI *unitAI = ecs_field(it, UnitAI, 1); Game *game = ecs_singleton_get_mut(ECS, Game);
UnitAction *action = ecs_field(it, UnitAction, 2); BZ_ASSERT(ecs_has(ECS, entity, BzBTState));
BZ_ASSERT(!blackboard || ecs_has(ECS, entity, AIBlackboard));
for (i32 i = 0; i < it->count; i++) { if (blackboard) {
ecs_entity_t entity = it->entities[i]; AIBlackboard *b = ecs_get_mut(ECS, entity, AIBlackboard);
*b = *blackboard;
Action *firstAction = action[i].first; if (b->proximity < 2.0f)
unitAI[i].action = firstAction; b->proximity = 2.0f;
updateUnitAI(entity, &unitAI[i], game);
} }
}
void updateUnitActionsSystem(ecs_iter_t *it) { BzBTState *state = ecs_get_mut(ECS, entity, BzBTState);
Game *game = ecs_singleton_get_mut(ECS, Game); bzBTDestroyState(state);
UnitAction *action = ecs_field(it, UnitAction, 1); *state = bzBTCreateState(&(BzBTStateDesc) {
.root = root,
for (i32 i = 0; i < it->count; i++) { .pool = game->pools.btNodeState
ecs_entity_t entity = it->entities[i]; });
updateAction(&action[i], game);
}
} }

View File

@@ -4,8 +4,6 @@
#include "../input.h" #include "../input.h"
#include "../buildings.h" #include "../buildings.h"
#include "../pathfinding.h" #include "../pathfinding.h"
#include "../unit_ai.h"
#include "../unit_actions.h"
#include <rlImGui.h> #include <rlImGui.h>
#include <raymath.h> #include <raymath.h>
@@ -92,12 +90,22 @@ void inputUnitAction(Game *game, InputState *input) {
for (i32 i = 0; i < it.count; i++) { for (i32 i = 0; i < it.count; i++) {
const ecs_entity_t entity = it.entities[i]; const ecs_entity_t entity = it.entities[i];
const Position target = *ecs_get(ECS, taskEntity, Position); const Position target = *ecs_get(ECS, taskEntity, Position);
setAIBehaviour(entity, game->BTs.workerHarvest, &(AIBlackboard) {
.as.worker = {
.harvestType = RES_WOOD,
.harvestTarget = taskEntity,
.harvestPos = target,
},
.proximity = 6.0f,
});
/*
setUnitAI(entity, game, &(const UnitAI) { setUnitAI(entity, game, &(const UnitAI) {
.type = AI_WORKER_HARVEST, .type = AI_WORKER_HARVEST,
.as.workerHarvest.resource = RES_WOOD, .as.workerHarvest.resource = RES_WOOD,
.as.workerHarvest.target = taskEntity, .as.workerHarvest.target = taskEntity,
.as.workerHarvest.targetPosition = target .as.workerHarvest.targetPosition = target
}); });
*/
//addAction(entity, game, &(const Action) { //addAction(entity, game, &(const Action) {
// .type = ACTION_MOVE_TO, // .type = ACTION_MOVE_TO,
// .as.moveTo.target = target, // .as.moveTo.target = target,
@@ -128,12 +136,18 @@ void inputUnitAction(Game *game, InputState *input) {
while (ecs_iter_next(&it)) { while (ecs_iter_next(&it)) {
for (i32 i = 0; i < it.count; i++) { for (i32 i = 0; i < it.count; i++) {
const ecs_entity_t entity = it.entities[i]; const ecs_entity_t entity = it.entities[i];
setAIBehaviour(entity, game->BTs.moveTo, &(AIBlackboard) {
.moveToPos = target,
.proximity = 6.0f,
});
/*
clearActions(entity, game); clearActions(entity, game);
addAction(entity, game, &(const Action) { addAction(entity, game, &(const Action) {
.type = ACTION_MOVE_TO, .type = ACTION_MOVE_TO,
.as.moveTo.target = target, .as.moveTo.target = target,
.as.moveTo.proximityThreshold = 6.0f, .as.moveTo.proximityThreshold = 6.0f,
}); });
*/
} }
} }
ecs_defer_end(ECS); ecs_defer_end(ECS);

View File

@@ -114,10 +114,7 @@ void setupSystems() {
ECS_SYSTEM(ECS, entityFollowPath, EcsOnUpdate, Path); ECS_SYSTEM(ECS, entityFollowPath, EcsOnUpdate, Path);
ECS_SYSTEM(ECS, entityUpdateArms, EcsOnUpdate, Position, Velocity, Rotation, Orientation, Arms); ECS_SYSTEM(ECS, entityUpdateArms, EcsOnUpdate, Position, Velocity, Rotation, Orientation, Arms);
ECS_SYSTEM(ECS, handleUnitActionsSystem, EcsOnUpdate, UnitAction); ECS_SYSTEM(ECS, updateAISystem, EcsOnUpdate, BzBTState);
ECS_SYSTEM(ECS, updateUnitAISystem, EcsOnUpdate, UnitAI, UnitAction);
// Needs to be called after AI update, since it removes finished actions
ECS_SYSTEM(ECS, updateUnitActionsSystem, EcsOnUpdate, UnitAction);
ECS_SYSTEM(ECS, updateAnimationState, EcsOnUpdate, Animation, TextureRegion); ECS_SYSTEM(ECS, updateAnimationState, EcsOnUpdate, Animation, TextureRegion);
ECS_SYSTEM(ECS, updateAnimation, EcsOnUpdate, Animation, TextureRegion); ECS_SYSTEM(ECS, updateAnimation, EcsOnUpdate, Animation, TextureRegion);

View File

@@ -4,6 +4,7 @@
#include <flecs.h> #include <flecs.h>
#include "../components.h" #include "../components.h"
#include "../ai_actions.h"
typedef struct Game Game; typedef struct Game Game;
@@ -19,22 +20,12 @@ bool entitySetPath(const ecs_entity_t entity, const Vector2 target, Game *game);
/* /*
* 0: Game (singleton) * 0: Game (singleton)
* 1: UnitAction * 1: BzBTState
*/ */
void handleUnitActionsSystem(ecs_iter_t *it); void updateAISystem(ecs_iter_t *it);
/* void setAIBehaviour(ecs_entity_t entity, const BzBTNode *root,
* 0: Game (singleton) const AIBlackboard *blackboard);
* 1: UnitAI
* 2: UnitAction
*/
void updateUnitAISystem(ecs_iter_t *it);
/*
* 0: Game (singleton)
* 1: UnitAction
*/
void updateUnitActionsSystem(ecs_iter_t *it);
/********************************** /**********************************