From f60099e7a310178770b5d93c28591d5993233ffc Mon Sep 17 00:00:00 2001 From: doyle Date: Mon, 18 Sep 2023 00:12:58 +1000 Subject: [PATCH] fp: Start decoupling action from animation --- External/tely | 2 +- feely_pona.cpp | 173 +++++++++++++++++++++++++++---------------------- feely_pona.h | 24 +++++-- 3 files changed, 113 insertions(+), 86 deletions(-) diff --git a/External/tely b/External/tely index 5bba4cd..b45db1a 160000 --- a/External/tely +++ b/External/tely @@ -1 +1 @@ -Subproject commit 5bba4cddbc9352be4083fb1ec02dfbab2de0c115 +Subproject commit b45db1a82cf5fb7f32d298c71b7dc0ac806a6f5f diff --git a/feely_pona.cpp b/feely_pona.cpp index a06dbf3..c3e4550 100644 --- a/feely_pona.cpp +++ b/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")); } -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 { 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 (TELY_Platform_InputScanCodeIsPressed(input, TELY_PlatformInputScanCode_Delete)) 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 { 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; } - // NOTE: Tick entity action ================================================================ + // NOTE: Handle input on entity ============================================================ + FP_GameEntityAction *action = &entity->action; { - bool action_is_done = false; - TELY_AssetSpriteAnimation const *sprite_anim = entity->sprite_anims.data + entity->anim.index; - if (sprite_anim) { - if (entity->anim.frame >= sprite_anim->count) { // NOTE: Animation is finished - entity->anim.frame = 0; - entity->anim.ticks = 0; - action_is_done = true; - } else { - if (entity->anim.ticks++ > 4 /*ticks_per_anim_frame*/) { - entity->anim.frame++; - entity->anim.ticks = 0; - } + bool we_are_clicked_entity = entity->handle == game->clicked_entity; + if (action->state == FP_GameEntityState_Nil) { + action->state = FP_GameEntityState_Idle; + action->dirty = true; + } + + if (action->state == FP_GameEntityState_Idle) { + if (we_are_clicked_entity && TELY_Platform_InputScanCodeIsDown(input, TELY_PlatformInputScanCode_J)) { + action->state = FP_GameEntityState_AttackA; + action->dirty = true; + } 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) { - switch (entity->state) { - case FP_GameEntityState_Nil: break; - case FP_GameEntityState_Idle: break; - case FP_GameEntityState_Attack: { - FP_Game_EntityChangeState(entity, FP_GameEntityState_Idle); - } break; - - case FP_GameEntityState_Run: { - if (dir_vector.x == 0 && dir_vector.y == 0) - FP_Game_EntityChangeState(entity, FP_GameEntityState_Idle); - } break; + if (action->state == FP_GameEntityState_AttackA) { + if (action->dirty) { + action->anim = {}; + action->anim.asset = entity->sprite_anims.data + TELY_Asset_GetSpriteAnimation(entity->sprite_anims, DQN_STRING8("Attack A")).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 AttackB if action is at the correct time + 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 ======================================================= - if (entity->state == FP_GameEntityState_Attack) { + if (action->state == FP_GameEntityState_AttackA) { entity->attack_box_size = entity->local_hit_box_size; 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 ============================================================= - 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_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_column = sprite_index % sprite_sheet->sprites_per_row; diff --git a/feely_pona.h b/feely_pona.h index da1377a..adef30d 100644 --- a/feely_pona.h +++ b/feely_pona.h @@ -46,16 +46,18 @@ enum FP_GameEntityState { FP_GameEntityState_Nil, FP_GameEntityState_Idle, - FP_GameEntityState_Attack, + FP_GameEntityState_AttackA, + FP_GameEntityState_AttackB, + FP_GameEntityState_AttackC, FP_GameEntityState_Run, }; struct FP_GameEntityAnimation { - uint16_t index; - uint16_t frame; - uint16_t ticks; - uint16_t ticks_per_frame; + TELY_AssetSpriteAnimation *asset; + uint16_t frame; + uint16_t ticks; + uint16_t ticks_per_frame; }; struct FP_GameWaypoint @@ -65,6 +67,15 @@ struct FP_GameWaypoint 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 { Dqn_String8 name; @@ -72,8 +83,7 @@ struct FP_GameEntity TELY_AssetSpriteSheet *sprite_sheet; Dqn_Slice sprite_anims; Dqn_V2 size_scale; - FP_GameEntityAnimation anim; - FP_GameEntityState state; + FP_GameEntityAction action; Dqn_V2 velocity; FP_GameEntityHandle stalk_entity;