Fix and integrate BT
This commit is contained in:
@@ -18,6 +18,8 @@ add_executable(PixelDefense
|
||||
game/systems/systems.c
|
||||
game/systems/systems.h
|
||||
|
||||
game/ai_actions.c
|
||||
game/ai_actions.h
|
||||
game/buildings.c
|
||||
game/buildings.h
|
||||
game/components.c
|
||||
@@ -38,10 +40,6 @@ add_executable(PixelDefense
|
||||
game/sounds.h
|
||||
game/ui_widgets.c
|
||||
game/ui_widgets.h
|
||||
game/unit_actions.c
|
||||
game/unit_actions.h
|
||||
game/unit_ai.c
|
||||
game/unit_ai.h
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -211,6 +211,17 @@ bool bzBTNodeMatchesState(const BzBTNode *node, const BzBTNodeState *state) {
|
||||
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) {
|
||||
BZ_ASSERT(state->node && state->node->type == BZ_BT_DECOR_REPEAT);
|
||||
return state->as.repeat.iter;
|
||||
@@ -226,14 +237,14 @@ BzBTState bzBTCreateState(const BzBTStateDesc *desc) {
|
||||
BZ_ASSERT(desc->root);
|
||||
return (BzBTState) {
|
||||
.root = desc->root,
|
||||
.first = NULL,
|
||||
.last = NULL,
|
||||
._first = NULL,
|
||||
._last = NULL,
|
||||
.nodeStatePool = desc->pool,
|
||||
.userData = desc->userData
|
||||
};
|
||||
}
|
||||
void bzBTDestroyState(BzBTState *state) {
|
||||
BzBTNodeState *pNodeState = state->first;
|
||||
BzBTNodeState *pNodeState = state->_first;
|
||||
while (pNodeState) {
|
||||
BzBTNodeState *next = pNodeState->next;
|
||||
bzObjectPoolRelease(state->nodeStatePool, pNodeState);
|
||||
@@ -244,16 +255,16 @@ void bzBTDestroyState(BzBTState *state) {
|
||||
|
||||
void bzBTStateAppend(BzBTState *state, BzBTNodeState *nodeState) {
|
||||
nodeState->next = NULL;
|
||||
nodeState->prev = state->last;
|
||||
if (state->last)
|
||||
state->last->next = nodeState;
|
||||
nodeState->prev = state->_last;
|
||||
if (state->_last)
|
||||
state->_last->next = nodeState;
|
||||
else
|
||||
state->first = nodeState;
|
||||
state->last = nodeState;
|
||||
state->_first = nodeState;
|
||||
state->_last = nodeState;
|
||||
}
|
||||
void bzBTStatePop(BzBTState *state, BzBTNodeState *nodeState) {
|
||||
if (state->first == nodeState) state->first = nodeState->next;
|
||||
if (state->last == nodeState) state->last = nodeState->prev;
|
||||
if (state->_first == nodeState) state->_first = nodeState->next;
|
||||
if (state->_last == nodeState) state->_last = nodeState->prev;
|
||||
BzBTNodeState *next = nodeState->next;
|
||||
BzBTNodeState *prev = nodeState->prev;
|
||||
if (nodeState->prev)
|
||||
@@ -276,6 +287,7 @@ BzBTNodeState *bzBTStatePool(BzBTState *state, const BzBTNode *node) {
|
||||
return nodeState;
|
||||
}
|
||||
void bzBTStateRelease(BzBTState *state, BzBTNodeState *nodeState) {
|
||||
bzBTStatePop(state, nodeState);
|
||||
bzObjectPoolRelease(state->nodeStatePool, nodeState);
|
||||
}
|
||||
|
||||
@@ -368,7 +380,7 @@ static inline BzBTStatus bzBTExecuteComposite(const BzBTNode *node, f32 dt,
|
||||
}
|
||||
|
||||
if (status == BZ_BT_ERROR) {
|
||||
bzBTStatePop(newState, nodeState);
|
||||
bzBTStateRelease(newState, nodeState);
|
||||
return BZ_BT_ERROR;
|
||||
}
|
||||
|
||||
@@ -376,7 +388,7 @@ static inline BzBTStatus bzBTExecuteComposite(const BzBTNode *node, f32 dt,
|
||||
status == BZ_BT_FAIL;
|
||||
if (finished) {
|
||||
// Dummy state is no longer needed
|
||||
bzBTStatePop(newState, nodeState);
|
||||
bzBTStateRelease(newState, nodeState);
|
||||
} else {
|
||||
BZ_ASSERT(status == BZ_BT_RUNNING);
|
||||
nodeState->as.composite.running = child;
|
||||
@@ -463,7 +475,7 @@ static inline BzBTStatus bzBTExecuteDecorator(const BzBTNode *node, f32 dt,
|
||||
BZ_ASSERT(nodeState->node == node);
|
||||
nodeState->as.repeat.iter++;
|
||||
if (nodeState->as.repeat.iter >= node->as.repeat.n) {
|
||||
bzBTStatePop(newState, nodeState);
|
||||
bzBTStateRelease(newState, nodeState);
|
||||
status = inStatus;
|
||||
break;
|
||||
}
|
||||
@@ -497,7 +509,7 @@ static inline BzBTStatus bzBTExecuteNode(const BzBTNode *node, f32 dt,
|
||||
break;
|
||||
case BZ_BT_ACTION:
|
||||
BZ_ASSERT(node->as.action.fn);
|
||||
return node->as.action.fn(oldState->userData);
|
||||
return node->as.action.fn(oldState->userData, dt);
|
||||
}
|
||||
return status;
|
||||
}
|
||||
@@ -506,25 +518,43 @@ BzBTStatus bzBTExecute(BzBTState *state, f32 dt) {
|
||||
BZ_ASSERT(state->nodeStatePool);
|
||||
BZ_ASSERT(bzObjectPoolGetObjectSize(state->nodeStatePool) == bzBTGetNodeStateSize());
|
||||
BZ_ASSERT(state);
|
||||
BZ_ASSERT(state->root);
|
||||
if (state->root == NULL) {
|
||||
return BZ_BT_FAIL;
|
||||
}
|
||||
|
||||
BzBTState newState = {
|
||||
.first = NULL,
|
||||
.last = NULL,
|
||||
._first = NULL,
|
||||
._last = NULL,
|
||||
.nodeStatePool = state->nodeStatePool
|
||||
};
|
||||
|
||||
BzBTNodeState *first = state->first;
|
||||
BzBTNodeState *first = state->_first;
|
||||
const BzBTNode *firstNode = first ? first->node : state->root;
|
||||
BzBTStatus status = bzBTExecuteNode(firstNode, dt, first, state, &newState);
|
||||
|
||||
// Release leftover states
|
||||
BzBTNodeState *pState = state->first;
|
||||
BzBTNodeState *pState = state->_first;
|
||||
while (pState) {
|
||||
BzBTNodeState *next = pState->next;
|
||||
bzBTStateRelease(state, pState);
|
||||
pState = next;
|
||||
}
|
||||
state->first = newState.first;
|
||||
state->last = newState.last;
|
||||
state->_first = newState._first;
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ typedef enum BzBTStatus {
|
||||
BZ_BT_ERROR,
|
||||
} BzBTStatus;
|
||||
|
||||
typedef BzBTStatus(*BzBTActionFn)(void *data);
|
||||
typedef BzBTStatus(*BzBTActionFn)(void *data, f32 dt);
|
||||
|
||||
typedef enum BzBTNodeType {
|
||||
// Composite
|
||||
@@ -38,8 +38,12 @@ typedef struct BzBTNodeState BzBTNodeState;
|
||||
|
||||
typedef struct BzBTState {
|
||||
const BzBTNode *root;
|
||||
BzBTNodeState *first;
|
||||
BzBTNodeState *last;
|
||||
BzBTNodeState *_first;
|
||||
BzBTNodeState *_last;
|
||||
|
||||
BzBTActionFn onSuccess;
|
||||
BzBTActionFn onFailure;
|
||||
BzBTActionFn onError;
|
||||
|
||||
BzObjectPool *nodeStatePool;
|
||||
void *userData;
|
||||
@@ -91,6 +95,8 @@ BzBTNode *bzBTNodeNext(const BzBTNode *node);
|
||||
const BzBTNodeState *bzBTNodeStateNext(const BzBTNodeState *state);
|
||||
bool bzBTNodeMatchesState(const BzBTNode *node, const BzBTNodeState *state);
|
||||
|
||||
BzBTNode *bzBTCompStateGetRunningChild(const BzBTNodeState *state);
|
||||
|
||||
i32 bzBTRepeatStateGetIter(const BzBTNodeState *state);
|
||||
f32 bzBTDelayStateGetElapsed(const BzBTNodeState *state);
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ BzObjectPool *nodeStatePool = NULL;
|
||||
BzBTNode *printBT = NULL;
|
||||
BzBTState agentState;
|
||||
|
||||
BzBTStatus printAction(void *data) {
|
||||
BzBTStatus printAction(void *data, f32 dt) {
|
||||
bzLogInfo("Hello, world!");
|
||||
return BZ_BT_SUCCESS;
|
||||
}
|
||||
@@ -48,7 +48,7 @@ void deinit(int *game) {
|
||||
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);
|
||||
BzBTNodeType type = bzBTGetNodeType(node);
|
||||
char extraInfo[128];
|
||||
@@ -109,7 +109,7 @@ void igRenderBTNode(const BzBTNode *node, const BzBTNodeState *state, bool sameL
|
||||
|
||||
while (child) {
|
||||
if (hasSingleChild) igSameLine(0, 0);
|
||||
igRenderBTNode(child, state, hasSingleChild, depth);
|
||||
igVisualizeBTState(child, state, hasSingleChild, depth);
|
||||
child = bzBTNodeNext(child);
|
||||
}
|
||||
}
|
||||
@@ -117,7 +117,7 @@ void igRenderBTNode(const BzBTNode *node, const BzBTNodeState *state, bool sameL
|
||||
void igRenderBT(BzBTState *state) {
|
||||
const BzBTNode *root = state->root;
|
||||
if (igBegin("BehaviourTree", NULL, 0)) {
|
||||
igRenderBTNode(root, state->first, false, 0);
|
||||
igVisualizeBTState(root, state->_first, false, 0);
|
||||
}
|
||||
igEnd();
|
||||
}
|
||||
|
||||
200
game/ai_actions.c
Normal file
200
game/ai_actions.c
Normal 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
43
game/ai_actions.h
Normal 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
|
||||
@@ -1,7 +1,6 @@
|
||||
#include "components.h"
|
||||
|
||||
#include "unit_ai.h"
|
||||
#include "unit_actions.h"
|
||||
#include "ai_actions.h"
|
||||
|
||||
ECS_TAG_DECLARE(GameEntity);
|
||||
|
||||
@@ -26,8 +25,8 @@ ECS_COMPONENT_DECLARE(Easing);
|
||||
|
||||
ECS_COMPONENT_DECLARE(Arms);
|
||||
ECS_COMPONENT_DECLARE(Arm);
|
||||
ECS_COMPONENT_DECLARE(UnitAI);
|
||||
ECS_COMPONENT_DECLARE(UnitAction);
|
||||
ECS_COMPONENT_DECLARE(BzBTState);
|
||||
ECS_COMPONENT_DECLARE(AIBlackboard);
|
||||
|
||||
ECS_TAG_DECLARE(Selectable);
|
||||
ECS_TAG_DECLARE(Selected);
|
||||
@@ -65,8 +64,8 @@ void initComponentIDs(ecs_world_t *ecs) {
|
||||
|
||||
ECS_COMPONENT_DEFINE(ecs, Arms);
|
||||
ECS_COMPONENT_DEFINE(ecs, Arm);
|
||||
ECS_COMPONENT_DEFINE(ecs, UnitAI);
|
||||
ECS_COMPONENT_DEFINE(ecs, UnitAction);
|
||||
ECS_COMPONENT_DEFINE(ecs, BzBTState);
|
||||
ECS_COMPONENT_DEFINE(ecs, AIBlackboard);
|
||||
|
||||
ECS_TAG_DEFINE(ecs, Selectable);
|
||||
ECS_TAG_DEFINE(ecs, Selected);
|
||||
@@ -191,14 +190,90 @@ void igArm(ecs_world_t *ecs,
|
||||
|
||||
}
|
||||
|
||||
void igUnitAction(ecs_world_t *ecs,
|
||||
ecs_entity_t entity, ecs_entity_t comp) {
|
||||
void igVisualizeBTState(const BzBTNode *node, const BzBTNodeState *state,
|
||||
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,
|
||||
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,
|
||||
ecs_entity_t entity, ecs_entity_t comp) {
|
||||
|
||||
|
||||
@@ -159,8 +159,8 @@ typedef struct Arm {
|
||||
} Arm;
|
||||
extern ECS_COMPONENT_DECLARE(Arm);
|
||||
|
||||
extern ECS_COMPONENT_DECLARE(UnitAction);
|
||||
extern ECS_COMPONENT_DECLARE(UnitAI);
|
||||
extern ECS_COMPONENT_DECLARE(BzBTState);
|
||||
extern ECS_COMPONENT_DECLARE(AIBlackboard);
|
||||
|
||||
extern ECS_TAG_DECLARE(Selectable);
|
||||
extern ECS_TAG_DECLARE(Selected);
|
||||
@@ -235,10 +235,13 @@ void igArms(ecs_world_t *ecs,
|
||||
void igArm(ecs_world_t *ecs,
|
||||
ecs_entity_t entity, ecs_entity_t comp);
|
||||
|
||||
void igUnitAction(ecs_world_t *ecs,
|
||||
ecs_entity_t entity, ecs_entity_t comp);
|
||||
void igUnitAI(ecs_world_t *ecs,
|
||||
ecs_entity_t entity, ecs_entity_t comp);
|
||||
void igVisualizeBTState(const BzBTNode *node, const BzBTNodeState *state,
|
||||
bool isActive, bool sameLine, i32 depth);
|
||||
void igBzBTState(ecs_world_t *ecs,
|
||||
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,
|
||||
ecs_entity_t entity, ecs_entity_t comp);
|
||||
void igUnit(ecs_world_t *ecs,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include "entity_factory.h"
|
||||
#include "unit_actions.h"
|
||||
|
||||
#include "ai_actions.h"
|
||||
|
||||
ecs_entity_t entityCreateEmpty() {
|
||||
ecs_entity_t e = ecs_new_id(ECS);
|
||||
@@ -35,7 +36,11 @@ ecs_entity_t entityCreateWorker(const Position position, Game *game) {
|
||||
.curFrame = 0,
|
||||
.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_set(ECS, e, Unit, {
|
||||
.acceleration = 80.0f,
|
||||
|
||||
@@ -37,9 +37,14 @@ typedef struct Game {
|
||||
i64 pop;
|
||||
} resources;
|
||||
BzStackAlloc stackAlloc;
|
||||
struct {
|
||||
BzBTNode *workerHarvest;
|
||||
BzBTNode *moveTo;
|
||||
} BTs;
|
||||
struct {
|
||||
BzObjectPool *pathData;
|
||||
BzObjectPool *actions;
|
||||
BzObjectPool *btNode;
|
||||
BzObjectPool *btNodeState;
|
||||
} pools;
|
||||
struct {
|
||||
bool drawPath;
|
||||
|
||||
90
game/main.c
90
game/main.c
@@ -12,8 +12,6 @@
|
||||
#include "map_layers.h"
|
||||
#include "buildings.h"
|
||||
#include "ui_widgets.h"
|
||||
#include "unit_ai.h"
|
||||
#include "unit_actions.h"
|
||||
|
||||
#include "pathfinding.h"
|
||||
#include "sounds.h"
|
||||
@@ -215,10 +213,74 @@ bool init(void *userData) {
|
||||
.objectSize = sizeof(PathData),
|
||||
.objectsPerPage = 512
|
||||
});
|
||||
game->pools.actions = bzObjectPoolCreate(&(BzObjectPoolDesc) {
|
||||
.objectSize = sizeof(Action),
|
||||
game->pools.btNode = bzObjectPoolCreate(&(BzObjectPoolDesc) {
|
||||
.objectSize = bzBTGetNodeSize(),
|
||||
.objectsPerPage = 64
|
||||
});
|
||||
game->pools.btNodeState = bzObjectPoolCreate(&(BzObjectPoolDesc) {
|
||||
.objectSize = bzBTGetNodeStateSize(),
|
||||
.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;
|
||||
@@ -273,7 +335,8 @@ void deinit(void *userData) {
|
||||
|
||||
bzStackAllocDestroy(&game->stackAlloc);
|
||||
bzObjectPoolDestroy(game->pools.pathData);
|
||||
bzObjectPoolDestroy(game->pools.actions);
|
||||
bzObjectPoolDestroy(game->pools.btNode);
|
||||
bzObjectPoolDestroy(game->pools.btNodeState);
|
||||
|
||||
bzArrayDestroy(game->drawData);
|
||||
|
||||
@@ -660,6 +723,14 @@ void igInspectWindow(ecs_entity_t entity, bool *open) {
|
||||
igTagCheckbox("Workable", ECS, entity, Workable);
|
||||
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("Owner", entity, ecs_id(Owner), igOwner);
|
||||
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("Arms", entity, ecs_id(Arms), igArms);
|
||||
igInspectComp("Arm", entity, ecs_id(Arm), igArm);
|
||||
igInspectComp("UnitAction", entity, ecs_id(UnitAction), igUnitAction);
|
||||
igInspectComp("UnitAI", entity, ecs_id(UnitAI), igUnitAI);
|
||||
igInspectComp("BzBTState", entity, ecs_id(BzBTState), igBzBTState);
|
||||
igInspectComp("AIBlackboard", entity, ecs_id(AIBlackboard), igAIBlackboard);
|
||||
igInspectComp("Worker", entity, ecs_id(Worker), igWorker);
|
||||
igInspectComp("Unit", entity, ecs_id(Unit), igUnit);
|
||||
}
|
||||
@@ -691,8 +762,9 @@ void imguiRender(float dt, void *userData) {
|
||||
|
||||
igSetNextWindowSize((ImVec2){300, 400}, ImGuiCond_FirstUseEver);
|
||||
igBegin("Debug Menu", NULL, 0);
|
||||
igText("PathData pool available: %llu", bzObjectPoolGetNumFree(game->pools.pathData));
|
||||
igText("Action pool available: %llu", bzObjectPoolGetNumFree(game->pools.actions));
|
||||
igText("PathData pool available: %llu", bzObjectPoolGetNumFree(game->pools.pathData));
|
||||
igText("BTNode pool available: %llu", bzObjectPoolGetNumFree(game->pools.btNode));
|
||||
igText("BTNodeState pool available: %llu", bzObjectPoolGetNumFree(game->pools.btNodeState));
|
||||
const char *inputState = "NONE";
|
||||
switch (input->state) {
|
||||
case INPUT_NONE:
|
||||
|
||||
@@ -8,9 +8,6 @@
|
||||
#include "game_state.h"
|
||||
#include "map_layers.h"
|
||||
|
||||
#include "unit_ai.h"
|
||||
#include "unit_actions.h"
|
||||
|
||||
bool initGameObjectsLayer(BzTileMap *map, BzTileObjectGroup *objectGroup) {
|
||||
Game *game = ecs_singleton_get_mut(ECS, Game);
|
||||
for (i32 i = 0; i < objectGroup->objectCount; i++) {
|
||||
|
||||
@@ -1,41 +1,47 @@
|
||||
#include "systems.h"
|
||||
|
||||
#include "../game_state.h"
|
||||
#include "../ai_actions.h"
|
||||
|
||||
#include "../unit_ai.h"
|
||||
#include "../unit_actions.h"
|
||||
|
||||
void handleUnitActionsSystem(ecs_iter_t *it) {
|
||||
Game *game = ecs_singleton_get_mut(ECS, Game);
|
||||
UnitAction *action = ecs_field(it, UnitAction, 1);
|
||||
void updateAISystem(ecs_iter_t *it) {
|
||||
Game *game = ecs_singleton_get_mut(ECS, Game);
|
||||
BzBTState *state = ecs_field(it, BzBTState, 1);
|
||||
|
||||
f32 dt = GetFrameTime();
|
||||
|
||||
for (i32 i = 0; i < it->count; 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) {
|
||||
Game *game = ecs_singleton_get_mut(ECS, Game);
|
||||
UnitAI *unitAI = ecs_field(it, UnitAI, 1);
|
||||
UnitAction *action = ecs_field(it, UnitAction, 2);
|
||||
void setAIBehaviour(ecs_entity_t entity, const BzBTNode *root,
|
||||
const AIBlackboard *blackboard) {
|
||||
Game *game = ecs_singleton_get_mut(ECS, Game);
|
||||
BZ_ASSERT(ecs_has(ECS, entity, BzBTState));
|
||||
BZ_ASSERT(!blackboard || ecs_has(ECS, entity, AIBlackboard));
|
||||
|
||||
for (i32 i = 0; i < it->count; i++) {
|
||||
ecs_entity_t entity = it->entities[i];
|
||||
|
||||
Action *firstAction = action[i].first;
|
||||
unitAI[i].action = firstAction;
|
||||
|
||||
updateUnitAI(entity, &unitAI[i], game);
|
||||
if (blackboard) {
|
||||
AIBlackboard *b = ecs_get_mut(ECS, entity, AIBlackboard);
|
||||
*b = *blackboard;
|
||||
if (b->proximity < 2.0f)
|
||||
b->proximity = 2.0f;
|
||||
}
|
||||
}
|
||||
|
||||
void updateUnitActionsSystem(ecs_iter_t *it) {
|
||||
Game *game = ecs_singleton_get_mut(ECS, Game);
|
||||
UnitAction *action = ecs_field(it, UnitAction, 1);
|
||||
|
||||
for (i32 i = 0; i < it->count; i++) {
|
||||
ecs_entity_t entity = it->entities[i];
|
||||
updateAction(&action[i], game);
|
||||
}
|
||||
BzBTState *state = ecs_get_mut(ECS, entity, BzBTState);
|
||||
bzBTDestroyState(state);
|
||||
*state = bzBTCreateState(&(BzBTStateDesc) {
|
||||
.root = root,
|
||||
.pool = game->pools.btNodeState
|
||||
});
|
||||
}
|
||||
|
||||
@@ -4,8 +4,6 @@
|
||||
#include "../input.h"
|
||||
#include "../buildings.h"
|
||||
#include "../pathfinding.h"
|
||||
#include "../unit_ai.h"
|
||||
#include "../unit_actions.h"
|
||||
|
||||
#include <rlImGui.h>
|
||||
#include <raymath.h>
|
||||
@@ -92,12 +90,22 @@ void inputUnitAction(Game *game, InputState *input) {
|
||||
for (i32 i = 0; i < it.count; i++) {
|
||||
const ecs_entity_t entity = it.entities[i];
|
||||
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) {
|
||||
.type = AI_WORKER_HARVEST,
|
||||
.as.workerHarvest.resource = RES_WOOD,
|
||||
.as.workerHarvest.target = taskEntity,
|
||||
.as.workerHarvest.targetPosition = target
|
||||
});
|
||||
*/
|
||||
//addAction(entity, game, &(const Action) {
|
||||
// .type = ACTION_MOVE_TO,
|
||||
// .as.moveTo.target = target,
|
||||
@@ -128,12 +136,18 @@ void inputUnitAction(Game *game, InputState *input) {
|
||||
while (ecs_iter_next(&it)) {
|
||||
for (i32 i = 0; i < it.count; i++) {
|
||||
const ecs_entity_t entity = it.entities[i];
|
||||
setAIBehaviour(entity, game->BTs.moveTo, &(AIBlackboard) {
|
||||
.moveToPos = target,
|
||||
.proximity = 6.0f,
|
||||
});
|
||||
/*
|
||||
clearActions(entity, game);
|
||||
addAction(entity, game, &(const Action) {
|
||||
.type = ACTION_MOVE_TO,
|
||||
.as.moveTo.target = target,
|
||||
.as.moveTo.proximityThreshold = 6.0f,
|
||||
});
|
||||
*/
|
||||
}
|
||||
}
|
||||
ecs_defer_end(ECS);
|
||||
|
||||
@@ -114,10 +114,7 @@ void setupSystems() {
|
||||
ECS_SYSTEM(ECS, entityFollowPath, EcsOnUpdate, Path);
|
||||
ECS_SYSTEM(ECS, entityUpdateArms, EcsOnUpdate, Position, Velocity, Rotation, Orientation, Arms);
|
||||
|
||||
ECS_SYSTEM(ECS, handleUnitActionsSystem, EcsOnUpdate, UnitAction);
|
||||
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, updateAISystem, EcsOnUpdate, BzBTState);
|
||||
|
||||
ECS_SYSTEM(ECS, updateAnimationState, EcsOnUpdate, Animation, TextureRegion);
|
||||
ECS_SYSTEM(ECS, updateAnimation, EcsOnUpdate, Animation, TextureRegion);
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#include <flecs.h>
|
||||
|
||||
#include "../components.h"
|
||||
#include "../ai_actions.h"
|
||||
|
||||
typedef struct Game Game;
|
||||
|
||||
@@ -19,22 +20,12 @@ bool entitySetPath(const ecs_entity_t entity, const Vector2 target, Game *game);
|
||||
|
||||
/*
|
||||
* 0: Game (singleton)
|
||||
* 1: UnitAction
|
||||
* 1: BzBTState
|
||||
*/
|
||||
void handleUnitActionsSystem(ecs_iter_t *it);
|
||||
void updateAISystem(ecs_iter_t *it);
|
||||
|
||||
/*
|
||||
* 0: Game (singleton)
|
||||
* 1: UnitAI
|
||||
* 2: UnitAction
|
||||
*/
|
||||
void updateUnitAISystem(ecs_iter_t *it);
|
||||
|
||||
/*
|
||||
* 0: Game (singleton)
|
||||
* 1: UnitAction
|
||||
*/
|
||||
void updateUnitActionsSystem(ecs_iter_t *it);
|
||||
void setAIBehaviour(ecs_entity_t entity, const BzBTNode *root,
|
||||
const AIBlackboard *blackboard);
|
||||
|
||||
|
||||
/**********************************
|
||||
|
||||
Reference in New Issue
Block a user