106 lines
3.2 KiB
TypeScript
106 lines
3.2 KiB
TypeScript
import React, { useEffect, useState, useRef } from "react";
|
|
import { assetsPath } from "./Common";
|
|
import "./Sheep.css";
|
|
|
|
const sheepSteps = 12;
|
|
|
|
const useAnimationFrame = (callback: (t: number) => void) => {
|
|
const requestRef = useRef<number | null>(null);
|
|
const animate = (time: number) => {
|
|
callback(time);
|
|
requestRef.current = requestAnimationFrame(animate);
|
|
};
|
|
useEffect(() => {
|
|
requestRef.current = requestAnimationFrame(animate);
|
|
return () => {
|
|
if (requestRef.current !== null) {
|
|
cancelAnimationFrame(requestRef.current);
|
|
}
|
|
};
|
|
}, []);
|
|
};
|
|
|
|
const Sheep: React.FC<{ radius: number; speed: number; size: number; style?: React.CSSProperties }> = ({
|
|
radius,
|
|
speed,
|
|
size,
|
|
style,
|
|
}) => {
|
|
const [time, setTime] = useState(0);
|
|
const [direction, setDirection] = useState(Math.random() * 2 * Math.PI);
|
|
const [y, setY] = useState((Math.random() - 0.5) * radius);
|
|
const [frame, setFrame] = useState(0);
|
|
const [x, setX] = useState((Math.random() - 0.5) * radius);
|
|
const previousTimeRef = useRef<number | undefined>();
|
|
|
|
useAnimationFrame((t) => {
|
|
if (previousTimeRef.current !== undefined) {
|
|
const deltaTime = t - previousTimeRef.current;
|
|
previousTimeRef.current = t;
|
|
setTime(deltaTime);
|
|
} else {
|
|
previousTimeRef.current = t;
|
|
}
|
|
});
|
|
|
|
useEffect(() => {
|
|
let alpha = time / speed;
|
|
const sheepSpeed = 0.05;
|
|
if (alpha > 1.0) alpha = 0.1;
|
|
let newX = x + sheepSpeed * Math.sin(direction) * alpha;
|
|
let newY = y + sheepSpeed * Math.cos(direction) * alpha;
|
|
if (Math.sqrt(newX * newX + newY * newY) > Math.sqrt(radius * radius)) {
|
|
let newDirection = direction + Math.PI + 0.5 * (Math.random() - 0.5) * Math.PI;
|
|
while (newDirection >= 2 * Math.PI) newDirection -= 2 * Math.PI;
|
|
while (newDirection <= -2 * Math.PI) newDirection += 2 * Math.PI;
|
|
setDirection(newDirection);
|
|
newX += sheepSpeed * Math.sin(newDirection) * alpha;
|
|
newY += sheepSpeed * Math.cos(newDirection) * alpha;
|
|
}
|
|
setX(newX);
|
|
setY(newY);
|
|
setFrame(frame + sheepSteps * alpha);
|
|
}, [time, speed]);
|
|
|
|
const cell = Math.floor(frame) % sheepSteps;
|
|
|
|
return (
|
|
<div
|
|
className={`Sheep`}
|
|
style={{
|
|
zIndex: `${Math.ceil(50 * y)}`,
|
|
top: `${Math.floor(50 + 50 * y)}%`,
|
|
left: `${Math.floor(50 + 50 * x)}%`,
|
|
width: `${size * 60}px`,
|
|
height: `${size * 52}px`,
|
|
backgroundRepeat: "no-repeat",
|
|
backgroundImage: `url(${assetsPath}/gfx/sheep.png)`,
|
|
backgroundPositionX: `${(100.0 * cell) / (sheepSteps - 1)}%`,
|
|
transformOrigin: `50% 50%`,
|
|
transform: `translate(-50%, -50%) scale(${Math.sin(direction) > 0 ? +1 : -1}, 1)`,
|
|
...style,
|
|
}}
|
|
/>
|
|
);
|
|
};
|
|
|
|
const Herd: React.FC<{ count: number; style?: React.CSSProperties }> = ({ count, style }) => {
|
|
const [sheep, setSheep] = useState<React.ReactNode[]>([]);
|
|
useEffect(() => {
|
|
const tmp: React.ReactNode[] = [];
|
|
for (let i = 0; i < count; i++) {
|
|
const scalar = Math.random();
|
|
tmp.push(<Sheep speed={1000 + 500 * scalar} size={0.25} radius={0.8} key={i} />);
|
|
}
|
|
setSheep(tmp);
|
|
}, [count]);
|
|
|
|
return (
|
|
<div className="Herd" style={style}>
|
|
{sheep}
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export { Sheep, Herd };
|