Add projectile attack skill

This commit is contained in:
Doyle Thai 2016-09-13 00:53:25 +10:00
parent 5b682ddcf6
commit 8ddac9c110
4 changed files with 334 additions and 135 deletions

View File

@ -55,6 +55,8 @@ void entity_updateAnim(Entity *const entity, const f32 dt)
case entitytype_hero:
case entitytype_mob:
case entitytype_npc:
case entitytype_weapon:
case entitytype_projectile:
char *frameName = anim->frameList[currEntityAnim->currFrame];
Rect texRect =
asset_getAtlasSubTex(anim->atlas, frameName);
@ -118,6 +120,7 @@ Entity *const entity_add(MemoryArena *const arena, World *const world,
entity.stats->entityIdToAttack = -1;
entity.stats->queuedAttack = entityattack_invalid;
entity.state = entitystate_idle;
entity.collidesWith[entitytype_mob] = TRUE;
break;
case entitytype_mob:
{
@ -130,8 +133,20 @@ Entity *const entity_add(MemoryArena *const arena, World *const world,
entity.stats->entityIdToAttack = -1;
entity.stats->queuedAttack = entityattack_invalid;
entity.state = entitystate_idle;
entity.collidesWith[entitytype_hero] = TRUE;
break;
}
case entitytype_projectile:
entity.stats = PLATFORM_MEM_ALLOC(arena, 1, EntityStats);
entity.stats->maxHealth = 100;
entity.stats->health = entity.stats->maxHealth;
entity.stats->actionRate = 100;
entity.stats->actionTimer = entity.stats->actionRate;
entity.stats->actionSpdMul = 100;
entity.stats->entityIdToAttack = -1;
entity.stats->queuedAttack = entityattack_invalid;
entity.state = entitystate_idle;
break;
default:
break;

View File

@ -272,6 +272,6 @@ void renderer_entity(Renderer *renderer, Rect camera, Entity *entity,
v2 posInCameraSpace = v2_sub(entity->pos, camera.pos);
// TODO(doyle): Scale temporarily
renderObject(renderer, posInCameraSpace, v2_scale(entity->renderSize, 2),
pivotPoint, rotate, color, entity->tex);
pivotPoint, entity->rotation + rotate, color, entity->tex);
}
}

View File

@ -267,6 +267,55 @@ INTERNAL void assetInit(GameState *state)
asset_addAnimation(assetManager, arena, "claudeEnergySword",
claudeAtlas, claudeEnergySword, numRects,
duration);
char *claudeAirSlash[7] = {"ClaudeSprite_Attack_AirSlash_01",
"ClaudeSprite_Attack_AirSlash_02",
"ClaudeSprite_Attack_AirSlash_03",
"ClaudeSprite_Attack_AirSlash_04",
"ClaudeSprite_Attack_AirSlash_05",
"ClaudeSprite_Attack_AirSlash_06",
"ClaudeSprite_Attack_AirSlash_07"};
numRects = ARRAY_COUNT(claudeAirSlash);
duration = 0.075f;
asset_addAnimation(assetManager, arena, "claudeAirSlash",
claudeAtlas, claudeAirSlash, numRects,
duration);
char *claudeAirSlashVfx[7] = {"ClaudeSprite_Attack_AirSlash_Vfx_01",
"ClaudeSprite_Attack_AirSlash_Vfx_02",
"ClaudeSprite_Attack_AirSlash_Vfx_03",
"ClaudeSprite_Attack_AirSlash_Vfx_04",
"ClaudeSprite_Attack_AirSlash_Vfx_05",
"ClaudeSprite_Attack_AirSlash_Vfx_06",
"ClaudeSprite_Attack_AirSlash_Vfx_07"};
numRects = ARRAY_COUNT(claudeAirSlashVfx);
duration = 0.075f;
asset_addAnimation(assetManager, arena, "claudeAirSlashVfx",
claudeAtlas, claudeAirSlashVfx, numRects,
duration);
char *claudeSword[1] = {
"ClaudeSprite_Sword_01",
};
numRects = ARRAY_COUNT(claudeSword);
duration = 0.4f;
asset_addAnimation(assetManager, arena, "claudeSword",
claudeAtlas, claudeSword, numRects,
duration);
char *claudeAttackSlashLeft[4] = {
"ClaudeSprite_Attack_Slash_Left_01",
"ClaudeSprite_Attack_Slash_Left_02",
"ClaudeSprite_Attack_Slash_Left_03",
"ClaudeSprite_Attack_Slash_Left_04",
};
numRects = ARRAY_COUNT(claudeAttackSlashLeft);
duration = 0.1f;
asset_addAnimation(assetManager, arena, "claudeAttackSlashLeft",
claudeAtlas, claudeAttackSlashLeft, numRects,
duration);
}
else
{
@ -473,6 +522,17 @@ INTERNAL void entityInit(GameState *state, v2 windowSize)
Entity *hero =
entity_add(arena, world, pos, size, type, dir, tex, collides);
#if 0
Entity *heroWeapon =
entity_add(arena, world, pos, V2(0, 0), entitytype_weapon,
hero->direction, hero->tex, FALSE);
heroWeapon->rotation = -90.0f;
heroWeapon->invisible = TRUE;
entity_addAnim(assetManager, heroWeapon, "claudeSword");
hero->stats->weapon = heroWeapon;
#endif
hero->numAudioRenderers = 4;
hero->audioRenderer =
PLATFORM_MEM_ALLOC(arena, hero->numAudioRenderers, AudioRenderer);
@ -489,6 +549,7 @@ INTERNAL void entityInit(GameState *state, v2 windowSize)
entity_addAnim(assetManager, hero, "claudeBattleIdle");
entity_addAnim(assetManager, hero, "claudeAttack");
entity_addAnim(assetManager, hero, "claudeEnergySword");
entity_addAnim(assetManager, hero, "claudeAirSlash");
entity_setActiveAnim(hero, "claudeIdle");
/* Create a NPC */
@ -992,7 +1053,8 @@ typedef struct AttackSpec
i32 damage;
} AttackSpec;
INTERNAL void beginAttack(EventQueue *eventQueue, World *world,
INTERNAL void beginAttack(AssetManager *assetManager, MemoryArena *arena,
EventQueue *eventQueue, World *world,
Entity *attacker)
{
#ifdef DENGINE_DEBUG
@ -1006,11 +1068,12 @@ INTERNAL void beginAttack(EventQueue *eventQueue, World *world,
case entityattack_tackle:
{
entity_setActiveAnim(attacker, "claudeAttack");
EntityAnim attackAnim = attacker->animList[attacker->currAnimId];
f32 busyDuration = attackAnim.anim->frameDuration *
CAST(f32) attackAnim.anim->numFrames;
attacker->stats->busyDuration = busyDuration;
if (attacker->stats->weapon)
{
attacker->stats->weapon->invisible = FALSE;
entity_setActiveAnim(attacker->stats->weapon,
"claudeAttackSlashLeft");
}
if (attacker->direction == direction_east)
attacker->dPos.x += (1.0f * METERS_TO_PIXEL);
else
@ -1021,10 +1084,22 @@ INTERNAL void beginAttack(EventQueue *eventQueue, World *world,
case entityattack_energySword:
{
entity_setActiveAnim(attacker, "claudeEnergySword");
EntityAnim attackAnim = attacker->animList[attacker->currAnimId];
f32 busyDuration = attackAnim.anim->frameDuration *
CAST(f32) attackAnim.anim->numFrames;
attacker->stats->busyDuration = busyDuration;
break;
}
case entityattack_airSlash:
{
entity_setActiveAnim(attacker, "claudeAirSlash");
Entity *projectile = entity_add(
arena, world, attacker->pos, V2(20, 20), entitytype_projectile,
attacker->direction, attacker->tex, TRUE);
projectile->collidesWith[entitytype_hero] = FALSE;
projectile->collidesWith[entitytype_mob] = TRUE;
projectile->stats->entityIdToAttack = attacker->stats->entityIdToAttack;
entity_addAnim(assetManager, projectile, "claudeAirSlashVfx");
entity_setActiveAnim(projectile, "claudeAirSlashVfx");
break;
}
@ -1034,6 +1109,11 @@ INTERNAL void beginAttack(EventQueue *eventQueue, World *world,
#endif
break;
}
EntityAnim attackAnim = attacker->animList[attacker->currAnimId];
f32 busyDuration =
attackAnim.anim->frameDuration * CAST(f32) attackAnim.anim->numFrames;
attacker->stats->busyDuration = busyDuration;
}
// TODO(doyle): MemArena here is temporary until we incorporate AttackSpec to
@ -1050,6 +1130,12 @@ INTERNAL void endAttack(MemoryArena *arena, EventQueue *eventQueue,
{
case entityattack_tackle:
// TODO(doyle): Move animation offsets out and into animation type
if (attacker->stats->weapon)
{
attacker->stats->weapon->invisible = TRUE;
}
if (attacker->direction == direction_east)
attacker->dPos.x -= (1.0f * METERS_TO_PIXEL);
else
@ -1057,9 +1143,11 @@ INTERNAL void endAttack(MemoryArena *arena, EventQueue *eventQueue,
break;
case entityattack_airSlash:
break;
case entityattack_energySword:
attacker->stats->health += 80;
AttackSpec *attackSpec = PLATFORM_MEM_ALLOC(arena, 1, AttackSpec);
attackSpec->attacker = attacker;
attackSpec->defender = attacker;
@ -1110,7 +1198,7 @@ INTERNAL void endAttack(MemoryArena *arena, EventQueue *eventQueue,
if (!noMoreValidTargets)
{
i32 damage = 50;
i32 damage = 25;
AttackSpec *attackSpec = PLATFORM_MEM_ALLOC(arena, 1, AttackSpec);
attackSpec->attacker = attacker;
@ -1122,14 +1210,14 @@ INTERNAL void endAttack(MemoryArena *arena, EventQueue *eventQueue,
if (attacker->type == entitytype_hero)
{
defender->stats->health -= damage;
if (defender->stats->health <= 0.0f) defender->stats->health = 10.0f;
}
else if (attacker->type == entitytype_mob)
{
defender->stats->health -= damage * 0.25f;
if (defender->stats->health <= 0.0f) defender->stats->health = 10.0f;
}
if (defender->stats->health <= 0.0f) defender->stats->health = 10.0f;
if (defender->stats->health <= 0)
{
entityStateSwitch(eventQueue, world, defender, entitystate_dead);
@ -1171,7 +1259,16 @@ INTERNAL enum EntityAttack selectBestAttack(Entity *entity)
}
else
{
return entityattack_tackle;
enum EntityAttack attack = entityattack_tackle;;
if (entity->type == entitytype_hero)
{
b32 choice = rand() % 2;
attack =
(choice == TRUE) ? entityattack_airSlash : entityattack_tackle;
//attack = entityattack_tackle;
}
return attack;
}
}
@ -1204,6 +1301,110 @@ typedef struct BattleState
GLOBAL_VAR BattleState battleState = {0};
INTERNAL b32 checkCollision(Entity *a, Entity *b)
{
b32 result = FALSE;
if (a->collides && b->collides && a->collidesWith[b->type])
{
Rect aRect = {a->pos, a->hitboxSize};
v2 aTopLeftP =
getPosRelativeToRect(aRect, V2(0, 0), rectbaseline_topLeft);
v2 aTopRightP =
getPosRelativeToRect(aRect, V2(0, 0), rectbaseline_topRight);
v2 aBottomLeftP =
getPosRelativeToRect(aRect, V2(0, 0), rectbaseline_bottomLeft);
v2 aBottomRightP =
getPosRelativeToRect(aRect, V2(0, 0), rectbaseline_bottomRight);
Rect bRect = {b->pos, b->hitboxSize};
if (math_pointInRect(bRect, aTopLeftP) ||
math_pointInRect(bRect, aTopRightP) ||
math_pointInRect(bRect, aBottomLeftP) ||
math_pointInRect(bRect, aBottomRightP))
{
result = TRUE;
return result;
}
}
return result;
}
INTERNAL b32 moveEntityAndReturnCollision(World *world, Entity *entity,
v2 ddPos, f32 speedInMs, f32 dt)
{
/*
**************************
* Calculate Hero Speed
**************************
*/
// NOTE(doyle): Clipping threshold for snapping velocity to 0
f32 epsilon = 15.0f;
v2 epsilonDpos = v2_sub(V2(epsilon, epsilon),
V2(ABS(entity->dPos.x), ABS(entity->dPos.y)));
if (epsilonDpos.x >= 0.0f && epsilonDpos.y >= 0.0f)
entity->dPos = V2(0.0f, 0.0f);
f32 speedInPixels = speedInMs * METERS_TO_PIXEL;
ddPos = v2_scale(ddPos, speedInPixels);
// TODO(doyle): Counteracting force on player's acceleration is
// arbitrary
ddPos = v2_sub(ddPos, v2_scale(entity->dPos, 5.5f));
/*
NOTE(doyle): Calculate new position from acceleration with old
velocity
new Position = (a/2) * (t^2) + (v*t) + p,
acceleration = (a/2) * (t^2)
old velocity = (v*t)
*/
v2 ddPosNew = v2_scale(v2_scale(ddPos, 0.5f), SQUARED(dt));
v2 dPos = v2_scale(entity->dPos, dt);
v2 oldP = entity->pos;
v2 newP = v2_add(v2_add(ddPosNew, dPos), entity->pos);
entity->pos = newP;
/*
**************************
* Collision Detection
**************************
*/
// TODO(doyle): Only check collision for entities within small
// bounding box of the hero
b32 entityCollided = FALSE;
if (entity->collides)
{
for (i32 i = 0; i < world->maxEntities; i++)
{
Entity *collider = &world->entities[i];
if (collider->state == entitystate_dead) continue;
if (collider->id == entity->id) continue;
entityCollided = checkCollision(entity, collider);
if (entityCollided) break;
}
}
if (entityCollided)
{
entity->dPos = V2(0.0f, 0.0f);
entity->pos = oldP;
}
else
{
// f'(t) = curr velocity = a*t + v, where v is old velocity
entity->dPos = v2_add(entity->dPos, v2_scale(ddPos, dt));
entity->pos = newP;
}
return entityCollided;
}
void worldTraveller_gameUpdateAndRender(GameState *state, f32 dt)
{
// TODO(doyle): use proper dt limiting techniques
@ -1325,6 +1526,7 @@ void worldTraveller_gameUpdateAndRender(GameState *state, f32 dt)
}
if (entity->state == entitystate_dead) continue;
if (entity->invisible == TRUE) continue;
if (entity->type == entitytype_soundscape)
{
@ -1453,14 +1655,14 @@ void worldTraveller_gameUpdateAndRender(GameState *state, f32 dt)
}
}
}
else if (entity->type == entitytype_hero)
{
/*
**************************
* Calculate Hero Movement
**************************
*/
if (entity->type == entitytype_hero)
{
/*
Equations of Motion
f(t) = position m
@ -1514,111 +1716,71 @@ void worldTraveller_gameUpdateAndRender(GameState *state, f32 dt)
}
}
/*
**************************
* Calculate Hero Speed
**************************
*/
// NOTE(doyle): Clipping threshold for snapping velocity to 0
f32 epsilon = 15.0f;
v2 epsilonDpos = v2_sub(V2(epsilon, epsilon),
V2(ABS(hero->dPos.x), ABS(hero->dPos.y)));
char *currAnimName = hero->animList[hero->currAnimId].anim->key;
if (epsilonDpos.x >= 0.0f && epsilonDpos.y >= 0.0f)
{
hero->dPos = V2(0.0f, 0.0f);
if (common_strcmp(currAnimName, "claudeRun") == 0)
{
entity_setActiveAnim(hero, "claudeIdle");
}
}
else if (common_strcmp(currAnimName, "claudeIdle") == 0)
{
entity_setActiveAnim(hero, "claudeRun");
}
f32 heroSpeed = 6.2f * METERS_TO_PIXEL;
f32 heroSpeed = 6.2f;
if (getKeyStatus(&state->input.keys[keycode_leftShift],
readkeytype_repeat, KEY_DELAY_NONE, dt))
{
// TODO: Context sensitive command separation
state->uiState.keyMod = keycode_leftShift;
heroSpeed = CAST(f32)(22.0f * 10.0f * METERS_TO_PIXEL);
heroSpeed *= 10.0f;
}
else
{
state->uiState.keyMod = keycode_null;
}
ddPos = v2_scale(ddPos, heroSpeed);
// TODO(doyle): Counteracting force on player's acceleration is
// arbitrary
ddPos = v2_sub(ddPos, v2_scale(hero->dPos, 5.5f));
moveEntityAndReturnCollision(world, hero, ddPos, heroSpeed, dt);
/*
NOTE(doyle): Calculate new position from acceleration with old
velocity
new Position = (a/2) * (t^2) + (v*t) + p,
acceleration = (a/2) * (t^2)
old velocity = (v*t)
*/
v2 ddPosNew = v2_scale(v2_scale(ddPos, 0.5f), SQUARED(dt));
v2 dPos = v2_scale(hero->dPos, dt);
v2 newHeroP = v2_add(v2_add(ddPosNew, dPos), hero->pos);
/*
**************************
* Collision Detection
**************************
*/
// TODO(doyle): Only check collision for entities within small
// bounding box of the hero
b32 heroCollided = FALSE;
if (hero->collides == TRUE)
char *currAnimName = hero->animList[hero->currAnimId].anim->key;
if (ABS(entity->dPos.x) > 0.0f || ABS(entity->dPos.y) > 0.0f)
{
for (i32 i = 0; i < world->maxEntities; i++)
if (common_strcmp(currAnimName, "claudeIdle") == 0)
{
Entity collider = world->entities[i];
if (collider.state == entitystate_dead) continue;
if (collider.id == world->heroId) continue;
if (collider.collides)
{
Rect heroRect = {newHeroP, hero->hitboxSize};
v2 heroTopLeftP = getPosRelativeToRect(
heroRect, V2(0, 0), rectbaseline_topLeft);
v2 heroTopRightP = getPosRelativeToRect(
heroRect, V2(0, 0), rectbaseline_topRight);
v2 heroBottomLeftP = getPosRelativeToRect(
heroRect, V2(0, 0), rectbaseline_bottomLeft);
v2 heroBottomRightP = getPosRelativeToRect(
heroRect, V2(0, 0), rectbaseline_bottomRight);
Rect colliderRect = {collider.pos, collider.hitboxSize};
if (math_pointInRect(colliderRect, heroTopLeftP) ||
math_pointInRect(colliderRect, heroTopRightP) ||
math_pointInRect(colliderRect, heroBottomLeftP) ||
math_pointInRect(colliderRect, heroBottomRightP))
{
heroCollided = TRUE;
break;
entity_setActiveAnim(hero, "claudeRun");
}
}
}
}
if (heroCollided)
{
hero->dPos = V2(0.0f, 0.0f);
}
else
{
// f'(t) = curr velocity = a*t + v, where v is old velocity
hero->dPos = v2_add(hero->dPos, v2_scale(ddPos, dt));
hero->pos = newHeroP;
if (common_strcmp(currAnimName, "claudeRun") == 0)
{
entity_setActiveAnim(hero, "claudeIdle");
}
}
}
else if (entity->type == entitytype_projectile)
{
Entity *projectile = entity;
i32 targetIndex =
entity_getIndex(world, projectile->stats->entityIdToAttack);
Entity *target = &world->entities[targetIndex];
v2 ddPos = V2(0, 0);
f32 projectileSpeed = 10.0f;
v2 difference = v2_sub(projectile->pos, target->pos);
f32 longSide = (ABS(difference.x) > ABS(difference.y))
? difference.x
: difference.y;
ddPos.x = (difference.x / longSide);
ddPos.y = (difference.y / longSide);
if (ddPos.x != 0.0f && ddPos.y != 0.0f)
{
// NOTE(doyle): Cheese it and pre-compute the vector for
// diagonal using pythagoras theorem on a unit triangle 1^2
// + 1^2 = c^2
ddPos = v2_scale(ddPos, 0.70710678118f);
}
if (moveEntityAndReturnCollision(world, projectile, ddPos,
projectileSpeed, dt))
{
// TODO(doyle): Unify concept of dead entity for mobs and
// projectiles
projectile->state = entitystate_dead;
registerEvent(&eventQueue, eventtype_entity_died, projectile);
}
}
@ -1630,6 +1792,12 @@ void worldTraveller_gameUpdateAndRender(GameState *state, f32 dt)
if (entity->type == entitytype_mob || entity->type == entitytype_hero)
{
EntityStats *stats = entity->stats;
if (stats->weapon)
{
stats->weapon->pos = entity->pos;
}
if (entity->state == entitystate_battle)
{
if (stats->actionTimer > 0)
@ -1645,7 +1813,8 @@ void worldTraveller_gameUpdateAndRender(GameState *state, f32 dt)
}
/* Launch up attack animation */
beginAttack(&eventQueue, world, entity);
beginAttack(assetManager, &state->arena, &eventQueue, world,
entity);
}
}
else if (entity->state == entitystate_attack)
@ -1676,7 +1845,8 @@ void worldTraveller_gameUpdateAndRender(GameState *state, f32 dt)
{
entity_updateAnim(entity, dt);
/* Calculate region to render */
renderer_entity(renderer, camera, entity, V2(0, 0), 0,
renderer_entity(renderer, camera, entity,
v2_scale(entity->renderSize, 0.5f), 0,
V4(1, 1, 1, 1));
}
}
@ -1705,25 +1875,11 @@ void worldTraveller_gameUpdateAndRender(GameState *state, f32 dt)
i32 freeAudioIndex = entityGetFreeAudioRendererIndex(attacker);
if (freeAudioIndex != -1)
{
char *attackSfx[19] = {"Attack_1",
"Attack_2",
"Attack_3",
"Attack_4",
"Attack_5",
"Attack_6",
"Attack_7",
"Attack_hit_it",
"Attack_take_that",
"Attack_hya",
"Attack_air_slash",
"Attack_Dragon_howl",
"Attack_burn",
"Attack_burst_knuckle",
"Attack_mirror_slice",
"Attack_shooting_star",
"Attack_sword_bomber",
"Attack_tear_into_pieces",
"Attack_twin_slash"};
char *attackSfx[11] = {
"Attack_1", "Attack_2", "Attack_3",
"Attack_4", "Attack_5", "Attack_6",
"Attack_7", "Attack_hit_it", "Attack_take_that",
"Attack_hya", "Attack_burn"};
i32 attackSfxIndex = rand() % ARRAY_COUNT(attackSfx);
audio_playVorbis(arena, audioManager,
@ -1763,9 +1919,21 @@ void worldTraveller_gameUpdateAndRender(GameState *state, f32 dt)
1);
}
}
else if (attacker->stats->queuedAttack == entityattack_airSlash)
{
i32 freeAudioIndex = entityGetFreeAudioRendererIndex(attacker);
if (freeAudioIndex != -1)
{
audio_playVorbis(
arena, audioManager,
&attacker->audioRenderer[freeAudioIndex],
asset_getVorbis(assetManager, "Attack_air_slash"),
1);
}
}
else
{
ASSERT(INVALID_CODE_PATH);
//ASSERT(INVALID_CODE_PATH);
}
attacker->stats->queuedAttack = entityattack_invalid;
@ -1820,7 +1988,6 @@ void worldTraveller_gameUpdateAndRender(GameState *state, f32 dt)
}
}
/*
****************************
* Update World From Results

View File

@ -11,6 +11,8 @@ typedef struct Texture Texture;
typedef struct Animation Animation;
typedef struct World World;
typedef struct Entity Entity;
enum Direction
{
direction_north,
@ -25,6 +27,8 @@ enum EntityType
{
entitytype_null,
entitytype_hero,
entitytype_weapon,
entitytype_projectile,
entitytype_npc,
entitytype_mob,
entitytype_tile,
@ -45,6 +49,7 @@ enum EntityState
enum EntityAttack
{
entityattack_tackle,
entityattack_airSlash,
entityattack_energySword,
entityattack_count,
entityattack_invalid,
@ -62,6 +67,8 @@ typedef struct EntityStats
f32 busyDuration;
i32 entityIdToAttack;
enum EntityAttack queuedAttack;
Entity *weapon;
} EntityStats;
typedef struct EntityAnim
@ -71,7 +78,7 @@ typedef struct EntityAnim
f32 currDuration;
} EntityAnim;
typedef struct Entity
struct Entity
{
i32 id;
@ -79,13 +86,23 @@ typedef struct Entity
v2 dPos; // Velocity
v2 hitboxSize;
v2 renderSize;
f32 rotation;
b32 invisible;
enum EntityState state;
enum EntityType type;
enum Direction direction;
Texture *tex;
// TODO(doyle): Two collision flags, we want certain entities to collide
// with certain types of entities only (i.e. projectile from hero to enemy,
// only collides with enemy). Having two flags is redundant, but! it does
// allow for early-exit in collision check if the entity doesn't collide at
// all
b32 collides;
enum EntityType collidesWith[entitytype_count];
EntityAnim animList[16];
i32 currAnimId;
@ -95,7 +112,7 @@ typedef struct Entity
// TODO(doyle): Audio mixing instead of multiple renderers
AudioRenderer *audioRenderer;
i32 numAudioRenderers;
} Entity;
};
void entity_setActiveAnim(Entity *const entity, const char *const animName);
void entity_updateAnim(Entity *const entity, const f32 dt);