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