tely: Decouple entity from sprite sheet

This commit is contained in:
doyle 2023-09-24 15:44:52 +10:00
parent 481da3a6dd
commit 8568996914
4 changed files with 133 additions and 102 deletions

2
External/tely vendored

@ -1 +1 @@
Subproject commit f874ab4d3d127c7caa9ccdffebdb9ba1edfa5067
Subproject commit b135bb46769657e730b49c173db7476160855984

View File

@ -3,20 +3,12 @@
#include "feely_pona_unity.h"
#endif
struct FP_LoadSpriteSheetFromSpecResult
TELY_AssetSpriteSheet FP_LoadSpriteSheetFromSpec(TELY_Platform *platform, TELY_Assets *assets, Dqn_Arena *arena, Dqn_String8 sheet_name)
{
TELY_AssetSpriteSheet sheet;
Dqn_Slice<TELY_AssetSpriteAnimation> anims;
};
FP_LoadSpriteSheetFromSpecResult FP_LoadSpriteSheetFromSpec(TELY_Platform *platform, TELY_Assets *assets, Dqn_Arena *arena, Dqn_String8 sheet_name)
{
FP_LoadSpriteSheetFromSpecResult result = {};
TELY_AssetSpriteSheet *sheet = &result.sheet;
TELY_AssetSpriteSheet result = {};
Dqn_ThreadScratch scratch = Dqn_Thread_GetScratch(arena);
sheet->sprite_size = Dqn_V2I_InitNx2(185, 170);
sheet->type = TELY_AssetSpriteSheetType_Rects;
result.sprite_size = Dqn_V2I_InitNx2(185, 170);
result.type = TELY_AssetSpriteSheetType_Rects;
// NOTE: Load the sprite meta file =========================================================
Dqn_String8 sprite_spec_path = Dqn_FsPath_ConvertF(scratch.arena, "%.*s/%.*s.txt", DQN_STRING_FMT(assets->textures_dir), DQN_STRING_FMT(sheet_name));
@ -35,13 +27,13 @@ FP_LoadSpriteSheetFromSpecResult FP_LoadSpriteSheetFromSpec(TELY_Platform *platf
// NOTE: Sprite sheet path
Dqn_String8 sprite_sheet_path = Dqn_FsPath_ConvertF(scratch.arena, "%.*s/%.*s", DQN_STRING_FMT(assets->textures_dir), DQN_STRING_FMT(line_splits.data[1]));
sheet->tex_handle = platform->func_load_texture(assets, sheet_name, sprite_sheet_path);
result.tex_handle = platform->func_load_texture(assets, sheet_name, sprite_sheet_path);
DQN_ASSERTF(Dqn_Fs_Exists(sprite_sheet_path), "Required file does not exist '%.*s'", DQN_STRING_FMT(sprite_sheet_path));
// NOTE: Total sprite frame count
Dqn_String8ToU64Result total_frame_count = Dqn_String8_ToU64(line_splits.data[2], 0);
DQN_ASSERT(total_frame_count.success);
sheet->rects = Dqn_Slice_Alloc<Dqn_Rect>(arena, total_frame_count.value, Dqn_ZeroMem_No);
result.rects = Dqn_Slice_Alloc<Dqn_Rect>(arena, total_frame_count.value, Dqn_ZeroMem_No);
// NOTE: Total animation count
Dqn_String8ToU64Result total_anim_count = Dqn_String8_ToU64(line_splits.data[3], 0);
@ -82,7 +74,7 @@ FP_LoadSpriteSheetFromSpecResult FP_LoadSpriteSheetFromSpec(TELY_Platform *platf
DQN_ASSERT(w.success);
DQN_ASSERT(h.success);
sheet->rects.data[sprite_rect_index++] =
result.rects.data[sprite_rect_index++] =
Dqn_Rect_InitNx4(DQN_CAST(Dqn_f32) x.value,
DQN_CAST(Dqn_f32) y.value,
DQN_CAST(Dqn_f32) w.value,
@ -90,7 +82,7 @@ FP_LoadSpriteSheetFromSpecResult FP_LoadSpriteSheetFromSpec(TELY_Platform *platf
}
}
DQN_ASSERT(sheet->rects.size == sprite_rect_index);
DQN_ASSERT(result.rects.size == sprite_rect_index);
DQN_ASSERT(result.anims.size == sprite_anim_index);
return result;
}
@ -492,17 +484,24 @@ void TELY_DLL_Init(void *user_data)
// NOTE: Load sprite sheets ====================================================================
{
{
FP_LoadSpriteSheetFromSpecResult terry = FP_LoadSpriteSheetFromSpec(platform, assets, &platform->arena, DQN_STRING8("terry_walk_resized_25%"));
game->terry_sprite_sheet = terry.sheet;
game->terry_sprite_anims = terry.anims;
}
game->terry_sprite_sheet = FP_LoadSpriteSheetFromSpec(platform, assets, &platform->arena, DQN_STRING8("terry_walk_resized_25%"));
game->terry_action_mappings = Dqn_Slice_CopyArray<FP_ActionToAnimationMapping>(&platform->arena, {
{FP_ActionType_Idle, &game->terry_sprite_sheet, TELY_Asset_GetSpriteAnimation(&game->terry_sprite_sheet, DQN_STRING8("terry_walk_idle"))},
{FP_ActionType_WalkUp, &game->terry_sprite_sheet, TELY_Asset_GetSpriteAnimation(&game->terry_sprite_sheet, DQN_STRING8("terry_walk_up"))},
{FP_ActionType_WalkDown, &game->terry_sprite_sheet, TELY_Asset_GetSpriteAnimation(&game->terry_sprite_sheet, DQN_STRING8("terry_walk_down"))},
{FP_ActionType_WalkLeft, &game->terry_sprite_sheet, TELY_Asset_GetSpriteAnimation(&game->terry_sprite_sheet, DQN_STRING8("terry_walk_left"))},
{FP_ActionType_WalkRight, &game->terry_sprite_sheet, TELY_Asset_GetSpriteAnimation(&game->terry_sprite_sheet, DQN_STRING8("terry_walk_right"))},
});
{
FP_LoadSpriteSheetFromSpecResult smoochie = FP_LoadSpriteSheetFromSpec(platform, assets, &platform->arena, DQN_STRING8("smoochie_resized_25%"));
game->smoochie_sprite_sheet = smoochie.sheet;
game->smoochie_sprite_anims = smoochie.anims;
}
game->smoochie_sprite_sheet = FP_LoadSpriteSheetFromSpec(platform, assets, &platform->arena, DQN_STRING8("smoochie_resized_25%"));
game->smoochie_action_mappings = Dqn_Slice_CopyArray<FP_ActionToAnimationMapping>(&platform->arena, {
{FP_ActionType_Idle, &game->smoochie_sprite_sheet, TELY_Asset_GetSpriteAnimation(&game->smoochie_sprite_sheet, DQN_STRING8("smoochie_walk_down"))},
{FP_ActionType_WalkUp, &game->smoochie_sprite_sheet, TELY_Asset_GetSpriteAnimation(&game->smoochie_sprite_sheet, DQN_STRING8("smoochie_walk_up"))},
{FP_ActionType_WalkDown, &game->smoochie_sprite_sheet, TELY_Asset_GetSpriteAnimation(&game->smoochie_sprite_sheet, DQN_STRING8("smoochie_walk_down"))},
{FP_ActionType_WalkLeft, &game->smoochie_sprite_sheet, TELY_Asset_GetSpriteAnimation(&game->smoochie_sprite_sheet, DQN_STRING8("smoochie_walk_left"))},
{FP_ActionType_WalkRight, &game->smoochie_sprite_sheet, TELY_Asset_GetSpriteAnimation(&game->smoochie_sprite_sheet, DQN_STRING8("smoochie_walk_right"))},
{FP_ActionType_Death, &game->smoochie_sprite_sheet, TELY_Asset_GetSpriteAnimation(&game->smoochie_sprite_sheet, DQN_STRING8("smoochie_death"))},
});
}
game->entities = Dqn_VArray_Init<FP_GameEntity>(&platform->arena, 1024 * 8);
@ -586,13 +585,10 @@ void TELY_DLL_Init(void *user_data)
// NOTE: Hero
{
TELY_AssetSpriteSheet *sheet = &game->terry_sprite_sheet;
Dqn_Slice<TELY_AssetSpriteAnimation> anims = game->terry_sprite_anims;
FP_GameEntity *entity = FP_Game_MakeEntityPointerF(game, "Terry");
entity->local_pos = Dqn_V2_InitNx2(1334, 396);
entity->sprite_sheet = sheet;
entity->sprite_anims = anims;
entity->size_scale = Dqn_V2_InitNx1(0.5f);
entity->action_to_anim_mapping = game->terry_action_mappings;
entity->size_scale = Dqn_V2_InitNx1(0.25f);
entity->local_hit_box_size = Dqn_V2_InitV2I(sheet->sprite_size);
entity->flags |= FP_GameEntityFlag_Clickable;
entity->flags |= FP_GameEntityFlag_MoveByKeyboard;
@ -668,6 +664,19 @@ 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"));
}
FP_ActionToAnimationMapping FP_Game_GetActionAnimMappingForType(FP_Game *game, FP_GameEntityHandle entity_handle, FP_ActionType type)
{
FP_GameEntity *entity = FP_Game_GetEntity(game, entity_handle);
FP_ActionToAnimationMapping result = {};
for (FP_ActionToAnimationMapping const &mapping : entity->action_to_anim_mapping) {
if (mapping.action_type == type) {
result = mapping;
break;
}
}
return result;
}
void FP_Update(TELY_Platform *platform, FP_Game *game, TELY_Renderer *renderer, TELY_PlatformInput *input)
{
Dqn_Profiler_ZoneScopeWithIndex("FP_Update", FP_ProfileZone_FPUpdate);
@ -695,7 +704,6 @@ void FP_Update(TELY_Platform *platform, FP_Game *game, TELY_Renderer *renderer,
}
}
if (dir_vector.x && dir_vector.y) {
dir_vector.x *= 0.7071067811865475244f;
dir_vector.y *= 0.7071067811865475244f;
@ -726,6 +734,7 @@ void FP_Update(TELY_Platform *platform, FP_Game *game, TELY_Renderer *renderer,
// NOTE: Stalk entity ======================================================================
Dqn_V2 entity_world_pos = FP_Game_CalcEntityWorldPos(game, entity->handle);
#if 0
{
FP_GameEntity *stalk_entity = FP_Game_GetEntity(game, entity->stalk_entity);
if (stalk_entity->handle.id) {
@ -774,10 +783,13 @@ void FP_Update(TELY_Platform *platform, FP_Game *game, TELY_Renderer *renderer,
TELY_ChunkPool_Dealloc(game->chunk_pool, waypoint);
} else {
Dqn_V2 entity_to_target_pos_norm = Dqn_V2_Normalise(entity_to_target_pos);
entity->local_pos += entity_to_target_pos_norm * (entity->local_hit_box_size.x * .05f);
if (acceleration.x == 0 && acceleration.y == 0) {
acceleration = entity_to_target_pos_norm * 700'000.f;
}
}
}
}
#endif
// NOTE: Core equations of motion ==========================================================
{
@ -887,6 +899,7 @@ void FP_Update(TELY_Platform *platform, FP_Game *game, TELY_Renderer *renderer,
// NOTE: Handle input on entity ============================================================
FP_GameEntityAction *action = &entity->action;
FP_ActionToAnimationMapping action_to_anim_mapping = {};
{
bool we_are_clicked_entity = entity->handle == game->clicked_entity;
bool action_has_finished = action->timer_s != FP_GAME_ENTITY_ACTION_INFINITE_TIMER && action->timer_s >= action->end_at_s;
@ -896,9 +909,10 @@ void FP_Update(TELY_Platform *platform, FP_Game *game, TELY_Renderer *renderer,
FP_Game_EntityActionSetState(action, FP_GameEntityState_Idle);
if (action->state == FP_GameEntityState_Idle) {
action_to_anim_mapping = FP_Game_GetActionAnimMappingForType(game, entity->handle, FP_ActionType_Idle);
if (action->flags & FP_GameEntityActionFlag_StateTransition) {
TELY_AssetSpriteAnimation *anim = entity->sprite_anims.data + TELY_Asset_GetSpriteAnimation(entity->sprite_anims, DQN_STRING8("terry_walk_idle")).index;
FP_Game_EntityActionReset(action, FP_GAME_ENTITY_ACTION_INFINITE_TIMER, anim);
FP_Game_EntityActionReset(action, FP_GAME_ENTITY_ACTION_INFINITE_TIMER, action_to_anim_mapping);
} else if (we_are_clicked_entity) {
if (TELY_Platform_InputScanCodeIsPressed(input, TELY_PlatformInputScanCode_J)) {
FP_Game_EntityActionSetState(action, FP_GameEntityState_AttackA);
@ -909,9 +923,10 @@ void FP_Update(TELY_Platform *platform, FP_Game *game, TELY_Renderer *renderer,
}
if (action->state == FP_GameEntityState_AttackA) {
action_to_anim_mapping = FP_Game_GetActionAnimMappingForType(game, entity->handle, FP_ActionType_Attack);
if (action->flags & FP_GameEntityActionFlag_StateTransition) {
TELY_AssetSpriteAnimation *anim = entity->sprite_anims.data + TELY_Asset_GetSpriteAnimation(entity->sprite_anims, DQN_STRING8("Attack A")).index;
FP_Game_EntityActionReset(action, anim->count * anim->seconds_per_frame, anim);
FP_Game_EntityActionReset(action, action_to_anim_mapping.anim.count * action_to_anim_mapping.anim.seconds_per_frame, action_to_anim_mapping);
} else if (action_has_finished) {
FP_Game_EntityActionSetState(action, FP_GameEntityState_Idle);
} else if (!FP_Game_EntityActionHasFailed(action) && we_are_clicked_entity) {
@ -926,9 +941,10 @@ void FP_Update(TELY_Platform *platform, FP_Game *game, TELY_Renderer *renderer,
}
if (action->state == FP_GameEntityState_AttackB) {
action_to_anim_mapping = FP_Game_GetActionAnimMappingForType(game, entity->handle, FP_ActionType_Attack);
if (action->flags & FP_GameEntityActionFlag_StateTransition) {
TELY_AssetSpriteAnimation *anim = entity->sprite_anims.data + TELY_Asset_GetSpriteAnimation(entity->sprite_anims, DQN_STRING8("Attack B")).index;
FP_Game_EntityActionReset(action, anim->count * anim->seconds_per_frame, anim);
FP_Game_EntityActionReset(action, action_to_anim_mapping.anim.count * action_to_anim_mapping.anim.seconds_per_frame, action_to_anim_mapping);
} else if (action_has_finished) {
FP_Game_EntityActionSetState(action, FP_GameEntityState_Idle);
} else if (!FP_Game_EntityActionHasFailed(action) && we_are_clicked_entity) {
@ -943,18 +959,20 @@ void FP_Update(TELY_Platform *platform, FP_Game *game, TELY_Renderer *renderer,
}
if (action->state == FP_GameEntityState_AttackC) {
action_to_anim_mapping = FP_Game_GetActionAnimMappingForType(game, entity->handle, FP_ActionType_Attack);
if (action->flags & FP_GameEntityActionFlag_StateTransition) {
TELY_AssetSpriteAnimation *anim = entity->sprite_anims.data + TELY_Asset_GetSpriteAnimation(entity->sprite_anims, DQN_STRING8("Attack C")).index;
FP_Game_EntityActionReset(action, anim->count * anim->seconds_per_frame, anim);
FP_Game_EntityActionReset(action, action_to_anim_mapping.anim.count * action_to_anim_mapping.anim.seconds_per_frame, action_to_anim_mapping);
} else if (action_has_finished) {
FP_Game_EntityActionSetState(action, FP_GameEntityState_Idle);
}
}
if (action->state == FP_GameEntityState_Run) {
action_to_anim_mapping = FP_Game_GetActionAnimMappingForType(game, entity->handle, FP_ActionType_WalkRight);
if (action->flags & FP_GameEntityActionFlag_StateTransition) {
TELY_AssetSpriteAnimation *anim = entity->sprite_anims.data + TELY_Asset_GetSpriteAnimation(entity->sprite_anims, DQN_STRING8("terry_walk_right")).index;
FP_Game_EntityActionReset(action, FP_GAME_ENTITY_ACTION_INFINITE_TIMER, anim);
FP_Game_EntityActionReset(action, FP_GAME_ENTITY_ACTION_INFINITE_TIMER, action_to_anim_mapping);
} else if (we_are_clicked_entity) {
if (TELY_Platform_InputScanCodeIsPressed(input, TELY_PlatformInputScanCode_J)) {
FP_Game_EntityActionSetState(action, FP_GameEntityState_AttackA);
@ -969,9 +987,10 @@ void FP_Update(TELY_Platform *platform, FP_Game *game, TELY_Renderer *renderer,
}
if (action->state == FP_GameEntityState_Dash) {
action_to_anim_mapping = FP_Game_GetActionAnimMappingForType(game, entity->handle, FP_ActionType_WalkRight);
if (action->flags & FP_GameEntityActionFlag_StateTransition) {
TELY_AssetSpriteAnimation *anim = entity->sprite_anims.data + TELY_Asset_GetSpriteAnimation(entity->sprite_anims, DQN_STRING8("Floor slide")).index;
FP_Game_EntityActionReset(action, anim->count * anim->seconds_per_frame, anim);
FP_Game_EntityActionReset(action, action_to_anim_mapping.anim.count * action_to_anim_mapping.anim.seconds_per_frame, action_to_anim_mapping);
Dqn_V2 dash_dir = {entity->facing_left ? -1.f : 1.f, 0.f};
Dqn_V2 dash_acceleration = dash_dir * 400'000'000.f;
@ -997,10 +1016,6 @@ void FP_Update(TELY_Platform *platform, FP_Game *game, TELY_Renderer *renderer,
action->state == FP_GameEntityState_AttackB ||
action->state == FP_GameEntityState_AttackC) {
entity->attack_box_size = entity->local_hit_box_size;
TELY_AssetSpriteSheet const *sprite_sheet = entity->sprite_sheet;
if (sprite_sheet) {
entity->attack_box_size = Dqn_V2_InitV2I(sprite_sheet->sprite_size);
}
// NOTE: Position the attack box
if (entity->facing_left) {
@ -1122,10 +1137,10 @@ void FP_Render(FP_Game *game, TELY_Platform *platform, TELY_Renderer *renderer)
}
// NOTE: Render entity sprites =============================================================
if (entity->sprite_sheet && entity->action.anim) {
TELY_AssetSpriteSheet const *sprite_sheet = entity->sprite_sheet;
if (entity->action.mapping.anim.label.size) {
FP_GameEntityAction const *action = &entity->action;
TELY_AssetSpriteAnimation const *sprite_anim = action->anim;
TELY_AssetSpriteSheet const *sprite_sheet = action->mapping.sheet;
TELY_AssetSpriteAnimation const *sprite_anim = &action->mapping.anim;
uint16_t anim_frame = DQN_CAST(uint16_t)(action->timer_s / sprite_anim->seconds_per_frame) % sprite_anim->count;
Dqn_usize sprite_index = sprite_anim->index + anim_frame;

View File

@ -434,11 +434,11 @@ static void FP_Game_EntityActionSetState(FP_GameEntityAction *action, FP_GameEnt
// Reset the timers and animation for the current action and set the duration
// for the new action.
static void FP_Game_EntityActionReset(FP_GameEntityAction *action, Dqn_f32 new_action_duration, TELY_AssetSpriteAnimation *anim)
static void FP_Game_EntityActionReset(FP_GameEntityAction *action, Dqn_f32 new_action_duration, FP_ActionToAnimationMapping mapping)
{
if (!action)
return;
action->anim = anim;
action->mapping = mapping;
action->timer_s = 0.f;
action->end_at_s = new_action_duration;
action->flags = 0;
@ -669,9 +669,8 @@ static FP_GameEntityHandle FP_Game_EntityAddMob(FP_Game *game, Dqn_V2 pos)
{
FP_GameEntity *entity = FP_Game_MakeEntityPointerF(game, "Mob");
entity->local_pos = pos;
entity->size_scale = Dqn_V2_InitNx1(.5f);
entity->sprite_sheet = &game->smoochie_sprite_sheet;
entity->sprite_anims = game->smoochie_sprite_anims;
entity->size_scale = Dqn_V2_InitNx1(.25f);
entity->action_to_anim_mapping = game->smoochie_action_mappings;
entity->local_hit_box_size = Dqn_V2_InitV2I(game->smoochie_sprite_sheet.sprite_size);
entity->flags |= FP_GameEntityFlag_Clickable;
entity->flags |= FP_GameEntityFlag_MoveByKeyboard;

View File

@ -68,12 +68,30 @@ enum FP_GameEntityActionFlag
FP_GameEntityActionFlag_Failed = 1 << 1,
};
enum FP_ActionType
{
FP_ActionType_Idle,
FP_ActionType_Attack,
FP_ActionType_WalkUp,
FP_ActionType_WalkDown,
FP_ActionType_WalkLeft,
FP_ActionType_WalkRight,
FP_ActionType_Death,
};
struct FP_ActionToAnimationMapping
{
FP_ActionType action_type;
TELY_AssetSpriteSheet *sheet;
TELY_AssetSpriteAnimation anim;
};
Dqn_f32 const FP_GAME_ENTITY_ACTION_INFINITE_TIMER = -1.f;
struct FP_GameEntityAction
{
FP_GameEntityState state;
uint32_t flags; // Bit flags corresponding with `FP_GameEntityActionFlag`
TELY_AssetSpriteAnimation *anim;
FP_ActionToAnimationMapping mapping;
Dqn_f32 timer_s;
Dqn_f32 end_at_s;
};
@ -88,8 +106,7 @@ struct FP_GameEntity
Dqn_String8 name;
FP_GameEntityHandle handle;
TELY_AssetSpriteSheet *sprite_sheet;
Dqn_Slice<TELY_AssetSpriteAnimation> sprite_anims;
Dqn_Slice<FP_ActionToAnimationMapping> action_to_anim_mapping;
Dqn_V2 size_scale;
FP_GameEntityAction action;
Dqn_V2 velocity;
@ -151,10 +168,10 @@ struct FP_Game
Dqn_VArray<FP_GameEntity> entities;
TELY_AssetSpriteSheet terry_sprite_sheet;
Dqn_Slice<TELY_AssetSpriteAnimation> terry_sprite_anims;
Dqn_Slice<FP_ActionToAnimationMapping> terry_action_mappings;
TELY_AssetSpriteSheet smoochie_sprite_sheet;
Dqn_Slice<TELY_AssetSpriteAnimation> smoochie_sprite_anims;
Dqn_Slice<FP_ActionToAnimationMapping> smoochie_action_mappings;
FP_GameEntity *root_entity;
FP_GameEntity *entity_free_list;