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
|
171
feely_pona.cpp
171
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;
|
||||
|
||||
|
18
feely_pona.h
18
feely_pona.h
@ -46,13 +46,15 @@ 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;
|
||||
TELY_AssetSpriteAnimation *asset;
|
||||
uint16_t frame;
|
||||
uint16_t ticks;
|
||||
uint16_t ticks_per_frame;
|
||||
@ -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<TELY_AssetSpriteAnimation> sprite_anims;
|
||||
Dqn_V2 size_scale;
|
||||
FP_GameEntityAnimation anim;
|
||||
FP_GameEntityState state;
|
||||
FP_GameEntityAction action;
|
||||
Dqn_V2 velocity;
|
||||
|
||||
FP_GameEntityHandle stalk_entity;
|
||||
|
Loading…
Reference in New Issue
Block a user