From e7641fa9a9374560080b30d2828c4e4c8894a90f Mon Sep 17 00:00:00 2001 From: doyle Date: Sat, 16 Sep 2023 17:32:25 +1000 Subject: [PATCH] fpona: Add equations of motion --- External/tely | 2 +- feely_pona.cpp | 486 +++++++++++++++++++++++++++++++++---------------- 2 files changed, 330 insertions(+), 158 deletions(-) diff --git a/External/tely b/External/tely index b59630f..9b91e7e 160000 --- a/External/tely +++ b/External/tely @@ -1 +1 @@ -Subproject commit b59630fb8f3ecaa4be1382d9bdcc236afea3ce93 +Subproject commit 9b91e7e1b9bb3a6eb103aeb5de457c4a56fee331 diff --git a/feely_pona.cpp b/feely_pona.cpp index d822e44..ec4a24f 100644 --- a/feely_pona.cpp +++ b/feely_pona.cpp @@ -165,17 +165,31 @@ void TELY_DLL_Init(void *user_data) DQN_ASSERT(game->root_entity->parent == nullptr); } - // NOTE: Test sprite animation entity + // NOTE: Hero { - TELY_GameEntity *first_entity = TELY_Game_MakeEntityPointerF(game, "Hero"); - first_entity->local_pos = Dqn_V2_InitNx2(100.f, 100.f); - first_entity->size_scale = Dqn_V2_InitNx1(4); - first_entity->sprite_sheet = &pona->hero_sprite_sheet; - first_entity->sprite_anims = pona->hero_sprite_anims; - first_entity->local_hit_box_size = Dqn_V2_InitV2I(pona->hero_sprite_sheet.sprite_size); - first_entity->flags |= TELY_EntityFlag_Clickable; - first_entity->flags |= TELY_EntityFlag_MoveByKeyboard; - first_entity->flags |= TELY_EntityFlag_MoveByMouse; + TELY_GameEntity *hero = TELY_Game_MakeEntityPointerF(game, "Hero"); + hero->local_pos = Dqn_V2_InitNx2(100.f, 100.f); + hero->size_scale = Dqn_V2_InitNx1(4); + hero->sprite_sheet = &pona->hero_sprite_sheet; + hero->sprite_anims = pona->hero_sprite_anims; + hero->local_hit_box_size = Dqn_V2_InitV2I(pona->hero_sprite_sheet.sprite_size); + hero->flags |= TELY_EntityFlag_Clickable; + hero->flags |= TELY_EntityFlag_MoveByKeyboard; + hero->flags |= TELY_EntityFlag_MoveByMouse; + game->clicked_entity = hero->handle; + } + + // NOTE: Enemy + { + TELY_GameEntity *enemy = TELY_Game_MakeEntityPointerF(game, "Enemy"); + enemy->local_pos = Dqn_V2_InitNx2(300.f, 300.f); + enemy->size_scale = Dqn_V2_InitNx1(4); + enemy->sprite_sheet = &pona->hero_sprite_sheet; + enemy->sprite_anims = pona->hero_sprite_anims; + enemy->local_hit_box_size = Dqn_V2_InitV2I(pona->hero_sprite_sheet.sprite_size); + enemy->flags |= TELY_EntityFlag_Clickable; + enemy->flags |= TELY_EntityFlag_MoveByKeyboard; + enemy->flags |= TELY_EntityFlag_MoveByMouse; } uint16_t font_size = 18; @@ -196,6 +210,308 @@ void TELY_DLL_Init(void *user_data) ui->arena.allocs_are_allowed_to_leak = true; } +void TELY_Game_EntityChangeState(TELY_GameEntity *entity, TELY_GameEntityState state) +{ + if (entity->state == state) + return; + entity->state = state; + entity->anim.frame = 0; + entity->anim.ticks = 0; +} + +void FP_GameUpdate(TELY_Game *game, TELY_Renderer *renderer, TELY_PlatformInput *input) +{ + if (TELY_Platform_InputKeyIsReleased(input->mouse_left)) + game->clicked_entity = game->prev_active_entity; + + Dqn_V2 dir_vector = {}; + if (TELY_Platform_InputScanCodeIsDown(input, TELY_PlatformInputScanCode_W)) + dir_vector.y = -1.f; + if (TELY_Platform_InputScanCodeIsDown(input, TELY_PlatformInputScanCode_A)) + dir_vector.x = -1.f; + if (TELY_Platform_InputScanCodeIsDown(input, TELY_PlatformInputScanCode_S)) + dir_vector.y = +1.f; + if (TELY_Platform_InputScanCodeIsDown(input, TELY_PlatformInputScanCode_D)) + dir_vector.x = +1.f; + + if (game->clicked_entity.id) { + if (TELY_Platform_InputScanCodeIsPressed(input, TELY_PlatformInputScanCode_Delete)) + TELY_Game_DeleteEntity(game, game->clicked_entity); + + TELY_GameEntity *player = TELY_Game_GetEntity(game, game->clicked_entity); + if (player) { + if (TELY_Platform_InputScanCodeIsDown(input, TELY_PlatformInputScanCode_J)) { + TELY_Game_EntityChangeState(player, TELY_GameEntityState_Attack); + } + + if (player->state != TELY_GameEntityState_Attack) { + if (dir_vector.x || dir_vector.y) { + TELY_Game_EntityChangeState(player, TELY_GameEntityState_Run); + } else { + TELY_Game_EntityChangeState(player, TELY_GameEntityState_Idle); + } + } + } + } else { + game->camera.world_pos += dir_vector * 5.f; + } + + for (TELY_GameEntityIterator it = {}; TELY_Game_DFSPostOrderWalkEntityTree(game, &it, game->root_entity); ) { + TELY_GameEntity *entity = it.entity; + entity->alive_time_s += input->delta_s; + + // NOTE: Move entity by keyboard =========================================================== + Dqn_V2 acceleration = {}; + if (game->clicked_entity == entity->handle) { + if (entity->flags & TELY_EntityFlag_MoveByKeyboard) { + acceleration = dir_vector * 10000000.f; + if (dir_vector.x) + entity->facing_left = dir_vector.x < 0.f; + } + } + + // NOTE: Core equations of motion ========================================================== + { + // f"(t) = a + // f'(t) = at + v + // f (t) = 0.5f*a(t^2) + vt + p + Dqn_f32 t = DQN_CAST(Dqn_f32)DQN_SQUARED(input->delta_s); + Dqn_f32 t_squared = DQN_SQUARED(t); + entity->velocity = (acceleration * t) + entity->velocity * 0.82f; + entity->local_pos += (acceleration * 0.5f * t_squared) + (entity->velocity * t); + } + + // NOTE: Move entity by mouse ============================================================== + if (game->active_entity == entity->handle && entity->flags & TELY_EntityFlag_MoveByMouse) { + if (entity->flags & TELY_EntityFlag_MoveByMouse) { + entity->velocity = {}; + entity->local_pos += input->mouse_p_delta; + } + } + + if (entity->flags & TELY_EntityFlag_DeriveHitBoxFromChildrenBoundingBox) { + Dqn_Rect children_bbox = {}; + + // TODO(doyle): Is the hit box supposed to include the containing + // entity itself? Not sure + children_bbox.pos = TELY_Game_CalcEntityWorldPos(game, entity->handle); + + for (TELY_GameEntityIterator child_it = {}; TELY_Game_DFSPreOrderWalkEntityTree(game, &child_it, entity);) { + TELY_GameEntity *child = child_it.entity; + DQN_ASSERT(child != entity); + + Dqn_Rect bbox = TELY_Game_CalcEntityWorldBoundingBox(game, child->handle); + children_bbox = Dqn_Rect_Union(children_bbox, bbox); + } + + Dqn_Rect padded_bbox = Dqn_Rect_Expand(children_bbox, 16.f); + entity->local_hit_box_offset = padded_bbox.pos - entity->local_pos + (padded_bbox.size * .5f); + entity->local_hit_box_size = padded_bbox.size; + } + + // NOTE: Handle animation state ============================================================ + uint16_t desired_anim_index = 0; + switch (entity->state) { + case TELY_GameEntityState_Idle: { + desired_anim_index = TELY_Asset_GetSpriteAnim(entity->sprite_anims, DQN_STRING8("Idle")); + } break; + + case TELY_GameEntityState_Attack: { + desired_anim_index = TELY_Asset_GetSpriteAnim(entity->sprite_anims, DQN_STRING8("Attack A")); + } break; + + case TELY_GameEntityState_Run: { + desired_anim_index = TELY_Asset_GetSpriteAnim(entity->sprite_anims, DQN_STRING8("Run")); + } break; + } + + { + TELY_AssetSpriteAnimation const *sprite_anim = entity->sprite_anims.data + entity->anim.index; + if (desired_anim_index == entity->anim.index) { // NOTE: Current anim matches, check if its finished + if (entity->anim.frame >= sprite_anim->count) { // NOTE: Animation is finished + entity->anim.frame = 0; + entity->anim.ticks = 0; + switch (entity->state) { + case TELY_GameEntityState_Idle: { + } break; + + case TELY_GameEntityState_Attack: { + entity->state = TELY_GameEntityState_Idle; + } break; + + case TELY_GameEntityState_Run: { + if (dir_vector.x == 0 && dir_vector.y == 0) { + entity->state = TELY_GameEntityState_Idle; + } + } break; + } + } else { + if (entity->anim.ticks++ > 4 /*ticks_per_anim_frame*/) { + entity->anim.frame++; + entity->anim.ticks = 0; + } + } + } else { + // NOTE: Current animation does not match the desired animation, change anim + entity->anim.index = desired_anim_index; + } + } + + // NOTE: Calculate entity attack box ======================================================= + if (entity->state == TELY_GameEntityState_Attack) { + + 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) { + entity->attack_box_offset = Dqn_V2_InitNx2(entity->local_hit_box_offset.x - entity->attack_box_size.w, + entity->local_hit_box_offset.y); + } else { + entity->attack_box_offset = Dqn_V2_InitNx2(entity->local_hit_box_offset.x + entity->local_hit_box_size.w, + entity->local_hit_box_offset.y); + } + } else { + entity->attack_box_size = {}; + } + } + + // NOTE: Resolve collision ==================================================================== + for (TELY_GameEntityIterator attacker_it = {}; TELY_Game_DFSPostOrderWalkEntityTree(game, &attacker_it, game->root_entity); ) { + TELY_GameEntity *attacker = attacker_it.entity; + + // NOTE: Resolve box collision + Dqn_Rect attacker_hit_box = TELY_Game_CalcEntityWorldHitBox(game, attacker->handle); + + // NOTE: Resolve attack boxes + if (Dqn_V2_Area(attacker->attack_box_size)) { + Dqn_Rect attacker_box = TELY_Game_CalcEntityAttackWorldHitBox(game, attacker->handle); + for (TELY_GameEntityIterator defender_it = {}; TELY_Game_DFSPostOrderWalkEntityTree(game, &defender_it, game->root_entity); ) { + TELY_GameEntity *defender = defender_it.entity; + if (defender->handle == attacker->handle) + continue; + + Dqn_Rect defender_box = TELY_Game_CalcEntityWorldHitBox(game, defender->handle); + Dqn_Rect hit_rect = Dqn_Rect_Intersection(attacker_box, defender_box); + + if (!Dqn_Rect_Area(hit_rect)) + continue; + + TELY_Render_CircleColourV4(renderer, hit_rect.pos, 10.f, TELY_RenderShapeMode_Fill, TELY_COLOUR_MAGENTA_V4); + Dqn_V2 attacker_center_pos = Dqn_Rect_Center(attacker_box); + Dqn_V2 defender_center_pos = Dqn_Rect_Center(defender_box); + Dqn_V2 attacker_to_defender = defender_center_pos - attacker_center_pos; + Dqn_V2 attacker_to_defender_norm = Dqn_V2_Normalise(attacker_to_defender); + + TELY_Render_LineColourV4(renderer, defender_center_pos, defender_center_pos + (attacker_to_defender_norm * 100.f), TELY_COLOUR_RED_V4, 3.f); + + Dqn_V2 acceleration = attacker_to_defender_norm * 10000000.f; + Dqn_f32 t = DQN_CAST(Dqn_f32)DQN_SQUARED(input->delta_s); + Dqn_f32 t_squared = DQN_SQUARED(t); + + Dqn_V2 delta_p = (acceleration * 0.5f * t_squared) + (defender->velocity * t); + defender->velocity = (acceleration * t) + defender->velocity * 2.0f; + defender->local_pos += delta_p; + } + } + } +} + +void FP_GameRender(TELY_Game *game, TELY_Platform *platform, TELY_Renderer *renderer) +{ + TELY_PlatformInput *input = &platform->input; + Dqn_M2x3 model_view = TELY_Game_CameraModelViewM2x3(game->camera, platform); + Dqn_V2 world_mouse_p = Dqn_M2x3_MulV2(model_view, input->mouse_p); + + for (TELY_GameEntityIterator it = {}; TELY_Game_DFSPostOrderWalkEntityTree(game, &it, game->root_entity); ) { + TELY_GameEntity *entity = it.entity; + entity->alive_time_s += input->delta_s; + + // NOTE: Render shapes in entity =========================================================== + Dqn_V2 world_pos = TELY_Game_CalcEntityWorldPos(game, entity->handle); + for (TELY_GameShape const &shape_ : entity->shapes) { + TELY_GameShape const *shape = &shape_; + Dqn_V2 local_to_world_p1 = world_pos + shape->p1; + Dqn_V2 local_to_world_p2 = world_pos + shape->p2; + switch (shape->type) { + case TELY_GameShapeType_None: { + } break; + + case TELY_GameShapeType_Circle: { + TELY_Render_CircleColourV4(renderer, local_to_world_p1, shape->circle_radius, shape->render_mode, shape->colour); + } break; + + case TELY_GameShapeType_Rect: { + Dqn_Rect rect = Dqn_Rect_InitV2x2(local_to_world_p1, local_to_world_p2 - local_to_world_p1); + rect.pos -= rect.size * .5f; + TELY_Render_RectColourV4(renderer, rect, shape->render_mode, shape->colour); + } break; + + case TELY_GameShapeType_Line: { + TELY_Render_LineColourV4(renderer, local_to_world_p1, local_to_world_p2, shape->colour, shape->line_thickness); + } break; + } + } + + // NOTE: Render entity sprites ============================================================= + if (entity->sprite_sheet && entity->sprite_anims.size) { + TELY_AssetSpriteSheet const *sprite_sheet = entity->sprite_sheet; + TELY_AssetSpriteAnimation const *sprite_anim = entity->sprite_anims.data + entity->anim.index; + + Dqn_usize sprite_index = (sprite_anim->index + (entity->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; + + 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; + + Dqn_Rect dest_rect = {}; + dest_rect.size = src_rect.size * entity->size_scale; + dest_rect.pos = world_pos - (dest_rect.size * .5f); + + if (entity->facing_left) + dest_rect.size.w *= -1.f; // NOTE: Flip the texture horizontally + + TELY_Render_TextureColourV4(renderer, sprite_sheet->tex_handle, src_rect, dest_rect, TELY_COLOUR_WHITE_V4); + } + + // NOTE: Render attack box ================================================================= + { + Dqn_Rect attack_box = TELY_Game_CalcEntityAttackWorldHitBox(game, entity->handle); + TELY_Render_RectColourV4(renderer, attack_box, TELY_RenderShapeMode_Line, TELY_COLOUR_RED_TOMATO_V4); + } + + // NOTE: Render world position ============================================================= + TELY_Render_CircleColourV4(renderer, world_pos, 4.f, TELY_RenderShapeMode_Fill, TELY_COLOUR_RED_TOMATO_V4); + + // NOTE: Render hot/active entity ========================================================== + Dqn_Rect world_hit_box = TELY_Game_CalcEntityWorldHitBox(game, entity->handle); + if (game->clicked_entity == entity->handle) { + TELY_Render_RectColourV4(renderer, world_hit_box, TELY_RenderShapeMode_Line, TELY_COLOUR_WHITE_PALE_GOLDENROD_V4); + } else if (game->hot_entity == entity->handle || (entity->flags & TELY_EntityFlag_DrawHitBox)) { + Dqn_V4 hot_colour = game->hot_entity == entity->handle ? TELY_COLOUR_RED_TOMATO_V4 : TELY_Colour_V4Alpha(TELY_COLOUR_YELLOW_SANDY_V4, .5f); + TELY_Render_RectColourV4(renderer, world_hit_box, TELY_RenderShapeMode_Line, hot_colour); + + if (game->hot_entity == entity->handle && (entity->name.size)) { + Dqn_V2 entity_world_pos = TELY_Game_CalcEntityWorldPos(game, entity->handle); + Dqn_ThreadScratch scratch = Dqn_Thread_GetScratch(nullptr); + Dqn_String8 label = Dqn_String8_InitF(scratch.allocator, + "%.*s (%.1f, %.1f)", + DQN_STRING_FMT(entity->name), + entity_world_pos.x, + entity_world_pos.y); + TELY_Render_Text(renderer, world_mouse_p, Dqn_V2_InitNx2(0.f, 1), label); + } + } + } +} + extern "C" __declspec(dllexport) void TELY_DLL_FrameUpdate(void *user_data) { @@ -254,17 +570,7 @@ void TELY_DLL_FrameUpdate(void *user_data) Dqn_FArray_Clear(&game->parent_entity_stack); Dqn_FArray_Add(&game->parent_entity_stack, game->root_entity->handle); - Dqn_M2x3 model_view = {}; - { - Dqn_V2 rotate_origin = game->camera.world_pos - (Dqn_V2_InitV2I(platform->core.window_size) * .5f); - model_view = Dqn_M2x3_Identity(); - model_view = Dqn_M2x3_Mul(model_view, Dqn_M2x3_Translate(rotate_origin)); - model_view = Dqn_M2x3_Mul(model_view, Dqn_M2x3_Rotate(game->camera.rotate_rads)); - model_view = Dqn_M2x3_Mul(model_view, Dqn_M2x3_Scale(game->camera.scale)); - model_view = Dqn_M2x3_Mul(model_view, Dqn_M2x3_Translate((rotate_origin * -1) + game->camera.world_pos)); - TELY_Render_PushTransform(renderer, model_view); - } - + Dqn_M2x3 model_view = TELY_Game_CameraModelViewM2x3(game->camera, platform); Dqn_V2 world_mouse_p = Dqn_M2x3_MulV2(model_view, input->mouse_p); // ============================================================================================= @@ -300,141 +606,7 @@ void TELY_DLL_FrameUpdate(void *user_data) } } - if (TELY_Platform_InputKeyIsReleased(input->mouse_left)) - game->clicked_entity = game->prev_active_entity; - - Dqn_V2 dir_vector = {}; - if (TELY_Platform_InputScanCodeIsDown(input, TELY_PlatformInputScanCode_W)) - dir_vector.y = -1.f; - if (TELY_Platform_InputScanCodeIsDown(input, TELY_PlatformInputScanCode_A)) - dir_vector.x = -1.f; - if (TELY_Platform_InputScanCodeIsDown(input, TELY_PlatformInputScanCode_S)) - dir_vector.y = +1.f; - if (TELY_Platform_InputScanCodeIsDown(input, TELY_PlatformInputScanCode_D)) - dir_vector.x = +1.f; - - if (game->clicked_entity.id) { - if (TELY_Platform_InputScanCodeIsPressed(input, TELY_PlatformInputScanCode_Delete)) - TELY_Game_DeleteEntity(game, game->clicked_entity); - } else { - game->camera.world_pos += dir_vector * 5.f; - } - - for (TELY_GameEntityIterator it = {}; TELY_Game_DFSPostOrderWalkEntityTree(game, &it, game->root_entity); ) { - TELY_GameEntity *entity = it.entity; - entity->alive_time_s += input->delta_s; - - // NOTE: Move entity by keyboard =========================================================== - if (game->clicked_entity == entity->handle) { - if (entity->flags & TELY_EntityFlag_MoveByKeyboard) { - entity->local_pos += dir_vector * DQN_CAST(Dqn_f32)(300.f * input->delta_s); - if (dir_vector.x) - entity->facing_left = dir_vector.x < 0.f; - } - } - - // NOTE: Move entity by mouse ============================================================== - if (game->active_entity == entity->handle && entity->flags & TELY_EntityFlag_MoveByMouse) { - if (entity->flags & TELY_EntityFlag_MoveByMouse) - entity->local_pos += input->mouse_p_delta; - } - - if (entity->flags & TELY_EntityFlag_DeriveHitBoxFromChildrenBoundingBox) { - Dqn_Rect children_bbox = {}; - - // TODO(doyle): Is the hit box supposed to include the containing - // entity itself? Not sure - children_bbox.pos = TELY_Game_CalcEntityWorldPos(game, entity->handle); - - for (TELY_GameEntityIterator child_it = {}; TELY_Game_DFSPreOrderWalkEntityTree(game, &child_it, entity);) { - TELY_GameEntity *child = child_it.entity; - DQN_ASSERT(child != entity); - - Dqn_Rect bbox = TELY_Game_CalcEntityWorldBoundingBox(game, child->handle); - children_bbox = Dqn_Rect_Union(children_bbox, bbox); - } - - Dqn_Rect padded_bbox = Dqn_Rect_Expand(children_bbox, 16.f); - entity->local_hit_box_offset = padded_bbox.pos - entity->local_pos + (padded_bbox.size * .5f); - entity->local_hit_box_size = padded_bbox.size; - } - - // NOTE: Render shapes in entity =========================================================== - Dqn_V2 world_pos = TELY_Game_CalcEntityWorldPos(game, entity->handle); - for (TELY_GameShape const &shape_ : entity->shapes) { - TELY_GameShape const *shape = &shape_; - Dqn_V2 local_to_world_p1 = world_pos + shape->p1; - Dqn_V2 local_to_world_p2 = world_pos + shape->p2; - switch (shape->type) { - case TELY_GameShapeType_None: { - } break; - - case TELY_GameShapeType_Circle: { - TELY_Render_CircleColourV4(renderer, local_to_world_p1, shape->circle_radius, shape->render_mode, shape->colour); - } break; - - case TELY_GameShapeType_Rect: { - Dqn_Rect rect = Dqn_Rect_InitV2x2(local_to_world_p1, local_to_world_p2 - local_to_world_p1); - rect.pos -= rect.size * .5f; - TELY_Render_RectColourV4(renderer, rect, shape->render_mode, shape->colour); - } break; - - case TELY_GameShapeType_Line: { - TELY_Render_LineColourV4(renderer, local_to_world_p1, local_to_world_p2, shape->colour, shape->line_thickness); - } break; - } - } - - // NOTE: Render entity sprites ============================================================= - if (entity->sprite_sheet && entity->sprite_anims.size) { - TELY_AssetSpriteSheet const *sprite_sheet = entity->sprite_sheet; - TELY_AssetSpriteAnimation const *sprite_anim = entity->sprite_anims.data + entity->sprite_anim_index; - uint32_t ticks_per_anim_frame = 10; - uint64_t anim_frame_counter = input->frame_counter / ticks_per_anim_frame; - - Dqn_usize sprite_index = (sprite_anim->index + (anim_frame_counter % 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; - - 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; - - Dqn_Rect dest_rect = {}; - dest_rect.size = src_rect.size * entity->size_scale; - dest_rect.pos = world_pos - (dest_rect.size * .5f); - - if (entity->facing_left) - dest_rect.size.w *= -1.f; // NOTE: Flip the texture horizontally - - TELY_Render_TextureColourV4(renderer, sprite_sheet->tex_handle, src_rect, dest_rect, TELY_COLOUR_WHITE_V4); - } - - // NOTE: Render world position ============================================================= - TELY_Render_CircleColourV4(renderer, world_pos, 4.f, TELY_RenderShapeMode_Fill, TELY_COLOUR_RED_TOMATO_V4); - - // NOTE: Render hot/active entity ========================================================== - Dqn_Rect world_hit_box = TELY_Game_CalcEntityWorldHitBox(game, entity->handle); - if (game->clicked_entity == entity->handle) { - TELY_Render_RectColourV4(renderer, world_hit_box, TELY_RenderShapeMode_Line, TELY_COLOUR_WHITE_PALE_GOLDENROD_V4); - } else if (game->hot_entity == entity->handle || (entity->flags & TELY_EntityFlag_DrawHitBox)) { - Dqn_V4 hot_colour = game->hot_entity == entity->handle ? TELY_COLOUR_RED_TOMATO_V4 : TELY_Colour_V4Alpha(TELY_COLOUR_YELLOW_SANDY_V4, .5f); - TELY_Render_RectColourV4(renderer, world_hit_box, TELY_RenderShapeMode_Line, hot_colour); - - if (game->hot_entity == entity->handle && (entity->name.size)) { - Dqn_V2 entity_world_pos = TELY_Game_CalcEntityWorldPos(game, entity->handle); - Dqn_ThreadScratch scratch = Dqn_Thread_GetScratch(nullptr); - Dqn_String8 label = Dqn_String8_InitF(scratch.allocator, - "%.*s (%.1f, %.1f)", - DQN_STRING_FMT(entity->name), - entity_world_pos.x, - entity_world_pos.y); - TELY_Render_Text(renderer, world_mouse_p, Dqn_V2_InitNx2(0.f, 1), label); - } - } - } - + FP_GameUpdate(game, renderer, input); + FP_GameRender(game, platform, renderer); TELY_Audio_MixPlaybackSamples(audio, assets); }