Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 092faa9449 | |||
| a163a12483 | |||
| 30498e4faa |
Binary file not shown.
|
Before Width: | Height: | Size: 2.4 MiB |
Binary file not shown.
|
After Width: | Height: | Size: 27 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 799 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 44 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 55 KiB |
+335
-103
@@ -5,17 +5,12 @@ import './page.css';
|
||||
import { PointerLockControls, useTexture } from "@react-three/drei";
|
||||
import { Canvas, useFrame, useThree } from "@react-three/fiber";
|
||||
import { BrightnessContrast, EffectComposer, Noise, Pixelation, Vignette } from "@react-three/postprocessing";
|
||||
import { Suspense, useEffect, useRef } from "react";
|
||||
import { Suspense, useEffect, useRef, useState } from "react";
|
||||
|
||||
import * as THREE from "three";
|
||||
import { AmbientSound } from './scene-components/ambient-sound';
|
||||
import { MeshStandardNodeMaterial } from 'three/webgpu';
|
||||
|
||||
const HALLWAY_LENGTH = 40;
|
||||
const HALLWAY_WIDTH = 6;
|
||||
const HALLWAY_HEIGHT = 5;
|
||||
|
||||
const PLAYER_HEIGHT = 3;
|
||||
import { fearState, FEAR_SETTINGS } from './state';
|
||||
|
||||
function Player() {
|
||||
const { camera } = useThree();
|
||||
@@ -24,21 +19,27 @@ function Player() {
|
||||
const flashlightRef = useRef<THREE.SpotLight>(null);
|
||||
const movementCounter = useRef<number>(0);
|
||||
|
||||
const frontVector = new THREE.Vector3();
|
||||
const sideVector = new THREE.Vector3();
|
||||
const forward = new THREE.Vector3();
|
||||
const side = new THREE.Vector3();
|
||||
const direction = new THREE.Vector3();
|
||||
|
||||
const viewDirection = new THREE.Vector3();
|
||||
|
||||
useFrame((state, delta) => {
|
||||
frontVector.set(0, 0, Number(controls.Backward) - Number(controls.Forward));
|
||||
sideVector.set(Number(controls.Left) - Number(controls.Right), 0, 0);
|
||||
camera.getWorldDirection(forward);
|
||||
forward.y = 0;
|
||||
forward.normalize();
|
||||
|
||||
direction
|
||||
.subVectors(frontVector, sideVector)
|
||||
.normalize()
|
||||
.multiplyScalar(4 * delta)
|
||||
.applyEuler(camera.rotation);
|
||||
side.crossVectors(forward, new THREE.Vector3(0, 1, 0)).normalize();
|
||||
|
||||
const moveForward = Number(controls.Forward) - Number(controls.Backward);
|
||||
const moveSide = Number(controls.Right) - Number(controls.Left);
|
||||
|
||||
direction.set(0, 0, 0);
|
||||
if (moveForward !== 0) direction.addScaledVector(forward, moveForward);
|
||||
if (moveSide !== 0) direction.addScaledVector(side, moveSide);
|
||||
|
||||
if (direction.lengthSq() > 0)
|
||||
direction.normalize().multiplyScalar(4 * delta);
|
||||
|
||||
camera.position.x += direction.x;
|
||||
camera.position.z += direction.z;
|
||||
@@ -46,33 +47,42 @@ function Player() {
|
||||
const isMoving = controls.Forward || controls.Backward || controls.Left || controls.Right;
|
||||
if (isMoving) {
|
||||
movementCounter.current += delta * 10;
|
||||
camera.position.y = PLAYER_HEIGHT + Math.sin(movementCounter.current) * 0.08;
|
||||
camera.position.y = FEAR_SETTINGS.PLAYER_HEIGHT + Math.sin(movementCounter.current) * 0.08;
|
||||
camera.position.x += Math.cos(movementCounter.current / 2) * 0.006;
|
||||
} else
|
||||
camera.position.y = THREE.MathUtils.lerp(camera.position.y, PLAYER_HEIGHT, 5 * delta);
|
||||
|
||||
} else {
|
||||
const breatheTime = state.clock.elapsedTime * 1.5;
|
||||
const breatheY = FEAR_SETTINGS.PLAYER_HEIGHT + Math.sin(breatheTime) * 0.1;
|
||||
camera.position.y = THREE.MathUtils.lerp(camera.position.y, breatheY, 4 * delta);
|
||||
}
|
||||
|
||||
if (flashlightRef.current) {
|
||||
flashlightRef.current.position.copy(camera.position);
|
||||
|
||||
camera.getWorldDirection(viewDirection);
|
||||
|
||||
flashlightRef.current.target.position
|
||||
const targetDest = new THREE.Vector3()
|
||||
.copy(camera.position)
|
||||
.addScaledVector(viewDirection, 10);
|
||||
|
||||
flashlightRef.current.target.position.lerp(targetDest, 10 * delta);
|
||||
flashlightRef.current.target.updateMatrixWorld();
|
||||
|
||||
flashlightRef.current.intensity = 5 + Math.sin(state.clock.elapsedTime * 30) * 0.3;
|
||||
}
|
||||
|
||||
const wallBuffer = 0.6;
|
||||
const HALLWAY_WIDTH = 6;
|
||||
const HALLWAY_LENGTH = 40;
|
||||
const minX = -fearState.currentWidth / 2 + FEAR_SETTINGS.WALL_BUFFER;
|
||||
const maxX = fearState.currentWidth / 2 - FEAR_SETTINGS.WALL_BUFFER;
|
||||
|
||||
if (camera.position.x < -HALLWAY_WIDTH / 2 + wallBuffer) camera.position.x = -HALLWAY_WIDTH / 2 + wallBuffer;
|
||||
if (camera.position.x > HALLWAY_WIDTH / 2 - wallBuffer) camera.position.x = HALLWAY_WIDTH / 2 - wallBuffer;
|
||||
if (camera.position.x < minX) camera.position.x = minX;
|
||||
if (camera.position.x > maxX) camera.position.x = maxX;
|
||||
|
||||
if (camera.position.z < -HALLWAY_LENGTH) camera.position.z += HALLWAY_LENGTH;
|
||||
if (camera.position.z > 0) camera.position.z -= HALLWAY_LENGTH;
|
||||
if (camera.position.z < -FEAR_SETTINGS.HALLWAY_LENGTH) {
|
||||
camera.position.z += FEAR_SETTINGS.HALLWAY_LENGTH;
|
||||
fearState.registerLoop('forward');
|
||||
}
|
||||
if (camera.position.z > 0) {
|
||||
camera.position.z -= FEAR_SETTINGS.HALLWAY_LENGTH;
|
||||
fearState.registerLoop('backward');
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
@@ -146,110 +156,320 @@ function Door({ position, rotation }: DoorProps) {
|
||||
}
|
||||
|
||||
function Hallway() {
|
||||
const [floorTex, wallTex] = useTexture([
|
||||
'fear/img/concrete-floor.jpg',
|
||||
'fear/img/concrete-wall.jpg'
|
||||
const [width, setWidth] = useState(fearState.currentWidth);
|
||||
const [floorTex, wallTex, rustTex] = useTexture([
|
||||
'fear/img/concrete-floor.png',
|
||||
'fear/img/concrete-wall.png',
|
||||
'fear/img/rust.png'
|
||||
]);
|
||||
|
||||
floorTex.wrapS = floorTex.wrapT = THREE.RepeatWrapping;
|
||||
floorTex.repeat.set(1, 10);
|
||||
floorTex.minFilter = THREE.NearestFilter;
|
||||
floorTex.magFilter = THREE.NearestFilter;
|
||||
|
||||
|
||||
wallTex.wrapS = wallTex.wrapT = THREE.RepeatWrapping;
|
||||
wallTex.repeat.set(10, 1);
|
||||
wallTex.minFilter = THREE.NearestFilter;
|
||||
wallTex.magFilter = THREE.NearestFilter;
|
||||
[floorTex, wallTex, rustTex].forEach((tex, index) => {
|
||||
tex.wrapS = tex.wrapT = THREE.RepeatWrapping;
|
||||
tex.minFilter = tex.magFilter = THREE.NearestFilter;
|
||||
tex.colorSpace = THREE.SRGBColorSpace;
|
||||
tex.repeat.set(index === 0 ? 1 : 10, index === 0 ? 10 : 1);
|
||||
});
|
||||
|
||||
const segments = [0, -1, 1, -2, 2];
|
||||
|
||||
const lightRef1 = useRef<THREE.PointLight>(null);
|
||||
const lightRef2 = useRef<THREE.PointLight>(null);
|
||||
const matRef1 = useRef<THREE.MeshStandardMaterial>(null);
|
||||
const matRef2 = useRef<THREE.MeshStandardMaterial>(null);
|
||||
const lightState = useRef<'normal' | 'flickering' | 'dead'>('normal');
|
||||
const stateEndTime = useRef<number>(0);
|
||||
const nextEventTime = useRef<number>(5);
|
||||
|
||||
useFrame((state) => {
|
||||
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 = [];
|
||||
|
||||
const [isRustActive, setIsRustActive] = useState(fearState.isRustActive);
|
||||
|
||||
useEffect(() => {
|
||||
const unsubscribe = fearState.subscribe(() => {
|
||||
setWidth(fearState.currentWidth);
|
||||
setIsRustActive(fearState.isRustActive);
|
||||
});
|
||||
return () => unsubscribe();
|
||||
}, []);
|
||||
|
||||
useFrame((state, delta) => {
|
||||
const time = state.clock.elapsedTime;
|
||||
const flicker = Math.sin(time * 10) * Math.cos(time * 3) > 0.4 ? 0 : 0.8;
|
||||
if (lightRef1.current) lightRef1.current.intensity = flicker;
|
||||
if (lightRef2.current) lightRef2.current.intensity = flicker * 0.5;
|
||||
|
||||
fearState.update(delta);
|
||||
const currentRust = fearState.isRustActive;
|
||||
if (currentRust !== isRustActive)
|
||||
setIsRustActive(currentRust);
|
||||
|
||||
/*
|
||||
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 intensity2 = lightState.current === 'dead' ? 0 : intensity1 * (0.7 + Math.sin(time * 3) * 0.1);
|
||||
|
||||
if (lightRef1.current) lightRef1.current.intensity = intensity1 * 1.2;
|
||||
if (matRef1.current) {
|
||||
matRef1.current.emissiveIntensity = intensity1 * 2.5;
|
||||
if (lightState.current !== 'normal') matRef1.current.emissive.setHSL(0.07, 0.4, Math.min(intensity1, 0.7));
|
||||
else matRef1.current.emissive.setHex(0xa8a1a1);
|
||||
}
|
||||
if (lightRef2.current) lightRef2.current.intensity = intensity2 * 0.6;
|
||||
if (matRef2.current) matRef2.current.emissiveIntensity = intensity2 * 1.5;
|
||||
|
||||
/*
|
||||
walls
|
||||
*/
|
||||
segmentsRef.current.forEach((segGroup) => {
|
||||
if (!segGroup) return;
|
||||
|
||||
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 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;
|
||||
|
||||
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;
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
/*
|
||||
rust
|
||||
*/
|
||||
wallMaterialsRef.current.forEach(mat => {
|
||||
if (!mat) return;
|
||||
const targetTex = isRustActive ? rustTex : wallTex;
|
||||
if (mat.map !== targetTex) {
|
||||
mat.map = targetTex;
|
||||
mat.needsUpdate = true;
|
||||
}
|
||||
if (isRustActive) {
|
||||
mat.color.set("#918a87");
|
||||
mat.roughness = 0.95;
|
||||
mat.metalness = 0.05;
|
||||
} else {
|
||||
mat.color.set("#ffffff");
|
||||
mat.roughness = 0.7;
|
||||
mat.metalness = 0.1;
|
||||
}
|
||||
});
|
||||
|
||||
floorMaterialsRef.current.forEach(mat => {
|
||||
if (!mat) return;
|
||||
const targetTex = isRustActive ? rustTex : floorTex;
|
||||
if (mat.map !== targetTex) {
|
||||
mat.map = targetTex;
|
||||
mat.needsUpdate = true;
|
||||
}
|
||||
if (isRustActive) {
|
||||
mat.color.set("#8b827f");
|
||||
mat.roughness = 0.95;
|
||||
mat.metalness = 0.05;
|
||||
} else {
|
||||
mat.color.set("#ffffff");
|
||||
mat.roughness = 0.8;
|
||||
mat.metalness = 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;
|
||||
});
|
||||
|
||||
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 (
|
||||
<>
|
||||
{/* <ambientLight intensity={1} /> */}
|
||||
<ambientLight intensity={0.02} />
|
||||
<ambientLight intensity={0.0225} />
|
||||
|
||||
{segments.map((i) => (
|
||||
<group key={i} position={[0, 0, i * HALLWAY_LENGTH]}>
|
||||
<pointLight ref={i === 0 ? lightRef1 : null} position={[0, HALLWAY_HEIGHT - 0.5, -HALLWAY_LENGTH / 4]} intensity={0.9} distance={15} color="#a8a1a1" />
|
||||
<pointLight ref={i === 0 ? lightRef2 : null} position={[0, HALLWAY_HEIGHT - 0.5, -HALLWAY_LENGTH * 0.75]} intensity={0.9} distance={15} color="#a8a1a1" />
|
||||
{segments.map((segmentValue, index) => (
|
||||
/*
|
||||
lights
|
||||
*/
|
||||
<group
|
||||
key={segmentValue}
|
||||
ref={(el) => { if (el) segmentsRef.current[index] = el; }}
|
||||
position={[0, 0, segmentValue * FEAR_SETTINGS.HALLWAY_LENGTH]}
|
||||
>
|
||||
<group position={[0, FEAR_SETTINGS.HALLWAY_HEIGHT - 0.1, -FEAR_SETTINGS.HALLWAY_LENGTH / 4]}>
|
||||
<pointLight
|
||||
ref={segmentValue === 0 ? lightRef1 : null}
|
||||
intensity={0.9}
|
||||
distance={15}
|
||||
color="#a8a1a1"
|
||||
/>
|
||||
<mesh position={[0, 0.09, 0]}>
|
||||
<boxGeometry args={[0.3, 0.01, 0.3]} />
|
||||
<meshStandardMaterial
|
||||
ref={segmentValue === 0 ? matRef1 : null}
|
||||
color="#111111"
|
||||
emissive="#a8a1a1"
|
||||
emissiveIntensity={segmentValue === 0 ? 0 : 0.8}
|
||||
roughness={0.9}
|
||||
/>
|
||||
</mesh>
|
||||
</group>
|
||||
|
||||
{/* floor */}
|
||||
<mesh rotation={[-Math.PI / 2, 0, 0]} position={[0, 0, -HALLWAY_LENGTH / 2]}>
|
||||
<planeGeometry args={[HALLWAY_WIDTH, HALLWAY_LENGTH]} />
|
||||
<meshStandardMaterial map={floorTex} roughness={0.8} metalness={0.2} />
|
||||
<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]} />
|
||||
<meshStandardMaterial
|
||||
ref={(el) => el && floorMaterialsRef.current.push(el)}
|
||||
map={floorTex}
|
||||
roughness={0.8}
|
||||
metalness={0.2}
|
||||
/>
|
||||
</mesh>
|
||||
|
||||
{/* roof */}
|
||||
<mesh rotation={[Math.PI / 2, 0, 0]} position={[0, HALLWAY_HEIGHT, -HALLWAY_LENGTH / 2]}>
|
||||
<planeGeometry args={[HALLWAY_WIDTH, HALLWAY_LENGTH]} />
|
||||
<meshStandardMaterial map={floorTex} roughness={0.8} metalness={0.2} />
|
||||
{/* 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]} />
|
||||
<meshStandardMaterial
|
||||
ref={(el) => el && floorMaterialsRef.current.push(el)}
|
||||
map={floorTex}
|
||||
roughness={0.8}
|
||||
metalness={0.2}
|
||||
/>
|
||||
</mesh>
|
||||
|
||||
{/* l wall */}
|
||||
<mesh rotation={[0, Math.PI / 2, 0]} position={[-HALLWAY_WIDTH / 2, HALLWAY_HEIGHT / 2, -HALLWAY_LENGTH / 2]}>
|
||||
<planeGeometry args={[HALLWAY_LENGTH, HALLWAY_HEIGHT]} />
|
||||
<meshStandardMaterial map={wallTex} roughness={0.7} metalness={0.1} />
|
||||
</mesh>
|
||||
<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]} />
|
||||
<meshStandardMaterial
|
||||
ref={(el) => el && wallMaterialsRef.current.push(el)}
|
||||
map={wallTex}
|
||||
roughness={0.7}
|
||||
metalness={0.1}
|
||||
/>
|
||||
</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>
|
||||
|
||||
{/* r wall */}
|
||||
<mesh rotation={[0, -Math.PI / 2, 0]} position={[HALLWAY_WIDTH / 2, HALLWAY_HEIGHT / 2, -HALLWAY_LENGTH / 2]}>
|
||||
<planeGeometry args={[HALLWAY_LENGTH, HALLWAY_HEIGHT]} />
|
||||
<meshStandardMaterial map={wallTex} roughness={0.7} metalness={0.1} />
|
||||
</mesh>
|
||||
|
||||
{/* doors */}
|
||||
<Door
|
||||
position={[-HALLWAY_WIDTH / 2 + 0.05, 0, -HALLWAY_LENGTH * 0.25]}
|
||||
rotation={[0, Math.PI / 2, 0]}
|
||||
/>
|
||||
|
||||
<Door
|
||||
position={[HALLWAY_WIDTH / 2 - 0.05, 0, -HALLWAY_LENGTH * 0.65]}
|
||||
rotation={[0, -Math.PI / 2, 0]}
|
||||
/>
|
||||
|
||||
<Door
|
||||
position={[-HALLWAY_WIDTH / 2 + 0.05, 0, -HALLWAY_LENGTH * 0.85]}
|
||||
rotation={[0, Math.PI / 2, 0]}
|
||||
/>
|
||||
<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]} />
|
||||
<meshStandardMaterial
|
||||
ref={(el) => el && wallMaterialsRef.current.push(el)}
|
||||
map={wallTex}
|
||||
roughness={0.7}
|
||||
metalness={0.1}
|
||||
/>
|
||||
</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={[-HALLWAY_WIDTH / 2 + 0.4 + (idx * 0.20), HALLWAY_HEIGHT - 0.2, -HALLWAY_LENGTH / 2]}
|
||||
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, HALLWAY_LENGTH, 4]} />
|
||||
<meshStandardMaterial color="#a5aca8" roughness={0.0} metalness={0.4} />
|
||||
<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}
|
||||
/>
|
||||
</mesh>
|
||||
))}
|
||||
|
||||
{Array.from({ length: 5 }).map((_, idx) => {
|
||||
const zOffset = -(idx * 8 + 4);
|
||||
|
||||
return (
|
||||
<mesh
|
||||
key={`bracket-${idx}`}
|
||||
position={[-HALLWAY_WIDTH / 2 + 0.6, HALLWAY_HEIGHT - 0.15, zOffset]}
|
||||
>
|
||||
<boxGeometry args={[0.7, 0.3, 0.15]} />
|
||||
<meshStandardMaterial color="#b3adad" roughness={0.8} metalness={0.2} />
|
||||
</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}
|
||||
/>
|
||||
</mesh>
|
||||
);
|
||||
})}
|
||||
</group>
|
||||
</group>
|
||||
))}
|
||||
</>
|
||||
@@ -258,7 +478,7 @@ function Hallway() {
|
||||
|
||||
function PostProcessing() {
|
||||
return (<EffectComposer>
|
||||
<Pixelation granularity={10} />
|
||||
<Pixelation granularity={12} />
|
||||
<Vignette />
|
||||
<Noise opacity={0.005} />
|
||||
<BrightnessContrast
|
||||
@@ -269,6 +489,15 @@ function PostProcessing() {
|
||||
}
|
||||
|
||||
export default function Fear() {
|
||||
const [isRustActive, setIsRustActive] = useState(fearState.isRustActive);
|
||||
|
||||
useEffect(() => {
|
||||
const unsubscribe = fearState.subscribe(() => {
|
||||
setIsRustActive(fearState.isRustActive);
|
||||
});
|
||||
return () => unsubscribe();
|
||||
}, []);
|
||||
|
||||
return (<>
|
||||
<Canvas
|
||||
shadows
|
||||
@@ -284,7 +513,10 @@ export default function Fear() {
|
||||
<Hallway />
|
||||
</Suspense>
|
||||
|
||||
<AmbientSound url='fear/snd/ambience.mp3' />
|
||||
<AmbientSound
|
||||
url='fear/snd/ambience.mp3'
|
||||
volume={isRustActive ? 0 : 1}
|
||||
/>
|
||||
|
||||
<Player />
|
||||
</Canvas>
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
import * as THREE from 'three';
|
||||
|
||||
export const FEAR_SETTINGS = {
|
||||
HALLWAY_LENGTH: 40,
|
||||
HALLWAY_WIDTH: 6,
|
||||
HALLWAY_HEIGHT: 5,
|
||||
PLAYER_HEIGHT: 3,
|
||||
WALL_BUFFER: 0.6,
|
||||
};
|
||||
|
||||
const listeners = new Set<() => void>();
|
||||
|
||||
export const fearState = {
|
||||
loopCount: 0,
|
||||
currentWidth: FEAR_SETTINGS.HALLWAY_WIDTH,
|
||||
isRustActive: false,
|
||||
|
||||
subscribe(listener: () => void) {
|
||||
listeners.add(listener);
|
||||
return () => { listeners.delete(listener); };
|
||||
},
|
||||
|
||||
emit() {
|
||||
listeners.forEach((listener) => listener());
|
||||
},
|
||||
|
||||
update(delta: number) {
|
||||
this.isRustActive = this.loopCount >= 3;
|
||||
|
||||
const targetWidth = this.loopCount >= 2 ? 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;
|
||||
this.emit();
|
||||
}
|
||||
},
|
||||
|
||||
registerLoop(direction: 'forward' | 'backward') {
|
||||
this.loopCount += 1;
|
||||
console.log(`Hallway looped ${direction}. Total loops: ${this.loopCount}`);
|
||||
this.emit();
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user