fp: Start decoupling action from animation
This commit is contained in:
parent
f847f5b0d0
commit
f60099e7a3
2
External/tely
vendored
2
External/tely
vendored
@ -1 +1 @@
|
|||||||
Subproject commit 5bba4cddbc9352be4083fb1ec02dfbab2de0c115
|
Subproject commit b45db1a82cf5fb7f32d298c71b7dc0ac806a6f5f
|
173
feely_pona.cpp
173
feely_pona.cpp
@ -214,40 +214,6 @@ void TELY_DLL_Init(void *user_data)
|
|||||||
game->test_audio = platform->func_load_audio(assets, DQN_STRING8("Test Audio"), DQN_STRING8("Data/Audio/Purrple Cat - Moonwinds.qoa"));
|
game->test_audio = platform->func_load_audio(assets, DQN_STRING8("Test Audio"), DQN_STRING8("Data/Audio/Purrple Cat - Moonwinds.qoa"));
|
||||||
}
|
}
|
||||||
|
|
||||||
void FP_Game_EntityChangeState(FP_GameEntity *entity, FP_GameEntityState state)
|
|
||||||
{
|
|
||||||
if (entity->state == state)
|
|
||||||
return;
|
|
||||||
|
|
||||||
entity->state = state;
|
|
||||||
entity->anim.frame = 0;
|
|
||||||
entity->anim.ticks = 0;
|
|
||||||
|
|
||||||
// decouple the state from the animation, e.g. the wall doesn't have an animation
|
|
||||||
uint16_t desired_sprite_anim_index = 0;
|
|
||||||
switch (state) {
|
|
||||||
case FP_GameEntityState_Nil: {
|
|
||||||
} break;
|
|
||||||
|
|
||||||
case FP_GameEntityState_Idle: {
|
|
||||||
desired_sprite_anim_index = TELY_Asset_GetSpriteAnim(entity->sprite_anims, DQN_STRING8("Idle"));
|
|
||||||
} break;
|
|
||||||
|
|
||||||
case FP_GameEntityState_Attack: {
|
|
||||||
desired_sprite_anim_index = TELY_Asset_GetSpriteAnim(entity->sprite_anims, DQN_STRING8("Attack A"));
|
|
||||||
} break;
|
|
||||||
|
|
||||||
case FP_GameEntityState_Run: {
|
|
||||||
desired_sprite_anim_index = TELY_Asset_GetSpriteAnim(entity->sprite_anims, DQN_STRING8("Running"));
|
|
||||||
} break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (entity->sprite_sheet && entity->sprite_anims.size) {
|
|
||||||
TELY_AssetSpriteAnimation const *sprite_anim = entity->sprite_anims.data + entity->anim.index;
|
|
||||||
entity->anim.index = desired_sprite_anim_index;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct AStarNode
|
struct AStarNode
|
||||||
{
|
{
|
||||||
Dqn_usize cost;
|
Dqn_usize cost;
|
||||||
@ -373,21 +339,6 @@ void FP_GameUpdate(TELY_Platform *platform, FP_Game *game, TELY_Renderer *render
|
|||||||
if (game->clicked_entity.id) {
|
if (game->clicked_entity.id) {
|
||||||
if (TELY_Platform_InputScanCodeIsPressed(input, TELY_PlatformInputScanCode_Delete))
|
if (TELY_Platform_InputScanCodeIsPressed(input, TELY_PlatformInputScanCode_Delete))
|
||||||
FP_Game_DeleteEntity(game, game->clicked_entity);
|
FP_Game_DeleteEntity(game, game->clicked_entity);
|
||||||
|
|
||||||
FP_GameEntity *player = FP_Game_GetEntity(game, game->clicked_entity);
|
|
||||||
if (player) {
|
|
||||||
if (TELY_Platform_InputScanCodeIsDown(input, TELY_PlatformInputScanCode_J)) {
|
|
||||||
FP_Game_EntityChangeState(player, FP_GameEntityState_Attack);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (player->state != FP_GameEntityState_Attack) {
|
|
||||||
if (dir_vector.x || dir_vector.y) {
|
|
||||||
FP_Game_EntityChangeState(player, FP_GameEntityState_Run);
|
|
||||||
} else {
|
|
||||||
FP_Game_EntityChangeState(player, FP_GameEntityState_Idle);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
game->camera.world_pos += dir_vector * 5.f;
|
game->camera.world_pos += dir_vector * 5.f;
|
||||||
}
|
}
|
||||||
@ -573,41 +524,107 @@ void FP_GameUpdate(TELY_Platform *platform, FP_Game *game, TELY_Renderer *render
|
|||||||
entity->local_hit_box_size = padded_bbox.size;
|
entity->local_hit_box_size = padded_bbox.size;
|
||||||
}
|
}
|
||||||
|
|
||||||
// NOTE: Tick entity action ================================================================
|
// NOTE: Handle input on entity ============================================================
|
||||||
|
FP_GameEntityAction *action = &entity->action;
|
||||||
{
|
{
|
||||||
bool action_is_done = false;
|
bool we_are_clicked_entity = entity->handle == game->clicked_entity;
|
||||||
TELY_AssetSpriteAnimation const *sprite_anim = entity->sprite_anims.data + entity->anim.index;
|
if (action->state == FP_GameEntityState_Nil) {
|
||||||
if (sprite_anim) {
|
action->state = FP_GameEntityState_Idle;
|
||||||
if (entity->anim.frame >= sprite_anim->count) { // NOTE: Animation is finished
|
action->dirty = true;
|
||||||
entity->anim.frame = 0;
|
}
|
||||||
entity->anim.ticks = 0;
|
|
||||||
action_is_done = true;
|
if (action->state == FP_GameEntityState_Idle) {
|
||||||
} else {
|
if (we_are_clicked_entity && TELY_Platform_InputScanCodeIsDown(input, TELY_PlatformInputScanCode_J)) {
|
||||||
if (entity->anim.ticks++ > 4 /*ticks_per_anim_frame*/) {
|
action->state = FP_GameEntityState_AttackA;
|
||||||
entity->anim.frame++;
|
action->dirty = true;
|
||||||
entity->anim.ticks = 0;
|
} else if (we_are_clicked_entity && (dir_vector.x || dir_vector.y)) {
|
||||||
}
|
action->state = FP_GameEntityState_Run;
|
||||||
|
action->dirty = true;
|
||||||
|
} else if (action->dirty) {
|
||||||
|
action->anim = {};
|
||||||
|
action->anim.asset = entity->sprite_anims.data + TELY_Asset_GetSpriteAnimation(entity->sprite_anims, DQN_STRING8("Idle")).index;
|
||||||
|
action->timer_s = FP_GAME_ENTITY_ACTION_INFINITE_TIMER;
|
||||||
|
action->dirty = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (action_is_done) {
|
if (action->state == FP_GameEntityState_AttackA) {
|
||||||
switch (entity->state) {
|
if (action->dirty) {
|
||||||
case FP_GameEntityState_Nil: break;
|
action->anim = {};
|
||||||
case FP_GameEntityState_Idle: break;
|
action->anim.asset = entity->sprite_anims.data + TELY_Asset_GetSpriteAnimation(entity->sprite_anims, DQN_STRING8("Attack A")).index;
|
||||||
case FP_GameEntityState_Attack: {
|
action->timer_s = action->anim.asset->count * 0.016f;
|
||||||
FP_Game_EntityChangeState(entity, FP_GameEntityState_Idle);
|
action->dirty = false;
|
||||||
} break;
|
} else if (action->timer_s <= 0.f) {
|
||||||
|
action->state = FP_GameEntityState_Idle;
|
||||||
case FP_GameEntityState_Run: {
|
action->dirty = true;
|
||||||
if (dir_vector.x == 0 && dir_vector.y == 0)
|
} else if (we_are_clicked_entity && TELY_Platform_InputScanCodeIsDown(input, TELY_PlatformInputScanCode_J)) {
|
||||||
FP_Game_EntityChangeState(entity, FP_GameEntityState_Idle);
|
// NOTE: Trigger AttackB if action is at the correct time
|
||||||
} break;
|
action->state = FP_GameEntityState_AttackB;
|
||||||
|
action->dirty = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (action->state == FP_GameEntityState_AttackB) {
|
||||||
|
if (action->dirty) {
|
||||||
|
action->anim = {};
|
||||||
|
action->anim.asset = entity->sprite_anims.data + TELY_Asset_GetSpriteAnimation(entity->sprite_anims, DQN_STRING8("Attack B")).index;
|
||||||
|
action->timer_s = action->anim.asset->count * 0.016f;
|
||||||
|
action->dirty = false;
|
||||||
|
} else if (action->timer_s <= 0.f) {
|
||||||
|
action->state = FP_GameEntityState_Idle;
|
||||||
|
action->dirty = true;
|
||||||
|
} else if (we_are_clicked_entity && TELY_Platform_InputScanCodeIsDown(input, TELY_PlatformInputScanCode_J)) {
|
||||||
|
// NOTE: Trigger AttackC if action is at the correct time
|
||||||
|
action->state = FP_GameEntityState_AttackC;
|
||||||
|
action->dirty = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (action->state == FP_GameEntityState_AttackC) {
|
||||||
|
if (action->dirty) {
|
||||||
|
action->anim = {};
|
||||||
|
action->anim.asset = entity->sprite_anims.data + TELY_Asset_GetSpriteAnimation(entity->sprite_anims, DQN_STRING8("Attack C")).index;
|
||||||
|
action->timer_s = action->anim.asset->count * 0.016f;
|
||||||
|
action->dirty = false;
|
||||||
|
} else if (action->timer_s <= 0.f) {
|
||||||
|
action->state = FP_GameEntityState_Idle;
|
||||||
|
action->dirty = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (action->state == FP_GameEntityState_Run) {
|
||||||
|
if (action->dirty) {
|
||||||
|
action->anim = {};
|
||||||
|
action->anim.asset = entity->sprite_anims.data + TELY_Asset_GetSpriteAnimation(entity->sprite_anims, DQN_STRING8("Run")).index;
|
||||||
|
action->timer_s = FP_GAME_ENTITY_ACTION_INFINITE_TIMER;
|
||||||
|
action->dirty = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (we_are_clicked_entity && (dir_vector.x == 0.f && dir_vector.y == 0.f)) {
|
||||||
|
action->state = FP_GameEntityState_Idle;
|
||||||
|
action->dirty = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOTE: Tick entity action ================================================================
|
||||||
|
TELY_AssetSpriteAnimation const *anim = action->anim.asset;
|
||||||
|
if (anim) {
|
||||||
|
if (action->anim.frame >= anim->count) { // NOTE: Animation is finished
|
||||||
|
action->anim.frame = 0;
|
||||||
|
action->anim.ticks = 0;
|
||||||
|
} else if (action->anim.ticks++ > 4 /*ticks_per_anim_frame*/) {
|
||||||
|
action->anim.frame++;
|
||||||
|
action->anim.ticks = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (action->timer_s != FP_GAME_ENTITY_ACTION_INFINITE_TIMER) {
|
||||||
|
action->timer_s = DQN_MAX(action->timer_s - DQN_CAST(Dqn_f32)input->delta_s, 0.f);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// NOTE: Calculate entity attack box =======================================================
|
// NOTE: Calculate entity attack box =======================================================
|
||||||
if (entity->state == FP_GameEntityState_Attack) {
|
if (action->state == FP_GameEntityState_AttackA) {
|
||||||
|
|
||||||
entity->attack_box_size = entity->local_hit_box_size;
|
entity->attack_box_size = entity->local_hit_box_size;
|
||||||
TELY_AssetSpriteSheet const *sprite_sheet = entity->sprite_sheet;
|
TELY_AssetSpriteSheet const *sprite_sheet = entity->sprite_sheet;
|
||||||
@ -720,11 +737,11 @@ void FP_GameRender(FP_Game *game, TELY_Platform *platform, TELY_Renderer *render
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NOTE: Render entity sprites =============================================================
|
// NOTE: Render entity sprites =============================================================
|
||||||
if (entity->sprite_sheet && entity->sprite_anims.size) {
|
if (entity->sprite_sheet && entity->action.anim.asset) {
|
||||||
TELY_AssetSpriteSheet const *sprite_sheet = entity->sprite_sheet;
|
TELY_AssetSpriteSheet const *sprite_sheet = entity->sprite_sheet;
|
||||||
TELY_AssetSpriteAnimation const *sprite_anim = entity->sprite_anims.data + entity->anim.index;
|
TELY_AssetSpriteAnimation const *sprite_anim = entity->action.anim.asset;
|
||||||
|
|
||||||
Dqn_usize sprite_index = (sprite_anim->index + (entity->anim.frame % sprite_anim->count)) % sprite_sheet->sprite_count;
|
Dqn_usize sprite_index = (sprite_anim->index + (entity->action.anim.frame % sprite_anim->count)) % sprite_sheet->sprite_count;
|
||||||
Dqn_usize sprite_sheet_row = sprite_index / sprite_sheet->sprites_per_row;
|
Dqn_usize sprite_sheet_row = sprite_index / sprite_sheet->sprites_per_row;
|
||||||
Dqn_usize sprite_sheet_column = sprite_index % sprite_sheet->sprites_per_row;
|
Dqn_usize sprite_sheet_column = sprite_index % sprite_sheet->sprites_per_row;
|
||||||
|
|
||||||
|
24
feely_pona.h
24
feely_pona.h
@ -46,16 +46,18 @@ enum FP_GameEntityState
|
|||||||
{
|
{
|
||||||
FP_GameEntityState_Nil,
|
FP_GameEntityState_Nil,
|
||||||
FP_GameEntityState_Idle,
|
FP_GameEntityState_Idle,
|
||||||
FP_GameEntityState_Attack,
|
FP_GameEntityState_AttackA,
|
||||||
|
FP_GameEntityState_AttackB,
|
||||||
|
FP_GameEntityState_AttackC,
|
||||||
FP_GameEntityState_Run,
|
FP_GameEntityState_Run,
|
||||||
};
|
};
|
||||||
|
|
||||||
struct FP_GameEntityAnimation
|
struct FP_GameEntityAnimation
|
||||||
{
|
{
|
||||||
uint16_t index;
|
TELY_AssetSpriteAnimation *asset;
|
||||||
uint16_t frame;
|
uint16_t frame;
|
||||||
uint16_t ticks;
|
uint16_t ticks;
|
||||||
uint16_t ticks_per_frame;
|
uint16_t ticks_per_frame;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct FP_GameWaypoint
|
struct FP_GameWaypoint
|
||||||
@ -65,6 +67,15 @@ struct FP_GameWaypoint
|
|||||||
FP_GameWaypoint *prev;
|
FP_GameWaypoint *prev;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Dqn_f32 const FP_GAME_ENTITY_ACTION_INFINITE_TIMER = -1.f;
|
||||||
|
struct FP_GameEntityAction
|
||||||
|
{
|
||||||
|
FP_GameEntityState state;
|
||||||
|
bool dirty;
|
||||||
|
FP_GameEntityAnimation anim;
|
||||||
|
Dqn_f32 timer_s;
|
||||||
|
};
|
||||||
|
|
||||||
struct FP_GameEntity
|
struct FP_GameEntity
|
||||||
{
|
{
|
||||||
Dqn_String8 name;
|
Dqn_String8 name;
|
||||||
@ -72,8 +83,7 @@ struct FP_GameEntity
|
|||||||
TELY_AssetSpriteSheet *sprite_sheet;
|
TELY_AssetSpriteSheet *sprite_sheet;
|
||||||
Dqn_Slice<TELY_AssetSpriteAnimation> sprite_anims;
|
Dqn_Slice<TELY_AssetSpriteAnimation> sprite_anims;
|
||||||
Dqn_V2 size_scale;
|
Dqn_V2 size_scale;
|
||||||
FP_GameEntityAnimation anim;
|
FP_GameEntityAction action;
|
||||||
FP_GameEntityState state;
|
|
||||||
Dqn_V2 velocity;
|
Dqn_V2 velocity;
|
||||||
|
|
||||||
FP_GameEntityHandle stalk_entity;
|
FP_GameEntityHandle stalk_entity;
|
||||||
|
Loading…
Reference in New Issue
Block a user