Add kill heal effect animation

This commit is contained in:
Horoli 2026-05-22 13:27:19 +09:00
parent 1509b0c5dd
commit be80cb17b0
4 changed files with 78 additions and 1 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 933 B

View File

@ -33,6 +33,10 @@ export const FIGHTER_HITBOX_OFFSET_Y = 40;
export const FIGHTER_MAX_HP = 100; export const FIGHTER_MAX_HP = 100;
// 적 처치 시 현재 체력 기준으로 회복되는 비율입니다. // 적 처치 시 현재 체력 기준으로 회복되는 비율입니다.
export const KILL_HEALTH_RECOVERY_RATIO = 0.3; export const KILL_HEALTH_RECOVERY_RATIO = 0.3;
// 처치 회복 이펙트 스프라이트시트의 프레임 수입니다.
export const KILL_HEAL_EFFECT_FRAMES = 4;
// 처치 회복 이펙트 애니메이션의 초당 프레임 수입니다.
export const KILL_HEAL_EFFECT_FRAME_RATE = 12;
// 적 처치 시 크기, 공격속도, 이동속도에 누적 적용되는 배율입니다. // 적 처치 시 크기, 공격속도, 이동속도에 누적 적용되는 배율입니다.
export const KILL_GROWTH_MULTIPLIER = 1.25; export const KILL_GROWTH_MULTIPLIER = 1.25;
// 처치 성장 연출 tween 지속 시간(ms)입니다. // 처치 성장 연출 tween 지속 시간(ms)입니다.

View File

@ -33,6 +33,8 @@ import {
fighterAttackEffectAnimationKey, fighterAttackEffectAnimationKey,
fighterAttackEffectKey, fighterAttackEffectKey,
fighterProjectileKey, fighterProjectileKey,
healEffectAnimationKey,
healEffectKey,
} from "./fighterAssets.js"; } from "./fighterAssets.js";
export function updateFighter(scene, fighter, time, onWinner) { export function updateFighter(scene, fighter, time, onWinner) {
@ -357,12 +359,18 @@ function applyKillReward(winner) {
winner.killCount = (winner.killCount ?? 0) + 1; winner.killCount = (winner.killCount ?? 0) + 1;
const rewardMultiplier = KILL_GROWTH_MULTIPLIER ** winner.killCount; const rewardMultiplier = KILL_GROWTH_MULTIPLIER ** winner.killCount;
const previousHp = winner.hp;
const nextHp = recoveredHealth(winner);
winner.killRewardMultiplier = rewardMultiplier; winner.killRewardMultiplier = rewardMultiplier;
winner.hp = recoveredHealth(winner); winner.hp = nextHp;
const nextScaleX = (winner.baseScaleX ?? FIGHTER_SCALE) * rewardMultiplier; const nextScaleX = (winner.baseScaleX ?? FIGHTER_SCALE) * rewardMultiplier;
const nextScaleY = (winner.baseScaleY ?? FIGHTER_SCALE) * rewardMultiplier; const nextScaleY = (winner.baseScaleY ?? FIGHTER_SCALE) * rewardMultiplier;
if (nextHp > previousHp) {
spawnKillHealEffect(winner, Math.max(Math.abs(nextScaleX), Math.abs(nextScaleY)));
}
winner.scene.tweens.add({ winner.scene.tweens.add({
targets: winner, targets: winner,
scaleX: nextScaleX, scaleX: nextScaleX,
@ -379,6 +387,35 @@ function recoveredHealth(fighter) {
return Math.min(maxHp, fighter.hp + recovery); return Math.min(maxHp, fighter.hp + recovery);
} }
function spawnKillHealEffect(fighter, effectScale) {
const scene = fighter.scene;
const effect = scene.add
.sprite(fighter.x, fighter.y, healEffectKey())
.setDepth(fighter.depth + 1)
.setScale(effectScale);
const syncEffectPosition = () => {
if (!effect.active || !fighter.active) {
return;
}
effect.setPosition(fighter.x, fighter.y);
effect.setDepth(fighter.depth + 1);
};
effect.cleanup = () => {
scene.events.off(Phaser.Scenes.Events.UPDATE, syncEffectPosition);
};
scene.events.on(Phaser.Scenes.Events.UPDATE, syncEffectPosition);
effect.play(healEffectAnimationKey());
trackCombatObject(scene, effect);
effect.once(Phaser.Animations.Events.ANIMATION_COMPLETE, () => {
disposeCombatObject(scene, effect);
});
}
function findNearestEnemy(fighters, fighter) { function findNearestEnemy(fighters, fighter) {
let nearestEnemy; let nearestEnemy;
let nearestDistance = Number.POSITIVE_INFINITY; let nearestDistance = Number.POSITIVE_INFINITY;

View File

@ -2,6 +2,8 @@ import {
FIGHTER_ANIMATION_OPTIONS, FIGHTER_ANIMATION_OPTIONS,
FIGHTER_FRAME_HEIGHT, FIGHTER_FRAME_HEIGHT,
FIGHTER_FRAME_WIDTH, FIGHTER_FRAME_WIDTH,
KILL_HEAL_EFFECT_FRAME_RATE,
KILL_HEAL_EFFECT_FRAMES,
SELECTED_FIGHTER_OUTLINE_ALPHA, SELECTED_FIGHTER_OUTLINE_ALPHA,
SELECTED_FIGHTER_OUTLINE_BLUE, SELECTED_FIGHTER_OUTLINE_BLUE,
SELECTED_FIGHTER_OUTLINE_GAP, SELECTED_FIGHTER_OUTLINE_GAP,
@ -11,6 +13,9 @@ import {
} from "../constants.js"; } from "../constants.js";
const SOURCE_ALPHA_THRESHOLD = 8; const SOURCE_ALPHA_THRESHOLD = 8;
const HEAL_EFFECT_PATH = "assets/effects/heal/Heal_Effect.png";
const HEAL_EFFECT_KEY = "kill-heal-effect";
const HEAL_EFFECT_ANIMATION_KEY = `${HEAL_EFFECT_KEY}-anim`;
export function fighterSheetKey(skin, action) { export function fighterSheetKey(skin, action) {
return `${skin.key}-${action}`; return `${skin.key}-${action}`;
@ -40,7 +45,20 @@ export function fighterProjectileKey(skin) {
return `${skin.key}-projectile`; return `${skin.key}-projectile`;
} }
export function healEffectKey() {
return HEAL_EFFECT_KEY;
}
export function healEffectAnimationKey() {
return HEAL_EFFECT_ANIMATION_KEY;
}
export function preloadFighterSheets(scene, skins) { export function preloadFighterSheets(scene, skins) {
scene.load.spritesheet(healEffectKey(), HEAL_EFFECT_PATH, {
frameWidth: FIGHTER_FRAME_WIDTH,
frameHeight: FIGHTER_FRAME_HEIGHT,
});
skins.forEach((skin) => { skins.forEach((skin) => {
Object.entries(skin.animations).forEach(([action, animation]) => { Object.entries(skin.animations).forEach(([action, animation]) => {
scene.load.spritesheet( scene.load.spritesheet(
@ -78,6 +96,8 @@ export function createFighterAnimations(scene, skins) {
createAttackEffectAnimation(scene, skin); createAttackEffectAnimation(scene, skin);
}); });
createHealEffectAnimation(scene);
} }
function preloadCombatAssets(scene, skin) { function preloadCombatAssets(scene, skin) {
@ -121,6 +141,22 @@ function createAttackEffectAnimation(scene, skin) {
}); });
} }
function createHealEffectAnimation(scene) {
if (scene.anims.exists(healEffectAnimationKey())) {
return;
}
scene.anims.create({
key: healEffectAnimationKey(),
frames: scene.anims.generateFrameNumbers(healEffectKey(), {
start: 0,
end: KILL_HEAL_EFFECT_FRAMES - 1,
}),
frameRate: KILL_HEAL_EFFECT_FRAME_RATE,
repeat: 0,
});
}
function createFighterOutlineSheet(scene, skin, action, frameCount) { function createFighterOutlineSheet(scene, skin, action, frameCount) {
const key = fighterOutlineSheetKey(skin, action); const key = fighterOutlineSheetKey(skin, action);