tely: Add idle animation for the player

This commit is contained in:
2023-09-23 21:21:08 +10:00
parent 1062e138d6
commit 1ff0daba4a
16 changed files with 244 additions and 14 deletions
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+1 -1
+5
View File
@@ -82,6 +82,11 @@ set clang_dll_cmd=clang-cl %dll_compile_flags% /Fotely_dll_clang /Fetely_dll_c
REM MSVC build =====================================================================================
if not exist "%build_dir%\feely_pona_sprite_packer" (
set msvc_sprite_packer_cmd=cl %common_compile_flags% %code_dir%\feely_pona_sprite_packer.cpp /Fofeely_pona_sprite_packer /Fefeely_pona_sprite_packer %common_link_flags%
call powershell -Command "$duration = Measure-Command {%msvc_sprite_packer_cmd% | Out-Default}; Write-Host 'msvc (sprite packer):' $duration.TotalSeconds 'seconds'"
)
set msvc_build_platform=$platform_time = Measure-Command {%msvc_cmd% ^| Out-Default};
set msvc_build_dll=$dll_time = Measure-Command {%msvc_dll_cmd% ^| Out-Default};
set msvc_print_dll_time=Write-Host 'msvc dll:'$dll_time.TotalSeconds's'
+85 -13
View File
@@ -398,6 +398,68 @@ 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));
}
{
TELY_AssetSpriteSheet *sheet = &game->protag_walk_sprite_sheet;
Dqn_Slice<TELY_AssetSpriteAnimation> *anims = &game->protag_walk_sprite_anims;
Dqn_ThreadScratch scratch = Dqn_Thread_GetScratch(nullptr);
Dqn_String8 sheet_path = Dqn_FsPath_ConvertF(scratch.arena, "%.*s/protagonist_walk.png", DQN_STRING_FMT(assets->textures_dir));
sheet->tex_handle = platform->func_load_texture(assets, DQN_STRING8("Protagonist Walk"), sheet_path);
sheet->sprite_count = 28;
sheet->sprites_per_row = 7;
sheet->sprite_size = Dqn_V2I_InitNx2(185, 170);
sheet->type = TELY_AssetSpriteSheetType_Rects;
struct LoadedSprite
{
Dqn_String8 name;
Dqn_Rect rect;
};
// NOTE: Load the sprite meta file =========================================================
{
Dqn_List<LoadedSprite> raw_sprites = Dqn_List_Init<LoadedSprite>(scratch.arena, 32);
Dqn_String8 sheet_meta_path = Dqn_FsPath_ConvertF(scratch.arena, "%.*s/protagonist_walk.txt", DQN_STRING_FMT(assets->textures_dir));
Dqn_String8 sheet_meta_file = Dqn_Fs_Read(sheet_meta_path, scratch.allocator);
Dqn_String8BinarySplitResult split = {};
Dqn_String8 first = sheet_meta_file;
do {
split = Dqn_String8_BinarySplit(first, DQN_STRING8("\n"));
Dqn_String8BinarySplitResult name = Dqn_String8_BinarySplit(split.lhs, DQN_STRING8(";"));
Dqn_String8BinarySplitResult x = Dqn_String8_BinarySplit(name.rhs, DQN_STRING8(";"));
Dqn_String8BinarySplitResult y = Dqn_String8_BinarySplit(x.rhs, DQN_STRING8(";"));
Dqn_String8BinarySplitResult width = Dqn_String8_BinarySplit(y.rhs, DQN_STRING8(";"));
Dqn_String8BinarySplitResult height = Dqn_String8_BinarySplit(width.rhs, DQN_STRING8(";"));
LoadedSprite *loaded_anim = Dqn_List_Make(&raw_sprites, 1);
loaded_anim->name = name.lhs;
loaded_anim->rect.pos.x = DQN_CAST(Dqn_f32)Dqn_String8_ToU64(x.lhs, 0).value;
loaded_anim->rect.pos.y = DQN_CAST(Dqn_f32)Dqn_String8_ToU64(y.lhs, 0).value;
loaded_anim->rect.size.w = DQN_CAST(Dqn_f32)Dqn_String8_ToU64(width.lhs, 0).value;
loaded_anim->rect.size.h = DQN_CAST(Dqn_f32)Dqn_String8_ToU64(height.lhs, 0).value;
first = split.rhs;
} while (first.size);
// NOTE: Populate the sheet ============================================================
sheet->rects = Dqn_Slice_Alloc<Dqn_Rect>(&platform->arena, raw_sprites.count, Dqn_ZeroMem_No);
for (Dqn_ListIterator<LoadedSprite> it = {}; Dqn_List_Iterate(&raw_sprites, &it, 0); ) {
sheet->rects.data[it.index] = it.data->rect;
}
}
TELY_AssetSpriteAnimation raw_anims[] = {
// {DQN_STRING8("Walk down"), /*index*/ 0, /*count*/ 7, /*seconds_per_frame*/ 1 / 8.f},
{DQN_STRING8("Walk idle"), /*index*/ 0, /*count*/ 8, /*seconds_per_frame*/ 1 / 8.f},
// {DQN_STRING8("Walk left"), /*index*/ 15, /*count*/ 7, /*seconds_per_frame*/ 1 / 8.f},
// {DQN_STRING8("Walk right"), /*index*/ 22, /*count*/ 7, /*seconds_per_frame*/ 1 / 8.f},
};
*anims = Dqn_Slice_Alloc<TELY_AssetSpriteAnimation>(&platform->arena, DQN_ARRAY_UCOUNT(raw_anims), Dqn_ZeroMem_No);
DQN_MEMCPY(anims->data, &raw_anims, sizeof(raw_anims[0]) * DQN_ARRAY_UCOUNT(raw_anims));
}
game->entities = Dqn_VArray_Init<FP_GameEntity>(&platform->arena, 1024 * 8);
game->root_entity = Dqn_VArray_Make(&game->entities, Dqn_ZeroMem_No);
Dqn_FArray_Add(&game->parent_entity_stack, game->root_entity->handle);
@@ -478,12 +540,14 @@ void TELY_DLL_Init(void *user_data)
// NOTE: Hero
{
TELY_AssetSpriteSheet *sheet = &game->protag_walk_sprite_sheet;
Dqn_Slice<TELY_AssetSpriteAnimation> anims = game->protag_walk_sprite_anims;
FP_GameEntity *entity = FP_Game_MakeEntityPointerF(game, "Terry");
entity->local_pos = Dqn_V2_InitNx2(1334, 396);
entity->size_scale = Dqn_V2_InitNx1(4);
entity->sprite_sheet = &game->hero_sprite_sheet;
entity->sprite_anims = game->hero_sprite_anims;
entity->local_hit_box_size = Dqn_V2_InitV2I(game->hero_sprite_sheet.sprite_size);
entity->sprite_sheet = sheet;
entity->sprite_anims = anims;
entity->local_hit_box_size = Dqn_V2_InitV2I(sheet->sprite_size);
entity->flags |= FP_GameEntityFlag_Clickable;
entity->flags |= FP_GameEntityFlag_MoveByKeyboard;
entity->flags |= FP_GameEntityFlag_MoveByMouse;
@@ -540,7 +604,7 @@ void TELY_DLL_Init(void *user_data)
}
// NOTE: Mob spawner
{
if (0) {
FP_GameEntity *entity = FP_Game_MakeEntityPointerF(game, "Mob spawner");
entity->local_pos = Dqn_V2_InitNx2(0, platform->core.window_size.y * .5f);
entity->flags |= FP_GameEntityFlag_Clickable;
@@ -775,7 +839,7 @@ void FP_Update(TELY_Platform *platform, FP_Game *game, TELY_Renderer *renderer,
if (action->state == FP_GameEntityState_Idle) {
if (action->flags & FP_GameEntityActionFlag_StateTransition) {
TELY_AssetSpriteAnimation *anim = entity->sprite_anims.data + TELY_Asset_GetSpriteAnimation(entity->sprite_anims, DQN_STRING8("Idle")).index;
TELY_AssetSpriteAnimation *anim = entity->sprite_anims.data + TELY_Asset_GetSpriteAnimation(entity->sprite_anims, DQN_STRING8("Walk idle")).index;
FP_Game_EntityActionReset(action, FP_GAME_ENTITY_ACTION_INFINITE_TIMER, anim);
} else if (we_are_clicked_entity) {
if (TELY_Platform_InputScanCodeIsPressed(input, TELY_PlatformInputScanCode_J)) {
@@ -831,7 +895,7 @@ void FP_Update(TELY_Platform *platform, FP_Game *game, TELY_Renderer *renderer,
if (action->state == FP_GameEntityState_Run) {
if (action->flags & FP_GameEntityActionFlag_StateTransition) {
TELY_AssetSpriteAnimation *anim = entity->sprite_anims.data + TELY_Asset_GetSpriteAnimation(entity->sprite_anims, DQN_STRING8("Run")).index;
TELY_AssetSpriteAnimation *anim = entity->sprite_anims.data + TELY_Asset_GetSpriteAnimation(entity->sprite_anims, DQN_STRING8("Walk right")).index;
FP_Game_EntityActionReset(action, FP_GAME_ENTITY_ACTION_INFINITE_TIMER, anim);
} else if (we_are_clicked_entity) {
if (TELY_Platform_InputScanCodeIsPressed(input, TELY_PlatformInputScanCode_J)) {
@@ -995,7 +1059,6 @@ 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;
FP_GameEntityAction const *action = &entity->action;
TELY_AssetSpriteAnimation const *sprite_anim = action->anim;
@@ -1006,10 +1069,19 @@ void FP_Render(FP_Game *game, TELY_Platform *platform, TELY_Renderer *renderer)
Dqn_usize sprite_sheet_column = sprite_index % sprite_sheet->sprites_per_row;
Dqn_Rect src_rect = {};
src_rect.pos.x = DQN_CAST(Dqn_f32)(sprite_sheet_column * sprite_sheet->sprite_size.w);
src_rect.pos.y = DQN_CAST(Dqn_f32)(sprite_sheet_row * sprite_sheet->sprite_size.y);
src_rect.size.w = DQN_CAST(Dqn_f32)sprite_sheet->sprite_size.w;
src_rect.size.h = DQN_CAST(Dqn_f32)sprite_sheet->sprite_size.h;
switch (sprite_sheet->type) {
case TELY_AssetSpriteSheetType_Uniform: {
src_rect.pos.x = DQN_CAST(Dqn_f32)(sprite_sheet_column * sprite_sheet->sprite_size.w);
src_rect.pos.y = DQN_CAST(Dqn_f32)(sprite_sheet_row * sprite_sheet->sprite_size.y);
src_rect.size.w = DQN_CAST(Dqn_f32)sprite_sheet->sprite_size.w;
src_rect.size.h = DQN_CAST(Dqn_f32)sprite_sheet->sprite_size.h;
} break;
case TELY_AssetSpriteSheetType_Rects: {
DQN_ASSERT(sprite_index < sprite_sheet->rects.size);
src_rect = sprite_sheet->rects.data[sprite_index];
} break;
}
Dqn_Rect dest_rect = {};
dest_rect.size = src_rect.size * entity->size_scale;
@@ -1122,7 +1194,7 @@ void TELY_DLL_FrameUpdate(void *user_data)
}
Dqn_f32 const PHYSICS_STEP = 1 / 60.f;
for (game->delta_s_accumulator += input->delta_s;
for (game->delta_s_accumulator += DQN_CAST(Dqn_f32)input->delta_s;
game->delta_s_accumulator > PHYSICS_STEP;
game->delta_s_accumulator -= PHYSICS_STEP) {
FP_Update(platform, game, renderer, input);
+3
View File
@@ -148,6 +148,9 @@ struct FP_Game
Dqn_FArray<FP_GameEntityHandle, 8> parent_entity_stack;
Dqn_VArray<FP_GameEntity> entities;
TELY_AssetSpriteSheet protag_walk_sprite_sheet;
Dqn_Slice<TELY_AssetSpriteAnimation> protag_walk_sprite_anims;
FP_GameEntity *root_entity;
FP_GameEntity *entity_free_list;
+120
View File
@@ -0,0 +1,120 @@
#define DQN_ASAN_POISON 1
#define DQN_ASAN_VET_POISON 1
#define DQN_ONLY_RECT
#define DQN_ONLY_WIN
#define DQN_ONLY_V2
#define DQN_ONLY_SLICE
#define DQN_ONLY_SARRAY
#define DQN_ONLY_LIST
#define DQN_ONLY_FS
#define _CRT_SECURE_NO_WARNINGS
#define DQN_IMPLEMENTATION
#include "External/tely/External/dqn/dqn.h"
DQN_MSVC_WARNING_PUSH
DQN_MSVC_WARNING_DISABLE(4244) // warning C4244: 'argument': conversion from 'int' to 'short', possible loss of data
#define STB_RECT_PACK_IMPLEMENTATION
#include "External/tely/external/stb/stb_rect_pack.h"
#define STB_IMAGE_WRITE_IMPLEMENTATION
#include "External/tely/external/stb/stb_image_write.h"
#define STB_IMAGE_IMPLEMENTATION
#include "External/tely/external/stb/stb_image.h"
DQN_MSVC_WARNING_POP
int main(int argc, char const *argv[])
{
if (argc != 2) {
Dqn_Log_InfoF("USAGE: feely_pona_sprite_packer \"<directory with sprites>\"");
return -1;
}
Dqn_Library_Init();
Dqn_ThreadScratch scratch = Dqn_Thread_GetScratch(nullptr);
Dqn_String8 dir = Dqn_String8_InitCString8(argv[1]);
// NOTE: Get the list of files =================================================================
Dqn_List<Dqn_String8> file_list = Dqn_List_Init<Dqn_String8>(scratch.arena, 128);
for (Dqn_Win_FolderIterator it = {}; Dqn_Win_FolderIterate(dir, &it); ) {
if (Dqn_String8_EndsWithInsensitive(it.file_name, DQN_STRING8(".png"))) {
Dqn_String8 *item = Dqn_List_Make(&file_list, Dqn_ZeroMem_Yes);
*item = Dqn_FsPath_ConvertF(scratch.arena, "%.*s/%.*s", DQN_STRING_FMT(dir), DQN_STRING_FMT(it.file_name));
}
}
// NOTE: Setup the rect-pack state =============================================================
int atlas_size = 2048;
stbrp_node *nodes = Dqn_Arena_NewArray(scratch.arena, stbrp_node, file_list.count, Dqn_ZeroMem_Yes);
stbrp_context pack_context = {};
stbrp_init_target(&pack_context, atlas_size, atlas_size, nodes, DQN_CAST(int)file_list.count);
// NOTE: Load the sprites to determine their dimensions for rect packing
Dqn_SArray<stbrp_rect> rects = Dqn_SArray_Init<stbrp_rect>(scratch.arena, file_list.count, Dqn_ZeroMem_Yes);
for (Dqn_ListIterator<Dqn_String8> it = {}; Dqn_List_Iterate<Dqn_String8>(&file_list, &it, 0);) {
int x = 0, y = 0, channels_in_file = 0;
stbi_uc *pixels = stbi_load(it.data->data, &x, &y, &channels_in_file, 4 /*desired_channels*/);
DQN_ASSERT(pixels);
stbi_image_free(pixels);
// NOTE: Add a rect to the packing context
stbrp_rect *rect = Dqn_SArray_Make(&rects, Dqn_ZeroMem_Yes);
rect->w = x;
rect->h = y;
Dqn_Log_InfoF("Packing sprite: %.*s", DQN_STRING_FMT(*it.data));
}
// NOTE: Pack the rects ========================================================================
if (stbrp_pack_rects(&pack_context, rects.data, DQN_CAST(int)rects.size) != 1) {
Dqn_Log_ErrorF("STB rect pack failed to pack font rects into rectangle [width=%d, height=%d, num_rects=%d]",
atlas_size,
atlas_size,
file_list.count);
return -1;
}
// NOTE: Load the files once more and generate the final image =================================
int final_bpp = 4;
int final_image_stride = atlas_size * final_bpp;
char *final_image = Dqn_Arena_NewArray(scratch.arena, char, atlas_size * final_image_stride, Dqn_ZeroMem_Yes);
// NOTE: Generate the meta file ================================================================
Dqn_String8 meta_path = Dqn_String8_InitF(scratch.allocator, "%.*s.txt", DQN_STRING_FMT(dir));
Dqn_FsFile meta_file = Dqn_Fs_OpenFile(meta_path, Dqn_FsFileOpen_CreateAlways, Dqn_FsFileAccess_Write);
Dqn_Log_InfoF("Generating meta file: %.*s", DQN_STRING_FMT(meta_path));
for (Dqn_ListIterator<Dqn_String8> it = {}; Dqn_List_Iterate<Dqn_String8>(&file_list, &it, 0);) {
int w, h, channels_in_file;
stbi_uc *loaded_image = stbi_load(it.data->data, &w, &h, &channels_in_file, 4 /*desired_channels*/);
stbrp_rect packed_rect = rects.data[it.index];
char *src = DQN_CAST(char *)loaded_image;
// NOTE: Generate the image ================================================================
for (int y = 0; y < packed_rect.h; y++) {
char *row = final_image + ((packed_rect.y + y) * final_image_stride) + (packed_rect.x * final_bpp);
for (int x = 0; x < packed_rect.w; x++) {
*row++ = *src++;
*row++ = *src++;
*row++ = *src++;
*row++ = *src++;
}
}
stbi_image_free(loaded_image);
// NOTE: Write the sprite and the rects to the sheet
Dqn_String8 file_name = Dqn_String8_FileNameFromPath(*it.data);
Dqn_String8 file_name_without_extension = Dqn_String8_BinarySplit(file_name, DQN_STRING8(".")).lhs;
Dqn_Fs_WriteFileF(&meta_file, "%.*s;%d;%d;%d;%d", DQN_STRING_FMT(file_name_without_extension), packed_rect.x, packed_rect.y, packed_rect.w, packed_rect.h);
if (it.index != (file_list.count - 1))
Dqn_Fs_WriteFileF(&meta_file, "\n");
}
Dqn_String8 atlas_path = Dqn_String8_InitF(scratch.allocator, "%.*s.png", DQN_STRING_FMT(dir));
Dqn_Log_InfoF("Generating atlas: %.*s", DQN_STRING_FMT(atlas_path));
stbi_write_png(atlas_path.data, atlas_size, atlas_size, 4, final_image, final_image_stride);
return 0;
}
BIN
View File
Binary file not shown.