perf: damage

This commit is contained in:
cha0s 2024-07-31 00:06:29 -05:00
parent 01d63e5ee0
commit c9e7b33d25
4 changed files with 241 additions and 160 deletions

View File

@ -1,58 +0,0 @@
import {memo, useEffect, useState} from 'react';
import {DamageTypes} from '@/ecs/components/vulnerable.js';
import styles from './damage.module.css';
const damageTypeMap = {
[DamageTypes.PAIN]: styles.pain,
[DamageTypes.HEALING]: styles.healing,
[DamageTypes.MANA]: styles.mana,
};
function Damage({
damage,
scale,
zIndex,
}) {
const [randomness] = useState({
radians: Math.random() * (Math.PI / 16) - (Math.PI / 32),
x: 1 * (Math.random() - 0.5),
y: Math.random(),
})
useEffect(() => {
const handle = setTimeout(() => {
damage.onClose();
}, 1_500);
return () => {
clearTimeout(handle);
};
}, [damage]);
const {amount, position} = damage;
return (
<div
className={styles.damage}
style={{
'--magnitude': Math.max(1, Math.floor(Math.log10(Math.abs(amount)))),
'--positionX': `${position.x * scale}px`,
'--positionY': `${position.y * scale}px`,
'--randomnessX': randomness.x,
'--randomnessY': randomness.y,
'--shimmer': damageTypeMap[damage.type],
'--uiScale': scale,
rotate: `${randomness.radians}rad`,
zIndex,
}}
>
<p
style={{
scale: `${scale}`,
}}
>
{Math.abs(amount)}
</p>
</div>
);
}
export default memo(Damage);

View File

@ -13,16 +13,6 @@
inherits: false; inherits: false;
syntax: '<number>'; syntax: '<number>';
} }
@property --uiScale {
initial-value: 0;
inherits: false;
syntax: '<number>';
}
@property --shimmer {
initial-value: '';
inherits: false;
syntax: '<custom-ident>';
}
@property --offsetX { @property --offsetX {
initial-value: 0; initial-value: 0;
inherits: false; inherits: false;
@ -52,14 +42,8 @@
--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%);
animation:
fade 1.5s linear forwards,
grow 1.5s ease-in forwards,
move 1.5s cubic-bezier(0.5, 1, 0.89, 1),
var(--shimmer) 300ms infinite cubic-bezier(0.87, 0, 0.13, 1)
;
color: var(--foreground); color: var(--foreground);
font-size: calc(5px + (var(--magnitude) * 6px)); font-size: calc(10px + (var(--magnitude) * 12px));
opacity: var(--opacity); opacity: var(--opacity);
overflow-wrap: break-word; overflow-wrap: break-word;
position: absolute; position: absolute;
@ -80,75 +64,8 @@
calc(-50% + (1px * var(--offsetY)) + var(--positionY)) calc(-50% + (1px * var(--offsetY)) + var(--positionY))
; ;
user-select: none; user-select: none;
will-change: color, scale, opacity, translate, transform;
p { p {
margin: 0; margin: 0;
} }
} }
@keyframes move {
0% {
--offsetX: 0;
--offsetY: 0;
}
33%, 91.6% {
--offsetX: calc(20 * var(--uiScale) * var(--randomnessX));
--offsetY: calc(-1 * (10 + var(--randomnessY) * 22.5 * var(--uiScale)));
}
}
@keyframes fade {
0% {
--opacity: 0.75;
}
25% {
--opacity: 1;
}
91.6% {
--opacity: 1;
}
100% {
--opacity: 0;
}
}
@keyframes grow {
0% {
--scale: 0.35;
}
33% {
--scale: 1;
}
45% {
--scale: 1;
}
40% {
--scale: 1.5;
}
45% {
--scale: 1;
}
91.6% {
--scale: 1;
}
95% {
--scale: 0;
}
}
@keyframes pain {
0% { --hue: 0 }
50% { --hue: 30 }
100% { --hue: 0 }
}
@keyframes healing {
0% { --hue: 120 }
50% { --hue: 90 }
100% { --hue: 120 }
}
@keyframes mana {
0% { --hue: 220 }
50% { --hue: 215 }
100% { --hue: 220 }
}

View File

@ -1,22 +1,172 @@
import {memo, useCallback, useRef} from 'react';
import {DamageTypes} from '@/ecs/components/vulnerable.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'; import styles from './damages.module.css';
import Damage from './damage.jsx'; function damageHue(type) {
let hue;
export default function Damages({damages, scale}) { switch(type) {
const elements = []; case DamageTypes.PAIN: {
for (const key in damages) { hue = [0, 30];
elements.push( break;
<Damage }
damage={damages[key]} case DamageTypes.HEALING: {
key={key} hue = [90, 120];
scale={scale} break;
zIndex={key} }
/> case DamageTypes.MANA: {
); hue = [215, 220];
break;
}
} }
if (0 === elements.length) { return hue;
return false;
}
return <div className={styles.damages}>{elements}</div>;
} }
function createDamageNode() {
const damage = document.createElement('div');
damage.classList.add(styles.damage);
damage.appendChild(document.createElement('p'));
return damage;
}
function Damages({damages, scale}) {
const animations = useRef({});
const pool = useRef([]);
const damagesRef = useRef();
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(animations.current);
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;
}
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}
ref={damagesRef}
/>
);
}
export default memo(Damages);

View File

@ -1,3 +1,75 @@
@property --hue {
initial-value: 0;
inherits: false;
syntax: '<number>';
}
@property --opacity {
initial-value: 0;
inherits: false;
syntax: '<number>';
}
@property --scale {
initial-value: 0;
inherits: false;
syntax: '<number>';
}
@property --offsetX {
initial-value: 0;
inherits: false;
syntax: '<number>';
}
@property --offsetY {
initial-value: 0;
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%);
color: var(--foreground);
font-size: calc(10px + (var(--magnitude) * 12px));
opacity: var(--opacity);
overflow-wrap: break-word;
position: absolute;
margin: 0;
text-shadow:
0px -1px 0px var(--background),
1px 0px 0px var(--background),
0px 1px 0px var(--background),
-1px 0px 0px var(--background),
0px -2px 0px var(--background),
2px 0px 0px var(--background),
0px 2px 0px var(--background),
-2px 0px 0px var(--background)
;
scale: var(--scale);
translate:
calc(-50% + (1px * var(--offsetX)) + var(--positionX))
calc(-50% + (1px * var(--offsetY)) + var(--positionY))
;
user-select: none;
will-change: color, scale, opacity, translate, transform;
p {
margin: 0;
}
}
.damages { .damages {
font-family: Joystix, 'Courier New', Courier, monospace; font-family: Joystix, 'Courier New', Courier, monospace;
} }