Compare commits
2 Commits
d506071ce2
...
8c4080f10c
| Author | SHA1 | Date | |
|---|---|---|---|
| 8c4080f10c | |||
| b9eeed848b |
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -15,6 +15,7 @@ import Player from './scene-components/player';
|
||||
import Hallway from './scene-components/hallway';
|
||||
|
||||
import { AudioListener } from 'three';
|
||||
import FinaleText from './scene-components/finale-text';
|
||||
|
||||
function PostProcessing() {
|
||||
const [wasCaught, setWasCaught] = useState(fearState.wasCaught);
|
||||
@@ -113,5 +114,7 @@ export default function Fear() {
|
||||
volume={1}
|
||||
/> : null}
|
||||
</Canvas>
|
||||
|
||||
<FinaleText />
|
||||
</>)
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
@font-face {
|
||||
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;
|
||||
|
||||
display: grid;
|
||||
align-items: center;
|
||||
align-content: center;
|
||||
justify-content: center;
|
||||
overflow: hidden;
|
||||
|
||||
grid-auto-rows: 2.5vh;
|
||||
/* grid-template-columns: 0; */
|
||||
grid-template-rows: repeat(auto-fit, max-content);
|
||||
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.finale-text {
|
||||
font-family: 'VCR', sans-serif;
|
||||
height: 0px;
|
||||
width: 100%;
|
||||
color: rgb(255, 255, 255);
|
||||
font-size: 5vh;
|
||||
|
||||
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);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
import { JSX, useEffect, useState } from "react"
|
||||
import { FEAR_SETTINGS, fearState } from "../state"
|
||||
|
||||
import './finale-text.css';
|
||||
|
||||
export default function FinaleText() {
|
||||
const [progression, setProgression] = useState(fearState.finaleProgression);
|
||||
const [wasCaught, setWasCaught] = useState(fearState.isRustActive);
|
||||
|
||||
useEffect(() => {
|
||||
const unsubscribe = fearState.subscribe(() => {
|
||||
setProgression(fearState.finaleProgression);
|
||||
setWasCaught(fearState.wasCaught)
|
||||
});
|
||||
return () => unsubscribe();
|
||||
});
|
||||
|
||||
|
||||
let elementCount = (FEAR_SETTINGS.EVENT_FINALE_TEXT_COUNT / FEAR_SETTINGS.EVENT_FINALE_DURATION) * progression;
|
||||
|
||||
let testElements: Array<JSX.Element> = [];
|
||||
|
||||
for (let x = 0; x < elementCount; x++)
|
||||
testElements.push(<span className="finale-text" key={x}>the deal has been sealed</span>)
|
||||
|
||||
|
||||
if (wasCaught)
|
||||
return (<>
|
||||
<div className="finale-container">
|
||||
{testElements}
|
||||
</div>
|
||||
<div className="scanlines" />
|
||||
</>)
|
||||
|
||||
return <></>
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { FEAR_SETTINGS, fearState } from "../state";
|
||||
import { useTexture } from "@react-three/drei";
|
||||
import { useTexture, PositionalAudio } from "@react-three/drei";
|
||||
|
||||
import * as THREE from "three";
|
||||
import { useFrame } from "@react-three/fiber";
|
||||
@@ -10,6 +10,27 @@ interface DoorProps {
|
||||
rotation: [number, number, number];
|
||||
}
|
||||
function Door({ position, rotation }: DoorProps) {
|
||||
const [soundUrl, setSoundUrl] = useState<string | null>(null);
|
||||
const currentSound = useRef<string | null>(null);
|
||||
|
||||
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);
|
||||
|
||||
return () => clearInterval(interval);
|
||||
}, []);
|
||||
|
||||
const handleAudioEnded = () => {
|
||||
setSoundUrl(null);
|
||||
currentSound.current = null;
|
||||
};
|
||||
|
||||
return (
|
||||
<group position={position} rotation={rotation}>
|
||||
<mesh position={[0, 2, -0.14]}>
|
||||
@@ -26,6 +47,16 @@ function Door({ position, rotation }: DoorProps) {
|
||||
<boxGeometry args={[0.08, 0.08, 0.15]} />
|
||||
<meshStandardMaterial color="#4e4b4b" roughness={0.4} metalness={0.2} />
|
||||
</mesh>
|
||||
|
||||
{soundUrl && (
|
||||
<PositionalAudio
|
||||
url={soundUrl}
|
||||
distance={25}
|
||||
loop={false}
|
||||
autoplay={true}
|
||||
onEnded={handleAudioEnded}
|
||||
/>
|
||||
)}
|
||||
</group>
|
||||
);
|
||||
}
|
||||
|
||||
+10
-2
@@ -12,7 +12,10 @@ export const FEAR_SETTINGS = {
|
||||
|
||||
EVENT_NARROW_LOOP_COUNT: 2,
|
||||
EVENT_RUST_LOOP_COUNT: 4,
|
||||
EVENT_FINALE_LOOP_COUNT: 5
|
||||
EVENT_FINALE_LOOP_COUNT: 5,
|
||||
|
||||
EVENT_FINALE_DURATION: 3,
|
||||
EVENT_FINALE_TEXT_COUNT: 128
|
||||
};
|
||||
|
||||
const listeners = new Set<() => void>();
|
||||
@@ -23,6 +26,7 @@ export const fearState = {
|
||||
isRustActive: false,
|
||||
finaleTriggered: false,
|
||||
wasCaught: false,
|
||||
finaleProgression: 0,
|
||||
|
||||
subscribe(listener: () => void) {
|
||||
listeners.add(listener);
|
||||
@@ -39,8 +43,12 @@ export const fearState = {
|
||||
|
||||
if (Math.abs(this.currentWidth - newWidth) > 0.001) {
|
||||
this.currentWidth = newWidth;
|
||||
this.emit();
|
||||
}
|
||||
|
||||
if (this.wasCaught && this.finaleProgression < FEAR_SETTINGS.EVENT_FINALE_DURATION)
|
||||
this.finaleProgression = Math.min(this.finaleProgression + delta, FEAR_SETTINGS.EVENT_FINALE_DURATION);
|
||||
|
||||
this.emit();
|
||||
},
|
||||
|
||||
registerLoop(direction: 'forward' | 'backward') {
|
||||
|
||||
Reference in New Issue
Block a user