175 lines
5.3 KiB
JavaScript
175 lines
5.3 KiB
JavaScript
import {memo, useCallback, useRef} from 'react';
|
|
|
|
import {DamageTypes} from '@/ecs/components/vulnerable.js';
|
|
import {useEcsTick} from '@/react/context/ecs.js';
|
|
import useAnimationFrame from '@/react/hooks/use-animation-frame.js';
|
|
import {easeInOutExpo, easeInQuint, easeOutQuad, linear} from '@/util/easing.js';
|
|
|
|
import styles from './damages.module.css';
|
|
|
|
function damageHue(type) {
|
|
let hue;
|
|
switch(type) {
|
|
case DamageTypes.PAIN: {
|
|
hue = [0, 30];
|
|
break;
|
|
}
|
|
case DamageTypes.HEALING: {
|
|
hue = [90, 120];
|
|
break;
|
|
}
|
|
case DamageTypes.MANA: {
|
|
hue = [215, 220];
|
|
break;
|
|
}
|
|
}
|
|
return hue;
|
|
}
|
|
|
|
class Damage {
|
|
elapsed = 0;
|
|
hue = [0, 30];
|
|
offsetX = 0;
|
|
offsetY = 0;
|
|
step = 0;
|
|
constructor() {
|
|
const element = document.createElement('div');
|
|
element.classList.add(styles.damage);
|
|
element.appendChild(document.createElement('p'));
|
|
this.element = element;
|
|
}
|
|
reset(key, scale, {amount, position, type}) {
|
|
this.elapsed = 0;
|
|
[this.hueStart, this.hueEnd] = damageHue(type);
|
|
this.offsetX = 20 * scale * 1 * (Math.random() - 0.5);
|
|
this.offsetY = -1 * (10 + Math.random() * 45 * scale);
|
|
this.step = 0;
|
|
const {element} = this;
|
|
element.style.setProperty('rotate', `${Math.random() * (Math.PI / 16) - (Math.PI / 32)}rad`);
|
|
element.style.setProperty('--magnitude', Math.max(1, Math.floor(Math.log10(Math.abs(amount)))));
|
|
element.style.setProperty('--positionX', `${position.x * scale}px`);
|
|
element.style.setProperty('--positionY', `${position.y * scale}px`);
|
|
element.style.setProperty('zIndex', key);
|
|
const p = element.querySelector('p');
|
|
p.style.scale = scale / 2;
|
|
p.innerText = Math.abs(amount);
|
|
}
|
|
tick(elapsed, stepSize) {
|
|
this.elapsed += elapsed;
|
|
this.step += elapsed;
|
|
if (this.step > stepSize) {
|
|
this.step = this.step % stepSize;
|
|
// offset
|
|
let offsetX = 0, offsetY = 0;
|
|
if (this.elapsed <= 0.5) {
|
|
offsetX = easeOutQuad(this.elapsed, 0, this.offsetX, 0.5);
|
|
offsetY = easeOutQuad(this.elapsed, 0, this.offsetY, 0.5);
|
|
}
|
|
else if (this.elapsed > 1.375) {
|
|
offsetX = easeOutQuad(this.elapsed - 1.375, this.offsetX, -this.offsetX, 0.125);
|
|
offsetY = easeOutQuad(this.elapsed - 1.375, this.offsetY, -this.offsetY, 0.125);
|
|
}
|
|
else {
|
|
offsetX = this.offsetX;
|
|
offsetY = this.offsetY;
|
|
}
|
|
this.element.style.setProperty('--offsetX', offsetX);
|
|
this.element.style.setProperty('--offsetY', offsetY);
|
|
// scale
|
|
let scale = 0.35;
|
|
if (this.elapsed <= 0.5) {
|
|
scale = easeOutQuad(this.elapsed, 0, 1, 0.5);
|
|
}
|
|
else if (this.elapsed > 0.5 && this.elapsed < 0.6) {
|
|
scale = linear(this.elapsed - 0.5, 1, 0.5, 0.1);
|
|
}
|
|
else if (this.elapsed > 0.6 && this.elapsed < 0.675) {
|
|
scale = easeInQuint(this.elapsed - 0.6, 1, 0.25, 0.075);
|
|
}
|
|
else if (this.elapsed > 0.675 && this.elapsed < 1.375) {
|
|
scale = 1;
|
|
}
|
|
else if (this.elapsed > 1.375) {
|
|
scale = easeOutQuad(this.elapsed - 1.375, 1, -1, 0.125);
|
|
}
|
|
this.element.style.setProperty('--scale', scale);
|
|
// opacity
|
|
let opacity = 0.75;
|
|
if (this.elapsed <= 0.375) {
|
|
opacity = linear(this.elapsed, 0.75, 0.25, 0.375);
|
|
}
|
|
else if (this.elapsed > 0.375 && this.elapsed < 1.375) {
|
|
opacity = 1;
|
|
}
|
|
else if (this.elapsed > 1.375) {
|
|
opacity = linear(this.elapsed - 1.375, 1, -1, 0.125);
|
|
}
|
|
this.element.style.setProperty('--opacity', opacity);
|
|
// hue
|
|
this.element.style.setProperty(
|
|
'--hue',
|
|
easeInOutExpo(
|
|
Math.abs((this.elapsed % 0.3) - 0.15) / 0.15,
|
|
this.hueStart,
|
|
this.hueEnd,
|
|
1,
|
|
),
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
function Damages({scale}) {
|
|
const damages = useRef({});
|
|
const damagesRef = useRef();
|
|
const pool = useRef([]);
|
|
const onEcsTick = useCallback((payload) => {
|
|
for (const id in payload) {
|
|
const update = payload[id];
|
|
if (false === update || !update.Vulnerable?.damage) {
|
|
continue;
|
|
}
|
|
const {damage: damageUpdate} = update.Vulnerable;
|
|
for (const key in damageUpdate) {
|
|
const damage = pool.current.length > 0 ? pool.current.pop() : new Damage();
|
|
damage.reset(key, scale, damageUpdate[key]);
|
|
damages.current[key] = damage;
|
|
damagesRef.current.appendChild(damage.element);
|
|
}
|
|
}
|
|
}, [scale]);
|
|
useEcsTick(onEcsTick);
|
|
const frame = useCallback((elapsed) => {
|
|
if (!damagesRef.current) {
|
|
return;
|
|
}
|
|
const keys = Object.keys(damages.current);
|
|
if (0 === keys.length && 0 === pool.current.length) {
|
|
for (let i = 0; i < 500; ++i) {
|
|
pool.current.push(new Damage());
|
|
}
|
|
}
|
|
const stepSize = keys.length > 150 ? (keys.length / 500) * 0.25 : elapsed;
|
|
for (const key of keys) {
|
|
const damage = damages.current[key];
|
|
damage.tick(elapsed, stepSize);
|
|
if (damage.elapsed > 1.5) {
|
|
damagesRef.current.removeChild(damage.element);
|
|
delete damages.current[key];
|
|
if (pool.current.length < 1000) {
|
|
pool.current.push(damage);
|
|
}
|
|
}
|
|
}
|
|
}, []);
|
|
useAnimationFrame(frame);
|
|
return (
|
|
<div
|
|
className={styles.damages}
|
|
ref={damagesRef}
|
|
/>
|
|
);
|
|
}
|
|
|
|
export default memo(Damages);
|