feat: it speaks

This commit is contained in:
2026-05-31 23:41:32 -03:00
parent 339c660bcb
commit eec01440f9
6 changed files with 67 additions and 12 deletions
Binary file not shown.
Binary file not shown.
+32 -6
View File
@@ -2,7 +2,7 @@
import './page.css'; import './page.css';
import { Canvas } from "@react-three/fiber"; import { Canvas, useThree } from "@react-three/fiber";
import { BrightnessContrast, EffectComposer, HueSaturation, Noise, Pixelation, Vignette } from "@react-three/postprocessing"; import { BrightnessContrast, EffectComposer, HueSaturation, Noise, Pixelation, Vignette } from "@react-three/postprocessing";
import { Suspense, useEffect, useState } from "react"; import { Suspense, useEffect, useState } from "react";
@@ -14,34 +14,53 @@ import TheCreature from './scene-components/creature';
import Player from './scene-components/player'; import Player from './scene-components/player';
import Hallway from './scene-components/hallway'; import Hallway from './scene-components/hallway';
import { AudioListener } from 'three';
function PostProcessing() { function PostProcessing() {
const [getCaught, setCaught] = useState(fearState.wasCaught); const [wasCaught, setWasCaught] = useState(fearState.wasCaught);
useEffect(() => { useEffect(() => {
const unsubscribe = fearState.subscribe(() => { const unsubscribe = fearState.subscribe(() => {
setCaught(fearState.wasCaught); setWasCaught(fearState.wasCaught);
}); });
return () => unsubscribe(); return () => unsubscribe();
}, []); }, []);
return (<EffectComposer> return (<EffectComposer>
<Pixelation granularity={getCaught ? 18 : 12} /> <Pixelation granularity={wasCaught ? 18 : 12} />
<Vignette /> <Vignette />
<Noise opacity={getCaught ? 0.01 : 0.005} /> <Noise opacity={wasCaught ? 0.01 : 0.005} />
<BrightnessContrast <BrightnessContrast
brightness={-0.01} brightness={-0.01}
contrast={0.05} contrast={0.05}
/> />
<HueSaturation saturation={getCaught ? 1 : 0} /> <HueSaturation saturation={wasCaught ? 1 : 0} />
</EffectComposer>) </EffectComposer>)
} }
function ListenerCreator() {
const { camera } = useThree();
useEffect(() => {
const listener = new AudioListener();
camera.add(listener);
return () => {
camera.remove(listener);
};
}, [camera]);
return null;
}
export default function Fear() { export default function Fear() {
const [isRustActive, setIsRustActive] = useState(fearState.isRustActive); const [isRustActive, setIsRustActive] = useState(fearState.isRustActive);
const [wasCaught, setWasCaught] = useState(fearState.isRustActive);
useEffect(() => { useEffect(() => {
const unsubscribe = fearState.subscribe(() => { const unsubscribe = fearState.subscribe(() => {
setIsRustActive(fearState.isRustActive); setIsRustActive(fearState.isRustActive);
setWasCaught(fearState.wasCaught)
}); });
return () => unsubscribe(); return () => unsubscribe();
}, []); }, []);
@@ -53,6 +72,8 @@ export default function Fear() {
className='canvas' className='canvas'
camera={{ position: [0, 3, -5], fov: 65, far: 100 }} camera={{ position: [0, 3, -5], fov: 65, far: 100 }}
> >
<ListenerCreator/>
<color attach="background" args={['#050505']} /> <color attach="background" args={['#050505']} />
<fogExp2 attach='fog' args={[0x050505, 0.035]} /> <fogExp2 attach='fog' args={[0x050505, 0.035]} />
@@ -67,6 +88,11 @@ export default function Fear() {
url='fear/snd/ambience.mp3' url='fear/snd/ambience.mp3'
volume={isRustActive ? 0 : 1} volume={isRustActive ? 0 : 1}
/> />
{wasCaught ? <AmbientSound
url='fear/snd/glitch.mp3'
volume={1}
/> : null}
</Suspense> </Suspense>
</Canvas> </Canvas>
</>) </>)
+31 -3
View File
@@ -1,4 +1,4 @@
import { useTexture } from "@react-three/drei"; import { useTexture, PositionalAudio } from "@react-three/drei";
import { useFrame, useThree } from "@react-three/fiber"; import { useFrame, useThree } from "@react-three/fiber";
import { useEffect, useRef, useState } from "react"; import { useEffect, useRef, useState } from "react";
@@ -8,6 +8,7 @@ import { fearState } from "../state";
export default function TheCreature() { export default function TheCreature() {
const texture = useTexture('fear/img/creature.png'); const texture = useTexture('fear/img/creature.png');
const meshRef = useRef<THREE.Mesh>(null); const meshRef = useRef<THREE.Mesh>(null);
const audioRef = useRef<THREE.PositionalAudio>(null);
const { camera } = useThree(); const { camera } = useThree();
const [hasTriggered, setHasTriggered] = useState(false); const [hasTriggered, setHasTriggered] = useState(false);
@@ -17,6 +18,8 @@ export default function TheCreature() {
const globalDistance = useRef<number>(32); const globalDistance = useRef<number>(32);
const [currentLoop, setCurrentLoop] = useState(fearState.loopCount); const [currentLoop, setCurrentLoop] = useState(fearState.loopCount);
const audioPlaying = useRef<boolean>(false);
useEffect(() => { useEffect(() => {
const unsubscribe = fearState.subscribe(() => { const unsubscribe = fearState.subscribe(() => {
setCurrentLoop(fearState.loopCount); setCurrentLoop(fearState.loopCount);
@@ -25,6 +28,11 @@ export default function TheCreature() {
setIsSpawned(false); setIsSpawned(false);
setHasTriggered(false); setHasTriggered(false);
globalDistance.current = 32; globalDistance.current = 32;
audioPlaying.current = false;
if (audioRef.current && audioRef.current.isPlaying) {
audioRef.current.stop();
}
} }
}); });
return () => unsubscribe(); return () => unsubscribe();
@@ -41,12 +49,22 @@ export default function TheCreature() {
globalDistance.current = 32; globalDistance.current = 32;
} }
if (!hasTriggered) if (!hasTriggered) {
if (globalDistance.current < 40) setHasTriggered(true); if (globalDistance.current < 40) {
setHasTriggered(true);
}
}
if (hasTriggered) { if (hasTriggered) {
globalDistance.current -= speed * delta; globalDistance.current -= 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; const shakeIntensity = Math.max(0, 1 - (globalDistance.current / 32)) * 0.22;
camera.position.x += (Math.random() - 0.5) * shakeIntensity; camera.position.x += (Math.random() - 0.5) * shakeIntensity;
camera.position.y += (Math.random() - 0.5) * shakeIntensity; camera.position.y += (Math.random() - 0.5) * shakeIntensity;
@@ -80,6 +98,16 @@ export default function TheCreature() {
depthWrite={false} depthWrite={false}
side={THREE.DoubleSide} side={THREE.DoubleSide}
/> />
{currentLoop >= 4 && (
<PositionalAudio
url="fear/snd/riser.mp3"
ref={audioRef}
distance={25}
loop={false}
autoplay={false}
/>
)}
</mesh> </mesh>
); );
} }
+2 -1
View File
@@ -1,9 +1,10 @@
import { useFrame, useThree } from "@react-three/fiber"; import { useFrame, useThree } from "@react-three/fiber";
import { useEffect, useRef } from "react"; import { useEffect, useRef } from "react";
import * as THREE from "three";
import { FEAR_SETTINGS, fearState } from "../state"; import { FEAR_SETTINGS, fearState } from "../state";
import { PointerLockControls } from "@react-three/drei"; import { PointerLockControls } from "@react-three/drei";
import * as THREE from "three";
const forward = new THREE.Vector3(); const forward = new THREE.Vector3();
const side = new THREE.Vector3(); const side = new THREE.Vector3();
const direction = new THREE.Vector3(); const direction = new THREE.Vector3();
+1 -1
View File
@@ -5,7 +5,7 @@ export const FEAR_SETTINGS = {
HALLWAY_WIDTH: 6, HALLWAY_WIDTH: 6,
HALLWAY_HEIGHT: 5, HALLWAY_HEIGHT: 5,
PLAYER_HEIGHT: 3, PLAYER_HEIGHT: 3,
PLAYER_SPEED: 6, PLAYER_SPEED: 4,
WALL_BUFFER: 0.6, WALL_BUFFER: 0.6,
}; };