| |
| |
|
|
| 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; |