perf: damage
This commit is contained in:
parent
2997370e3b
commit
cb3d9ad8c6
|
@ -1,6 +1,7 @@
|
|||
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';
|
||||
|
||||
|
@ -25,142 +26,143 @@ function damageHue(type) {
|
|||
return hue;
|
||||
}
|
||||
|
||||
function createDamageNode() {
|
||||
const damage = document.createElement('div');
|
||||
damage.classList.add(styles.damage);
|
||||
damage.appendChild(document.createElement('p'));
|
||||
return damage;
|
||||
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({damages, scale}) {
|
||||
const animations = useRef({});
|
||||
const pool = useRef([]);
|
||||
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;
|
||||
}
|
||||
if (0 === pool.current.length) {
|
||||
for (let i = 0; i < 512; ++i) {
|
||||
const damage = createDamageNode();
|
||||
damagesRef.current.appendChild(damage);
|
||||
pool.current.push(damage);
|
||||
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 keys = Object.keys(animations.current);
|
||||
const stepSize = keys.length > 150 ? (keys.length / 500) * 0.25 : elapsed;
|
||||
for (const key of keys) {
|
||||
const animation = animations.current[key];
|
||||
if (!damages[key]) {
|
||||
if (animation.element) {
|
||||
if (pool.current.length < 512) {
|
||||
pool.current.push(animation.element);
|
||||
}
|
||||
animation.element = undefined;
|
||||
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);
|
||||
}
|
||||
delete animations.current[key];
|
||||
continue;
|
||||
}
|
||||
if (!animation.element) {
|
||||
const {amount, position} = damages[key];
|
||||
let damage;
|
||||
if (pool.current.length > 0) {
|
||||
damage = pool.current.pop();
|
||||
}
|
||||
else {
|
||||
damage = createDamageNode();
|
||||
damagesRef.current.appendChild(damage);
|
||||
}
|
||||
const p = damage.querySelector('p');
|
||||
p.style.scale = scale / 2;
|
||||
p.innerText = Math.abs(amount);
|
||||
damage.style.setProperty('--randomnessX', animation['--randomnessX']);
|
||||
damage.style.setProperty('--randomnessY', animation['--randomnessY']);
|
||||
damage.style.setProperty('rotate', animation['rotate']);
|
||||
damage.style.setProperty('rotate', animation['rotate']);
|
||||
damage.style.setProperty('--magnitude', Math.max(1, Math.floor(Math.log10(Math.abs(amount)))));
|
||||
damage.style.setProperty('--positionX', `${position.x * scale}px`);
|
||||
damage.style.setProperty('--positionY', `${position.y * scale}px`);
|
||||
damage.style.setProperty('zIndex', key);
|
||||
animation.element = damage;
|
||||
}
|
||||
animation.elapsed += elapsed;
|
||||
animation.step += elapsed;
|
||||
const offsetX = 20 * scale * animation['--randomnessX'];
|
||||
const offsetY = -1 * (10 + animation['--randomnessY'] * 45 * scale);
|
||||
// offset
|
||||
if (animation.elapsed <= 0.5) {
|
||||
animation['--offsetX'] = easeOutQuad(animation.elapsed, 0, offsetX, 0.5);
|
||||
animation['--offsetY'] = easeOutQuad(animation.elapsed, 0, offsetY, 0.5);
|
||||
}
|
||||
else if (animation.elapsed > 1.375) {
|
||||
animation['--offsetX'] = offsetX - easeOutQuad(animation.elapsed - 1.375, 0, offsetX, 0.125);
|
||||
animation['--offsetY'] = offsetY - easeOutQuad(animation.elapsed - 1.375, 0, offsetY, 0.125);
|
||||
}
|
||||
// scale
|
||||
if (animation.elapsed <= 0.5) {
|
||||
animation['--scale'] = easeOutQuad(animation.elapsed, 0, 1, 0.5);
|
||||
}
|
||||
else if (animation.elapsed > 0.5 && animation.elapsed < 0.6) {
|
||||
animation['--scale'] = linear(animation.elapsed - 0.5, 1, 0.5, 0.1);
|
||||
}
|
||||
else if (animation.elapsed > 0.6 && animation.elapsed < 0.675) {
|
||||
animation['--scale'] = 1.5 - easeInQuint(animation.elapsed - 0.6, 1, 0.5, 0.075);
|
||||
}
|
||||
else if (animation.elapsed > 0.675 && animation.elapsed < 1.375) {
|
||||
animation['--scale'] = 1;
|
||||
}
|
||||
else if (animation.elapsed > 1.375) {
|
||||
animation['--scale'] = 1 - easeOutQuad(animation.elapsed - 1.375, 0, 1, 0.125);
|
||||
}
|
||||
// fade
|
||||
if (animation.elapsed <= 0.375) {
|
||||
animation['--opacity'] = linear(animation.elapsed, 0.75, 0.25, 0.375);
|
||||
}
|
||||
else if (animation.elapsed > 0.375 && animation.elapsed < 1.375) {
|
||||
animation['--opacity'] = 1;
|
||||
}
|
||||
else if (animation.elapsed > 1.375) {
|
||||
animation['--opacity'] = 1 - linear(animation.elapsed - 1.375, 0, 1, 0.125);
|
||||
}
|
||||
// hue
|
||||
const h = Math.abs((animation.elapsed % 0.3) - 0.15) / 0.15;
|
||||
animation['--hue'] = easeInOutExpo(h, animation.hue[0], animation.hue[1], 1);
|
||||
const step = keys.length > 150 ? (keys.length / 500) * 0.25 : elapsed;
|
||||
if (animation.step > step) {
|
||||
animation.step = animation.step % step;
|
||||
animation.element.style.setProperty('--hue', animation['--hue']);
|
||||
animation.element.style.setProperty('--opacity', animation['--opacity']);
|
||||
animation.element.style.setProperty('--offsetX', animation['--offsetX']);
|
||||
animation.element.style.setProperty('--offsetY', animation['--offsetY']);
|
||||
animation.element.style.setProperty('--scale', animation['--scale']);
|
||||
}
|
||||
if (animation.elapsed > 1.5) {
|
||||
if (pool.current.length < 512) {
|
||||
pool.current.push(animation.element);
|
||||
}
|
||||
animation.element.style.setProperty('--opacity', 0);
|
||||
animation.element = undefined;
|
||||
damages[key].onClose();
|
||||
}
|
||||
}
|
||||
}, [damages, scale]);
|
||||
}, []);
|
||||
useAnimationFrame(frame);
|
||||
for (const key in damages) {
|
||||
if (!animations.current[key]) {
|
||||
animations.current[key] = {
|
||||
elapsed: 0,
|
||||
hue: damageHue(damages[key].type),
|
||||
rotate: `${Math.random() * (Math.PI / 16) - (Math.PI / 32)}rad`,
|
||||
step: 0,
|
||||
'--scale': 0.35,
|
||||
'--opacity': 0.75,
|
||||
'--offsetX': 0,
|
||||
'--offsetY': 0,
|
||||
'--randomnessX': 1 * (Math.random() - 0.5),
|
||||
'--randomnessY': Math.random(),
|
||||
};
|
||||
}
|
||||
}
|
||||
return (
|
||||
<div
|
||||
className={styles.damages}
|
||||
|
|
|
@ -23,22 +23,10 @@
|
|||
inherits: false;
|
||||
syntax: '<number>';
|
||||
}
|
||||
@property --randomnessX {
|
||||
initial-value: 0;
|
||||
inherits: false;
|
||||
syntax: '<number>';
|
||||
}
|
||||
@property --randomnessY {
|
||||
initial-value: 0;
|
||||
inherits: false;
|
||||
syntax: '<number>';
|
||||
}
|
||||
|
||||
.damage {
|
||||
--hue: 0;
|
||||
--opacity: 0.75;
|
||||
--randomnessX: 0;
|
||||
--randomnessY: 0;
|
||||
--scale: 0.35;
|
||||
--background: hsl(var(--hue) 100% 12.5%);
|
||||
--foreground: hsl(var(--hue) 100% 50%);
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import {useCallback, useRef, useState} from 'react';
|
||||
import {useCallback, useState} from 'react';
|
||||
|
||||
import {usePacket} from '@/react/context/client.js';
|
||||
import {useEcsTick} from '@/react/context/ecs.js';
|
||||
|
@ -14,8 +14,6 @@ export default function Entities({
|
|||
setMonopolizers,
|
||||
}) {
|
||||
const [entities, setEntities] = useState({});
|
||||
const [damages, setDamages] = useState({});
|
||||
const {current: pendingDamage} = useRef({accumulated: [], handle: undefined});
|
||||
usePacket('EcsChange', async () => {
|
||||
setEntities({});
|
||||
});
|
||||
|
@ -87,36 +85,6 @@ export default function Entities({
|
|||
};
|
||||
}
|
||||
}
|
||||
const {damage} = update.Vulnerable || {};
|
||||
if (damage) {
|
||||
for (const key in damage) {
|
||||
damage[key].onClose = () => {
|
||||
setDamages((damages) => {
|
||||
const {[key]: _, ...rest} = damages; // eslint-disable-line no-unused-vars
|
||||
return rest;
|
||||
})
|
||||
};
|
||||
}
|
||||
pendingDamage.accumulated.push(damage);
|
||||
if (!pendingDamage.handle) {
|
||||
pendingDamage.handle = setTimeout(() => {
|
||||
const update = {};
|
||||
for (const damage of pendingDamage.accumulated) {
|
||||
for (const key in damage) {
|
||||
update[key] = damage[key];
|
||||
}
|
||||
}
|
||||
pendingDamage.accumulated.length = 0;
|
||||
setDamages((damages) => {
|
||||
return {
|
||||
...damages,
|
||||
...update,
|
||||
};
|
||||
});
|
||||
pendingDamage.handle = undefined;
|
||||
}, 50);
|
||||
}
|
||||
}
|
||||
}
|
||||
setEntities((entities) => {
|
||||
for (const id in deleting) {
|
||||
|
@ -127,7 +95,7 @@ export default function Entities({
|
|||
...updating,
|
||||
};
|
||||
});
|
||||
}, [pendingDamage, setChatMessages, setMonopolizers]);
|
||||
}, [setChatMessages, setMonopolizers]);
|
||||
useEcsTick(onEcsTick);
|
||||
const renderables = [];
|
||||
for (const id in entities) {
|
||||
|
@ -151,7 +119,6 @@ export default function Entities({
|
|||
>
|
||||
{renderables}
|
||||
<Damages
|
||||
damages={damages}
|
||||
scale={scale}
|
||||
/>
|
||||
</div>
|
||||
|
|
Loading…
Reference in New Issue
Block a user