Compare commits
4 Commits
4691a9fbf4
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| ab3bf047d4 | |||
| f1ab2b692d | |||
| 673aabce50 | |||
| b3a5712c85 |
@@ -30,7 +30,7 @@ function PostProcessing() {
|
|||||||
return (<EffectComposer>
|
return (<EffectComposer>
|
||||||
<Pixelation granularity={wasCaught ? 18 : 10} />
|
<Pixelation granularity={wasCaught ? 18 : 10} />
|
||||||
<Vignette />
|
<Vignette />
|
||||||
<Noise opacity={wasCaught ? 0.01 : 0.005} />
|
<Noise opacity={wasCaught ? 0.01 : 0.003} />
|
||||||
<BrightnessContrast
|
<BrightnessContrast
|
||||||
brightness={-0.01}
|
brightness={-0.01}
|
||||||
contrast={0.05}
|
contrast={0.05}
|
||||||
|
|||||||
@@ -1,14 +1,22 @@
|
|||||||
import { useTexture, PositionalAudio } from "@react-three/drei";
|
import { useTexture, PositionalAudio } from "@react-three/drei";
|
||||||
import { useFrame, useThree } from "@react-three/fiber";
|
import { useFrame, useThree } from "@react-three/fiber";
|
||||||
import { useEffect, useRef, useState } from "react";
|
import { useEffect, useMemo, useRef, useState } from "react";
|
||||||
|
|
||||||
import * as THREE from "three";
|
import * as THREE from "three";
|
||||||
import { FEAR_SETTINGS, fearState } from "../state";
|
import { FEAR_SETTINGS, fearState } from "../state";
|
||||||
|
import { ShaderPatch } from "../shader-patch";
|
||||||
|
|
||||||
useTexture.preload('fear/img/creature.png');
|
useTexture.preload('fear/img/creature.png');
|
||||||
|
|
||||||
export default function TheCreature() {
|
export default function TheCreature() {
|
||||||
const texture = 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 meshRef = useRef<THREE.Mesh>(null);
|
const meshRef = useRef<THREE.Mesh>(null);
|
||||||
const audioRef = useRef<THREE.PositionalAudio>(null);
|
const audioRef = useRef<THREE.PositionalAudio>(null);
|
||||||
const { camera } = useThree();
|
const { camera } = useThree();
|
||||||
@@ -21,6 +29,13 @@ export default function TheCreature() {
|
|||||||
|
|
||||||
const audioPlaying = useRef<boolean>(false);
|
const audioPlaying = useRef<boolean>(false);
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const unsubscribe = fearState.subscribe(() => {
|
const unsubscribe = fearState.subscribe(() => {
|
||||||
setFinaleTriggered(fearState.finaleTriggered);
|
setFinaleTriggered(fearState.finaleTriggered);
|
||||||
@@ -30,6 +45,8 @@ export default function TheCreature() {
|
|||||||
setHasTriggered(false);
|
setHasTriggered(false);
|
||||||
globalDistance.current = 32;
|
globalDistance.current = 32;
|
||||||
audioPlaying.current = false;
|
audioPlaying.current = false;
|
||||||
|
movePhase.current = 'frozen';
|
||||||
|
phaseTimer.current = 1.5;
|
||||||
|
|
||||||
if (audioRef.current && audioRef.current.isPlaying)
|
if (audioRef.current && audioRef.current.isPlaying)
|
||||||
audioRef.current.stop();
|
audioRef.current.stop();
|
||||||
@@ -47,16 +64,33 @@ export default function TheCreature() {
|
|||||||
if (!isSpawned) {
|
if (!isSpawned) {
|
||||||
setIsSpawned(true);
|
setIsSpawned(true);
|
||||||
globalDistance.current = 32;
|
globalDistance.current = 32;
|
||||||
|
movePhase.current = 'frozen';
|
||||||
|
phaseTimer.current = 1.0 + Math.random() * 1.5;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!hasTriggered) {
|
if (!hasTriggered) {
|
||||||
if (globalDistance.current < 40) {
|
if (globalDistance.current < 40)
|
||||||
setHasTriggered(true);
|
setHasTriggered(true);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hasTriggered) {
|
if (hasTriggered) {
|
||||||
globalDistance.current -= FEAR_SETTINGS.CREATURE_SPEED * delta;
|
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 (movePhase.current === 'lurching') {
|
||||||
|
globalDistance.current -= FEAR_SETTINGS.CREATURE_SPEED * 3 * delta;
|
||||||
|
}
|
||||||
|
|
||||||
if (audioRef.current && !audioPlaying.current) {
|
if (audioRef.current && !audioPlaying.current) {
|
||||||
audioPlaying.current = true;
|
audioPlaying.current = true;
|
||||||
@@ -70,6 +104,7 @@ export default function TheCreature() {
|
|||||||
camera.position.y += (Math.random() - 0.5) * shakeIntensity;
|
camera.position.y += (Math.random() - 0.5) * shakeIntensity;
|
||||||
|
|
||||||
if (globalDistance.current <= 0.1) {
|
if (globalDistance.current <= 0.1) {
|
||||||
|
window.location.href = '/';
|
||||||
fearState.registerCaught();
|
fearState.registerCaught();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -83,6 +118,51 @@ export default function TheCreature() {
|
|||||||
|
|
||||||
creature.position.set(0, 1.6, calculatedZ);
|
creature.position.set(0, 1.6, calculatedZ);
|
||||||
creature.lookAt(camera.position.x, creature.position.y, camera.position.z);
|
creature.lookAt(camera.position.x, creature.position.y, camera.position.z);
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -91,11 +171,15 @@ export default function TheCreature() {
|
|||||||
visible={finaleTriggered}
|
visible={finaleTriggered}
|
||||||
>
|
>
|
||||||
<planeGeometry args={[3.0, 4.8]} />
|
<planeGeometry args={[3.0, 4.8]} />
|
||||||
<meshBasicMaterial
|
<meshStandardMaterial
|
||||||
map={texture}
|
map={texture}
|
||||||
transparent={true}
|
transparent={true}
|
||||||
depthWrite={false}
|
depthWrite={false}
|
||||||
side={THREE.DoubleSide}
|
side={THREE.DoubleSide}
|
||||||
|
onBeforeCompile={ShaderPatch}
|
||||||
|
emissive="#ffffff"
|
||||||
|
emissiveMap={texture}
|
||||||
|
emissiveIntensity={0.15}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{finaleTriggered && (
|
{finaleTriggered && (
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ export default function FinaleText() {
|
|||||||
const interval = setInterval(() => {
|
const interval = setInterval(() => {
|
||||||
if (Math.random() > 0.9) return;
|
if (Math.random() > 0.9) return;
|
||||||
|
|
||||||
const baseText = "the deal has been sealed";
|
const baseText = "bwaaaaaaaaa";
|
||||||
const corrupted = baseText
|
const corrupted = baseText
|
||||||
.split("")
|
.split("")
|
||||||
.map((char) => (Math.random() > 0.98 ? BLOCKS[Math.floor(Math.random() * BLOCKS.length)] : char))
|
.map((char) => (Math.random() > 0.98 ? BLOCKS[Math.floor(Math.random() * BLOCKS.length)] : char))
|
||||||
|
|||||||
@@ -4,64 +4,7 @@ import { useTexture, PositionalAudio } from "@react-three/drei";
|
|||||||
|
|
||||||
import * as THREE from "three";
|
import * as THREE from "three";
|
||||||
import { useFrame } from "@react-three/fiber";
|
import { useFrame } from "@react-three/fiber";
|
||||||
|
import { ShaderPatch } from "../shader-patch";
|
||||||
function ShaderPatch(shader: { vertexShader: string, fragmentShader: string, uniforms: Object }) {
|
|
||||||
shader.vertexShader = `
|
|
||||||
varying float vDepth;
|
|
||||||
#ifdef USE_MAP
|
|
||||||
varying vec2 vAffineUv;
|
|
||||||
#endif
|
|
||||||
${shader.vertexShader}
|
|
||||||
`;
|
|
||||||
|
|
||||||
shader.vertexShader = shader.vertexShader.replace(
|
|
||||||
`#include <project_vertex>`,
|
|
||||||
`
|
|
||||||
vec4 mvPosition = modelViewMatrix * vec4( position, 1.0 );
|
|
||||||
gl_Position = projectionMatrix * mvPosition;
|
|
||||||
|
|
||||||
float precisionModifier = 200.0;
|
|
||||||
gl_Position.xy /= gl_Position.w;
|
|
||||||
gl_Position.xy = floor(gl_Position.xy * precisionModifier) / precisionModifier;
|
|
||||||
gl_Position.xy *= gl_Position.w;
|
|
||||||
|
|
||||||
vDepth = gl_Position.w;
|
|
||||||
|
|
||||||
#ifdef USE_MAP
|
|
||||||
vAffineUv = vMapUv * gl_Position.w;
|
|
||||||
#endif
|
|
||||||
`
|
|
||||||
);
|
|
||||||
|
|
||||||
shader.fragmentShader = `
|
|
||||||
varying float vDepth;
|
|
||||||
#ifdef USE_MAP
|
|
||||||
varying vec2 vAffineUv;
|
|
||||||
#endif
|
|
||||||
${shader.fragmentShader}
|
|
||||||
`;
|
|
||||||
|
|
||||||
shader.fragmentShader = shader.fragmentShader.replace(
|
|
||||||
`#include <map_fragment>`,
|
|
||||||
`
|
|
||||||
#ifdef USE_MAP
|
|
||||||
vec2 flatAffineUV = vAffineUv / max(vDepth, 0.001);
|
|
||||||
|
|
||||||
vec2 warpDiff = flatAffineUV - vMapUv;
|
|
||||||
float warpDist = length(warpDiff);
|
|
||||||
|
|
||||||
float maxDistortion = 0.25;
|
|
||||||
|
|
||||||
float falloff = maxDistortion / (maxDistortion + warpDist);
|
|
||||||
|
|
||||||
vec2 finalUV = vMapUv + (warpDiff * falloff);
|
|
||||||
|
|
||||||
vec4 texelColor = texture2D( map, finalUV );
|
|
||||||
diffuseColor *= texelColor;
|
|
||||||
#endif
|
|
||||||
`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
interface DoorProps {
|
interface DoorProps {
|
||||||
position: [number, number, number];
|
position: [number, number, number];
|
||||||
|
|||||||
@@ -0,0 +1,57 @@
|
|||||||
|
export function ShaderPatch(shader: { vertexShader: string, fragmentShader: string, uniforms: Object }) {
|
||||||
|
shader.vertexShader = `
|
||||||
|
varying float vDepth;
|
||||||
|
#ifdef USE_MAP
|
||||||
|
varying vec2 vAffineUv;
|
||||||
|
#endif
|
||||||
|
${shader.vertexShader}
|
||||||
|
`;
|
||||||
|
|
||||||
|
shader.vertexShader = shader.vertexShader.replace(
|
||||||
|
`#include <project_vertex>`,
|
||||||
|
`
|
||||||
|
vec4 mvPosition = modelViewMatrix * vec4( position, 1.0 );
|
||||||
|
gl_Position = projectionMatrix * mvPosition;
|
||||||
|
|
||||||
|
float precisionModifier = 200.0;
|
||||||
|
gl_Position.xy /= gl_Position.w;
|
||||||
|
gl_Position.xy = floor(gl_Position.xy * precisionModifier) / precisionModifier;
|
||||||
|
gl_Position.xy *= gl_Position.w;
|
||||||
|
|
||||||
|
vDepth = gl_Position.w;
|
||||||
|
|
||||||
|
#ifdef USE_MAP
|
||||||
|
vAffineUv = vMapUv * gl_Position.w;
|
||||||
|
#endif
|
||||||
|
`
|
||||||
|
);
|
||||||
|
|
||||||
|
shader.fragmentShader = `
|
||||||
|
varying float vDepth;
|
||||||
|
#ifdef USE_MAP
|
||||||
|
varying vec2 vAffineUv;
|
||||||
|
#endif
|
||||||
|
${shader.fragmentShader}
|
||||||
|
`;
|
||||||
|
|
||||||
|
shader.fragmentShader = shader.fragmentShader.replace(
|
||||||
|
`#include <map_fragment>`,
|
||||||
|
`
|
||||||
|
#ifdef USE_MAP
|
||||||
|
vec2 flatAffineUV = vAffineUv / max(vDepth, 0.001);
|
||||||
|
|
||||||
|
vec2 warpDiff = flatAffineUV - vMapUv;
|
||||||
|
float warpDist = length(warpDiff);
|
||||||
|
|
||||||
|
float maxDistortion = 0.25;
|
||||||
|
|
||||||
|
float falloff = maxDistortion / (maxDistortion + warpDist);
|
||||||
|
|
||||||
|
vec2 finalUV = vMapUv + (warpDiff * falloff);
|
||||||
|
|
||||||
|
vec4 texelColor = texture2D( map, finalUV );
|
||||||
|
diffuseColor *= texelColor;
|
||||||
|
#endif
|
||||||
|
`
|
||||||
|
);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user