|
|
|
|
@ -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 (
|
|
|
|
|
<div style={{
|
|
|
|
|
|