Files
neru.rip/src/app/fear/scene-components/creature.tsx
T
2026-06-01 15:42:25 -03:00

113 lines
3.6 KiB
TypeScript

import { useTexture, PositionalAudio } from "@react-three/drei";
import { useFrame, useThree } from "@react-three/fiber";
import { useEffect, useRef, useState } from "react";
import * as THREE from "three";
import { FEAR_SETTINGS, fearState } from "../state";
useTexture.preload('fear/img/creature.png');
export default function TheCreature() {
const texture = useTexture('fear/img/creature.png');
const meshRef = useRef<THREE.Mesh>(null);
const audioRef = useRef<THREE.PositionalAudio>(null);
const { camera } = useThree();
const [hasTriggered, setHasTriggered] = useState(false);
const [isSpawned, setIsSpawned] = useState(false);
const globalDistance = useRef<number>(32);
const [finaleTriggered, setFinaleTriggered] = useState(fearState.finaleTriggered);
const audioPlaying = useRef<boolean>(false);
useEffect(() => {
const unsubscribe = fearState.subscribe(() => {
setFinaleTriggered(fearState.finaleTriggered);
if (!fearState.finaleTriggered) {
setIsSpawned(false);
setHasTriggered(false);
globalDistance.current = 32;
audioPlaying.current = false;
if (audioRef.current && audioRef.current.isPlaying)
audioRef.current.stop();
}
});
return () => unsubscribe();
}, []);
useFrame((state, delta) => {
if (!fearState.finaleTriggered) return;
const creature = meshRef.current;
if (!creature) return;
if (!isSpawned) {
setIsSpawned(true);
globalDistance.current = 32;
}
if (!hasTriggered) {
if (globalDistance.current < 40) {
setHasTriggered(true);
}
}
if (hasTriggered) {
globalDistance.current -= FEAR_SETTINGS.CREATURE_SPEED * delta;
if (audioRef.current && !audioPlaying.current) {
audioPlaying.current = true;
if (audioRef.current.context.state === 'suspended')
audioRef.current.context.resume();
audioRef.current.play();
}
const shakeIntensity = Math.max(0, 1 - (globalDistance.current / 32)) * 0.22;
camera.position.x += (Math.random() - 0.5) * shakeIntensity;
camera.position.y += (Math.random() - 0.5) * shakeIntensity;
if (globalDistance.current <= 0.1) {
window.location.href = '/';
fearState.registerCaught();
return;
}
}
const forwardVector = new THREE.Vector3();
camera.getWorldDirection(forwardVector);
const lookDirZ = forwardVector.z < 0 ? -1 : 1;
const calculatedZ = camera.position.z + (lookDirZ * globalDistance.current);
creature.position.set(0, 1.6, calculatedZ);
creature.lookAt(camera.position.x, creature.position.y, camera.position.z);
});
return (
<mesh
ref={meshRef}
visible={finaleTriggered}
>
<planeGeometry args={[3.0, 4.8]} />
<meshBasicMaterial
map={texture}
transparent={true}
depthWrite={false}
side={THREE.DoubleSide}
/>
{finaleTriggered && (
<PositionalAudio
url="fear/snd/riser.mp3"
ref={audioRef}
distance={25}
loop={false}
autoplay={false}
/>
)}
</mesh>
);
}