tely/asset: Fix sprite packer emitting wrong anim rect count

This commit is contained in:
doyle 2023-09-24 14:20:27 +10:00
parent 8af79b75d5
commit 481da3a6dd
6 changed files with 139 additions and 105 deletions

BIN
Data/Textures/smoochie_resized_25%.txt (Stored with Git LFS)

Binary file not shown.

View File

@ -3,6 +3,98 @@
#include "feely_pona_unity.h" #include "feely_pona_unity.h"
#endif #endif
struct FP_LoadSpriteSheetFromSpecResult
{
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;
Dqn_ThreadScratch scratch = Dqn_Thread_GetScratch(arena);
sheet->sprite_size = Dqn_V2I_InitNx2(185, 170);
sheet->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));
Dqn_String8 sprite_spec_buffer = platform->func_load_file(scratch.arena, sprite_spec_path);
Dqn_String8SplitAllocResult lines = Dqn_String8_SplitAlloc(scratch.allocator, sprite_spec_buffer, DQN_STRING8("\n"));
Dqn_usize sprite_rect_index = 0;
Dqn_usize sprite_anim_index = 0;
DQN_FOR_UINDEX(line_index, lines.size) {
Dqn_String8 line = lines.data[line_index];
Dqn_String8SplitAllocResult line_splits = Dqn_String8_SplitAlloc(scratch.allocator, line, DQN_STRING8(";"));
if (line_index == 0) {
DQN_ASSERTF(line_splits.size == 4, "Expected 4 splits for @file lines");
DQN_ASSERT(Dqn_String8_StartsWith(line_splits.data[0], DQN_STRING8("@file"), Dqn_String8EqCase_Sensitive));
// 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);
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);
// NOTE: Total animation count
Dqn_String8ToU64Result total_anim_count = Dqn_String8_ToU64(line_splits.data[3], 0);
DQN_ASSERT(total_anim_count.success);
result.anims = Dqn_Slice_Alloc<TELY_AssetSpriteAnimation>(arena, total_anim_count.value, Dqn_ZeroMem_No);
// TODO: Sprite size?
// TODO: Texture name?
continue;
}
if (Dqn_String8_StartsWith(line, DQN_STRING8("@anim"))) {
DQN_ASSERTF(line_splits.size == 4, "Expected 4 splits for @anim lines");
Dqn_String8 anim_name = line_splits.data[1];
Dqn_String8ToU64Result frames_per_second = Dqn_String8_ToU64(line_splits.data[2], 0);
Dqn_String8ToU64Result frame_count = Dqn_String8_ToU64(line_splits.data[3], 0);
DQN_ASSERT(anim_name.size);
DQN_ASSERT(frame_count.success);
DQN_ASSERT(frames_per_second.success);
Dqn_Allocator allocator = Dqn_Arena_Allocator(arena);
TELY_AssetSpriteAnimation *anim = result.anims.data + sprite_anim_index++;
anim->label = Dqn_String8_Copy(allocator, anim_name);
anim->index = DQN_CAST(uint16_t)sprite_rect_index;
anim->count = DQN_CAST(uint16_t)frame_count.value;
anim->seconds_per_frame = 1.f / frames_per_second.value;
} else {
DQN_ASSERTF(line_splits.size == 4, "Expected 4 splits for sprite frame lines");
Dqn_String8ToU64Result x = Dqn_String8_ToU64(line_splits.data[0], 0);
Dqn_String8ToU64Result y = Dqn_String8_ToU64(line_splits.data[1], 0);
Dqn_String8ToU64Result w = Dqn_String8_ToU64(line_splits.data[2], 0);
Dqn_String8ToU64Result h = Dqn_String8_ToU64(line_splits.data[3], 0);
DQN_ASSERT(x.success);
DQN_ASSERT(y.success);
DQN_ASSERT(w.success);
DQN_ASSERT(h.success);
sheet->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,
DQN_CAST(Dqn_f32) h.value);
}
}
DQN_ASSERT(sheet->rects.size == sprite_rect_index);
DQN_ASSERT(result.anims.size == sprite_anim_index);
return result;
}
extern "C" __declspec(dllexport) extern "C" __declspec(dllexport)
void TELY_DLL_Reload(void *user_data) void TELY_DLL_Reload(void *user_data)
{ {
@ -398,91 +490,21 @@ void TELY_DLL_Init(void *user_data)
DQN_MEMCPY(game->hero_sprite_anims.data, &hero_anims, sizeof(hero_anims[0]) * DQN_ARRAY_UCOUNT(hero_anims)); DQN_MEMCPY(game->hero_sprite_anims.data, &hero_anims, sizeof(hero_anims[0]) * DQN_ARRAY_UCOUNT(hero_anims));
} }
// NOTE: Load sprite sheets ====================================================================
{ {
TELY_AssetSpriteSheet *sheet = &game->protag_walk_sprite_sheet; {
Dqn_Slice<TELY_AssetSpriteAnimation> *anims = &game->protag_walk_sprite_anims; FP_LoadSpriteSheetFromSpecResult terry = FP_LoadSpriteSheetFromSpec(platform, assets, &platform->arena, DQN_STRING8("terry_walk_resized_25%"));
game->terry_sprite_sheet = terry.sheet;
Dqn_ThreadScratch scratch = Dqn_Thread_GetScratch(nullptr); game->terry_sprite_anims = terry.anims;
sheet->sprite_size = Dqn_V2I_InitNx2(185, 170);
sheet->type = TELY_AssetSpriteSheetType_Rects;
// NOTE: Load the sprite meta file =========================================================
Dqn_String8 sheet_name = DQN_STRING8("terry_walk_resized_25%");
Dqn_String8 sprite_spec_path = Dqn_FsPath_ConvertF(scratch.arena, "%.*s/%.*s.txt", DQN_STRING_FMT(assets->textures_dir), DQN_STRING_FMT(sheet_name));
Dqn_String8 sprite_spec_buffer = platform->func_load_file(scratch.arena, sprite_spec_path);
Dqn_String8SplitAllocResult lines = Dqn_String8_SplitAlloc(scratch.allocator, sprite_spec_buffer, DQN_STRING8("\n"));
Dqn_usize sprite_rect_index = 0;
Dqn_usize sprite_anim_index = 0;
DQN_FOR_UINDEX(line_index, lines.size) {
Dqn_String8 line = lines.data[line_index];
Dqn_String8SplitAllocResult line_splits = Dqn_String8_SplitAlloc(scratch.allocator, line, DQN_STRING8(";"));
if (line_index == 0) {
DQN_ASSERTF(line_splits.size == 4, "Expected 4 splits for @file lines");
DQN_ASSERT(Dqn_String8_StartsWith(line_splits.data[0], DQN_STRING8("@file"), Dqn_String8EqCase_Sensitive));
// 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);
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>(&platform->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);
DQN_ASSERT(total_anim_count.success);
*anims = Dqn_Slice_Alloc<TELY_AssetSpriteAnimation>(&platform->arena, total_anim_count.value, Dqn_ZeroMem_No);
// TODO: Sprite size?
// TODO: Texture name?
continue;
} }
if (Dqn_String8_StartsWith(line, DQN_STRING8("@anim"))) { {
DQN_ASSERTF(line_splits.size == 4, "Expected 4 splits for @anim lines"); FP_LoadSpriteSheetFromSpecResult smoochie = FP_LoadSpriteSheetFromSpec(platform, assets, &platform->arena, DQN_STRING8("smoochie_resized_25%"));
Dqn_String8 anim_name = line_splits.data[1]; game->smoochie_sprite_sheet = smoochie.sheet;
Dqn_String8ToU64Result frames_per_second = Dqn_String8_ToU64(line_splits.data[2], 0); game->smoochie_sprite_anims = smoochie.anims;
Dqn_String8ToU64Result frame_count = Dqn_String8_ToU64(line_splits.data[3], 0);
DQN_ASSERT(anim_name.size);
DQN_ASSERT(frame_count.success);
DQN_ASSERT(frames_per_second.success);
TELY_AssetSpriteAnimation *anim = anims->data + sprite_anim_index++;
anim->label = Dqn_String8_Copy(platform->allocator, anim_name);
anim->index = DQN_CAST(uint16_t)sprite_rect_index;
anim->count = DQN_CAST(uint16_t)frame_count.value;
anim->seconds_per_frame = 1.f / frames_per_second.value;
} else {
DQN_ASSERTF(line_splits.size == 4, "Expected 4 splits for sprite frame lines");
Dqn_String8ToU64Result x = Dqn_String8_ToU64(line_splits.data[0], 0);
Dqn_String8ToU64Result y = Dqn_String8_ToU64(line_splits.data[1], 0);
Dqn_String8ToU64Result w = Dqn_String8_ToU64(line_splits.data[2], 0);
Dqn_String8ToU64Result h = Dqn_String8_ToU64(line_splits.data[3], 0);
DQN_ASSERT(x.success);
DQN_ASSERT(y.success);
DQN_ASSERT(w.success);
DQN_ASSERT(h.success);
sheet->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,
DQN_CAST(Dqn_f32) h.value);
} }
} }
DQN_ASSERT(sheet->rects.size == sprite_rect_index);
DQN_ASSERT(anims->size == sprite_anim_index);
}
game->entities = Dqn_VArray_Init<FP_GameEntity>(&platform->arena, 1024 * 8); game->entities = Dqn_VArray_Init<FP_GameEntity>(&platform->arena, 1024 * 8);
game->root_entity = Dqn_VArray_Make(&game->entities, Dqn_ZeroMem_No); game->root_entity = Dqn_VArray_Make(&game->entities, Dqn_ZeroMem_No);
Dqn_FArray_Add(&game->parent_entity_stack, game->root_entity->handle); Dqn_FArray_Add(&game->parent_entity_stack, game->root_entity->handle);
@ -563,8 +585,8 @@ void TELY_DLL_Init(void *user_data)
// NOTE: Hero // NOTE: Hero
{ {
TELY_AssetSpriteSheet *sheet = &game->protag_walk_sprite_sheet; TELY_AssetSpriteSheet *sheet = &game->terry_sprite_sheet;
Dqn_Slice<TELY_AssetSpriteAnimation> anims = game->protag_walk_sprite_anims; Dqn_Slice<TELY_AssetSpriteAnimation> anims = game->terry_sprite_anims;
FP_GameEntity *entity = FP_Game_MakeEntityPointerF(game, "Terry"); FP_GameEntity *entity = FP_Game_MakeEntityPointerF(game, "Terry");
entity->local_pos = Dqn_V2_InitNx2(1334, 396); entity->local_pos = Dqn_V2_InitNx2(1334, 396);
@ -627,14 +649,15 @@ void TELY_DLL_Init(void *user_data)
} }
} }
// NOTE: Mob spawner // NOTE: Mob spawner ===========================================================================
if (0) { {
FP_GameEntity *entity = FP_Game_MakeEntityPointerF(game, "Mob spawner"); FP_GameEntity *entity = FP_Game_MakeEntityPointerF(game, "Mob spawner");
entity->local_pos = Dqn_V2_InitNx2(0, platform->core.window_size.y * .5f); entity->local_pos = Dqn_V2_InitNx2(0, platform->core.window_size.y * .5f);
entity->flags |= FP_GameEntityFlag_Clickable; entity->flags |= FP_GameEntityFlag_Clickable;
entity->flags |= FP_GameEntityFlag_MoveByKeyboard; entity->flags |= FP_GameEntityFlag_MoveByKeyboard;
entity->flags |= FP_GameEntityFlag_MoveByMouse; entity->flags |= FP_GameEntityFlag_MoveByMouse;
entity->flags |= FP_GameEntityFlag_MobSpawner; entity->flags |= FP_GameEntityFlag_MobSpawner;
entity->spawn_cap = 1;
} }
uint16_t font_size = 18; uint16_t font_size = 18;
@ -992,11 +1015,17 @@ void FP_Update(TELY_Platform *platform, FP_Game *game, TELY_Renderer *renderer,
} }
// NOTE: Mob spawner ======================================================================= // NOTE: Mob spawner =======================================================================
if ((entity->flags & FP_GameEntityFlag_MobSpawner) && input->timer_s >= entity->next_spawn_timestamp_s) { if (entity->flags & FP_GameEntityFlag_MobSpawner) {
if (entity->spawn_count < entity->spawn_cap) {
if (input->timer_s >= entity->next_spawn_timestamp_s) {
entity->next_spawn_timestamp_s = DQN_CAST(uint64_t)(input->timer_s + 5.f); entity->next_spawn_timestamp_s = DQN_CAST(uint64_t)(input->timer_s + 5.f);
entity->spawn_count++;
FP_Game_EntityAddMob(game, entity_world_pos); FP_Game_EntityAddMob(game, entity_world_pos);
} }
} }
}
}
Dqn_Profiler_EndZone(update_zone); Dqn_Profiler_EndZone(update_zone);
// NOTE: Do attacks ============================================================================ // NOTE: Do attacks ============================================================================

View File

@ -669,10 +669,10 @@ static FP_GameEntityHandle FP_Game_EntityAddMob(FP_Game *game, Dqn_V2 pos)
{ {
FP_GameEntity *entity = FP_Game_MakeEntityPointerF(game, "Mob"); FP_GameEntity *entity = FP_Game_MakeEntityPointerF(game, "Mob");
entity->local_pos = pos; entity->local_pos = pos;
entity->size_scale = Dqn_V2_InitNx1(4); entity->size_scale = Dqn_V2_InitNx1(.5f);
entity->sprite_sheet = &game->hero_sprite_sheet; entity->sprite_sheet = &game->smoochie_sprite_sheet;
entity->sprite_anims = game->hero_sprite_anims; entity->sprite_anims = game->smoochie_sprite_anims;
entity->local_hit_box_size = Dqn_V2_InitV2I(game->hero_sprite_sheet.sprite_size); entity->local_hit_box_size = Dqn_V2_InitV2I(game->smoochie_sprite_sheet.sprite_size);
entity->flags |= FP_GameEntityFlag_Clickable; entity->flags |= FP_GameEntityFlag_Clickable;
entity->flags |= FP_GameEntityFlag_MoveByKeyboard; entity->flags |= FP_GameEntityFlag_MoveByKeyboard;
entity->flags |= FP_GameEntityFlag_MoveByMouse; entity->flags |= FP_GameEntityFlag_MoveByMouse;

View File

@ -106,6 +106,8 @@ struct FP_GameEntity
Dqn_V2 attack_box_offset; Dqn_V2 attack_box_offset;
uint64_t next_spawn_timestamp_s; uint64_t next_spawn_timestamp_s;
uint64_t spawn_count;
uint64_t spawn_cap;
uint64_t flags; uint64_t flags;
bool facing_left; bool facing_left;
@ -148,8 +150,11 @@ struct FP_Game
Dqn_FArray<FP_GameEntityHandle, 8> parent_entity_stack; Dqn_FArray<FP_GameEntityHandle, 8> parent_entity_stack;
Dqn_VArray<FP_GameEntity> entities; Dqn_VArray<FP_GameEntity> entities;
TELY_AssetSpriteSheet protag_walk_sprite_sheet; TELY_AssetSpriteSheet terry_sprite_sheet;
Dqn_Slice<TELY_AssetSpriteAnimation> protag_walk_sprite_anims; Dqn_Slice<TELY_AssetSpriteAnimation> terry_sprite_anims;
TELY_AssetSpriteSheet smoochie_sprite_sheet;
Dqn_Slice<TELY_AssetSpriteAnimation> smoochie_sprite_anims;
FP_GameEntity *root_entity; FP_GameEntity *root_entity;
FP_GameEntity *entity_free_list; FP_GameEntity *entity_free_list;

View File

@ -171,11 +171,13 @@ int main(int argc, char const *argv[])
// NOTE: Generate the meta file ================================================================ // NOTE: Generate the meta file ================================================================
// NOTE: Count the number of animations we loaded frames fore // NOTE: Count the number of animations we loaded frames fore
Dqn_usize num_animations = 0; Dqn_usize num_anims = 0;
Dqn_usize num_anim_rects = 0;
for (Dqn_usize index = 1 /*Sentinel*/; index < sprite_spec_table.occupied; index++) { for (Dqn_usize index = 1 /*Sentinel*/; index < sprite_spec_table.occupied; index++) {
Dqn_DSMapSlot<SpriteSpecification> *slot = sprite_spec_table.slots + index; Dqn_DSMapSlot<SpriteSpecification> *slot = sprite_spec_table.slots + index;
if (slot->value.frame_count) { if (slot->value.frame_count) {
num_animations++; num_anims++;
num_anim_rects += slot->value.frame_count;
} }
} }
@ -184,8 +186,8 @@ int main(int argc, char const *argv[])
Dqn_Fs_WriteFileF(&meta_file, Dqn_Fs_WriteFileF(&meta_file,
"@file;%.*s;%d;%d\n", "@file;%.*s;%d;%d\n",
DQN_STRING_FMT(Dqn_String8_FileNameFromPath(atlas_path)), DQN_STRING_FMT(Dqn_String8_FileNameFromPath(atlas_path)),
DQN_CAST(int) file_list.count, DQN_CAST(int) num_anim_rects,
DQN_CAST(int) num_animations); DQN_CAST(int) num_anims);
Dqn_Log_InfoF("Generating meta file: %.*s", DQN_STRING_FMT(meta_path)); Dqn_Log_InfoF("Generating meta file: %.*s", DQN_STRING_FMT(meta_path));
Dqn_String8 anim_prefix = {}; Dqn_String8 anim_prefix = {};
@ -211,14 +213,12 @@ int main(int argc, char const *argv[])
// NOTE: Write the sprite and the rects to the sheet // NOTE: Write the sprite and the rects to the sheet
Dqn_String8 file_name = Dqn_String8_FileNameFromPath(*it.data); Dqn_String8 file_name = Dqn_String8_FileNameFromPath(*it.data);
Dqn_String8 file_name_without_extension = Dqn_String8_BinarySplit(file_name, DQN_STRING8(".")).lhs; Dqn_String8 file_name_to_anim_prefix = Dqn_String8_BinarySplitReverse(file_name, DQN_STRING8("_")).lhs;
// NOTE: Detect what animation we are currently processing ================================= // NOTE: Detect what animation we are currently processing =================================
if (anim_prefix.size == 0 || !Dqn_String8_StartsWith(file_name_without_extension, anim_prefix)) { if (anim_prefix.size == 0 || file_name_to_anim_prefix != anim_prefix) {
// NOTE: Anim prefix is different, we are starting a new animation- mark it accordingly // NOTE: Anim prefix is different, we are starting a new animation- mark it accordingly
Dqn_String8BinarySplitResult split = Dqn_String8_BinarySplitReverse(file_name_without_extension, DQN_STRING8("_")); anim_prefix = file_name_to_anim_prefix;
anim_prefix = split.lhs;
Dqn_DSMapResult<SpriteSpecification> slot = Dqn_DSMap_FindKeyString8(&sprite_spec_table, anim_prefix); Dqn_DSMapResult<SpriteSpecification> slot = Dqn_DSMap_FindKeyString8(&sprite_spec_table, anim_prefix);
Dqn_Fs_WriteFileF(&meta_file, "@anim;%.*s;%u;%u\n", DQN_STRING_FMT(anim_prefix), slot.value->frames_per_second, slot.value->frame_count); Dqn_Fs_WriteFileF(&meta_file, "@anim;%.*s;%u;%u\n", DQN_STRING_FMT(anim_prefix), slot.value->frames_per_second, slot.value->frame_count);
} }

Binary file not shown.