|
|
|
|
|
|
|
class Background { |
|
constructor() { |
|
|
|
this.scene = new THREE.Scene(); |
|
this.camera = new THREE.PerspectiveCamera( |
|
75, |
|
window.innerWidth / window.innerHeight, |
|
0.1, |
|
1000 |
|
); |
|
|
|
|
|
this.renderer = new THREE.WebGLRenderer({ |
|
canvas: document.querySelector('#webgl-background'), |
|
alpha: true, |
|
antialias: true |
|
}); |
|
this.renderer.setSize(window.innerWidth, window.innerHeight); |
|
this.renderer.setClearColor(0x0a0a0a, 1); |
|
|
|
|
|
this.particles = []; |
|
this.particleCount = 100; |
|
this.particleGeometry = new THREE.BufferGeometry(); |
|
this.particleMaterial = new THREE.PointsMaterial({ |
|
size: 2, |
|
color: 0xffffff, |
|
transparent: true, |
|
opacity: 0.5, |
|
blending: THREE.AdditiveBlending |
|
}); |
|
|
|
|
|
this.camera.position.z = 100; |
|
|
|
|
|
this.init(); |
|
|
|
|
|
this.bindEvents(); |
|
} |
|
|
|
init() { |
|
|
|
const positions = new Float32Array(this.particleCount * 3); |
|
|
|
|
|
for (let i = 0; i < this.particleCount; i++) { |
|
const i3 = i * 3; |
|
positions[i3] = (Math.random() - 0.5) * window.innerWidth; |
|
positions[i3 + 1] = (Math.random() - 0.5) * window.innerHeight; |
|
positions[i3 + 2] = (Math.random() - 0.5) * 500; |
|
|
|
|
|
this.particles.push({ |
|
velocity: (Math.random() - 0.5) * 0.2, |
|
baseX: positions[i3], |
|
baseY: positions[i3 + 1] |
|
}); |
|
} |
|
|
|
|
|
this.particleGeometry.setAttribute( |
|
'position', |
|
new THREE.BufferAttribute(positions, 3) |
|
); |
|
|
|
|
|
this.particleSystem = new THREE.Points( |
|
this.particleGeometry, |
|
this.particleMaterial |
|
); |
|
this.scene.add(this.particleSystem); |
|
|
|
|
|
this.animate(); |
|
} |
|
|
|
|
|
bindEvents() { |
|
window.addEventListener('resize', () => { |
|
|
|
this.camera.aspect = window.innerWidth / window.innerHeight; |
|
this.camera.updateProjectionMatrix(); |
|
|
|
|
|
this.renderer.setSize(window.innerWidth, window.innerHeight); |
|
}); |
|
} |
|
|
|
|
|
animate() { |
|
requestAnimationFrame(() => this.animate()); |
|
|
|
const positions = this.particleGeometry.attributes.position.array; |
|
const time = Date.now() * 0.0005; |
|
|
|
|
|
for (let i = 0; i < this.particleCount; i++) { |
|
const i3 = i * 3; |
|
const particle = this.particles[i]; |
|
|
|
|
|
positions[i3] = particle.baseX + Math.sin(time + i) * 2; |
|
positions[i3 + 1] = particle.baseY + Math.cos(time + i) * 2; |
|
|
|
|
|
positions[i3 + 2] += particle.velocity; |
|
|
|
|
|
if (Math.abs(positions[i3 + 2]) > 250) { |
|
positions[i3 + 2] = -250; |
|
} |
|
} |
|
|
|
|
|
this.particleGeometry.attributes.position.needsUpdate = true; |
|
|
|
|
|
this.camera.position.x = Math.sin(time) * 10; |
|
this.camera.position.y = Math.cos(time) * 10; |
|
this.camera.lookAt(this.scene.position); |
|
|
|
|
|
this.renderer.render(this.scene, this.camera); |
|
} |
|
|
|
|
|
addDramaticEffect(type) { |
|
switch(type) { |
|
case 'impostor_reveal': |
|
|
|
this.particleMaterial.color.setHex(0xff0000); |
|
setTimeout(() => { |
|
this.particleMaterial.color.setHex(0xffffff); |
|
}, 1000); |
|
break; |
|
|
|
case 'round_start': |
|
|
|
const originalVelocities = this.particles.map(p => p.velocity); |
|
this.particles.forEach(p => p.velocity *= 2); |
|
setTimeout(() => { |
|
this.particles.forEach((p, i) => p.velocity = originalVelocities[i]); |
|
}, 2000); |
|
break; |
|
|
|
case 'voting': |
|
|
|
const pulseAnimation = () => { |
|
this.particleMaterial.size = 2 + Math.sin(Date.now() * 0.005) * 1; |
|
}; |
|
const pulseInterval = setInterval(pulseAnimation, 16); |
|
setTimeout(() => { |
|
clearInterval(pulseInterval); |
|
this.particleMaterial.size = 2; |
|
}, 3000); |
|
break; |
|
} |
|
} |
|
} |
|
|
|
|
|
document.addEventListener('DOMContentLoaded', () => { |
|
const background = new Background(); |
|
|
|
|
|
window.gameBackground = background; |
|
}); |
|
|
|
|
|
export default Background; |