Create notion of state switching for entities

Encapsulate the process required to switch an entity from state to state
using a state switching function. Add extra minor debug markers.
This commit is contained in:
Doyle Thai 2016-07-19 15:19:08 +10:00
parent 403999b566
commit 5fc58ca643
5 changed files with 190 additions and 79 deletions

View File

@ -44,12 +44,22 @@ void debug_pushString(char *formatString, void *data, char *dataType)
}
else if (common_strcmp(dataType, "char") == 0)
{
char *val = CAST(char *)data;
if (data)
{
char *val = CAST(char *) data;
snprintf(GLOBAL_debug.debugStrings[numDebugStrings],
ARRAY_COUNT(GLOBAL_debug.debugStrings[0]),
formatString, val);
snprintf(GLOBAL_debug.debugStrings[numDebugStrings],
ARRAY_COUNT(GLOBAL_debug.debugStrings[0]),
formatString, val);
}
else
{
snprintf(GLOBAL_debug.debugStrings[numDebugStrings],
ARRAY_COUNT(GLOBAL_debug.debugStrings[0]),
formatString);
}
}
else
{
ASSERT(INVALID_CODE_PATH);
@ -142,9 +152,9 @@ void debug_drawUi(GameState *state, f32 dt)
color);
}
for (i32 entityId = 0; entityId < world->maxEntities; entityId++)
for (i32 i = 0; i < world->maxEntities; i++)
{
Entity *const entity = &world->entities[entityId];
Entity *const entity = &world->entities[i];
/* Render debug markers on entities */
v4 color = V4(1, 1, 1, 1);
char *debugString = NULL;
@ -189,8 +199,8 @@ void debug_drawUi(GameState *state, f32 dt)
strPos.y -= GLOBAL_debug.stringLineGap;
char entityIDStr[32];
snprintf(entityIDStr, ARRAY_COUNT(entityIDStr), "ID: %4d/%d", entityId,
world->maxEntities);
snprintf(entityIDStr, ARRAY_COUNT(entityIDStr), "ID: %4d/%d", entity->id,
world->uniqueIdAccumulator-1);
renderer_string(&state->renderer, cameraBounds, font, entityIDStr,
strPos, 0, color);
@ -210,27 +220,48 @@ void debug_drawUi(GameState *state, f32 dt)
renderer_string(&state->renderer, cameraBounds, font,
entityTimer, strPos, 0, color);
}
strPos.y -= GLOBAL_debug.stringLineGap;
char *entityStateStr = debug_entitystate_string(entity->state);
renderer_string(&state->renderer, cameraBounds, font,
entityStateStr, strPos, 0, color);
}
}
/* Render debug info stack */
DEBUG_PUSH_STRING("Hero Pos: %06.2f, %06.2f", hero->pos, "v2");
DEBUG_PUSH_STRING("Hero dPos: %06.2f, %06.2f", hero->dPos, "v2");
DEBUG_PUSH_STRING("Hero Busy Duration: %05.3f", hero->stats->busyDuration, "f32");
DEBUG_PUSH_STRING("== Hero Properties == ");
DEBUG_PUSH_VAR("Hero Pos: %06.2f, %06.2f", hero->pos, "v2");
DEBUG_PUSH_VAR("Hero dPos: %06.2f, %06.2f", hero->dPos, "v2");
DEBUG_PUSH_VAR("Hero Busy Duration: %05.3f", hero->stats->busyDuration, "f32");
char *heroStateString = debug_entitystate_string(hero->state);
DEBUG_PUSH_STRING("Hero State: %s", *heroStateString, "char");
DEBUG_PUSH_VAR("Hero State: %s", *heroStateString, "char");
char *heroQueuedAttackStr =
debug_entityattack_string(hero->stats->queuedAttack);
DEBUG_PUSH_STRING("Hero QueuedAttack: %s", *heroQueuedAttackStr, "char");
DEBUG_PUSH_STRING("FreeEntityIndex: %d", world->freeEntityIndex, "i32");
DEBUG_PUSH_STRING("glDrawArray Calls: %d",
GLOBAL_debug.callCount[debugcallcount_drawArrays],
"i32");
DEBUG_PUSH_VAR("Hero QueuedAttack: %s", *heroQueuedAttackStr, "char");
DEBUG_PUSH_STRING("== State Properties == ");
DEBUG_PUSH_VAR("FreeEntityIndex: %d", world->freeEntityIndex, "i32");
DEBUG_PUSH_VAR("glDrawArray Calls: %d",
GLOBAL_debug.callCount[debugcallcount_drawArrays], "i32");
i32 debug_kbAllocated = GLOBAL_debug.totalMemoryAllocated / 1024;
DEBUG_PUSH_STRING("TotalMemoryAllocated: %dkb", debug_kbAllocated, "i32");
DEBUG_PUSH_VAR("TotalMemoryAllocated: %dkb", debug_kbAllocated, "i32");
DEBUG_PUSH_STRING("== EntityIDs in Battle List == ");
DEBUG_PUSH_VAR("NumEntitiesInBattle: %d", world->numEntitiesInBattle,
"i32");
if (world->numEntitiesInBattle > 0)
{
for (i32 i = 0; i < world->maxEntities; i++)
{
if (world->entityIdInBattle[i])
DEBUG_PUSH_VAR("Entity ID: %d", i, "i32");
}
}
else
{
DEBUG_PUSH_STRING("-none-");
}
debug_stringUpdateAndRender(&state->renderer, font, dt);
debug_clearCallCounter();
}

View File

@ -21,6 +21,7 @@ INTERNAL Entity *addEntity(World *world, v2 pos, v2 size, enum EntityType type,
#endif
Entity entity = {0};
entity.id = world->uniqueIdAccumulator++;
entity.pos = pos;
entity.hitboxSize = size;
entity.renderSize = size;
@ -215,12 +216,13 @@ void worldTraveller_gameInit(GameState *state, v2 windowSize)
World *const world = &state->world[i];
world->maxEntities = 16384;
world->entities = PLATFORM_MEM_ALLOC(world->maxEntities, Entity);
world->entitiesInBattleIds = PLATFORM_MEM_ALLOC(world->maxEntities, i32);
world->entityIdInBattle = PLATFORM_MEM_ALLOC(world->maxEntities, i32);
world->numEntitiesInBattle = 0;
world->texType = texlist_terrain;
world->bounds =
math_getRect(V2(0, 0), v2_scale(worldDimensionInTiles,
CAST(f32) state->tileSize));
world->uniqueIdAccumulator = 0;
TexAtlas *const atlas =
asset_getTextureAtlas(assetManager, world->texType);
@ -299,7 +301,9 @@ void worldTraveller_gameInit(GameState *state, v2 windowSize)
/* Populate mob animation references */
addAnim(assetManager, animlist_hero_idle, mob);
addAnim(assetManager, animlist_hero_walk, mob);
mob->currAnimId = animlist_hero_idle;
addAnim(assetManager, animlist_hero_battlePose, mob);
addAnim(assetManager, animlist_hero_tackle, mob);
hero->currAnimId = animlist_hero_idle;
}
INTERNAL inline void setActiveEntityAnim(Entity *entity,
@ -596,97 +600,168 @@ INTERNAL v4 createCameraBounds(World *world, v2 size)
return result;
}
#define ENTITY_IN_BATTLE TRUE
#define ENTITY_NOT_IN_BATTLE FALSE
INTERNAL inline void updateWorldBattleEntities(World *world, Entity *entity,
b32 isInBattle)
{
world->entityIdInBattle[entity->id] = isInBattle;
if (isInBattle)
world->numEntitiesInBattle++;
else
world->numEntitiesInBattle--;
}
INTERNAL void entityStateSwitch(World *world, Entity *entity,
enum EntityState newState)
{
if (entity->state == newState) return;
switch(entity->state)
{
case entitystate_idle:
switch (newState)
{
case entitystate_battle:
updateWorldBattleEntities(world, entity, ENTITY_IN_BATTLE);
case entitystate_attack:
case entitystate_dead:
break;
default:
ASSERT(INVALID_CODE_PATH);
}
break;
case entitystate_battle:
switch (newState)
{
case entitystate_idle:
updateWorldBattleEntities(world, entity, ENTITY_NOT_IN_BATTLE);
entity->stats->actionTimer = entity->stats->actionRate;
entity->stats->queuedAttack = entityattack_invalid;
setActiveEntityAnim(entity, animlist_hero_idle);
break;
case entitystate_attack:
case entitystate_dead:
break;
default:
ASSERT(INVALID_CODE_PATH);
}
case entitystate_attack:
switch (newState)
{
case entitystate_idle:
case entitystate_battle:
case entitystate_dead:
break;
default:
ASSERT(INVALID_CODE_PATH);
}
case entitystate_dead:
switch (newState)
{
case entitystate_idle:
case entitystate_battle:
case entitystate_attack:
break;
default:
ASSERT(INVALID_CODE_PATH);
}
}
entity->state = newState;
}
INTERNAL void updateEntityAndRender(Renderer *renderer, World *world, f32 dt)
{
for (i32 entityId = 0; entityId < world->freeEntityIndex; entityId++)
for (i32 i = 0; i < world->freeEntityIndex; i++)
{
Entity *const entity = &world->entities[entityId];
Entity *const entity = &world->entities[i];
Entity *hero = &world->entities[world->heroIndex];
if (entity->type == entitytype_mob)
switch(entity->type)
{
case entitytype_mob:
{
f32 distance = v2_magnitude(hero->pos, entity->pos);
// TODO(doyle): Currently calculated in pixels, how about meaningful
// game units?
f32 battleThreshold = 500.0f;
f32 distance = v2_magnitude(hero->pos, entity->pos);
enum EntityState newState = entitystate_invalid;
if (distance <= battleThreshold)
{
entity->state = entitystate_battle;
if (!world->entitiesInBattleIds[entityId])
{
world->entitiesInBattleIds[entityId] = TRUE;
world->numEntitiesInBattle++;
}
}
newState = entitystate_battle;
else
{
if (world->entitiesInBattleIds[entityId])
{
world->entitiesInBattleIds[entityId] = FALSE;
world->numEntitiesInBattle--;
}
entity->state = entitystate_idle;
entity->stats->actionTimer = entity->stats->actionRate;
entity->stats->queuedAttack = entityattack_invalid;
}
newState = entitystate_idle;
entityStateSwitch(world, entity, newState);
}
if ((entity->state == entitystate_battle ||
entity->state == entitystate_attack) &&
entity->type == entitytype_hero)
// NOTE(doyle): Allow fall through to entitytype_hero here
case entitytype_hero:
{
EntityStats *stats = entity->stats;
if (stats->health > 0)
if (entity->state == entitystate_battle ||
entity->state == entitystate_attack)
{
if (entity->state == entitystate_battle)
EntityStats *stats = entity->stats;
if (stats->health > 0)
{
if (stats->actionTimer > 0)
stats->actionTimer -= dt * stats->actionSpdMul;
if (stats->actionTimer < 0)
if (entity->state == entitystate_battle)
{
stats->actionTimer = 0;
if (stats->queuedAttack == entityattack_invalid)
stats->queuedAttack = entityattack_tackle;
if (stats->actionTimer > 0)
stats->actionTimer -= dt * stats->actionSpdMul;
beginAttack(entity);
if (stats->actionTimer < 0)
{
stats->actionTimer = 0;
if (stats->queuedAttack == entityattack_invalid)
stats->queuedAttack = entityattack_tackle;
beginAttack(entity);
}
}
else
{
stats->busyDuration -= dt;
if (stats->busyDuration <= 0)
endAttack(world, entity);
}
}
else
{
stats->busyDuration -= dt;
if (stats->busyDuration <= 0)
endAttack(world, entity);
// TODO(doyle): Generalise for all entities
hero->stats->entityIdToAttack = -1;
hero->state = entitystate_idle;
entity->state = entitystate_dead;
}
}
else
{
// TODO(doyle): Generalise for all entities
hero->stats->entityIdToAttack = -1;
hero->state = entitystate_idle;
entity->state = entitystate_dead;
}
break;
}
default:
break;
}
if (world->numEntitiesInBattle > 0)
{
if (hero->state == entitystate_idle)
{
hero->state = entitystate_battle;
world->entityIdInBattle[hero->id] = TRUE;
}
if (hero->stats->entityIdToAttack == -1)
hero->stats->entityIdToAttack = entityId;
hero->stats->entityIdToAttack = i;
}
else
{
if (hero->state == entitystate_battle)
{
hero->state = entitystate_idle;
hero->state = entitystate_idle;
world->entityIdInBattle[hero->id] = FALSE;
setActiveEntityAnim(hero, animlist_hero_idle);
}
hero->stats->entityIdToAttack = -1;
hero->stats->actionTimer = hero->stats->actionRate;
hero->stats->busyDuration = 0;
hero->stats->actionTimer = hero->stats->actionRate;
hero->stats->busyDuration = 0;
}
updateEntityAnim(entity, dt);

View File

@ -10,9 +10,6 @@
#include "WorldTraveller/WorldTraveller.h"
#define INVALID_CODE_PATH TRUE
#define DEBUG_PUSH_STRING(formatString, data, type) \
debug_pushString(formatString, CAST(void *)&data, type)
enum DebugCallCount
{
debugcallcount_drawArrays,
@ -101,7 +98,12 @@ inline char *debug_entityattack_string(i32 val)
void debug_init();
#define DEBUG_PUSH_STRING(string) debug_pushString(string, NULL, "char")
#define DEBUG_PUSH_VAR(formatString, data, type) \
debug_pushString(formatString, CAST(void *)&data, type)
void debug_pushString(char *formatString, void *data, char *dataType);
void debug_stringUpdateAndRender(Renderer *renderer, Font *font, f32 dt);
void debug_drawUi(GameState *state, f32 dt);

View File

@ -65,6 +65,8 @@ typedef struct EntityAnim_
typedef struct Entity
{
u32 id;
v2 pos; // Position
v2 dPos; // Velocity
v2 hitboxSize;

View File

@ -16,9 +16,9 @@ typedef struct World
{
Entity *entities;
i32 maxEntities;
b32 *entitiesInBattleIds;
b32 *entityIdInBattle;
i32 numEntitiesInBattle;
enum TexList texType;
v2 cameraPos; // In pixels
@ -26,6 +26,7 @@ typedef struct World
i32 heroIndex;
i32 freeEntityIndex;
u32 uniqueIdAccumulator;
} World;
typedef struct GameState