perf: damage
This commit is contained in:
parent
2997370e3b
commit
cb3d9ad8c6
|
@ -1,6 +1,7 @@
|
||||||
import {memo, useCallback, useRef} from 'react';
|
import {memo, useCallback, useRef} from 'react';
|
||||||
|
|
||||||
import {DamageTypes} from '@/ecs/components/vulnerable.js';
|
import {DamageTypes} from '@/ecs/components/vulnerable.js';
|
||||||
|
import {useEcsTick} from '@/react/context/ecs.js';
|
||||||
import useAnimationFrame from '@/react/hooks/use-animation-frame.js';
|
import useAnimationFrame from '@/react/hooks/use-animation-frame.js';
|
||||||
import {easeInOutExpo, easeInQuint, easeOutQuad, linear} from '@/util/easing.js';
|
import {easeInOutExpo, easeInQuint, easeOutQuad, linear} from '@/util/easing.js';
|
||||||
|
|
||||||
|
@ -25,142 +26,143 @@ function damageHue(type) {
|
||||||
return hue;
|
return hue;
|
||||||
}
|
}
|
||||||
|
|
||||||
function createDamageNode() {
|
class Damage {
|
||||||
const damage = document.createElement('div');
|
elapsed = 0;
|
||||||
damage.classList.add(styles.damage);
|
hue = [0, 30];
|
||||||
damage.appendChild(document.createElement('p'));
|
offsetX = 0;
|
||||||
return damage;
|
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}) {
|
function Damages({scale}) {
|
||||||
const animations = useRef({});
|
const damages = useRef({});
|
||||||
const pool = useRef([]);
|
|
||||||
const damagesRef = 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) => {
|
const frame = useCallback((elapsed) => {
|
||||||
if (!damagesRef.current) {
|
if (!damagesRef.current) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (0 === pool.current.length) {
|
const keys = Object.keys(damages.current);
|
||||||
for (let i = 0; i < 512; ++i) {
|
if (0 === keys.length && 0 === pool.current.length) {
|
||||||
const damage = createDamageNode();
|
for (let i = 0; i < 500; ++i) {
|
||||||
damagesRef.current.appendChild(damage);
|
pool.current.push(new Damage());
|
||||||
pool.current.push(damage);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const keys = Object.keys(animations.current);
|
const stepSize = keys.length > 150 ? (keys.length / 500) * 0.25 : elapsed;
|
||||||
for (const key of keys) {
|
for (const key of keys) {
|
||||||
const animation = animations.current[key];
|
const damage = damages.current[key];
|
||||||
if (!damages[key]) {
|
damage.tick(elapsed, stepSize);
|
||||||
if (animation.element) {
|
if (damage.elapsed > 1.5) {
|
||||||
if (pool.current.length < 512) {
|
damagesRef.current.removeChild(damage.element);
|
||||||
pool.current.push(animation.element);
|
delete damages.current[key];
|
||||||
}
|
if (pool.current.length < 1000) {
|
||||||
animation.element = undefined;
|
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);
|
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 (
|
return (
|
||||||
<div
|
<div
|
||||||
className={styles.damages}
|
className={styles.damages}
|
||||||
|
|
|
@ -23,22 +23,10 @@
|
||||||
inherits: false;
|
inherits: false;
|
||||||
syntax: '<number>';
|
syntax: '<number>';
|
||||||
}
|
}
|
||||||
@property --randomnessX {
|
|
||||||
initial-value: 0;
|
|
||||||
inherits: false;
|
|
||||||
syntax: '<number>';
|
|
||||||
}
|
|
||||||
@property --randomnessY {
|
|
||||||
initial-value: 0;
|
|
||||||
inherits: false;
|
|
||||||
syntax: '<number>';
|
|
||||||
}
|
|
||||||
|
|
||||||
.damage {
|
.damage {
|
||||||
--hue: 0;
|
--hue: 0;
|
||||||
--opacity: 0.75;
|
--opacity: 0.75;
|
||||||
--randomnessX: 0;
|
|
||||||
--randomnessY: 0;
|
|
||||||
--scale: 0.35;
|
--scale: 0.35;
|
||||||
--background: hsl(var(--hue) 100% 12.5%);
|
--background: hsl(var(--hue) 100% 12.5%);
|
||||||
--foreground: hsl(var(--hue) 100% 50%);
|
--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 {usePacket} from '@/react/context/client.js';
|
||||||
import {useEcsTick} from '@/react/context/ecs.js';
|
import {useEcsTick} from '@/react/context/ecs.js';
|
||||||
|
@ -14,8 +14,6 @@ export default function Entities({
|
||||||
setMonopolizers,
|
setMonopolizers,
|
||||||
}) {
|
}) {
|
||||||
const [entities, setEntities] = useState({});
|
const [entities, setEntities] = useState({});
|
||||||
const [damages, setDamages] = useState({});
|
|
||||||
const {current: pendingDamage} = useRef({accumulated: [], handle: undefined});
|
|
||||||
usePacket('EcsChange', async () => {
|
usePacket('EcsChange', async () => {
|
||||||
setEntities({});
|
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) => {
|
setEntities((entities) => {
|
||||||
for (const id in deleting) {
|
for (const id in deleting) {
|
||||||
|
@ -127,7 +95,7 @@ export default function Entities({
|
||||||
...updating,
|
...updating,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
}, [pendingDamage, setChatMessages, setMonopolizers]);
|
}, [setChatMessages, setMonopolizers]);
|
||||||
useEcsTick(onEcsTick);
|
useEcsTick(onEcsTick);
|
||||||
const renderables = [];
|
const renderables = [];
|
||||||
for (const id in entities) {
|
for (const id in entities) {
|
||||||
|
@ -151,7 +119,6 @@ export default function Entities({
|
||||||
>
|
>
|
||||||
{renderables}
|
{renderables}
|
||||||
<Damages
|
<Damages
|
||||||
damages={damages}
|
|
||||||
scale={scale}
|
scale={scale}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
Loading…
Reference in New Issue
Block a user