feat: Add ambient audio and dynamic dust particles that react to the player's flashlight.
This commit is contained in:
@@ -39,6 +39,7 @@ export class Game {
|
||||
|
||||
if (this.isRunning) {
|
||||
this.player.update(dt);
|
||||
this.world.update(dt, this.player);
|
||||
}
|
||||
|
||||
this.graphics.render();
|
||||
|
||||
@@ -72,6 +72,7 @@ export class Player {
|
||||
if (this.ctx && this.ctx.state === 'suspended') {
|
||||
this.ctx.resume();
|
||||
this.audioEnabled = true;
|
||||
this.startAmbience();
|
||||
}
|
||||
|
||||
switch (event.code) {
|
||||
@@ -279,4 +280,40 @@ export class Player {
|
||||
noise.start();
|
||||
noise.stop(t + 0.1);
|
||||
}
|
||||
|
||||
startAmbience() {
|
||||
if (!this.ctx || this.ambienceStarted) return;
|
||||
this.ambienceStarted = true;
|
||||
|
||||
const t = this.ctx.currentTime;
|
||||
|
||||
// Oscillator 1: The "Hum" (60hz roughly)
|
||||
const osc1 = this.ctx.createOscillator();
|
||||
osc1.type = 'sine';
|
||||
osc1.frequency.setValueAtTime(55, t); // Low A (ish)
|
||||
|
||||
// Oscillator 2: The "Detune" (creates beating/unsettling texture)
|
||||
const osc2 = this.ctx.createOscillator();
|
||||
osc2.type = 'triangle';
|
||||
osc2.frequency.setValueAtTime(58, t); // Slightly off
|
||||
|
||||
// Filter to keep it dark/muddy
|
||||
const filter = this.ctx.createBiquadFilter();
|
||||
filter.type = 'lowpass';
|
||||
filter.frequency.setValueAtTime(120, t); // Very muffled
|
||||
|
||||
// Gain (Volume)
|
||||
const gain = this.ctx.createGain();
|
||||
gain.gain.setValueAtTime(0.3, t); // 30% volume
|
||||
|
||||
// Connect graph
|
||||
osc1.connect(filter);
|
||||
osc2.connect(filter);
|
||||
filter.connect(gain);
|
||||
gain.connect(this.ctx.destination);
|
||||
|
||||
// Start forever
|
||||
osc1.start();
|
||||
osc2.start();
|
||||
}
|
||||
}
|
||||
|
||||
114
src/World.js
114
src/World.js
@@ -4,6 +4,7 @@ export class World {
|
||||
constructor(scene) {
|
||||
this.scene = scene;
|
||||
this.colliders = [];
|
||||
this.dustParticles = null;
|
||||
}
|
||||
|
||||
load() {
|
||||
@@ -42,6 +43,49 @@ export class World {
|
||||
target.position.set(5, 0.5, -5);
|
||||
this.scene.add(target);
|
||||
this.colliders.push(target);
|
||||
|
||||
this.createDust();
|
||||
}
|
||||
|
||||
createDust() {
|
||||
// Create 2000 dust particles
|
||||
const count = 2000;
|
||||
const geometry = new THREE.BufferGeometry();
|
||||
const positions = new Float32Array(count * 3);
|
||||
const colors = new Float32Array(count * 3); // For controlling opacity/brightness per particle
|
||||
const velocities = [];
|
||||
|
||||
for (let i = 0; i < count; i++) {
|
||||
positions[i * 3] = (Math.random() - 0.5) * 40;
|
||||
positions[i * 3 + 1] = Math.random() * 5;
|
||||
positions[i * 3 + 2] = (Math.random() - 0.5) * 40;
|
||||
|
||||
colors[i * 3] = 0; // Start invisible (black)
|
||||
colors[i * 3 + 1] = 0;
|
||||
colors[i * 3 + 2] = 0;
|
||||
|
||||
velocities.push({
|
||||
x: (Math.random() - 0.5) * 0.1,
|
||||
y: (Math.random() - 0.5) * 0.1,
|
||||
z: (Math.random() - 0.5) * 0.1
|
||||
});
|
||||
}
|
||||
|
||||
geometry.setAttribute('position', new THREE.BufferAttribute(positions, 3));
|
||||
geometry.setAttribute('color', new THREE.BufferAttribute(colors, 3));
|
||||
|
||||
const material = new THREE.PointsMaterial({
|
||||
size: 0.05,
|
||||
vertexColors: true, // IMPORTANT: Use per-particle colors
|
||||
transparent: true,
|
||||
opacity: 0.8,
|
||||
sizeAttenuation: true,
|
||||
blending: THREE.AdditiveBlending
|
||||
});
|
||||
|
||||
this.dustParticles = new THREE.Points(geometry, material);
|
||||
this.dustParticles.userData = { velocities: velocities };
|
||||
this.scene.add(this.dustParticles);
|
||||
}
|
||||
|
||||
createWall(x, y, z, width, height, rotate = false) {
|
||||
@@ -62,4 +106,74 @@ export class World {
|
||||
this.scene.add(pillar);
|
||||
this.colliders.push(pillar);
|
||||
}
|
||||
|
||||
update(dt, player) {
|
||||
if (!this.dustParticles) return;
|
||||
|
||||
const positions = this.dustParticles.geometry.attributes.position.array;
|
||||
const colors = this.dustParticles.geometry.attributes.color.array;
|
||||
const velocities = this.dustParticles.userData.velocities;
|
||||
|
||||
// Flashlight info
|
||||
let lightPos, lightDir, lightAngle, lightDist, isLightOn;
|
||||
if (player && player.flashlight) {
|
||||
// Get world position and direction of camera/flashlight
|
||||
lightPos = new THREE.Vector3();
|
||||
lightDir = new THREE.Vector3();
|
||||
player.camera.getWorldPosition(lightPos);
|
||||
player.camera.getWorldDirection(lightDir);
|
||||
|
||||
lightAngle = player.flashlight.angle; // Cone half-angle
|
||||
lightDist = player.flashlight.distance;
|
||||
isLightOn = player.flashlightOn;
|
||||
}
|
||||
|
||||
const pPos = new THREE.Vector3(); // Temp vector
|
||||
|
||||
for (let i = 0; i < velocities.length; i++) {
|
||||
const v = velocities[i];
|
||||
|
||||
// 1. Move
|
||||
positions[i * 3] += v.x * dt;
|
||||
positions[i * 3 + 1] += v.y * dt;
|
||||
positions[i * 3 + 2] += v.z * dt;
|
||||
|
||||
// Wrap
|
||||
if (positions[i * 3 + 1] < 0) positions[i * 3 + 1] = 5;
|
||||
if (positions[i * 3 + 1] > 5) positions[i * 3 + 1] = 0;
|
||||
if (Math.abs(positions[i * 3]) > 20) positions[i * 3] *= -0.9;
|
||||
if (Math.abs(positions[i * 3 + 2]) > 20) positions[i * 3 + 2] *= -0.9;
|
||||
|
||||
// 2. Lighting Check
|
||||
let brightness = 0;
|
||||
if (isLightOn) {
|
||||
// Check distance
|
||||
pPos.set(positions[i * 3], positions[i * 3 + 1], positions[i * 3 + 2]);
|
||||
const dist = pPos.distanceTo(lightPos);
|
||||
|
||||
if (dist < lightDist) {
|
||||
// Check angle
|
||||
// Vector from light to particle
|
||||
const toPart = pPos.sub(lightPos).normalize();
|
||||
const angle = toPart.angleTo(lightDir);
|
||||
|
||||
if (angle < lightAngle) {
|
||||
// Inside cone!
|
||||
// Fade out at edges of cone? optional
|
||||
// Fade out with distance
|
||||
const atten = 1.0 - (dist / lightDist);
|
||||
brightness = atten * atten; // clearer near camera
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Apply color (White * brightness)
|
||||
colors[i * 3] = brightness;
|
||||
colors[i * 3 + 1] = brightness;
|
||||
colors[i * 3 + 2] = brightness;
|
||||
}
|
||||
|
||||
this.dustParticles.geometry.attributes.position.needsUpdate = true;
|
||||
this.dustParticles.geometry.attributes.color.needsUpdate = true;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user