diff --git a/frontend/src/game/engine.ts b/frontend/src/game/engine.ts index 6b196ac..2bf167c 100644 --- a/frontend/src/game/engine.ts +++ b/frontend/src/game/engine.ts @@ -440,8 +440,8 @@ export class GameEngine { if (isBlocked || isEvaded) { this._emitDamage( 0, - defender === 'enemy' ? viewport.width / 2 + 60 : viewport.width / 2 - 60, - viewport.height / 2 - 30, + defender === 'enemy' ? viewport.width / 2 + 88 : viewport.width / 2 - 88, + viewport.height / 2 - 42, false, isBlocked ? 'blocked' : 'evaded', defender, @@ -449,8 +449,8 @@ export class GameEngine { } else { this._emitDamage( damage, - defender === 'enemy' ? viewport.width / 2 + 60 : viewport.width / 2 - 60, - viewport.height / 2 - 30, + defender === 'enemy' ? viewport.width / 2 + 88 : viewport.width / 2 - 88, + viewport.height / 2 - 42, source === 'hero' ? isCrit : false, 'damage', defender, @@ -473,8 +473,8 @@ export class GameEngine { const viewport = getViewport(); this._emitDamage( amount, - viewport.width / 2 + 60, - viewport.height / 2 - 30, + viewport.width / 2 + 88, + viewport.height / 2 - 42, false, 'regen', 'enemy', diff --git a/frontend/src/shared/constants.ts b/frontend/src/shared/constants.ts index 0860ea6..1fecfcf 100644 --- a/frontend/src/shared/constants.ts +++ b/frontend/src/shared/constants.ts @@ -47,10 +47,13 @@ export const WS_HEARTBEAT_TIMEOUT_MS = 5000; export const MAX_ACCUMULATED_MS = 250; /** Floating damage number duration in milliseconds */ -export const DAMAGE_NUMBER_DURATION_MS = 1800; +export const DAMAGE_NUMBER_DURATION_MS = 2600; -/** Floating damage rise distance in pixels */ -export const DAMAGE_NUMBER_RISE_PX = 60; +/** Floating damage rise distance in pixels (vertical flight from anchor) */ +export const DAMAGE_NUMBER_RISE_PX = 96; + +/** Horizontal drift during float (per target side; scales with progress) */ +export const DAMAGE_NUMBER_DRIFT_PX = 44; /** Buff cooldown overlay animation fps */ export const BUFF_OVERLAY_FPS = 30; diff --git a/frontend/src/ui/FloatingDamage.tsx b/frontend/src/ui/FloatingDamage.tsx index 9832be4..da7fe6f 100644 --- a/frontend/src/ui/FloatingDamage.tsx +++ b/frontend/src/ui/FloatingDamage.tsx @@ -1,5 +1,9 @@ -import { useEffect, useState, type CSSProperties } from 'react'; -import { DAMAGE_NUMBER_DURATION_MS, DAMAGE_NUMBER_RISE_PX } from '../shared/constants'; +import { useCallback, useEffect, useState, type CSSProperties } from 'react'; +import { + DAMAGE_NUMBER_DURATION_MS, + DAMAGE_NUMBER_DRIFT_PX, + DAMAGE_NUMBER_RISE_PX, +} from '../shared/constants'; import type { FloatingDamageData } from '../game/types'; interface FloatingDamageProps { @@ -34,6 +38,8 @@ function DamageNumber({ data, onExpire }: DamageNumberProps) { return () => cancelAnimationFrame(rafId); }, [data.createdAt, data.id, onExpire]); + const driftDir = data.target === 'enemy' ? 1 : -1; + const offsetX = progress * DAMAGE_NUMBER_DRIFT_PX * driftDir; const offsetY = -progress * DAMAGE_NUMBER_RISE_PX; const opacity = 1 - progress * progress; // ease-out fade const scale = data.isCrit && data.kind === 'damage' ? 1.4 - progress * 0.4 : 1; @@ -47,7 +53,7 @@ function DamageNumber({ data, onExpire }: DamageNumberProps) { const style: CSSProperties = { position: 'absolute', - left: data.x, + left: data.x + offsetX, top: data.y + offsetY, transform: `translate(-50%, -50%) scale(${scale})`, opacity, @@ -90,9 +96,9 @@ export function FloatingDamage({ damages }: FloatingDamageProps) { }); }, [damages]); - const handleExpire = (id: number) => { + const handleExpire = useCallback((id: number) => { setActiveDamages((prev) => prev.filter((d) => d.id !== id)); - }; + }, []); return (