style: run format:apply
This commit is contained in:
@@ -7,4 +7,4 @@
|
||||
outline: none;
|
||||
user-select: none;
|
||||
touch-action: none;
|
||||
}
|
||||
}
|
||||
|
||||
+95
-79
@@ -2,9 +2,16 @@
|
||||
|
||||
import './page.css';
|
||||
|
||||
import { Canvas, useFrame, useThree } from "@react-three/fiber";
|
||||
import { BrightnessContrast, EffectComposer, HueSaturation, Noise, Pixelation, Vignette } from "@react-three/postprocessing";
|
||||
import { Suspense, useEffect, useState } from "react";
|
||||
import { Canvas, useFrame, useThree } from '@react-three/fiber';
|
||||
import {
|
||||
BrightnessContrast,
|
||||
EffectComposer,
|
||||
HueSaturation,
|
||||
Noise,
|
||||
Pixelation,
|
||||
Vignette
|
||||
} from '@react-three/postprocessing';
|
||||
import { Suspense, useEffect, useState } from 'react';
|
||||
|
||||
import { AmbientSound } from './scene-components/ambient-sound';
|
||||
|
||||
@@ -18,103 +25,112 @@ import { AudioListener } from 'three';
|
||||
import FinaleText from './scene-components/finale-text';
|
||||
|
||||
function PostProcessing() {
|
||||
const [wasCaught, setWasCaught] = useState(fearState.wasCaught);
|
||||
const [wasCaught, setWasCaught] = useState(fearState.wasCaught);
|
||||
|
||||
useEffect(() => {
|
||||
const unsubscribe = fearState.subscribe(() => {
|
||||
setWasCaught(fearState.wasCaught);
|
||||
});
|
||||
return () => unsubscribe();
|
||||
}, []);
|
||||
useEffect(() => {
|
||||
const unsubscribe = fearState.subscribe(() => {
|
||||
setWasCaught(fearState.wasCaught);
|
||||
});
|
||||
return () => unsubscribe();
|
||||
}, []);
|
||||
|
||||
return (<EffectComposer>
|
||||
<Pixelation granularity={wasCaught ? 18 : 10} />
|
||||
<Vignette />
|
||||
<Noise opacity={wasCaught ? 0.01 : 0.003} />
|
||||
<BrightnessContrast
|
||||
brightness={-0.01}
|
||||
contrast={0.05}
|
||||
/>
|
||||
<HueSaturation saturation={wasCaught ? 1 : 0} />
|
||||
</EffectComposer>)
|
||||
return (
|
||||
<EffectComposer>
|
||||
<Pixelation granularity={wasCaught ? 18 : 10} />
|
||||
<Vignette />
|
||||
<Noise opacity={wasCaught ? 0.01 : 0.003} />
|
||||
<BrightnessContrast brightness={-0.01} contrast={0.05} />
|
||||
<HueSaturation saturation={wasCaught ? 1 : 0} />
|
||||
</EffectComposer>
|
||||
);
|
||||
}
|
||||
|
||||
function ListenerCreator() {
|
||||
const { camera } = useThree();
|
||||
const { camera } = useThree();
|
||||
|
||||
useEffect(() => {
|
||||
const listener = new AudioListener();
|
||||
camera.add(listener);
|
||||
useEffect(() => {
|
||||
const listener = new AudioListener();
|
||||
camera.add(listener);
|
||||
|
||||
return () => {
|
||||
camera.remove(listener);
|
||||
};
|
||||
}, [camera]);
|
||||
return () => {
|
||||
camera.remove(listener);
|
||||
};
|
||||
}, [camera]);
|
||||
|
||||
return null;
|
||||
return null;
|
||||
}
|
||||
|
||||
function FearStateUpdater() {
|
||||
useFrame((state, delta) => {
|
||||
fearState.update(delta);
|
||||
});
|
||||
return null;
|
||||
useFrame((state, delta) => {
|
||||
fearState.update(delta);
|
||||
});
|
||||
return null;
|
||||
}
|
||||
|
||||
export default function Fear() {
|
||||
const [isRustActive, setIsRustActive] = useState(fearState.isRustActive);
|
||||
const [wasCaught, setWasCaught] = useState(fearState.isRustActive);
|
||||
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();
|
||||
}, []);
|
||||
useEffect(() => {
|
||||
const unsubscribe = fearState.subscribe(() => {
|
||||
setIsRustActive(fearState.isRustActive);
|
||||
setWasCaught(fearState.wasCaught);
|
||||
});
|
||||
return () => unsubscribe();
|
||||
}, []);
|
||||
|
||||
return (<>
|
||||
<Canvas
|
||||
shadows
|
||||
gl={{ antialias: true }}
|
||||
className='canvas'
|
||||
camera={{ position: [0, 3, -5], fov: 55, far: 100 }}
|
||||
>
|
||||
<FearStateUpdater />
|
||||
return (
|
||||
<>
|
||||
<Canvas
|
||||
shadows
|
||||
gl={{ antialias: true }}
|
||||
className='canvas'
|
||||
camera={{ position: [0, 3, -5], fov: 55, far: 100 }}
|
||||
>
|
||||
<FearStateUpdater />
|
||||
|
||||
<ListenerCreator />
|
||||
<ListenerCreator />
|
||||
|
||||
<color attach="background" args={['#050505']} />
|
||||
<color attach='background' args={['#050505']} />
|
||||
|
||||
{FEAR_SETTINGS.TEST_MODE ? <ambientLight intensity={2} /> : <ambientLight intensity={0.0225} />}
|
||||
{FEAR_SETTINGS.TEST_MODE ? null : <fogExp2 attach='fog' args={[0x050505, 0.035]} />}
|
||||
{FEAR_SETTINGS.TEST_MODE ? null : < PostProcessing />}
|
||||
{FEAR_SETTINGS.TEST_MODE ? (
|
||||
<ambientLight intensity={2} />
|
||||
) : (
|
||||
<ambientLight intensity={0.0225} />
|
||||
)}
|
||||
{FEAR_SETTINGS.TEST_MODE ? null : (
|
||||
<fogExp2 attach='fog' args={[0x050505, 0.035]} />
|
||||
)}
|
||||
{FEAR_SETTINGS.TEST_MODE ? null : <PostProcessing />}
|
||||
|
||||
<Suspense fallback={null}>
|
||||
<Hallway />
|
||||
<TheCreature />
|
||||
<Player />
|
||||
</Suspense>
|
||||
<Suspense fallback={null}>
|
||||
<Hallway />
|
||||
<TheCreature />
|
||||
<Player />
|
||||
</Suspense>
|
||||
|
||||
<AmbientSound
|
||||
key="ambient-1"
|
||||
url='fear/snd/ambience.mp3'
|
||||
volume={isRustActive ? 0 : 0.5}
|
||||
/>
|
||||
<AmbientSound
|
||||
key='ambient-1'
|
||||
url='fear/snd/ambience.mp3'
|
||||
volume={isRustActive ? 0 : 0.5}
|
||||
/>
|
||||
|
||||
<AmbientSound
|
||||
key="ambient-2"
|
||||
url='fear/snd/ambience2.mp3'
|
||||
volume={isRustActive ? 1 : 0}
|
||||
/>
|
||||
<AmbientSound
|
||||
key='ambient-2'
|
||||
url='fear/snd/ambience2.mp3'
|
||||
volume={isRustActive ? 1 : 0}
|
||||
/>
|
||||
|
||||
{wasCaught ? <AmbientSound
|
||||
key="ambient-glitch"
|
||||
url='fear/snd/glitch.mp3'
|
||||
volume={1}
|
||||
/> : null}
|
||||
</Canvas>
|
||||
{wasCaught ? (
|
||||
<AmbientSound
|
||||
key='ambient-glitch'
|
||||
url='fear/snd/glitch.mp3'
|
||||
volume={1}
|
||||
/>
|
||||
) : null}
|
||||
</Canvas>
|
||||
|
||||
<FinaleText />
|
||||
</>)
|
||||
}
|
||||
<FinaleText />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,64 +1,67 @@
|
||||
import { useEffect, useRef } from 'react'
|
||||
import { useEffect, useRef } from 'react';
|
||||
|
||||
interface AmbientSoundProps {
|
||||
url: string
|
||||
volume?: number
|
||||
url: string;
|
||||
volume?: number;
|
||||
}
|
||||
|
||||
export function AmbientSound({ url, volume = 0.5 }: AmbientSoundProps) {
|
||||
const audioRef = useRef<HTMLAudioElement | null>(null)
|
||||
const targetVolumeRef = useRef<number>(volume)
|
||||
const audioRef = useRef<HTMLAudioElement | null>(null);
|
||||
const targetVolumeRef = useRef<number>(volume);
|
||||
|
||||
targetVolumeRef.current = volume
|
||||
targetVolumeRef.current = volume;
|
||||
|
||||
useEffect(() => {
|
||||
const audio = new Audio(url)
|
||||
audio.loop = true
|
||||
audio.volume = 0
|
||||
audioRef.current = audio
|
||||
useEffect(() => {
|
||||
const audio = new Audio(url);
|
||||
audio.loop = true;
|
||||
audio.volume = 0;
|
||||
audioRef.current = audio;
|
||||
|
||||
let componentsMounted = true
|
||||
let componentsMounted = true;
|
||||
|
||||
const attemptPlay = () => {
|
||||
if (!audioRef.current || !componentsMounted) return
|
||||
|
||||
audio.volume = targetVolumeRef.current
|
||||
const attemptPlay = () => {
|
||||
if (!audioRef.current || !componentsMounted) return;
|
||||
|
||||
if (audio.volume > 0 && audio.paused) {
|
||||
audio.play().catch((err) => {
|
||||
console.warn('Autoplay management holding clip playback execution.', err)
|
||||
})
|
||||
}
|
||||
}
|
||||
audio.volume = targetVolumeRef.current;
|
||||
|
||||
attemptPlay()
|
||||
if (audio.volume > 0 && audio.paused) {
|
||||
audio.play().catch((err) => {
|
||||
console.warn(
|
||||
'Autoplay management holding clip playback execution.',
|
||||
err
|
||||
);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
window.addEventListener('click', attemptPlay)
|
||||
window.addEventListener('keydown', attemptPlay)
|
||||
attemptPlay();
|
||||
|
||||
return () => {
|
||||
componentsMounted = false
|
||||
window.removeEventListener('click', attemptPlay)
|
||||
window.removeEventListener('keydown', attemptPlay)
|
||||
audio.pause()
|
||||
audio.src = ''
|
||||
audioRef.current = null
|
||||
}
|
||||
}, [url])
|
||||
window.addEventListener('click', attemptPlay);
|
||||
window.addEventListener('keydown', attemptPlay);
|
||||
|
||||
useEffect(() => {
|
||||
const audio = audioRef.current
|
||||
if (!audio) return
|
||||
return () => {
|
||||
componentsMounted = false;
|
||||
window.removeEventListener('click', attemptPlay);
|
||||
window.removeEventListener('keydown', attemptPlay);
|
||||
audio.pause();
|
||||
audio.src = '';
|
||||
audioRef.current = null;
|
||||
};
|
||||
}, [url]);
|
||||
|
||||
if (volume === 0) {
|
||||
if (!audio.paused) audio.pause()
|
||||
} else {
|
||||
audio.volume = volume
|
||||
if (audio.paused) {
|
||||
audio.play().catch(() => {})
|
||||
}
|
||||
}
|
||||
}, [volume])
|
||||
useEffect(() => {
|
||||
const audio = audioRef.current;
|
||||
if (!audio) return;
|
||||
|
||||
return null
|
||||
}
|
||||
if (volume === 0) {
|
||||
if (!audio.paused) audio.pause();
|
||||
} else {
|
||||
audio.volume = volume;
|
||||
if (audio.paused) {
|
||||
audio.play().catch(() => {});
|
||||
}
|
||||
}
|
||||
}, [volume]);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -1,196 +1,196 @@
|
||||
import { useTexture, PositionalAudio } from "@react-three/drei";
|
||||
import { useFrame, useThree } from "@react-three/fiber";
|
||||
import { useEffect, useMemo, useRef, useState } from "react";
|
||||
import { useTexture, PositionalAudio } from '@react-three/drei';
|
||||
import { useFrame, useThree } from '@react-three/fiber';
|
||||
import { useEffect, useMemo, useRef, useState } from 'react';
|
||||
|
||||
import * as THREE from "three";
|
||||
import { FEAR_SETTINGS, fearState } from "../state";
|
||||
import { ShaderPatch } from "../shader-patch";
|
||||
import * as THREE from 'three';
|
||||
import { FEAR_SETTINGS, fearState } from '../state';
|
||||
import { ShaderPatch } from '../shader-patch';
|
||||
|
||||
useTexture.preload('fear/img/creature.png');
|
||||
|
||||
export default function TheCreature() {
|
||||
const baseTexture = useTexture('fear/img/creature.png');
|
||||
const baseTexture = useTexture('fear/img/creature.png');
|
||||
|
||||
const texture = useMemo(() => {
|
||||
const t = baseTexture.clone();
|
||||
t.needsUpdate = true;
|
||||
return t;
|
||||
}, [baseTexture]);
|
||||
const texture = useMemo(() => {
|
||||
const t = baseTexture.clone();
|
||||
t.needsUpdate = true;
|
||||
return t;
|
||||
}, [baseTexture]);
|
||||
|
||||
const meshRef = useRef<THREE.Mesh>(null);
|
||||
const audioRef = useRef<THREE.PositionalAudio>(null);
|
||||
const { camera } = useThree();
|
||||
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 [hasTriggered, setHasTriggered] = useState(false);
|
||||
const [isSpawned, setIsSpawned] = useState(false);
|
||||
|
||||
const globalDistance = useRef<number>(32);
|
||||
const [finaleTriggered, setFinaleTriggered] = useState(fearState.finaleTriggered);
|
||||
const globalDistance = useRef<number>(32);
|
||||
const [finaleTriggered, setFinaleTriggered] = useState(
|
||||
fearState.finaleTriggered
|
||||
);
|
||||
|
||||
const audioPlaying = useRef<boolean>(false);
|
||||
const audioPlaying = useRef<boolean>(false);
|
||||
|
||||
const movePhase = useRef<'frozen' | 'lurching'>('frozen');
|
||||
const phaseTimer = useRef<number>(1.5);
|
||||
const movePhase = useRef<'frozen' | 'lurching'>('frozen');
|
||||
const phaseTimer = useRef<number>(1.5);
|
||||
|
||||
const glitchCooldown = useRef<number>(0);
|
||||
const isGlitchSpiking = useRef<boolean>(false);
|
||||
const flickerCooldown = useRef<number>(0);
|
||||
const glitchCooldown = useRef<number>(0);
|
||||
const isGlitchSpiking = useRef<boolean>(false);
|
||||
const flickerCooldown = useRef<number>(0);
|
||||
|
||||
useEffect(() => {
|
||||
const unsubscribe = fearState.subscribe(() => {
|
||||
setFinaleTriggered(fearState.finaleTriggered);
|
||||
useEffect(() => {
|
||||
const unsubscribe = fearState.subscribe(() => {
|
||||
setFinaleTriggered(fearState.finaleTriggered);
|
||||
|
||||
if (!fearState.finaleTriggered) {
|
||||
setIsSpawned(false);
|
||||
setHasTriggered(false);
|
||||
globalDistance.current = 32;
|
||||
audioPlaying.current = false;
|
||||
movePhase.current = 'frozen';
|
||||
phaseTimer.current = 1.5;
|
||||
if (!fearState.finaleTriggered) {
|
||||
setIsSpawned(false);
|
||||
setHasTriggered(false);
|
||||
globalDistance.current = 32;
|
||||
audioPlaying.current = false;
|
||||
movePhase.current = 'frozen';
|
||||
phaseTimer.current = 1.5;
|
||||
|
||||
if (audioRef.current && audioRef.current.isPlaying)
|
||||
audioRef.current.stop();
|
||||
}
|
||||
});
|
||||
return () => unsubscribe();
|
||||
}, []);
|
||||
if (audioRef.current && audioRef.current.isPlaying)
|
||||
audioRef.current.stop();
|
||||
}
|
||||
});
|
||||
return () => unsubscribe();
|
||||
}, []);
|
||||
|
||||
useFrame((state, delta) => {
|
||||
if (!fearState.finaleTriggered) return;
|
||||
useFrame((state, delta) => {
|
||||
if (!fearState.finaleTriggered) return;
|
||||
|
||||
const creature = meshRef.current;
|
||||
if (!creature) return;
|
||||
const creature = meshRef.current;
|
||||
if (!creature) return;
|
||||
|
||||
if (!isSpawned) {
|
||||
setIsSpawned(true);
|
||||
globalDistance.current = 32;
|
||||
movePhase.current = 'frozen';
|
||||
phaseTimer.current = 1.0 + Math.random() * 1.5;
|
||||
}
|
||||
if (!isSpawned) {
|
||||
setIsSpawned(true);
|
||||
globalDistance.current = 32;
|
||||
movePhase.current = 'frozen';
|
||||
phaseTimer.current = 1.0 + Math.random() * 1.5;
|
||||
}
|
||||
|
||||
if (!hasTriggered) {
|
||||
if (globalDistance.current < 40)
|
||||
setHasTriggered(true);
|
||||
if (!hasTriggered) {
|
||||
if (globalDistance.current < 40) setHasTriggered(true);
|
||||
}
|
||||
|
||||
}
|
||||
if (hasTriggered) {
|
||||
phaseTimer.current -= delta;
|
||||
|
||||
if (hasTriggered) {
|
||||
phaseTimer.current -= delta;
|
||||
if (phaseTimer.current <= 0) {
|
||||
if (movePhase.current === 'frozen') {
|
||||
movePhase.current = 'lurching';
|
||||
phaseTimer.current = 0.05 + Math.random() * 0.2;
|
||||
} else {
|
||||
movePhase.current = 'frozen';
|
||||
const proximityFactor = Math.max(0.05, globalDistance.current / 32);
|
||||
phaseTimer.current = (0.2 + Math.random() * 1.0) * proximityFactor;
|
||||
}
|
||||
}
|
||||
|
||||
if (phaseTimer.current <= 0) {
|
||||
if (movePhase.current === 'frozen') {
|
||||
movePhase.current = 'lurching';
|
||||
phaseTimer.current = 0.05 + Math.random() * 0.2;
|
||||
} else {
|
||||
movePhase.current = 'frozen';
|
||||
const proximityFactor = Math.max(0.05, globalDistance.current / 32);
|
||||
phaseTimer.current = (0.2 + Math.random() * 1.0) * proximityFactor;
|
||||
}
|
||||
}
|
||||
if (movePhase.current === 'lurching') {
|
||||
globalDistance.current -= FEAR_SETTINGS.CREATURE_SPEED * 3 * delta;
|
||||
}
|
||||
|
||||
if (movePhase.current === 'lurching') {
|
||||
globalDistance.current -= FEAR_SETTINGS.CREATURE_SPEED * 3 * delta;
|
||||
}
|
||||
if (audioRef.current && !audioPlaying.current) {
|
||||
audioPlaying.current = true;
|
||||
if (audioRef.current.context.state === 'suspended')
|
||||
audioRef.current.context.resume();
|
||||
audioRef.current.play();
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
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 forwardVector = new THREE.Vector3();
|
||||
camera.getWorldDirection(forwardVector);
|
||||
const lookDirZ = forwardVector.z < 0 ? -1 : 1;
|
||||
const calculatedZ = camera.position.z + lookDirZ * globalDistance.current;
|
||||
|
||||
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);
|
||||
|
||||
creature.position.set(0, 1.6, calculatedZ);
|
||||
creature.lookAt(camera.position.x, creature.position.y, camera.position.z);
|
||||
if (!hasTriggered) return;
|
||||
|
||||
if (!hasTriggered) return;
|
||||
const proximity = 1 - Math.max(0, Math.min(1, globalDistance.current / 32));
|
||||
const jitterX = 1.0 + (Math.random() - 0.5) * 0.04 * proximity;
|
||||
let jitterY = 1.0 + (Math.random() - 0.5) * 0.06 * proximity;
|
||||
|
||||
const proximity = 1 - Math.max(0, Math.min(1, globalDistance.current / 32));
|
||||
const jitterX = 1.0 + (Math.random() - 0.5) * 0.04 * proximity;
|
||||
let jitterY = 1.0 + (Math.random() - 0.5) * 0.06 * proximity;
|
||||
glitchCooldown.current -= delta;
|
||||
if (glitchCooldown.current <= 0) {
|
||||
if (Math.random() < 0.25 + proximity * 0.35) {
|
||||
isGlitchSpiking.current = true;
|
||||
glitchCooldown.current = 0.03 + Math.random() * 0.08;
|
||||
} else {
|
||||
isGlitchSpiking.current = false;
|
||||
glitchCooldown.current =
|
||||
0.08 + Math.random() * 0.4 * (1 - proximity * 0.7);
|
||||
}
|
||||
}
|
||||
|
||||
glitchCooldown.current -= delta;
|
||||
if (glitchCooldown.current <= 0) {
|
||||
if (Math.random() < 0.25 + proximity * 0.35) {
|
||||
isGlitchSpiking.current = true;
|
||||
glitchCooldown.current = 0.03 + Math.random() * 0.08;
|
||||
} else {
|
||||
isGlitchSpiking.current = false;
|
||||
glitchCooldown.current = 0.08 + Math.random() * 0.4 * (1 - proximity * 0.7);
|
||||
}
|
||||
}
|
||||
if (isGlitchSpiking.current) {
|
||||
const spike = 0.15 + Math.random() * 0.35;
|
||||
jitterY += Math.random() > 0.5 ? spike : -spike * 0.6;
|
||||
}
|
||||
|
||||
if (isGlitchSpiking.current) {
|
||||
const spike = 0.15 + Math.random() * 0.35;
|
||||
jitterY += Math.random() > 0.5 ? spike : -spike * 0.6;
|
||||
}
|
||||
creature.scale.set(jitterX, jitterY, 1.0);
|
||||
|
||||
creature.scale.set(jitterX, jitterY, 1.0);
|
||||
flickerCooldown.current -= delta;
|
||||
if (flickerCooldown.current <= 0) {
|
||||
if (creature.visible && Math.random() < 0.12 + proximity * 0.08) {
|
||||
creature.visible = false;
|
||||
flickerCooldown.current = 0.02 + Math.random() * 0.05;
|
||||
} else {
|
||||
creature.visible = true;
|
||||
flickerCooldown.current =
|
||||
0.05 + Math.random() * 0.3 * (1 - proximity * 0.5);
|
||||
}
|
||||
}
|
||||
|
||||
flickerCooldown.current -= delta;
|
||||
if (flickerCooldown.current <= 0) {
|
||||
if (creature.visible && Math.random() < 0.12 + proximity * 0.08) {
|
||||
creature.visible = false;
|
||||
flickerCooldown.current = 0.02 + Math.random() * 0.05;
|
||||
} else {
|
||||
creature.visible = true;
|
||||
flickerCooldown.current = 0.05 + Math.random() * 0.3 * (1 - proximity * 0.5);
|
||||
}
|
||||
}
|
||||
texture.offset.set(
|
||||
(Math.random() - 0.5) * 0.025 * proximity,
|
||||
(Math.random() - 0.5) * 0.025 * proximity
|
||||
);
|
||||
|
||||
texture.offset.set(
|
||||
(Math.random() - 0.5) * 0.025 * proximity,
|
||||
(Math.random() - 0.5) * 0.025 * proximity
|
||||
);
|
||||
if (proximity > 0.2) {
|
||||
creature.position.x += (Math.random() - 0.5) * 0.12 * proximity;
|
||||
creature.position.y += (Math.random() - 0.5) * 0.06 * proximity;
|
||||
}
|
||||
});
|
||||
|
||||
if (proximity > 0.2) {
|
||||
creature.position.x += (Math.random() - 0.5) * 0.12 * proximity;
|
||||
creature.position.y += (Math.random() - 0.5) * 0.06 * proximity;
|
||||
}
|
||||
});
|
||||
return (
|
||||
<mesh ref={meshRef} visible={finaleTriggered}>
|
||||
<planeGeometry args={[3.0, 4.8]} />
|
||||
<meshStandardMaterial
|
||||
map={texture}
|
||||
transparent={true}
|
||||
depthWrite={false}
|
||||
side={THREE.DoubleSide}
|
||||
onBeforeCompile={ShaderPatch}
|
||||
emissive='#ffffff'
|
||||
emissiveMap={texture}
|
||||
emissiveIntensity={0.15}
|
||||
/>
|
||||
|
||||
return (
|
||||
<mesh
|
||||
ref={meshRef}
|
||||
visible={finaleTriggered}
|
||||
>
|
||||
<planeGeometry args={[3.0, 4.8]} />
|
||||
<meshStandardMaterial
|
||||
map={texture}
|
||||
transparent={true}
|
||||
depthWrite={false}
|
||||
side={THREE.DoubleSide}
|
||||
onBeforeCompile={ShaderPatch}
|
||||
emissive="#ffffff"
|
||||
emissiveMap={texture}
|
||||
emissiveIntensity={0.15}
|
||||
/>
|
||||
|
||||
{finaleTriggered && (
|
||||
<PositionalAudio
|
||||
url="fear/snd/riser.mp3"
|
||||
ref={audioRef}
|
||||
distance={25}
|
||||
loop={false}
|
||||
autoplay={false}
|
||||
/>
|
||||
)}
|
||||
</mesh>
|
||||
);
|
||||
}
|
||||
{finaleTriggered && (
|
||||
<PositionalAudio
|
||||
url='fear/snd/riser.mp3'
|
||||
ref={audioRef}
|
||||
distance={25}
|
||||
loop={false}
|
||||
autoplay={false}
|
||||
/>
|
||||
)}
|
||||
</mesh>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,84 +1,82 @@
|
||||
@font-face {
|
||||
font-family: 'VCR';
|
||||
src: url('/fear/fonts/vcr.ttf') format('truetype');
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
font-family: 'VCR';
|
||||
src: url('/fear/fonts/vcr.ttf') format('truetype');
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
.finale-container {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
left: 0;
|
||||
top: 0vh;
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
left: 0;
|
||||
top: 0vh;
|
||||
|
||||
display: grid;
|
||||
align-items: center;
|
||||
align-content: center;
|
||||
justify-content: center;
|
||||
overflow: hidden;
|
||||
display: grid;
|
||||
align-items: center;
|
||||
align-content: center;
|
||||
justify-content: center;
|
||||
overflow: hidden;
|
||||
|
||||
/* filter: invert(100%); */
|
||||
backdrop-filter: brightness(100%);
|
||||
/* filter: invert(100%); */
|
||||
backdrop-filter: brightness(100%);
|
||||
|
||||
grid-auto-rows: 5vh;
|
||||
/* grid-template-columns: 0; */
|
||||
grid-template-rows: repeat(auto-fit, max-content);
|
||||
grid-auto-rows: 5vh;
|
||||
/* grid-template-columns: 0; */
|
||||
grid-template-rows: repeat(auto-fit, max-content);
|
||||
|
||||
user-select: none;
|
||||
|
||||
will-change: filter;
|
||||
animation: invertFlicker 0.07s infinite alternate;
|
||||
user-select: none;
|
||||
|
||||
will-change: filter;
|
||||
animation: invertFlicker 0.07s infinite alternate;
|
||||
}
|
||||
|
||||
@keyframes invertFlicker {
|
||||
0%,
|
||||
43%,
|
||||
45%,
|
||||
88%,
|
||||
92% {
|
||||
filter: invert(0%) contrast(100%) brightness(100%);
|
||||
backdrop-filter: brightness(100%) hue-rotate(0deg);
|
||||
}
|
||||
|
||||
0%,
|
||||
43%,
|
||||
45%,
|
||||
88%,
|
||||
92% {
|
||||
filter: invert(0%) contrast(100%) brightness(100%);
|
||||
backdrop-filter: brightness(100%) hue-rotate(0deg);
|
||||
}
|
||||
|
||||
44%,
|
||||
46%,
|
||||
89%,
|
||||
93%,
|
||||
100% {
|
||||
filter: invert(100%) contrast(300%) brightness(150%);
|
||||
backdrop-filter: brightness(30%) hue-rotate(180deg) saturate(500%);
|
||||
}
|
||||
44%,
|
||||
46%,
|
||||
89%,
|
||||
93%,
|
||||
100% {
|
||||
filter: invert(100%) contrast(300%) brightness(150%);
|
||||
backdrop-filter: brightness(30%) hue-rotate(180deg) saturate(500%);
|
||||
}
|
||||
}
|
||||
|
||||
.finale-text {
|
||||
font-family: 'VCR', sans-serif;
|
||||
font-variant-numeric: tabular-nums;
|
||||
letter-spacing: 0.1em;
|
||||
font-family: 'VCR', sans-serif;
|
||||
font-variant-numeric: tabular-nums;
|
||||
letter-spacing: 0.1em;
|
||||
|
||||
height: 0px;
|
||||
width: 100%;
|
||||
color: rgb(255, 255, 255);
|
||||
font-size: 8vh;
|
||||
|
||||
text-align: center;
|
||||
white-space: nowrap;
|
||||
height: 0px;
|
||||
width: 100%;
|
||||
color: rgb(255, 255, 255);
|
||||
font-size: 8vh;
|
||||
|
||||
text-align: center;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.scanlines {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: 900;
|
||||
background: repeating-linear-gradient(rgba(0, 0, 0, 0) 0px,
|
||||
rgba(0, 0, 0, 0) 2px,
|
||||
rgba(0, 0, 0, 0.3) 2px,
|
||||
rgba(0, 0, 0, 0.3) 4px);
|
||||
|
||||
}
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: 900;
|
||||
background: repeating-linear-gradient(
|
||||
rgba(0, 0, 0, 0) 0px,
|
||||
rgba(0, 0, 0, 0) 2px,
|
||||
rgba(0, 0, 0, 0.3) 2px,
|
||||
rgba(0, 0, 0, 0.3) 4px
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,56 +1,86 @@
|
||||
import { JSX, useEffect, useState } from "react"
|
||||
import { fearState } from "../state"
|
||||
import { JSX, useEffect, useState } from 'react';
|
||||
import { fearState } from '../state';
|
||||
|
||||
import './finale-text.css';
|
||||
|
||||
const BLOCKS = [
|
||||
"▀", "▂", "▃", "▄", "▅", "▆", "▇",
|
||||
"█", "▉", "▊", "▋", "▌", "▍", "▎", "▏",
|
||||
"▐", "░", "▒", "▓", "▔", "▕", "▖", "▗",
|
||||
"▘", "▙", "▚", "▛", "▜", "▝", "▞", "▟"
|
||||
'▀',
|
||||
'▂',
|
||||
'▃',
|
||||
'▄',
|
||||
'▅',
|
||||
'▆',
|
||||
'▇',
|
||||
'█',
|
||||
'▉',
|
||||
'▊',
|
||||
'▋',
|
||||
'▌',
|
||||
'▍',
|
||||
'▎',
|
||||
'▏',
|
||||
'▐',
|
||||
'░',
|
||||
'▒',
|
||||
'▓',
|
||||
'▔',
|
||||
'▕',
|
||||
'▖',
|
||||
'▗',
|
||||
'▘',
|
||||
'▙',
|
||||
'▚',
|
||||
'▛',
|
||||
'▜',
|
||||
'▝',
|
||||
'▞',
|
||||
'▟'
|
||||
];
|
||||
|
||||
export default function FinaleText() {
|
||||
const [wasCaught, setWasCaught] = useState(fearState.wasCaught);
|
||||
const [elements, setElements] = useState<JSX.Element[]>([]);
|
||||
const [wasCaught, setWasCaught] = useState(fearState.wasCaught);
|
||||
const [elements, setElements] = useState<JSX.Element[]>([]);
|
||||
|
||||
useEffect(() => {
|
||||
const unsubscribe = fearState.subscribe(() => {
|
||||
setWasCaught(fearState.wasCaught)
|
||||
});
|
||||
return () => unsubscribe();
|
||||
}, []);
|
||||
useEffect(() => {
|
||||
const unsubscribe = fearState.subscribe(() => {
|
||||
setWasCaught(fearState.wasCaught);
|
||||
});
|
||||
return () => unsubscribe();
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (!wasCaught) return;
|
||||
|
||||
useEffect(() => {
|
||||
if (!wasCaught)
|
||||
return;
|
||||
const interval = setInterval(() => {
|
||||
if (Math.random() > 0.9) return;
|
||||
|
||||
const interval = setInterval(() => {
|
||||
if (Math.random() > 0.9) return;
|
||||
const baseText = 'bwaaaaaaaaa';
|
||||
const corrupted = baseText
|
||||
.split('')
|
||||
.map((char) =>
|
||||
Math.random() > 0.98
|
||||
? BLOCKS[Math.floor(Math.random() * BLOCKS.length)]
|
||||
: char
|
||||
)
|
||||
.join('');
|
||||
|
||||
const baseText = "bwaaaaaaaaa";
|
||||
const corrupted = baseText
|
||||
.split("")
|
||||
.map((char) => (Math.random() > 0.98 ? BLOCKS[Math.floor(Math.random() * BLOCKS.length)] : char))
|
||||
.join("");
|
||||
setElements((prev) => [
|
||||
...prev.slice(-30),
|
||||
<span className='finale-text' key={crypto.randomUUID()}>
|
||||
{corrupted}
|
||||
</span>
|
||||
]);
|
||||
}, 10);
|
||||
|
||||
setElements((prev) => [...prev.slice(-30),
|
||||
<span className="finale-text" key={crypto.randomUUID()}>
|
||||
{corrupted}
|
||||
</span>
|
||||
]);
|
||||
}, 10);
|
||||
return () => clearInterval(interval);
|
||||
}, [wasCaught]);
|
||||
|
||||
return () => clearInterval(interval);
|
||||
}, [wasCaught]);
|
||||
if (!wasCaught) return null;
|
||||
|
||||
if (!wasCaught) return null;
|
||||
|
||||
return (<>
|
||||
<div className="finale-container">
|
||||
{elements}
|
||||
</div>
|
||||
<div className="scanlines" />
|
||||
</>)
|
||||
}
|
||||
return (
|
||||
<>
|
||||
<div className='finale-container'>{elements}</div>
|
||||
<div className='scanlines' />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,401 +1,532 @@
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { FEAR_SETTINGS, fearState } from "../state";
|
||||
import { useTexture, PositionalAudio } from "@react-three/drei";
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import { FEAR_SETTINGS, fearState } from '../state';
|
||||
import { useTexture, PositionalAudio } from '@react-three/drei';
|
||||
|
||||
import * as THREE from "three";
|
||||
import { useFrame } from "@react-three/fiber";
|
||||
import { ShaderPatch } from "../shader-patch";
|
||||
import * as THREE from 'three';
|
||||
import { useFrame } from '@react-three/fiber';
|
||||
import { ShaderPatch } from '../shader-patch';
|
||||
|
||||
interface DoorProps {
|
||||
position: [number, number, number];
|
||||
rotation: [number, number, number];
|
||||
position: [number, number, number];
|
||||
rotation: [number, number, number];
|
||||
}
|
||||
function Door({ position, rotation }: DoorProps) {
|
||||
const [soundUrl, setSoundUrl] = useState<string | null>(null);
|
||||
const currentSound = useRef<string | null>(null);
|
||||
const steelTex = useTexture('fear/img/steel.png');
|
||||
const [soundUrl, setSoundUrl] = useState<string | null>(null);
|
||||
const currentSound = useRef<string | null>(null);
|
||||
const steelTex = useTexture('fear/img/steel.png');
|
||||
|
||||
useEffect(() => {
|
||||
const interval = setInterval(() => {
|
||||
if (Math.random() < 0.02) {
|
||||
const chosenSound = Math.random() < 0.5 ? "fear/snd/knock1.mp3" : "fear/snd/knock2.mp3";
|
||||
useEffect(() => {
|
||||
const interval = setInterval(() => {
|
||||
if (Math.random() < 0.02) {
|
||||
const chosenSound =
|
||||
Math.random() < 0.5 ? 'fear/snd/knock1.mp3' : 'fear/snd/knock2.mp3';
|
||||
|
||||
setSoundUrl(chosenSound);
|
||||
currentSound.current = chosenSound;
|
||||
}
|
||||
}, 5000);
|
||||
setSoundUrl(chosenSound);
|
||||
currentSound.current = chosenSound;
|
||||
}
|
||||
}, 5000);
|
||||
|
||||
return () => clearInterval(interval);
|
||||
}, []);
|
||||
return () => clearInterval(interval);
|
||||
}, []);
|
||||
|
||||
const handleAudioEnded = () => {
|
||||
setSoundUrl(null);
|
||||
currentSound.current = null;
|
||||
};
|
||||
const handleAudioEnded = () => {
|
||||
setSoundUrl(null);
|
||||
currentSound.current = null;
|
||||
};
|
||||
|
||||
return (
|
||||
<group position={position} rotation={rotation}>
|
||||
{/* frame */}
|
||||
<mesh position={[0, 2, -0.1]}>
|
||||
<boxGeometry args={[2.4, 4.0, 0.2, 4, 4, 1]} />
|
||||
<meshStandardMaterial map={steelTex} color="#8d8d8d" onBeforeCompile={ShaderPatch} />
|
||||
</mesh>
|
||||
return (
|
||||
<group position={position} rotation={rotation}>
|
||||
{/* frame */}
|
||||
<mesh position={[0, 2, -0.1]}>
|
||||
<boxGeometry args={[2.4, 4.0, 0.2, 4, 4, 1]} />
|
||||
<meshStandardMaterial
|
||||
map={steelTex}
|
||||
color='#8d8d8d'
|
||||
onBeforeCompile={ShaderPatch}
|
||||
/>
|
||||
</mesh>
|
||||
|
||||
{/* panel */}
|
||||
<mesh position={[0, 1.95, -0.0]}>
|
||||
<boxGeometry args={[2.1, 3.8, 0.1, 4, 4, 1]} />
|
||||
<meshStandardMaterial map={steelTex} color="#4e4a4a" onBeforeCompile={ShaderPatch} />
|
||||
</mesh>
|
||||
{/* panel */}
|
||||
<mesh position={[0, 1.95, -0.0]}>
|
||||
<boxGeometry args={[2.1, 3.8, 0.1, 4, 4, 1]} />
|
||||
<meshStandardMaterial
|
||||
map={steelTex}
|
||||
color='#4e4a4a'
|
||||
onBeforeCompile={ShaderPatch}
|
||||
/>
|
||||
</mesh>
|
||||
|
||||
{/* handle */}
|
||||
<mesh position={[0.75, 1.8, .085]}>
|
||||
<boxGeometry args={[0.3, 0.08, 0.1]} />
|
||||
<meshStandardMaterial map={steelTex} color="#ffffff" onBeforeCompile={ShaderPatch} />
|
||||
</mesh>
|
||||
{/* handle */}
|
||||
<mesh position={[0.75, 1.8, 0.085]}>
|
||||
<boxGeometry args={[0.3, 0.08, 0.1]} />
|
||||
<meshStandardMaterial
|
||||
map={steelTex}
|
||||
color='#ffffff'
|
||||
onBeforeCompile={ShaderPatch}
|
||||
/>
|
||||
</mesh>
|
||||
|
||||
{soundUrl && (
|
||||
<PositionalAudio
|
||||
url={soundUrl}
|
||||
distance={25}
|
||||
loop={false}
|
||||
autoplay={true}
|
||||
onEnded={handleAudioEnded}
|
||||
/>
|
||||
)}
|
||||
</group>
|
||||
);
|
||||
{soundUrl && (
|
||||
<PositionalAudio
|
||||
url={soundUrl}
|
||||
distance={25}
|
||||
loop={false}
|
||||
autoplay={true}
|
||||
onEnded={handleAudioEnded}
|
||||
/>
|
||||
)}
|
||||
</group>
|
||||
);
|
||||
}
|
||||
|
||||
export default function Hallway() {
|
||||
const [width, setWidth] = useState(fearState.currentWidth);
|
||||
const [floorTex, wallTex, rustWallTex, rustFloorTex] = useTexture([
|
||||
'fear/img/concrete-floor.png',
|
||||
'fear/img/concrete-wall.png',
|
||||
'fear/img/rust.png',
|
||||
'fear/img/rust.png'
|
||||
]);
|
||||
const [width, setWidth] = useState(fearState.currentWidth);
|
||||
const [floorTex, wallTex, rustWallTex, rustFloorTex] = useTexture([
|
||||
'fear/img/concrete-floor.png',
|
||||
'fear/img/concrete-wall.png',
|
||||
'fear/img/rust.png',
|
||||
'fear/img/rust.png'
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
[floorTex, wallTex, rustWallTex, rustFloorTex].forEach((tex) => {
|
||||
tex.wrapS = tex.wrapT = THREE.RepeatWrapping;
|
||||
tex.minFilter = tex.magFilter = THREE.NearestFilter;
|
||||
tex.colorSpace = THREE.SRGBColorSpace;
|
||||
});
|
||||
}, [floorTex, wallTex, rustWallTex, rustFloorTex]);
|
||||
useEffect(() => {
|
||||
[floorTex, wallTex, rustWallTex, rustFloorTex].forEach((tex) => {
|
||||
tex.wrapS = tex.wrapT = THREE.RepeatWrapping;
|
||||
tex.minFilter = tex.magFilter = THREE.NearestFilter;
|
||||
tex.colorSpace = THREE.SRGBColorSpace;
|
||||
});
|
||||
}, [floorTex, wallTex, rustWallTex, rustFloorTex]);
|
||||
|
||||
const segmentPool = [0, 1, 2, 3, 4];
|
||||
const segmentCount = segmentPool.length;
|
||||
|
||||
const segmentPool = [0, 1, 2, 3, 4];
|
||||
const segmentCount = segmentPool.length;
|
||||
const lightRefs = useRef<(THREE.PointLight | null)[]>([]);
|
||||
const matRefs = useRef<(THREE.MeshStandardMaterial | null)[]>([]);
|
||||
|
||||
const lightRefs = useRef<(THREE.PointLight | null)[]>([]);
|
||||
const matRefs = useRef<(THREE.MeshStandardMaterial | null)[]>([]);
|
||||
const lightState = useRef<'normal' | 'flickering' | 'dead'>('normal');
|
||||
const stateEndTime = useRef<number>(0);
|
||||
const nextEventTime = useRef<number>(5);
|
||||
|
||||
const lightState = useRef<'normal' | 'flickering' | 'dead'>('normal');
|
||||
const stateEndTime = useRef<number>(0);
|
||||
const nextEventTime = useRef<number>(5);
|
||||
const segmentsRef = useRef<THREE.Group[]>([]);
|
||||
const wallMaterialsRef = useRef<THREE.MeshStandardMaterial[]>([]);
|
||||
const floorMaterialsRef = useRef<THREE.MeshStandardMaterial[]>([]);
|
||||
const pipeMaterialsRef = useRef<THREE.MeshStandardMaterial[]>([]);
|
||||
const bracketMaterialsRef = useRef<THREE.MeshStandardMaterial[]>([]);
|
||||
|
||||
const segmentsRef = useRef<THREE.Group[]>([]);
|
||||
const wallMaterialsRef = useRef<THREE.MeshStandardMaterial[]>([]);
|
||||
const floorMaterialsRef = useRef<THREE.MeshStandardMaterial[]>([]);
|
||||
const pipeMaterialsRef = useRef<THREE.MeshStandardMaterial[]>([]);
|
||||
const bracketMaterialsRef = useRef<THREE.MeshStandardMaterial[]>([]);
|
||||
wallMaterialsRef.current = [];
|
||||
floorMaterialsRef.current = [];
|
||||
pipeMaterialsRef.current = [];
|
||||
bracketMaterialsRef.current = [];
|
||||
|
||||
wallMaterialsRef.current = [];
|
||||
floorMaterialsRef.current = [];
|
||||
pipeMaterialsRef.current = [];
|
||||
bracketMaterialsRef.current = [];
|
||||
const [isRustActive, setIsRustActive] = useState(fearState.isRustActive);
|
||||
|
||||
const [isRustActive, setIsRustActive] = useState(fearState.isRustActive);
|
||||
useEffect(() => {
|
||||
const unsubscribe = fearState.subscribe(() => {
|
||||
setWidth(fearState.currentWidth);
|
||||
setIsRustActive(fearState.isRustActive);
|
||||
});
|
||||
return () => unsubscribe();
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const unsubscribe = fearState.subscribe(() => {
|
||||
setWidth(fearState.currentWidth);
|
||||
setIsRustActive(fearState.isRustActive);
|
||||
});
|
||||
return () => unsubscribe();
|
||||
}, []);
|
||||
useFrame((state, delta) => {
|
||||
const time = state.clock.elapsedTime;
|
||||
|
||||
useFrame((state, delta) => {
|
||||
const time = state.clock.elapsedTime;
|
||||
|
||||
/*
|
||||
/*
|
||||
lights
|
||||
*/
|
||||
let intensity1 = 0.85 + Math.sin(time * 2) * 0.03;
|
||||
if (time > nextEventTime.current && lightState.current === 'normal') {
|
||||
lightState.current = 'flickering';
|
||||
stateEndTime.current = time + 1.5 + Math.random() * 2;
|
||||
}
|
||||
if (lightState.current === 'flickering') {
|
||||
if (time > stateEndTime.current) {
|
||||
if (Math.random() > 0.4) {
|
||||
lightState.current = 'dead';
|
||||
stateEndTime.current = time + 1.0 + Math.random() * 2.5;
|
||||
} else {
|
||||
lightState.current = 'normal';
|
||||
nextEventTime.current = time + 10 + Math.random() * 20;
|
||||
}
|
||||
} else {
|
||||
const baseWave = Math.sin(time * 45) * 0.4 + Math.sin(time * 90) * 0.3;
|
||||
intensity1 = 0.5 + baseWave;
|
||||
if (Math.sin(time * 150) + Math.cos(time * 220) > 1.2) intensity1 *= Math.random() > 0.5 ? 0.0 : 0.15;
|
||||
}
|
||||
}
|
||||
if (lightState.current === 'dead') {
|
||||
if (time > stateEndTime.current) {
|
||||
lightState.current = 'normal';
|
||||
nextEventTime.current = time + 12 + Math.random() * 15;
|
||||
} else {
|
||||
intensity1 = Math.random() > 0.98 ? 0.08 : 0.0;
|
||||
}
|
||||
}
|
||||
let intensity1 = 0.85 + Math.sin(time * 2) * 0.03;
|
||||
if (time > nextEventTime.current && lightState.current === 'normal') {
|
||||
lightState.current = 'flickering';
|
||||
stateEndTime.current = time + 1.5 + Math.random() * 2;
|
||||
}
|
||||
if (lightState.current === 'flickering') {
|
||||
if (time > stateEndTime.current) {
|
||||
if (Math.random() > 0.4) {
|
||||
lightState.current = 'dead';
|
||||
stateEndTime.current = time + 1.0 + Math.random() * 2.5;
|
||||
} else {
|
||||
lightState.current = 'normal';
|
||||
nextEventTime.current = time + 10 + Math.random() * 20;
|
||||
}
|
||||
} else {
|
||||
const baseWave = Math.sin(time * 45) * 0.4 + Math.sin(time * 90) * 0.3;
|
||||
intensity1 = 0.5 + baseWave;
|
||||
if (Math.sin(time * 150) + Math.cos(time * 220) > 1.2)
|
||||
intensity1 *= Math.random() > 0.5 ? 0.0 : 0.15;
|
||||
}
|
||||
}
|
||||
if (lightState.current === 'dead') {
|
||||
if (time > stateEndTime.current) {
|
||||
lightState.current = 'normal';
|
||||
nextEventTime.current = time + 12 + Math.random() * 15;
|
||||
} else {
|
||||
intensity1 = Math.random() > 0.98 ? 0.08 : 0.0;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
/*
|
||||
objects
|
||||
*/
|
||||
const length = FEAR_SETTINGS.HALLWAY_LENGTH;
|
||||
const playerSegmentZ = Math.floor(state.camera.position.z / length);
|
||||
const length = FEAR_SETTINGS.HALLWAY_LENGTH;
|
||||
const playerSegmentZ = Math.floor(state.camera.position.z / length);
|
||||
|
||||
const horizontalTexRepeat = width / FEAR_SETTINGS.HALLWAY_WIDTH;
|
||||
floorTex.repeat.set(horizontalTexRepeat, 10);
|
||||
wallTex.repeat.set(10, 1);
|
||||
rustWallTex.repeat.set(10, 1);
|
||||
rustFloorTex.repeat.set(horizontalTexRepeat, 10);
|
||||
const horizontalTexRepeat = width / FEAR_SETTINGS.HALLWAY_WIDTH;
|
||||
floorTex.repeat.set(horizontalTexRepeat, 10);
|
||||
wallTex.repeat.set(10, 1);
|
||||
rustWallTex.repeat.set(10, 1);
|
||||
rustFloorTex.repeat.set(horizontalTexRepeat, 10);
|
||||
|
||||
floorTex.needsUpdate = true;
|
||||
wallTex.needsUpdate = true;
|
||||
rustWallTex.needsUpdate = true;
|
||||
rustFloorTex.needsUpdate = true;
|
||||
floorTex.needsUpdate = true;
|
||||
wallTex.needsUpdate = true;
|
||||
rustWallTex.needsUpdate = true;
|
||||
rustFloorTex.needsUpdate = true;
|
||||
|
||||
let closestPoolIndex = 0;
|
||||
let minDistance = Infinity;
|
||||
let closestPoolIndex = 0;
|
||||
let minDistance = Infinity;
|
||||
|
||||
segmentsRef.current.forEach((segGroup, poolIndex) => {
|
||||
if (!segGroup) return;
|
||||
segmentsRef.current.forEach((segGroup, poolIndex) => {
|
||||
if (!segGroup) return;
|
||||
|
||||
let segmentZIndex = poolIndex - Math.floor(segmentCount / 2) + playerSegmentZ;
|
||||
segGroup.position.z = segmentZIndex * length;
|
||||
let segmentZIndex =
|
||||
poolIndex - Math.floor(segmentCount / 2) + playerSegmentZ;
|
||||
segGroup.position.z = segmentZIndex * length;
|
||||
|
||||
const distance = Math.abs(segGroup.position.z - state.camera.position.z);
|
||||
if (distance < minDistance) {
|
||||
minDistance = distance;
|
||||
closestPoolIndex = poolIndex;
|
||||
}
|
||||
const distance = Math.abs(segGroup.position.z - state.camera.position.z);
|
||||
if (distance < minDistance) {
|
||||
minDistance = distance;
|
||||
closestPoolIndex = poolIndex;
|
||||
}
|
||||
|
||||
const leftWallGroup = segGroup.getObjectByName("left-wall-group");
|
||||
if (leftWallGroup) leftWallGroup.position.x = -width / 2;
|
||||
const leftWallGroup = segGroup.getObjectByName('left-wall-group');
|
||||
if (leftWallGroup) leftWallGroup.position.x = -width / 2;
|
||||
|
||||
const rightWallGroup = segGroup.getObjectByName("right-wall-group");
|
||||
if (rightWallGroup) rightWallGroup.position.x = width / 2;
|
||||
const rightWallGroup = segGroup.getObjectByName('right-wall-group');
|
||||
if (rightWallGroup) rightWallGroup.position.x = width / 2;
|
||||
|
||||
const floorMesh = segGroup.getObjectByName("floor-mesh");
|
||||
if (floorMesh) floorMesh.scale.x = width / FEAR_SETTINGS.HALLWAY_WIDTH;
|
||||
const floorMesh = segGroup.getObjectByName('floor-mesh');
|
||||
if (floorMesh) floorMesh.scale.x = width / FEAR_SETTINGS.HALLWAY_WIDTH;
|
||||
|
||||
const ceilingMesh = segGroup.getObjectByName("ceiling-mesh");
|
||||
if (ceilingMesh) ceilingMesh.scale.x = width / FEAR_SETTINGS.HALLWAY_WIDTH;
|
||||
const ceilingMesh = segGroup.getObjectByName('ceiling-mesh');
|
||||
if (ceilingMesh)
|
||||
ceilingMesh.scale.x = width / FEAR_SETTINGS.HALLWAY_WIDTH;
|
||||
|
||||
for (let i = 0; i < 3; i++) {
|
||||
const pipe = segGroup.getObjectByName(`pipe-${i}`);
|
||||
if (pipe) pipe.position.x = -width / 2 + 0.4 + (i * 0.20);
|
||||
}
|
||||
const bracketGroup = segGroup.getObjectByName("brackets-group");
|
||||
if (bracketGroup) {
|
||||
bracketGroup.children.forEach(b => {
|
||||
b.position.x = -width / 2 + 0.6;
|
||||
});
|
||||
}
|
||||
});
|
||||
for (let i = 0; i < 3; i++) {
|
||||
const pipe = segGroup.getObjectByName(`pipe-${i}`);
|
||||
if (pipe) pipe.position.x = -width / 2 + 0.4 + i * 0.2;
|
||||
}
|
||||
const bracketGroup = segGroup.getObjectByName('brackets-group');
|
||||
if (bracketGroup) {
|
||||
bracketGroup.children.forEach((b) => {
|
||||
b.position.x = -width / 2 + 0.6;
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
/*
|
||||
/*
|
||||
dyn light
|
||||
*/
|
||||
segmentPool.forEach((poolIndex) => {
|
||||
const light = lightRefs.current[poolIndex];
|
||||
const mat = matRefs.current[poolIndex];
|
||||
segmentPool.forEach((poolIndex) => {
|
||||
const light = lightRefs.current[poolIndex];
|
||||
const mat = matRefs.current[poolIndex];
|
||||
|
||||
if (poolIndex === closestPoolIndex) {
|
||||
if (light) light.intensity = intensity1 * 1.2;
|
||||
if (mat) {
|
||||
mat.emissiveIntensity = intensity1 * 2.5;
|
||||
if (lightState.current !== 'normal') mat.emissive.setHSL(0.07, 0.4, Math.min(intensity1, 0.7));
|
||||
else mat.emissive.setHex(0xa8a1a1);
|
||||
}
|
||||
} else {
|
||||
if (light) light.intensity = 0.9;
|
||||
if (mat) {
|
||||
mat.emissiveIntensity = 0.8;
|
||||
mat.emissive.setHex(0xa8a1a1);
|
||||
}
|
||||
}
|
||||
});
|
||||
if (poolIndex === closestPoolIndex) {
|
||||
if (light) light.intensity = intensity1 * 1.2;
|
||||
if (mat) {
|
||||
mat.emissiveIntensity = intensity1 * 2.5;
|
||||
if (lightState.current !== 'normal')
|
||||
mat.emissive.setHSL(0.07, 0.4, Math.min(intensity1, 0.7));
|
||||
else mat.emissive.setHex(0xa8a1a1);
|
||||
}
|
||||
} else {
|
||||
if (light) light.intensity = 0.9;
|
||||
if (mat) {
|
||||
mat.emissiveIntensity = 0.8;
|
||||
mat.emissive.setHex(0xa8a1a1);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
/*
|
||||
/*
|
||||
materials
|
||||
*/
|
||||
const updateMaterials = (materials: THREE.MeshStandardMaterial[], defaultTex: THREE.Texture, targetRustTex: THREE.Texture, activeColor: string, defaultColor: string, activeRough: number, defaultRough: number, activeMetal: number, defaultMetal: number) => {
|
||||
materials.forEach(mat => {
|
||||
if (!mat) return;
|
||||
const targetTex = isRustActive ? targetRustTex : defaultTex;
|
||||
if (mat.map !== targetTex) {
|
||||
mat.map = targetTex;
|
||||
mat.needsUpdate = true;
|
||||
}
|
||||
mat.color.set(isRustActive ? activeColor : defaultColor);
|
||||
mat.roughness = isRustActive ? activeRough : defaultRough;
|
||||
mat.metalness = isRustActive ? activeMetal : defaultMetal;
|
||||
});
|
||||
};
|
||||
const updateMaterials = (
|
||||
materials: THREE.MeshStandardMaterial[],
|
||||
defaultTex: THREE.Texture,
|
||||
targetRustTex: THREE.Texture,
|
||||
activeColor: string,
|
||||
defaultColor: string,
|
||||
activeRough: number,
|
||||
defaultRough: number,
|
||||
activeMetal: number,
|
||||
defaultMetal: number
|
||||
) => {
|
||||
materials.forEach((mat) => {
|
||||
if (!mat) return;
|
||||
const targetTex = isRustActive ? targetRustTex : defaultTex;
|
||||
if (mat.map !== targetTex) {
|
||||
mat.map = targetTex;
|
||||
mat.needsUpdate = true;
|
||||
}
|
||||
mat.color.set(isRustActive ? activeColor : defaultColor);
|
||||
mat.roughness = isRustActive ? activeRough : defaultRough;
|
||||
mat.metalness = isRustActive ? activeMetal : defaultMetal;
|
||||
});
|
||||
};
|
||||
|
||||
updateMaterials(wallMaterialsRef.current, wallTex, rustWallTex, "#c5c0be", "#ffffff", 0.95, 0.7, 0.05, 0.1);
|
||||
updateMaterials(floorMaterialsRef.current, floorTex, rustFloorTex, "#cabdb9", "#ffffff", 0.95, 0.8, 0.05, 0.2);
|
||||
updateMaterials(
|
||||
wallMaterialsRef.current,
|
||||
wallTex,
|
||||
rustWallTex,
|
||||
'#c5c0be',
|
||||
'#ffffff',
|
||||
0.95,
|
||||
0.7,
|
||||
0.05,
|
||||
0.1
|
||||
);
|
||||
updateMaterials(
|
||||
floorMaterialsRef.current,
|
||||
floorTex,
|
||||
rustFloorTex,
|
||||
'#cabdb9',
|
||||
'#ffffff',
|
||||
0.95,
|
||||
0.8,
|
||||
0.05,
|
||||
0.2
|
||||
);
|
||||
|
||||
pipeMaterialsRef.current.forEach(mat => {
|
||||
if (!mat) return;
|
||||
mat.color.set(isRustActive ? "#3d1b0f" : "#a5aca8");
|
||||
mat.roughness = isRustActive ? 0.95 : 0.0;
|
||||
mat.metalness = isRustActive ? 0.05 : 0.4;
|
||||
});
|
||||
pipeMaterialsRef.current.forEach((mat) => {
|
||||
if (!mat) return;
|
||||
mat.color.set(isRustActive ? '#3d1b0f' : '#a5aca8');
|
||||
mat.roughness = isRustActive ? 0.95 : 0.0;
|
||||
mat.metalness = isRustActive ? 0.05 : 0.4;
|
||||
});
|
||||
|
||||
bracketMaterialsRef.current.forEach(mat => {
|
||||
if (!mat) return;
|
||||
mat.color.set(isRustActive ? "#1b0b05" : "#a5aca8");
|
||||
mat.roughness = isRustActive ? 0.95 : 0.0;
|
||||
mat.metalness = isRustActive ? 0.05 : 0.4;
|
||||
});
|
||||
});
|
||||
bracketMaterialsRef.current.forEach((mat) => {
|
||||
if (!mat) return;
|
||||
mat.color.set(isRustActive ? '#1b0b05' : '#a5aca8');
|
||||
mat.roughness = isRustActive ? 0.95 : 0.0;
|
||||
mat.metalness = isRustActive ? 0.05 : 0.4;
|
||||
});
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
{segmentPool.map((poolIndex) => (
|
||||
<group
|
||||
key={poolIndex}
|
||||
ref={(el) => { if (el) segmentsRef.current[poolIndex] = el; }}
|
||||
position={[0, 0, 0]}
|
||||
>
|
||||
{/* lights */}
|
||||
<group position={[0, FEAR_SETTINGS.HALLWAY_HEIGHT - 0.1, -FEAR_SETTINGS.HALLWAY_LENGTH / 4]}>
|
||||
<pointLight
|
||||
ref={(el) => { lightRefs.current[poolIndex] = el; }}
|
||||
intensity={0.9}
|
||||
distance={15}
|
||||
color="#a8a1a1"
|
||||
/>
|
||||
<mesh position={[0, 0.09, 0]}>
|
||||
<boxGeometry args={[0.3, 0.01, 0.3]} />
|
||||
<meshStandardMaterial
|
||||
ref={(el) => { matRefs.current[poolIndex] = el; }}
|
||||
color="#111111"
|
||||
emissive="#a8a1a1"
|
||||
emissiveIntensity={0.8}
|
||||
roughness={0.9}
|
||||
onBeforeCompile={ShaderPatch}
|
||||
/>
|
||||
</mesh>
|
||||
</group>
|
||||
return (
|
||||
<>
|
||||
{segmentPool.map((poolIndex) => (
|
||||
<group
|
||||
key={poolIndex}
|
||||
ref={(el) => {
|
||||
if (el) segmentsRef.current[poolIndex] = el;
|
||||
}}
|
||||
position={[0, 0, 0]}
|
||||
>
|
||||
{/* lights */}
|
||||
<group
|
||||
position={[
|
||||
0,
|
||||
FEAR_SETTINGS.HALLWAY_HEIGHT - 0.1,
|
||||
-FEAR_SETTINGS.HALLWAY_LENGTH / 4
|
||||
]}
|
||||
>
|
||||
<pointLight
|
||||
ref={(el) => {
|
||||
lightRefs.current[poolIndex] = el;
|
||||
}}
|
||||
intensity={0.9}
|
||||
distance={15}
|
||||
color='#a8a1a1'
|
||||
/>
|
||||
<mesh position={[0, 0.09, 0]}>
|
||||
<boxGeometry args={[0.3, 0.01, 0.3]} />
|
||||
<meshStandardMaterial
|
||||
ref={(el) => {
|
||||
matRefs.current[poolIndex] = el;
|
||||
}}
|
||||
color='#111111'
|
||||
emissive='#a8a1a1'
|
||||
emissiveIntensity={0.8}
|
||||
roughness={0.9}
|
||||
onBeforeCompile={ShaderPatch}
|
||||
/>
|
||||
</mesh>
|
||||
</group>
|
||||
|
||||
{/* floor */}
|
||||
<mesh
|
||||
name="floor-mesh"
|
||||
rotation={[-Math.PI / 2, 0, 0]}
|
||||
position={[0, 0, -FEAR_SETTINGS.HALLWAY_LENGTH / 2]}
|
||||
>
|
||||
<planeGeometry args={[FEAR_SETTINGS.HALLWAY_WIDTH, FEAR_SETTINGS.HALLWAY_LENGTH, 4, 10]} />
|
||||
<meshStandardMaterial
|
||||
ref={(el) => { if (el) floorMaterialsRef.current.push(el); }}
|
||||
map={floorTex}
|
||||
onBeforeCompile={ShaderPatch}
|
||||
/>
|
||||
</mesh>
|
||||
{/* floor */}
|
||||
<mesh
|
||||
name='floor-mesh'
|
||||
rotation={[-Math.PI / 2, 0, 0]}
|
||||
position={[0, 0, -FEAR_SETTINGS.HALLWAY_LENGTH / 2]}
|
||||
>
|
||||
<planeGeometry
|
||||
args={[
|
||||
FEAR_SETTINGS.HALLWAY_WIDTH,
|
||||
FEAR_SETTINGS.HALLWAY_LENGTH,
|
||||
4,
|
||||
10
|
||||
]}
|
||||
/>
|
||||
<meshStandardMaterial
|
||||
ref={(el) => {
|
||||
if (el) floorMaterialsRef.current.push(el);
|
||||
}}
|
||||
map={floorTex}
|
||||
onBeforeCompile={ShaderPatch}
|
||||
/>
|
||||
</mesh>
|
||||
|
||||
{/* ceiling */}
|
||||
<mesh
|
||||
name="ceiling-mesh"
|
||||
rotation={[Math.PI / 2, 0, 0]}
|
||||
position={[0, FEAR_SETTINGS.HALLWAY_HEIGHT, -FEAR_SETTINGS.HALLWAY_LENGTH / 2]}
|
||||
>
|
||||
<planeGeometry args={[FEAR_SETTINGS.HALLWAY_WIDTH, FEAR_SETTINGS.HALLWAY_LENGTH, 4, 10]} />
|
||||
<meshStandardMaterial
|
||||
ref={(el) => { if (el) floorMaterialsRef.current.push(el); }}
|
||||
map={floorTex}
|
||||
onBeforeCompile={ShaderPatch}
|
||||
/>
|
||||
</mesh>
|
||||
{/* ceiling */}
|
||||
<mesh
|
||||
name='ceiling-mesh'
|
||||
rotation={[Math.PI / 2, 0, 0]}
|
||||
position={[
|
||||
0,
|
||||
FEAR_SETTINGS.HALLWAY_HEIGHT,
|
||||
-FEAR_SETTINGS.HALLWAY_LENGTH / 2
|
||||
]}
|
||||
>
|
||||
<planeGeometry
|
||||
args={[
|
||||
FEAR_SETTINGS.HALLWAY_WIDTH,
|
||||
FEAR_SETTINGS.HALLWAY_LENGTH,
|
||||
4,
|
||||
10
|
||||
]}
|
||||
/>
|
||||
<meshStandardMaterial
|
||||
ref={(el) => {
|
||||
if (el) floorMaterialsRef.current.push(el);
|
||||
}}
|
||||
map={floorTex}
|
||||
onBeforeCompile={ShaderPatch}
|
||||
/>
|
||||
</mesh>
|
||||
|
||||
{/* left wall */}
|
||||
<group name="left-wall-group">
|
||||
<mesh rotation={[0, Math.PI / 2, 0]} position={[0, FEAR_SETTINGS.HALLWAY_HEIGHT / 2, -FEAR_SETTINGS.HALLWAY_LENGTH / 2]}>
|
||||
<planeGeometry args={[FEAR_SETTINGS.HALLWAY_LENGTH, FEAR_SETTINGS.HALLWAY_HEIGHT, 10, 4]} />
|
||||
<meshStandardMaterial
|
||||
ref={(el) => { if (el) wallMaterialsRef.current.push(el); }}
|
||||
map={wallTex}
|
||||
onBeforeCompile={ShaderPatch}
|
||||
/>
|
||||
</mesh>
|
||||
{!isRustActive && (
|
||||
<>
|
||||
<Door position={[0.05, 0, -FEAR_SETTINGS.HALLWAY_LENGTH * 0.25]} rotation={[0, Math.PI / 2, 0]} />
|
||||
<Door position={[0.05, 0, -FEAR_SETTINGS.HALLWAY_LENGTH * 0.85]} rotation={[0, Math.PI / 2, 0]} />
|
||||
</>
|
||||
)}
|
||||
</group>
|
||||
{/* left wall */}
|
||||
<group name='left-wall-group'>
|
||||
<mesh
|
||||
rotation={[0, Math.PI / 2, 0]}
|
||||
position={[
|
||||
0,
|
||||
FEAR_SETTINGS.HALLWAY_HEIGHT / 2,
|
||||
-FEAR_SETTINGS.HALLWAY_LENGTH / 2
|
||||
]}
|
||||
>
|
||||
<planeGeometry
|
||||
args={[
|
||||
FEAR_SETTINGS.HALLWAY_LENGTH,
|
||||
FEAR_SETTINGS.HALLWAY_HEIGHT,
|
||||
10,
|
||||
4
|
||||
]}
|
||||
/>
|
||||
<meshStandardMaterial
|
||||
ref={(el) => {
|
||||
if (el) wallMaterialsRef.current.push(el);
|
||||
}}
|
||||
map={wallTex}
|
||||
onBeforeCompile={ShaderPatch}
|
||||
/>
|
||||
</mesh>
|
||||
{!isRustActive && (
|
||||
<>
|
||||
<Door
|
||||
position={[0.05, 0, -FEAR_SETTINGS.HALLWAY_LENGTH * 0.25]}
|
||||
rotation={[0, Math.PI / 2, 0]}
|
||||
/>
|
||||
<Door
|
||||
position={[0.05, 0, -FEAR_SETTINGS.HALLWAY_LENGTH * 0.85]}
|
||||
rotation={[0, Math.PI / 2, 0]}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</group>
|
||||
|
||||
{/* right wall */}
|
||||
<group name="right-wall-group">
|
||||
<mesh rotation={[0, -Math.PI / 2, 0]} position={[0, FEAR_SETTINGS.HALLWAY_HEIGHT / 2, -FEAR_SETTINGS.HALLWAY_LENGTH / 2]}>
|
||||
<planeGeometry args={[FEAR_SETTINGS.HALLWAY_LENGTH, FEAR_SETTINGS.HALLWAY_HEIGHT, 10, 4]} />
|
||||
<meshStandardMaterial
|
||||
ref={(el) => { if (el) wallMaterialsRef.current.push(el); }}
|
||||
map={wallTex}
|
||||
onBeforeCompile={ShaderPatch}
|
||||
/>
|
||||
</mesh>
|
||||
{!isRustActive && (
|
||||
<Door position={[-0.05, 0, -FEAR_SETTINGS.HALLWAY_LENGTH * 0.65]} rotation={[0, -Math.PI / 2, 0]} />
|
||||
)}
|
||||
</group>
|
||||
{/* right wall */}
|
||||
<group name='right-wall-group'>
|
||||
<mesh
|
||||
rotation={[0, -Math.PI / 2, 0]}
|
||||
position={[
|
||||
0,
|
||||
FEAR_SETTINGS.HALLWAY_HEIGHT / 2,
|
||||
-FEAR_SETTINGS.HALLWAY_LENGTH / 2
|
||||
]}
|
||||
>
|
||||
<planeGeometry
|
||||
args={[
|
||||
FEAR_SETTINGS.HALLWAY_LENGTH,
|
||||
FEAR_SETTINGS.HALLWAY_HEIGHT,
|
||||
10,
|
||||
4
|
||||
]}
|
||||
/>
|
||||
<meshStandardMaterial
|
||||
ref={(el) => {
|
||||
if (el) wallMaterialsRef.current.push(el);
|
||||
}}
|
||||
map={wallTex}
|
||||
onBeforeCompile={ShaderPatch}
|
||||
/>
|
||||
</mesh>
|
||||
{!isRustActive && (
|
||||
<Door
|
||||
position={[-0.05, 0, -FEAR_SETTINGS.HALLWAY_LENGTH * 0.65]}
|
||||
rotation={[0, -Math.PI / 2, 0]}
|
||||
/>
|
||||
)}
|
||||
</group>
|
||||
|
||||
{/* pipes */}
|
||||
{Array.from({ length: 3 }).map((_, idx) => (
|
||||
<mesh
|
||||
key={idx}
|
||||
name={`pipe-${idx}`}
|
||||
rotation={[Math.PI / 2, 0, 0]}
|
||||
position={[-FEAR_SETTINGS.HALLWAY_WIDTH / 2 + 0.4 + (idx * 0.20), FEAR_SETTINGS.HALLWAY_HEIGHT - 0.2, -FEAR_SETTINGS.HALLWAY_LENGTH / 2]}
|
||||
>
|
||||
<cylinderGeometry args={[0.06, 0.06, FEAR_SETTINGS.HALLWAY_LENGTH, 4]} />
|
||||
<meshStandardMaterial
|
||||
ref={(el) => el && pipeMaterialsRef.current.push(el)}
|
||||
color="#a5aca8"
|
||||
roughness={0.0}
|
||||
metalness={0.4}
|
||||
onBeforeCompile={ShaderPatch}
|
||||
/>
|
||||
</mesh>
|
||||
))}
|
||||
{/* pipes */}
|
||||
{Array.from({ length: 3 }).map((_, idx) => (
|
||||
<mesh
|
||||
key={idx}
|
||||
name={`pipe-${idx}`}
|
||||
rotation={[Math.PI / 2, 0, 0]}
|
||||
position={[
|
||||
-FEAR_SETTINGS.HALLWAY_WIDTH / 2 + 0.4 + idx * 0.2,
|
||||
FEAR_SETTINGS.HALLWAY_HEIGHT - 0.2,
|
||||
-FEAR_SETTINGS.HALLWAY_LENGTH / 2
|
||||
]}
|
||||
>
|
||||
<cylinderGeometry
|
||||
args={[0.06, 0.06, FEAR_SETTINGS.HALLWAY_LENGTH, 4]}
|
||||
/>
|
||||
<meshStandardMaterial
|
||||
ref={(el) => el && pipeMaterialsRef.current.push(el)}
|
||||
color='#a5aca8'
|
||||
roughness={0.0}
|
||||
metalness={0.4}
|
||||
onBeforeCompile={ShaderPatch}
|
||||
/>
|
||||
</mesh>
|
||||
))}
|
||||
|
||||
{/* brackets */}
|
||||
<group name="brackets-group">
|
||||
{Array.from({ length: 5 }).map((_, idx) => {
|
||||
const zOffset = -(idx * 8 + 4);
|
||||
return (
|
||||
<mesh
|
||||
key={`bracket-${idx}`}
|
||||
position={[-FEAR_SETTINGS.HALLWAY_WIDTH / 2 + 0.6, FEAR_SETTINGS.HALLWAY_HEIGHT - 0.15, zOffset]}
|
||||
>
|
||||
<boxGeometry args={[0.7, 0.3, 0.15]} />
|
||||
<meshStandardMaterial
|
||||
ref={(el) => el && bracketMaterialsRef.current.push(el)}
|
||||
color="#a5aca8"
|
||||
roughness={0.0}
|
||||
metalness={0.4}
|
||||
onBeforeCompile={ShaderPatch}
|
||||
/>
|
||||
</mesh>
|
||||
);
|
||||
})}
|
||||
</group>
|
||||
</group>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
{/* brackets */}
|
||||
<group name='brackets-group'>
|
||||
{Array.from({ length: 5 }).map((_, idx) => {
|
||||
const zOffset = -(idx * 8 + 4);
|
||||
return (
|
||||
<mesh
|
||||
key={`bracket-${idx}`}
|
||||
position={[
|
||||
-FEAR_SETTINGS.HALLWAY_WIDTH / 2 + 0.6,
|
||||
FEAR_SETTINGS.HALLWAY_HEIGHT - 0.15,
|
||||
zOffset
|
||||
]}
|
||||
>
|
||||
<boxGeometry args={[0.7, 0.3, 0.15]} />
|
||||
<meshStandardMaterial
|
||||
ref={(el) => el && bracketMaterialsRef.current.push(el)}
|
||||
color='#a5aca8'
|
||||
roughness={0.0}
|
||||
metalness={0.4}
|
||||
onBeforeCompile={ShaderPatch}
|
||||
/>
|
||||
</mesh>
|
||||
);
|
||||
})}
|
||||
</group>
|
||||
</group>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { useFrame, useThree } from "@react-three/fiber";
|
||||
import { useEffect, useRef } from "react";
|
||||
import { FEAR_SETTINGS, fearState } from "../state";
|
||||
import { PointerLockControls } from "@react-three/drei";
|
||||
import * as THREE from "three";
|
||||
import { useFrame, useThree } from '@react-three/fiber';
|
||||
import { useEffect, useRef } from 'react';
|
||||
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();
|
||||
@@ -14,181 +14,209 @@ const targetVelocity = new THREE.Vector3();
|
||||
const currentVelocity = new THREE.Vector3();
|
||||
|
||||
function usePlayerControls() {
|
||||
const keys = useRef({ Forward: false, Backward: false, Left: false, Right: false });
|
||||
const keys = useRef({
|
||||
Forward: false,
|
||||
Backward: false,
|
||||
Left: false,
|
||||
Right: false
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
const handleKeyDown = (e: KeyboardEvent) => {
|
||||
if (e.code === 'KeyW' || e.code === 'ArrowUp') keys.current.Forward = true;
|
||||
if (e.code === 'KeyS' || e.code === 'ArrowDown') keys.current.Backward = true;
|
||||
if (e.code === 'KeyA' || e.code === 'ArrowLeft') keys.current.Left = true;
|
||||
if (e.code === 'KeyD' || e.code === 'ArrowRight') keys.current.Right = true;
|
||||
};
|
||||
useEffect(() => {
|
||||
const handleKeyDown = (e: KeyboardEvent) => {
|
||||
if (e.code === 'KeyW' || e.code === 'ArrowUp')
|
||||
keys.current.Forward = true;
|
||||
if (e.code === 'KeyS' || e.code === 'ArrowDown')
|
||||
keys.current.Backward = true;
|
||||
if (e.code === 'KeyA' || e.code === 'ArrowLeft') keys.current.Left = true;
|
||||
if (e.code === 'KeyD' || e.code === 'ArrowRight')
|
||||
keys.current.Right = true;
|
||||
};
|
||||
|
||||
const handleKeyUp = (e: KeyboardEvent) => {
|
||||
if (e.code === 'KeyW' || e.code === 'ArrowUp') keys.current.Forward = false;
|
||||
if (e.code === 'KeyS' || e.code === 'ArrowDown') keys.current.Backward = false;
|
||||
if (e.code === 'KeyA' || e.code === 'ArrowLeft') keys.current.Left = false;
|
||||
if (e.code === 'KeyD' || e.code === 'ArrowRight') keys.current.Right = false;
|
||||
};
|
||||
const handleKeyUp = (e: KeyboardEvent) => {
|
||||
if (e.code === 'KeyW' || e.code === 'ArrowUp')
|
||||
keys.current.Forward = false;
|
||||
if (e.code === 'KeyS' || e.code === 'ArrowDown')
|
||||
keys.current.Backward = false;
|
||||
if (e.code === 'KeyA' || e.code === 'ArrowLeft')
|
||||
keys.current.Left = false;
|
||||
if (e.code === 'KeyD' || e.code === 'ArrowRight')
|
||||
keys.current.Right = false;
|
||||
};
|
||||
|
||||
window.addEventListener('keydown', handleKeyDown);
|
||||
window.addEventListener('keyup', handleKeyUp);
|
||||
window.addEventListener('keydown', handleKeyDown);
|
||||
window.addEventListener('keyup', handleKeyUp);
|
||||
|
||||
return () => {
|
||||
window.removeEventListener('keydown', handleKeyDown);
|
||||
window.removeEventListener('keyup', handleKeyUp);
|
||||
};
|
||||
}, []);
|
||||
return () => {
|
||||
window.removeEventListener('keydown', handleKeyDown);
|
||||
window.removeEventListener('keyup', handleKeyUp);
|
||||
};
|
||||
}, []);
|
||||
|
||||
return keys.current;
|
||||
return keys.current;
|
||||
}
|
||||
|
||||
export default function Player() {
|
||||
const { camera } = useThree();
|
||||
const controls = usePlayerControls();
|
||||
const { camera } = useThree();
|
||||
const controls = usePlayerControls();
|
||||
|
||||
const flashlightRef = useRef<THREE.SpotLight>(null);
|
||||
const movementCounter = useRef<number>(0);
|
||||
const bobIntensity = useRef<number>(0);
|
||||
const flashlightRef = useRef<THREE.SpotLight>(null);
|
||||
const movementCounter = useRef<number>(0);
|
||||
const bobIntensity = useRef<number>(0);
|
||||
|
||||
const confirmedSegment = useRef<number>(0);
|
||||
const hasTriggeredThisSegment = useRef<boolean>(false);
|
||||
const confirmedSegment = useRef<number>(0);
|
||||
const hasTriggeredThisSegment = useRef<boolean>(false);
|
||||
|
||||
const footstepAudio = useRef<HTMLAudioElement[]>([]);
|
||||
const hasStepped = useRef<boolean>(false);
|
||||
const footstepAudio = useRef<HTMLAudioElement[]>([]);
|
||||
const hasStepped = useRef<boolean>(false);
|
||||
|
||||
useEffect(() => {
|
||||
playerRoot.set(
|
||||
camera.position.x,
|
||||
FEAR_SETTINGS.PLAYER_HEIGHT,
|
||||
camera.position.z
|
||||
);
|
||||
footstepAudio.current = Array.from({ length: 6 }, (_, i) => {
|
||||
const audio = new Audio(`fear/snd/footstep${i + 1}.mp3`);
|
||||
audio.volume = 0.4;
|
||||
return audio;
|
||||
});
|
||||
}, []);
|
||||
|
||||
const playRandomFootstep = () => {
|
||||
if (footstepAudio.current.length === 0) return;
|
||||
|
||||
useEffect(() => {
|
||||
playerRoot.set(camera.position.x, FEAR_SETTINGS.PLAYER_HEIGHT, camera.position.z);
|
||||
footstepAudio.current = Array.from({ length: 6 }, (_, i) => {
|
||||
const audio = new Audio(`fear/snd/footstep${i + 1}.mp3`);
|
||||
audio.volume = 0.4;
|
||||
return audio;
|
||||
});
|
||||
}, []);
|
||||
const randomIndex = Math.floor(
|
||||
Math.random() * footstepAudio.current.length
|
||||
);
|
||||
const audio = footstepAudio.current[randomIndex];
|
||||
|
||||
const playRandomFootstep = () => {
|
||||
if (footstepAudio.current.length === 0) return;
|
||||
audio.currentTime = 0;
|
||||
audio.play().catch((err) => {
|
||||
console.warn(
|
||||
'Footstep playback blocked by browser autocomplete/interaction rules.',
|
||||
err
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
const randomIndex = Math.floor(Math.random() * footstepAudio.current.length);
|
||||
const audio = footstepAudio.current[randomIndex];
|
||||
useFrame((state, delta) => {
|
||||
const dt = Math.min(delta, 0.1);
|
||||
|
||||
audio.currentTime = 0;
|
||||
audio.play().catch((err) => {
|
||||
console.warn("Footstep playback blocked by browser autocomplete/interaction rules.", err);
|
||||
});
|
||||
};
|
||||
camera.getWorldDirection(forward);
|
||||
forward.y = 0;
|
||||
forward.normalize();
|
||||
side.crossVectors(forward, THREE.Object3D.DEFAULT_UP).normalize();
|
||||
|
||||
useFrame((state, delta) => {
|
||||
const dt = Math.min(delta, 0.1);
|
||||
const moveForward = Number(controls.Forward) - Number(controls.Backward);
|
||||
const moveSide = Number(controls.Right) - Number(controls.Left);
|
||||
|
||||
camera.getWorldDirection(forward);
|
||||
forward.y = 0;
|
||||
forward.normalize();
|
||||
side.crossVectors(forward, THREE.Object3D.DEFAULT_UP).normalize();
|
||||
targetVelocity.set(0, 0, 0);
|
||||
if (moveForward !== 0) targetVelocity.addScaledVector(forward, moveForward);
|
||||
if (moveSide !== 0) targetVelocity.addScaledVector(side, moveSide);
|
||||
|
||||
const moveForward = Number(controls.Forward) - Number(controls.Backward);
|
||||
const moveSide = Number(controls.Right) - Number(controls.Left);
|
||||
if (targetVelocity.lengthSq() > 0)
|
||||
targetVelocity.normalize().multiplyScalar(FEAR_SETTINGS.PLAYER_SPEED);
|
||||
|
||||
targetVelocity.set(0, 0, 0);
|
||||
if (moveForward !== 0) targetVelocity.addScaledVector(forward, moveForward);
|
||||
if (moveSide !== 0) targetVelocity.addScaledVector(side, moveSide);
|
||||
currentVelocity.lerp(targetVelocity, 10 * dt);
|
||||
|
||||
if (targetVelocity.lengthSq() > 0)
|
||||
targetVelocity.normalize().multiplyScalar(FEAR_SETTINGS.PLAYER_SPEED);
|
||||
playerRoot.x += currentVelocity.x * dt;
|
||||
playerRoot.z += currentVelocity.z * dt;
|
||||
|
||||
currentVelocity.lerp(targetVelocity, 10 * dt);
|
||||
const minX = -fearState.currentWidth / 2 + FEAR_SETTINGS.WALL_BUFFER;
|
||||
const maxX = fearState.currentWidth / 2 - FEAR_SETTINGS.WALL_BUFFER;
|
||||
playerRoot.x = THREE.MathUtils.clamp(playerRoot.x, minX, maxX);
|
||||
|
||||
playerRoot.x += currentVelocity.x * dt;
|
||||
playerRoot.z += currentVelocity.z * dt;
|
||||
const isMoving =
|
||||
controls.Forward || controls.Backward || controls.Left || controls.Right;
|
||||
|
||||
const minX = -fearState.currentWidth / 2 + FEAR_SETTINGS.WALL_BUFFER;
|
||||
const maxX = fearState.currentWidth / 2 - FEAR_SETTINGS.WALL_BUFFER;
|
||||
playerRoot.x = THREE.MathUtils.clamp(playerRoot.x, minX, maxX);
|
||||
bobIntensity.current = THREE.MathUtils.lerp(
|
||||
bobIntensity.current,
|
||||
isMoving ? 1 : 0,
|
||||
8 * dt
|
||||
);
|
||||
|
||||
const isMoving = controls.Forward || controls.Backward || controls.Left || controls.Right;
|
||||
if (isMoving) movementCounter.current += dt * 12;
|
||||
|
||||
bobIntensity.current = THREE.MathUtils.lerp(bobIntensity.current, isMoving ? 1 : 0, 8 * dt);
|
||||
const sinWave = Math.sin(movementCounter.current);
|
||||
const moveBobY = sinWave * 0.06 * bobIntensity.current;
|
||||
const moveBobX =
|
||||
Math.cos(movementCounter.current / 2) * 0.04 * bobIntensity.current;
|
||||
|
||||
if (isMoving)
|
||||
movementCounter.current += dt * 12;
|
||||
if (isMoving && sinWave < -0.9) {
|
||||
if (!hasStepped.current) {
|
||||
playRandomFootstep();
|
||||
hasStepped.current = true;
|
||||
}
|
||||
} else if (sinWave > 0) {
|
||||
hasStepped.current = false;
|
||||
}
|
||||
|
||||
const sinWave = Math.sin(movementCounter.current);
|
||||
const moveBobY = sinWave * 0.06 * bobIntensity.current;
|
||||
const moveBobX = Math.cos(movementCounter.current / 2) * 0.04 * bobIntensity.current;
|
||||
const breatheTime = state.clock.elapsedTime * 1.8;
|
||||
const breatheBobY =
|
||||
Math.sin(breatheTime) * 0.03 * (1 - bobIntensity.current * 0.5);
|
||||
|
||||
if (isMoving && sinWave < -0.9) {
|
||||
if (!hasStepped.current) {
|
||||
playRandomFootstep();
|
||||
hasStepped.current = true;
|
||||
}
|
||||
} else if (sinWave > 0) {
|
||||
hasStepped.current = false;
|
||||
}
|
||||
camera.position.copy(playerRoot);
|
||||
camera.position.y += moveBobY + breatheBobY;
|
||||
camera.position.addScaledVector(side, moveBobX);
|
||||
|
||||
const breatheTime = state.clock.elapsedTime * 1.8;
|
||||
const breatheBobY = Math.sin(breatheTime) * 0.03 * (1 - bobIntensity.current * 0.5);
|
||||
if (flashlightRef.current) {
|
||||
flashlightRef.current.position.lerp(camera.position, 7 * dt);
|
||||
camera.getWorldDirection(viewDirection);
|
||||
|
||||
camera.position.copy(playerRoot);
|
||||
camera.position.y += moveBobY + breatheBobY;
|
||||
camera.position.addScaledVector(side, moveBobX);
|
||||
targetDest.copy(camera.position).addScaledVector(viewDirection, 10);
|
||||
|
||||
if (flashlightRef.current) {
|
||||
flashlightRef.current.position.lerp(camera.position, 7 * dt);
|
||||
camera.getWorldDirection(viewDirection);
|
||||
flashlightRef.current.target.position.lerp(targetDest, 12 * dt);
|
||||
flashlightRef.current.target.updateMatrixWorld();
|
||||
|
||||
targetDest
|
||||
.copy(camera.position)
|
||||
.addScaledVector(viewDirection, 10);
|
||||
flashlightRef.current.intensity =
|
||||
FEAR_SETTINGS.FLASHLIGHT_INTENSITY_BASE +
|
||||
Math.sin(state.clock.elapsedTime * 30) *
|
||||
0.15 *
|
||||
Math.cos(state.clock.elapsedTime * 3);
|
||||
}
|
||||
|
||||
flashlightRef.current.target.position.lerp(targetDest, 12 * dt);
|
||||
flashlightRef.current.target.updateMatrixWorld();
|
||||
const length = FEAR_SETTINGS.HALLWAY_LENGTH;
|
||||
const absoluteZ = -playerRoot.z;
|
||||
const rawSegmentIndex = Math.floor(absoluteZ / length);
|
||||
const progressZ = (((absoluteZ % length) + length) % length) / length;
|
||||
|
||||
flashlightRef.current.intensity =
|
||||
FEAR_SETTINGS.FLASHLIGHT_INTENSITY_BASE +
|
||||
Math.sin(state.clock.elapsedTime * 30) * 0.15 * Math.cos(state.clock.elapsedTime * 3);
|
||||
}
|
||||
if (rawSegmentIndex > confirmedSegment.current && progressZ > 0.25) {
|
||||
if (!hasTriggeredThisSegment.current) {
|
||||
fearState.registerLoop('forward');
|
||||
hasTriggeredThisSegment.current = true;
|
||||
}
|
||||
confirmedSegment.current = rawSegmentIndex;
|
||||
} else if (rawSegmentIndex < confirmedSegment.current && progressZ < 0.75) {
|
||||
if (!hasTriggeredThisSegment.current) {
|
||||
fearState.registerLoop('backward');
|
||||
hasTriggeredThisSegment.current = true;
|
||||
}
|
||||
confirmedSegment.current = rawSegmentIndex;
|
||||
}
|
||||
|
||||
const length = FEAR_SETTINGS.HALLWAY_LENGTH;
|
||||
const absoluteZ = -playerRoot.z;
|
||||
const rawSegmentIndex = Math.floor(absoluteZ / length);
|
||||
const progressZ = ((absoluteZ % length) + length) % length / length;
|
||||
if (
|
||||
rawSegmentIndex === confirmedSegment.current &&
|
||||
progressZ > 0.35 &&
|
||||
progressZ < 0.65
|
||||
) {
|
||||
hasTriggeredThisSegment.current = false;
|
||||
}
|
||||
});
|
||||
|
||||
if (rawSegmentIndex > confirmedSegment.current && progressZ > 0.25) {
|
||||
if (!hasTriggeredThisSegment.current) {
|
||||
fearState.registerLoop('forward');
|
||||
hasTriggeredThisSegment.current = true;
|
||||
}
|
||||
confirmedSegment.current = rawSegmentIndex;
|
||||
}
|
||||
else if (rawSegmentIndex < confirmedSegment.current && progressZ < 0.75) {
|
||||
if (!hasTriggeredThisSegment.current) {
|
||||
fearState.registerLoop('backward');
|
||||
hasTriggeredThisSegment.current = true;
|
||||
}
|
||||
confirmedSegment.current = rawSegmentIndex;
|
||||
}
|
||||
|
||||
if (rawSegmentIndex === confirmedSegment.current && progressZ > 0.35 && progressZ < 0.65) {
|
||||
hasTriggeredThisSegment.current = false;
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
<PointerLockControls />
|
||||
<spotLight
|
||||
ref={flashlightRef}
|
||||
distance={25}
|
||||
angle={0.35}
|
||||
penumbra={0.8}
|
||||
intensity={0}
|
||||
color="#fffaed"
|
||||
decay={2}
|
||||
castShadow
|
||||
shadow-bias={-0.001}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<>
|
||||
<PointerLockControls />
|
||||
<spotLight
|
||||
ref={flashlightRef}
|
||||
distance={25}
|
||||
angle={0.35}
|
||||
penumbra={0.8}
|
||||
intensity={0}
|
||||
color='#fffaed'
|
||||
decay={2}
|
||||
castShadow
|
||||
shadow-bias={-0.001}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
export function ShaderPatch(shader: { vertexShader: string, fragmentShader: string, uniforms: Object }) {
|
||||
shader.vertexShader = `
|
||||
export function ShaderPatch(shader: {
|
||||
vertexShader: string;
|
||||
fragmentShader: string;
|
||||
uniforms: Object;
|
||||
}) {
|
||||
shader.vertexShader = `
|
||||
varying float vDepth;
|
||||
#ifdef USE_MAP
|
||||
varying vec2 vAffineUv;
|
||||
@@ -7,9 +11,9 @@ export function ShaderPatch(shader: { vertexShader: string, fragmentShader: stri
|
||||
${shader.vertexShader}
|
||||
`;
|
||||
|
||||
shader.vertexShader = shader.vertexShader.replace(
|
||||
`#include <project_vertex>`,
|
||||
`
|
||||
shader.vertexShader = shader.vertexShader.replace(
|
||||
`#include <project_vertex>`,
|
||||
`
|
||||
vec4 mvPosition = modelViewMatrix * vec4( position, 1.0 );
|
||||
gl_Position = projectionMatrix * mvPosition;
|
||||
|
||||
@@ -24,9 +28,9 @@ export function ShaderPatch(shader: { vertexShader: string, fragmentShader: stri
|
||||
vAffineUv = vMapUv * gl_Position.w;
|
||||
#endif
|
||||
`
|
||||
);
|
||||
);
|
||||
|
||||
shader.fragmentShader = `
|
||||
shader.fragmentShader = `
|
||||
varying float vDepth;
|
||||
#ifdef USE_MAP
|
||||
varying vec2 vAffineUv;
|
||||
@@ -34,9 +38,9 @@ export function ShaderPatch(shader: { vertexShader: string, fragmentShader: stri
|
||||
${shader.fragmentShader}
|
||||
`;
|
||||
|
||||
shader.fragmentShader = shader.fragmentShader.replace(
|
||||
`#include <map_fragment>`,
|
||||
`
|
||||
shader.fragmentShader = shader.fragmentShader.replace(
|
||||
`#include <map_fragment>`,
|
||||
`
|
||||
#ifdef USE_MAP
|
||||
vec2 flatAffineUV = vAffineUv / max(vDepth, 0.001);
|
||||
|
||||
@@ -53,5 +57,5 @@ export function ShaderPatch(shader: { vertexShader: string, fragmentShader: stri
|
||||
diffuseColor *= texelColor;
|
||||
#endif
|
||||
`
|
||||
);
|
||||
);
|
||||
}
|
||||
|
||||
+65
-53
@@ -1,74 +1,86 @@
|
||||
import * as THREE from 'three';
|
||||
|
||||
export const FEAR_SETTINGS = {
|
||||
HALLWAY_LENGTH: 40,
|
||||
HALLWAY_WIDTH: 6,
|
||||
HALLWAY_HEIGHT: 5,
|
||||
PLAYER_HEIGHT: 3,
|
||||
PLAYER_SPEED: 4,
|
||||
FLASHLIGHT_INTENSITY_BASE: 8,
|
||||
WALL_BUFFER: 0.6,
|
||||
CREATURE_SPEED: 8,
|
||||
HALLWAY_LENGTH: 40,
|
||||
HALLWAY_WIDTH: 6,
|
||||
HALLWAY_HEIGHT: 5,
|
||||
PLAYER_HEIGHT: 3,
|
||||
PLAYER_SPEED: 4,
|
||||
FLASHLIGHT_INTENSITY_BASE: 8,
|
||||
WALL_BUFFER: 0.6,
|
||||
CREATURE_SPEED: 8,
|
||||
|
||||
EVENT_NARROW_LOOP_COUNT: 2,
|
||||
EVENT_RUST_LOOP_COUNT: 4,
|
||||
EVENT_FINALE_LOOP_COUNT: 5,
|
||||
EVENT_NARROW_LOOP_COUNT: 2,
|
||||
EVENT_RUST_LOOP_COUNT: 4,
|
||||
EVENT_FINALE_LOOP_COUNT: 5,
|
||||
|
||||
EVENT_FINALE_DURATION: 1,
|
||||
EVENT_FINALE_DURATION: 1,
|
||||
|
||||
TEST_MODE: false
|
||||
TEST_MODE: false
|
||||
};
|
||||
|
||||
const listeners = new Set<() => void>();
|
||||
|
||||
export const fearState = {
|
||||
loopCount: 0,
|
||||
currentWidth: FEAR_SETTINGS.HALLWAY_WIDTH,
|
||||
isRustActive: false,
|
||||
finaleTriggered: false,
|
||||
wasCaught: false,
|
||||
finaleProgression: 0,
|
||||
loopCount: 0,
|
||||
currentWidth: FEAR_SETTINGS.HALLWAY_WIDTH,
|
||||
isRustActive: false,
|
||||
finaleTriggered: false,
|
||||
wasCaught: false,
|
||||
finaleProgression: 0,
|
||||
|
||||
subscribe(listener: () => void) {
|
||||
listeners.add(listener);
|
||||
return () => { listeners.delete(listener); };
|
||||
},
|
||||
subscribe(listener: () => void) {
|
||||
listeners.add(listener);
|
||||
return () => {
|
||||
listeners.delete(listener);
|
||||
};
|
||||
},
|
||||
|
||||
emit() {
|
||||
listeners.forEach((listener) => listener());
|
||||
},
|
||||
emit() {
|
||||
listeners.forEach((listener) => listener());
|
||||
},
|
||||
|
||||
update(delta: number) {
|
||||
const targetWidth = this.loopCount >= FEAR_SETTINGS.EVENT_NARROW_LOOP_COUNT ? 2.5 : FEAR_SETTINGS.HALLWAY_WIDTH;
|
||||
const newWidth = THREE.MathUtils.lerp(this.currentWidth, targetWidth, 2 * delta);
|
||||
update(delta: number) {
|
||||
const targetWidth =
|
||||
this.loopCount >= FEAR_SETTINGS.EVENT_NARROW_LOOP_COUNT
|
||||
? 2.5
|
||||
: FEAR_SETTINGS.HALLWAY_WIDTH;
|
||||
const newWidth = THREE.MathUtils.lerp(
|
||||
this.currentWidth,
|
||||
targetWidth,
|
||||
2 * delta
|
||||
);
|
||||
|
||||
if (Math.abs(this.currentWidth - newWidth) > 0.001) {
|
||||
this.currentWidth = newWidth;
|
||||
}
|
||||
if (Math.abs(this.currentWidth - newWidth) > 0.001) {
|
||||
this.currentWidth = newWidth;
|
||||
}
|
||||
|
||||
if (this.wasCaught) {
|
||||
if (this.finaleProgression < FEAR_SETTINGS.EVENT_FINALE_DURATION) {
|
||||
if (this.wasCaught) {
|
||||
if (this.finaleProgression < FEAR_SETTINGS.EVENT_FINALE_DURATION) {
|
||||
this.finaleProgression = Math.min(
|
||||
this.finaleProgression + delta,
|
||||
FEAR_SETTINGS.EVENT_FINALE_DURATION
|
||||
);
|
||||
} else {
|
||||
window.location.href = '/';
|
||||
}
|
||||
}
|
||||
|
||||
this.finaleProgression = Math.min(this.finaleProgression + delta, FEAR_SETTINGS.EVENT_FINALE_DURATION);
|
||||
} else {
|
||||
window.location.href = '/';
|
||||
}
|
||||
}
|
||||
this.emit();
|
||||
},
|
||||
|
||||
this.emit();
|
||||
},
|
||||
registerLoop(direction: 'forward' | 'backward') {
|
||||
this.loopCount += 1;
|
||||
|
||||
registerLoop(direction: 'forward' | 'backward') {
|
||||
this.loopCount += 1;
|
||||
this.isRustActive = this.loopCount >= FEAR_SETTINGS.EVENT_RUST_LOOP_COUNT;
|
||||
this.finaleTriggered =
|
||||
this.loopCount >= FEAR_SETTINGS.EVENT_FINALE_LOOP_COUNT;
|
||||
|
||||
this.isRustActive = this.loopCount >= FEAR_SETTINGS.EVENT_RUST_LOOP_COUNT;
|
||||
this.finaleTriggered = this.loopCount >= FEAR_SETTINGS.EVENT_FINALE_LOOP_COUNT;
|
||||
this.emit();
|
||||
},
|
||||
|
||||
this.emit();
|
||||
},
|
||||
|
||||
registerCaught() {
|
||||
this.wasCaught = true;
|
||||
this.emit();
|
||||
}
|
||||
};
|
||||
registerCaught() {
|
||||
this.wasCaught = true;
|
||||
this.emit();
|
||||
}
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user