fp: Add hit particles

This commit is contained in:
doyle 2023-10-22 21:17:25 +11:00
parent de7d8c1341
commit 8d01a3631f
4 changed files with 127 additions and 6 deletions

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

Binary file not shown.

View File

@ -3,6 +3,37 @@
#include "feely_pona_unity.h"
#endif
struct FP_ParticleDescriptor
{
Dqn_String8 anim_name;
Dqn_V2 pos;
Dqn_V2 velocity;
Dqn_V2 velocity_variance;
Dqn_V4 colour_begin;
Dqn_V4 colour_end;
Dqn_usize duration_ms;
};
static void FP_EmitParticle(FP_GamePlay *play, FP_ParticleDescriptor descriptor, Dqn_usize count)
{
DQN_FOR_UINDEX (index, count) {
uint32_t particle_index = play->particle_next_index++ & (DQN_ARRAY_UCOUNT(play->particles) - 1);
FP_Particle *particle = play->particles + particle_index;
if (particle->alive)
continue;
particle->anim_name = descriptor.anim_name;
particle->alive = true;
particle->pos = descriptor.pos;
particle->velocity.x = descriptor.velocity.x + (descriptor.velocity_variance.x * (Dqn_PCG32_NextF32(&play->rng) - 0.5f));
particle->velocity.y = descriptor.velocity.y + (descriptor.velocity_variance.y * (Dqn_PCG32_NextF32(&play->rng) - 0.5f));
particle->colour_begin = descriptor.colour_begin;
particle->colour_end = descriptor.colour_end;
particle->start_ms = play->clock_ms;
particle->end_ms = play->clock_ms + descriptor.duration_ms;
}
}
static TELY_AssetSpriteSheet FP_LoadSpriteSheetFromSpec(TELY_Platform *platform, TELY_Assets *assets, Dqn_Arena *arena, Dqn_String8 sheet_name)
{
TELY_AssetSpriteSheet result = {};
@ -2108,7 +2139,29 @@ void FP_Update(TELY_Platform *platform, FP_Game *game, TELY_PlatformInput *input
if (!Dqn_Rect_Intersects(attacker_box, defender_box))
continue;
// NOTE: Do HP =========================================================================
// NOTE: Emit hit particles ========================================================
FP_ParticleDescriptor particle_desc = {};
Dqn_usize particle_selector = Dqn_PCG32_Range(&game->play.rng, 0, 3);
if (particle_selector == 0) {
particle_desc.anim_name = g_anim_names.particle_hit_1;
} else if (particle_selector == 1) {
particle_desc.anim_name = g_anim_names.particle_hit_2;
} else {
particle_desc.anim_name = g_anim_names.particle_hit_3;
DQN_ASSERT(particle_selector == 2);
}
particle_desc.pos = Dqn_Rect_InterpolatedPoint(defender_box, Dqn_V2_InitNx2(0.5f, 0.0f));
particle_desc.velocity.y = -16.f;
particle_desc.velocity_variance.y = (particle_desc.velocity.y * .5f);
particle_desc.velocity_variance.x = DQN_ABS(particle_desc.velocity.y);
particle_desc.colour_begin = TELY_COLOUR_WHITE_V4;
particle_desc.colour_end = TELY_Colour_V4Alpha(TELY_COLOUR_WHITE_V4, 0.f);
particle_desc.duration_ms = 1000;
FP_EmitParticle(&game->play, particle_desc, Dqn_PCG32_Range(&game->play.rng, 1, 3));
// NOTE: God mode override =========================================================
if (game->play.heart == defender->handle) {
if (game->play.god_mode)
continue;
@ -2127,6 +2180,7 @@ void FP_Update(TELY_Platform *platform, FP_Game *game, TELY_PlatformInput *input
if (god_mode_override)
continue;
// NOTE: Do hit logic ==============================================================
defender->hp = defender->hp >= attacker->base_attack ? defender->hp - attacker->base_attack : 0;
defender->hit_on_clock_ms = game->play.clock_ms;
defender->trauma01 = 1.f - (defender->hp / DQN_CAST(Dqn_f32)defender->hp_cap);
@ -2176,6 +2230,17 @@ void FP_Update(TELY_Platform *platform, FP_Game *game, TELY_PlatformInput *input
game->play.global_camera_trauma01 = DQN_MAX(0.f, game->play.global_camera_trauma01 - 0.05f);
// NOTE: Update all particles ==================================================================
for (FP_Particle &particle_ : game->play.particles) {
FP_Particle *particle = &particle_;
if (game->play.clock_ms >= particle->end_ms)
particle->alive = false;
if (!particle->alive)
continue;
particle->pos += particle->velocity * DQN_CAST(Dqn_f32)input->delta_s;
}
// NOTE: Camera ================================================================================
FP_GamePlay *play = &game->play;
@ -2773,6 +2838,39 @@ void FP_Render(FP_Game *game, TELY_Platform *platform, TELY_Renderer *renderer,
}
}
for (FP_Particle &particle_ : game->play.particles) {
FP_Particle *particle = &particle_;
if (!particle->alive)
continue;
Dqn_usize elapsed_ms = game->play.clock_ms - particle->start_ms;
Dqn_usize duration_ms = particle->end_ms - particle->start_ms;
Dqn_f32 t = DQN_MIN(1.f, elapsed_ms / DQN_CAST(Dqn_f32)duration_ms);
Dqn_V4 colour = {};
colour.r = Dqn_Lerp_F32(particle->colour_begin.r, t, particle->colour_end.r);
colour.b = Dqn_Lerp_F32(particle->colour_begin.b, t, particle->colour_end.b);
colour.g = Dqn_Lerp_F32(particle->colour_begin.g, t, particle->colour_end.g);
colour.a = Dqn_Lerp_F32(particle->colour_begin.a, t, particle->colour_end.a);
if (particle->anim_name.size) {
TELY_AssetSpriteAnimation *anim = TELY_Asset_GetSpriteAnimation(&game->atlas_sprite_sheet, particle->anim_name);
Dqn_Rect tex_rect = game->atlas_sprite_sheet.rects.data[anim->index];
Dqn_Rect dest_rect = {};
dest_rect.size = tex_rect.size;
dest_rect.pos = particle->pos - (tex_rect.size * .5f);
TELY_Render_TextureColourV4(renderer,
game->atlas_sprite_sheet.tex_handle,
tex_rect,
dest_rect,
Dqn_V2_Zero /*rotate origin*/,
0.f /*rotation*/,
colour);
} else {
TELY_Render_CircleColourV4(renderer, particle->pos, 20.f, TELY_RenderShapeMode_Fill, colour);
}
}
// NOTE: Render overlay UI =====================================================================
if (!game->play.debug_hide_hud && (game->play.state == FP_GameState_Pause || game->play.state == FP_GameState_Play)) {

View File

@ -88,9 +88,17 @@ struct FP_GlobalAnimations
Dqn_String8 merchant_terry = DQN_STRING8("merchant_terry");
Dqn_String8 merchant_terry_menu = DQN_STRING8("merchant_terry_menu");
Dqn_String8 particle_drunk = DQN_STRING8("particle_drunk");
Dqn_String8 particle_heart = DQN_STRING8("particle_heart");
Dqn_String8 particle_purchase = DQN_STRING8("particle_purchase");
Dqn_String8 particle_church_halo = DQN_STRING8("particle_church_halo");
Dqn_String8 particle_church_cross = DQN_STRING8("particle_church_cross");
Dqn_String8 particle_drunk = DQN_STRING8("particle_drunk");
Dqn_String8 particle_drunk_bottle = DQN_STRING8("particle_drunk_bottle");
Dqn_String8 particle_drunk_martini = DQN_STRING8("particle_drunk_martini");
Dqn_String8 particle_heart = DQN_STRING8("particle_heart");
Dqn_String8 particle_hit_1 = DQN_STRING8("particle_hit_1");
Dqn_String8 particle_hit_2 = DQN_STRING8("particle_hit_2");
Dqn_String8 particle_hit_3 = DQN_STRING8("particle_hit_3");
Dqn_String8 particle_purchase = DQN_STRING8("particle_purchase");
Dqn_String8 portal = DQN_STRING8("portal");
Dqn_String8 portal_break = DQN_STRING8("portal_break");
Dqn_String8 portal_monk = DQN_STRING8("portal_monk");

View File

@ -351,6 +351,18 @@ enum FP_GamePerryJoined
FP_GamePerryJoins_PostEnter,
};
struct FP_Particle
{
Dqn_String8 anim_name;
bool alive;
Dqn_V2 pos;
Dqn_V2 velocity;
Dqn_V4 colour_begin;
Dqn_V4 colour_end;
Dqn_usize start_ms;
Dqn_usize end_ms;
};
struct FP_GamePlay
{
TELY_ChunkPool *chunk_pool;
@ -410,6 +422,9 @@ struct FP_GamePlay
Dqn_V2 perry_join_splash_screen_pos;
uint64_t perry_join_splash_screen_end_ms;
Dqn_V2 perry_join_splash_pos_offset;
FP_Particle particles[256];
uint32_t particle_next_index;
};
struct FP_Game