perf: damage rendering in CSS

This commit is contained in:
cha0s 2024-07-27 09:47:17 -05:00
parent ebf62613ef
commit 2b91a49997
8 changed files with 143 additions and 70 deletions

View File

@ -1,72 +1,43 @@
import {useEffect, useState} from 'react'; import {memo, useEffect, useState} from 'react';
import {useRadians} from '@/react/context/radians.js';
import styles from './damage.module.css'; import styles from './damage.module.css';
export default function Damage({ function Damage({
camera, camera,
damage, damage,
scale, scale,
zIndex,
}) { }) {
const [hue, setHue] = useState(0);
const [opacity, setOpacity] = useState(0.5);
const [growth, setGrowth] = useState(0.25);
const [offset, setOffset] = useState({x: 0, y: 0});
const [randomness] = useState({ const [randomness] = useState({
hue: Math.random() * 10, radians: Math.random() * (Math.PI / 16) - (Math.PI / 32),
x: 1 * (Math.random() - 0.5), x: 1 * (Math.random() - 0.5),
y: 150 * (Math.random()), y: Math.random(),
rotation: Math.random() * (Math.PI / 8) - (Math.PI / 16), })
});
const radians = useRadians();
useEffect(() => { useEffect(() => {
setHue(15 + (Math.cos(radians * 4) * (5 + randomness.hue))); const handle = setTimeout(() => {
}, [radians, randomness.hue]); damage.onClose();
useEffect(() => { }, 1_500);
let handle;
let accumulated = 0;
let last = Date.now();
function float() {
const elapsed = (Date.now() - last) / 1000;
accumulated += elapsed;
last = Date.now();
if (accumulated < 0.25) {
setOpacity(0.5 + accumulated * 2)
}
if (accumulated < 0.5) {
setGrowth(0.25 + (accumulated * 1.5))
setOffset({
x: 80 * (accumulated * randomness.x),
y: -((80 + randomness.y) * (accumulated)),
});
}
if (accumulated >= 1.5) {
damage.onClose();
}
handle = requestAnimationFrame(float);
}
handle = requestAnimationFrame(float);
return () => { return () => {
cancelAnimationFrame(handle); clearTimeout(handle);
}; };
}, [damage, randomness.x, randomness.y]); }, [damage]);
const {amount, position} = damage; const {amount, position} = damage;
const left = position.x * scale - camera.x + offset.x;
const top = position.y * scale - camera.y + offset.y;
return ( return (
<div <div
className={styles.damage} className={styles.damage}
style={{ style={{
color: `hsl(${hue} 100% 50%)`, '--magnitude': Math.max(1, Math.floor(Math.log10(amount))),
left: `${left}px`, '--positionX': `${position.x * scale - camera.x}px`,
opacity, '--positionY': `${position.y * scale - camera.y}px`,
top: `${top}px`, '--randomnessX': randomness.x,
transform: `scale(${growth}) rotate(${randomness.rotation}rad)`, '--randomnessY': randomness.y,
rotate: `${randomness.radians}rad`,
zIndex,
}} }}
> >
<p>{amount}</p> <p>{amount}</p>
</div> </div>
); );
} }
export default memo(Damage);

View File

@ -1,25 +1,110 @@
@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 { .damage {
color: red; --hue: 0;
overflow-wrap: break-word; --opacity: 0.75;
position: fixed; --randomnessX: 0;
margin: 0; --randomnessY: 0;
margin-right: -66%; --scale: 0.35;
text-shadow: --background: hsl(var(--hue) 100% 12.5%);
0px -1px 0px black, --foreground: hsl(var(--hue) 100% 50%);
1px 0px 0px black, animation:
0px 1px 0px black, fade 1.5s linear forwards,
-1px 0px 0px black, grow 1.5s ease-in forwards,
move 1.5s cubic-bezier(0.5, 1, 0.89, 1),
0px -2px 0px black, shimmer 250ms infinite ease-in-out
2px 0px 0px black, ;
0px 2px 0px black, color: var(--foreground);
-2px 0px 0px black 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))
; ;
transform: translate(-50%, -50%);
user-select: none; user-select: none;
max-width: 66%;
p { p {
margin: 0; margin: 0;
} }
} }
@keyframes move {
0% {
--offsetX: 0;
--offsetY: 0;
}
33%, 91.6% {
--offsetX: calc(80 * var(--randomnessX));
--offsetY: calc(-1 * (10 + var(--randomnessY) * 90));
}
}
@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 shimmer {
0% { --hue: 0 }
50% { --hue: 30 }
100% { --hue: 0 }
}

View File

@ -11,6 +11,7 @@ export default function Damages({camera, damages, scale}) {
damage={damages[key]} damage={damages[key]}
key={key} key={key}
scale={scale} scale={scale}
zIndex={key}
/> />
); );
} }

View File

@ -1,4 +1,3 @@
.damages { .damages {
font-family: Cookbook, Georgia, 'Times New Roman', Times, serif; font-family: Joystix, 'Courier New', Courier, monospace;
font-size: 40px;
} }

View File

@ -31,3 +31,8 @@ body {
font-family: "Cookbook"; font-family: "Cookbook";
src: url("/assets/fonts/Cookbook.woff") format("woff"); src: url("/assets/fonts/Cookbook.woff") format("woff");
} }
@font-face {
font-family: "Joystix";
src: url("/assets/fonts/Joystix.ttf") format("ttf");
}

View File

@ -27,6 +27,7 @@ export const {
log10, log10,
max, max,
min, min,
pow,
round, round,
sign, sign,
sin, sin,
@ -317,6 +318,10 @@ export function removeCollinear([...vertices]) {
return trimmed; return trimmed;
} }
export const smoothstep = (x) => x * x * (3 - 2 * x);
export const smootherstep = (x) => x * x * x * (x * (x * 6 - 15) + 10);
export function transform( export function transform(
vertices, vertices,
{ {

Binary file not shown.

View File

@ -1,4 +1,11 @@
const playerEntity = ecs.lookupPlayerEntity(entity.Owned.owner); const playerEntity = ecs.lookupPlayerEntity(entity.Owned.owner);
if (playerEntity !== other && other.Vulnerable) { if (playerEntity !== other && other.Vulnerable) {
other.Vulnerable.damage({amount: Math.round(60 + Math.random() * 10), position: other.Position.toJSON()}) const magnitude = Math.floor(Math.random() * 3)
other.Vulnerable.damage({
amount: Math.floor(
Math.pow(10, magnitude)
+ Math.random() * (Math.pow(10, magnitude + 1) - Math.pow(10, magnitude)),
),
position: other.Position.toJSON(),
})
} }