Spaces:
Running
Running
gunship999
commited on
Commit
β’
070a756
1
Parent(s):
5326e18
Update game.js
Browse files
game.js
CHANGED
@@ -5,9 +5,9 @@ import { PointerLockControls } from 'three/addons/controls/PointerLockControls.j
|
|
5 |
// κ²μ μμ
|
6 |
const GAME_DURATION = 180;
|
7 |
const MAP_SIZE = 2000;
|
8 |
-
const HELICOPTER_HEIGHT =
|
9 |
-
const ENEMY_GROUND_HEIGHT =
|
10 |
-
const ENEMY_SCALE =
|
11 |
const MAX_HEALTH = 1000;
|
12 |
const ENEMY_MOVE_SPEED = 0.1;
|
13 |
const ENEMY_MODELS = [
|
@@ -33,10 +33,66 @@ let currentStage = 1;
|
|
33 |
let isGameOver = false;
|
34 |
let lastTime = performance.now();
|
35 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
36 |
// μ¬μ΄λ μμ€ν
|
37 |
class SoundSystem {
|
38 |
constructor() {
|
39 |
this.audioContext = new (window.AudioContext || window.webkitAudioContext)();
|
|
|
40 |
this.buffers = new Map();
|
41 |
this.pools = new Map();
|
42 |
}
|
@@ -66,6 +122,10 @@ class SoundSystem {
|
|
66 |
this.pools.set(name, pool);
|
67 |
}
|
68 |
|
|
|
|
|
|
|
|
|
69 |
play(name) {
|
70 |
const pool = this.pools.get(name);
|
71 |
const buffer = this.buffers.get(name);
|
@@ -105,7 +165,6 @@ const moveState = {
|
|
105 |
|
106 |
async function initSounds() {
|
107 |
try {
|
108 |
-
await soundSystem.loadSound('gunshot', 'gun.wav');
|
109 |
await soundSystem.loadSound('explosion', 'explosion.wav');
|
110 |
console.log('All sounds loaded successfully');
|
111 |
} catch (error) {
|
@@ -169,72 +228,21 @@ async function init() {
|
|
169 |
}
|
170 |
}
|
171 |
|
172 |
-
|
173 |
-
|
174 |
-
|
175 |
-
|
|
|
176 |
|
177 |
-
|
178 |
-
|
179 |
-
|
180 |
-
|
181 |
-
|
182 |
-
} catch (error) {
|
183 |
-
console.error('Test model loading failed:', error);
|
184 |
-
console.error('Test path was:', testPath);
|
185 |
-
}
|
186 |
-
}
|
187 |
-
|
188 |
-
function createTerrain() {
|
189 |
-
return new Promise((resolve) => {
|
190 |
-
const geometry = new THREE.PlaneGeometry(MAP_SIZE, MAP_SIZE, 200, 200);
|
191 |
-
const material = new THREE.MeshStandardMaterial({
|
192 |
-
color: 0xD2B48C,
|
193 |
-
roughness: 0.8,
|
194 |
-
metalness: 0.2
|
195 |
-
});
|
196 |
-
|
197 |
-
const vertices = geometry.attributes.position.array;
|
198 |
-
for (let i = 0; i < vertices.length; i += 3) {
|
199 |
-
vertices[i + 2] = Math.sin(vertices[i] * 0.01) * Math.cos(vertices[i + 1] * 0.01) * 20;
|
200 |
}
|
201 |
-
|
202 |
-
geometry.attributes.position.needsUpdate = true;
|
203 |
-
geometry.computeVertexNormals();
|
204 |
-
|
205 |
-
const terrain = new THREE.Mesh(geometry, material);
|
206 |
-
terrain.rotation.x = -Math.PI / 2;
|
207 |
-
terrain.receiveShadow = true;
|
208 |
-
scene.add(terrain);
|
209 |
-
|
210 |
-
addObstacles();
|
211 |
-
resolve();
|
212 |
-
});
|
213 |
-
}
|
214 |
-
|
215 |
-
function addObstacles() {
|
216 |
-
const rockGeometry = new THREE.DodecahedronGeometry(10);
|
217 |
-
const rockMaterial = new THREE.MeshStandardMaterial({
|
218 |
-
color: 0x8B4513,
|
219 |
-
roughness: 0.9
|
220 |
});
|
221 |
-
|
222 |
-
|
223 |
-
const rock = new THREE.Mesh(rockGeometry, rockMaterial);
|
224 |
-
rock.position.set(
|
225 |
-
(Math.random() - 0.5) * MAP_SIZE * 0.9,
|
226 |
-
Math.random() * 10,
|
227 |
-
(Math.random() - 0.5) * MAP_SIZE * 0.9
|
228 |
-
);
|
229 |
-
rock.rotation.set(
|
230 |
-
Math.random() * Math.PI,
|
231 |
-
Math.random() * Math.PI,
|
232 |
-
Math.random() * Math.PI
|
233 |
-
);
|
234 |
-
rock.castShadow = true;
|
235 |
-
rock.receiveShadow = true;
|
236 |
-
scene.add(rock);
|
237 |
-
}
|
238 |
}
|
239 |
|
240 |
async function loadEnemies() {
|
@@ -259,7 +267,9 @@ async function loadEnemies() {
|
|
259 |
const tempMaterial = new THREE.MeshPhongMaterial({
|
260 |
color: 0xff0000,
|
261 |
transparent: true,
|
262 |
-
opacity: 0.8
|
|
|
|
|
263 |
});
|
264 |
const tempEnemy = new THREE.Mesh(tempGeometry, tempMaterial);
|
265 |
tempEnemy.position.copy(position);
|
@@ -267,6 +277,11 @@ async function loadEnemies() {
|
|
267 |
tempEnemy.receiveShadow = true;
|
268 |
scene.add(tempEnemy);
|
269 |
|
|
|
|
|
|
|
|
|
|
|
270 |
const enemy = {
|
271 |
model: tempEnemy,
|
272 |
health: 100,
|
@@ -293,9 +308,19 @@ async function loadEnemies() {
|
|
293 |
node.receiveShadow = true;
|
294 |
node.material.metalness = 0.2;
|
295 |
node.material.roughness = 0.8;
|
|
|
|
|
296 |
}
|
297 |
});
|
298 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
299 |
scene.remove(tempEnemy);
|
300 |
scene.add(model);
|
301 |
enemy.model = model;
|
@@ -321,6 +346,58 @@ async function loadEnemies() {
|
|
321 |
}
|
322 |
}
|
323 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
324 |
function createExplosion(position) {
|
325 |
const particleCount = 30;
|
326 |
const particles = [];
|
@@ -331,7 +408,9 @@ function createExplosion(position) {
|
|
331 |
new THREE.MeshBasicMaterial({
|
332 |
color: 0xff4400,
|
333 |
transparent: true,
|
334 |
-
opacity: 1
|
|
|
|
|
335 |
})
|
336 |
);
|
337 |
|
@@ -344,6 +423,12 @@ function createExplosion(position) {
|
|
344 |
|
345 |
particles.push(particle);
|
346 |
scene.add(particle);
|
|
|
|
|
|
|
|
|
|
|
|
|
347 |
}
|
348 |
|
349 |
function animateExplosion() {
|
@@ -370,7 +455,6 @@ function createExplosion(position) {
|
|
370 |
function onClick() {
|
371 |
if (!controls.isLocked) {
|
372 |
controls.lock();
|
373 |
-
// μ¬μ΄λ 컨ν
μ€νΈ μμ (λ§μ λΈλΌμ°μ μμ νμ)
|
374 |
soundSystem.audioContext.resume();
|
375 |
} else if (ammo > 0) {
|
376 |
shoot();
|
@@ -411,282 +495,11 @@ function shoot() {
|
|
411 |
const bullet = createBullet();
|
412 |
bullets.push(bullet);
|
413 |
|
414 |
-
soundSystem.
|
415 |
-
|
416 |
-
|
417 |
-
|
418 |
-
|
419 |
-
|
420 |
-
|
421 |
-
|
422 |
-
emissiveIntensity: 1
|
423 |
-
});
|
424 |
-
const bullet = new THREE.Mesh(bulletGeometry, bulletMaterial);
|
425 |
-
|
426 |
-
bullet.position.copy(camera.position);
|
427 |
-
const direction = new THREE.Vector3();
|
428 |
-
camera.getWorldDirection(direction);
|
429 |
-
bullet.velocity = direction.multiplyScalar(3);
|
430 |
-
|
431 |
-
scene.add(bullet);
|
432 |
-
return bullet;
|
433 |
-
}
|
434 |
-
|
435 |
-
function createEnemyBullet(enemy) {
|
436 |
-
const bulletGeometry = new THREE.SphereGeometry(0.5);
|
437 |
-
const bulletMaterial = new THREE.MeshBasicMaterial({
|
438 |
-
color: 0xff0000,
|
439 |
-
emissive: 0xff0000,
|
440 |
-
emissiveIntensity: 1
|
441 |
-
});
|
442 |
-
const bullet = new THREE.Mesh(bulletGeometry, bulletMaterial);
|
443 |
-
|
444 |
-
bullet.position.copy(enemy.model.position);
|
445 |
-
bullet.position.y += 2; // μ΄κ΅¬ λμ΄ μ‘°μ
|
446 |
-
|
447 |
-
const direction = new THREE.Vector3();
|
448 |
-
direction.subVectors(camera.position, enemy.model.position).normalize();
|
449 |
-
bullet.velocity = direction.multiplyScalar(ENEMY_CONFIG.BULLET_SPEED);
|
450 |
-
|
451 |
-
scene.add(bullet);
|
452 |
-
return bullet;
|
453 |
-
}
|
454 |
-
|
455 |
-
function reload() {
|
456 |
-
ammo = 30;
|
457 |
-
updateAmmoDisplay();
|
458 |
-
}
|
459 |
-
|
460 |
-
function updateAmmoDisplay() {
|
461 |
-
document.getElementById('ammo').textContent = `Ammo: ${ammo}/30`;
|
462 |
-
}
|
463 |
-
|
464 |
-
function updateHealthBar() {
|
465 |
-
const healthElement = document.getElementById('health');
|
466 |
-
const healthPercentage = (playerHealth / MAX_HEALTH) * 100;
|
467 |
-
healthElement.style.width = `${healthPercentage}%`;
|
468 |
-
}
|
469 |
-
|
470 |
-
function updateHelicopterHUD() {
|
471 |
-
const altitude = Math.round(camera.position.y);
|
472 |
-
document.querySelector('#altitude-indicator span').textContent = altitude;
|
473 |
-
|
474 |
-
const speed = Math.round(
|
475 |
-
Math.sqrt(
|
476 |
-
moveState.forward * moveState.forward +
|
477 |
-
moveState.right * moveState.right
|
478 |
-
) * 100
|
479 |
-
);
|
480 |
-
document.querySelector('#speed-indicator span').textContent = speed;
|
481 |
-
|
482 |
-
const heading = Math.round(
|
483 |
-
(camera.rotation.y * (180 / Math.PI) + 360) % 360
|
484 |
-
);
|
485 |
-
document.querySelector('#compass span').textContent = heading;
|
486 |
-
|
487 |
-
updateRadar();
|
488 |
-
}
|
489 |
-
|
490 |
-
function updateRadar() {
|
491 |
-
const radarTargets = document.querySelector('.radar-targets');
|
492 |
-
radarTargets.innerHTML = '';
|
493 |
-
|
494 |
-
enemies.forEach(enemy => {
|
495 |
-
if (!enemy || !enemy.model) return;
|
496 |
-
|
497 |
-
const relativePos = enemy.model.position.clone().sub(camera.position);
|
498 |
-
const distance = relativePos.length();
|
499 |
-
|
500 |
-
if (distance < 500) {
|
501 |
-
// νλ μ΄μ΄μ λ°©ν₯μ κΈ°μ€μΌλ‘ μλμ μΈ κ°λ κ³μ°
|
502 |
-
const playerAngle = camera.rotation.y;
|
503 |
-
const enemyAngle = Math.atan2(relativePos.x, relativePos.z);
|
504 |
-
const relativeAngle = enemyAngle - playerAngle;
|
505 |
-
|
506 |
-
const normalizedDistance = distance / 500;
|
507 |
-
|
508 |
-
const dot = document.createElement('div');
|
509 |
-
dot.className = 'radar-dot';
|
510 |
-
dot.style.left = `${50 + Math.sin(relativeAngle) * normalizedDistance * 45}%`;
|
511 |
-
dot.style.top = `${50 + Math.cos(relativeAngle) * normalizedDistance * 45}%`;
|
512 |
-
radarTargets.appendChild(dot);
|
513 |
-
}
|
514 |
-
});
|
515 |
-
}
|
516 |
-
|
517 |
-
function updateMovement() {
|
518 |
-
if (controls.isLocked) {
|
519 |
-
const speed = 2.0;
|
520 |
-
if (moveState.forward) controls.moveForward(speed);
|
521 |
-
if (moveState.backward) controls.moveForward(-speed);
|
522 |
-
if (moveState.left) controls.moveRight(-speed);
|
523 |
-
if (moveState.right) controls.moveRight(speed);
|
524 |
-
|
525 |
-
// μ΅μ κ³ λ μ μ§
|
526 |
-
if (camera.position.y < HELICOPTER_HEIGHT) {
|
527 |
-
camera.position.y = HELICOPTER_HEIGHT;
|
528 |
-
}
|
529 |
-
}
|
530 |
-
}
|
531 |
-
|
532 |
-
function updateBullets() {
|
533 |
-
for (let i = bullets.length - 1; i >= 0; i--) {
|
534 |
-
if (!bullets[i]) continue;
|
535 |
-
|
536 |
-
bullets[i].position.add(bullets[i].velocity);
|
537 |
-
|
538 |
-
enemies.forEach(enemy => {
|
539 |
-
if (!enemy || !enemy.model) return;
|
540 |
-
|
541 |
-
if (bullets[i] && bullets[i].position.distanceTo(enemy.model.position) < 5) {
|
542 |
-
scene.remove(bullets[i]);
|
543 |
-
bullets.splice(i, 1);
|
544 |
-
enemy.health -= 25;
|
545 |
-
|
546 |
-
// νΌκ²© ν¨κ³Ό
|
547 |
-
createExplosion(enemy.model.position.clone());
|
548 |
-
|
549 |
-
if (enemy.health <= 0) {
|
550 |
-
// νκ΄΄ ν¨κ³Ό
|
551 |
-
createExplosion(enemy.model.position.clone());
|
552 |
-
createExplosion(enemy.model.position.clone().add(new THREE.Vector3(2, 0, 2)));
|
553 |
-
createExplosion(enemy.model.position.clone().add(new THREE.Vector3(-2, 0, -2)));
|
554 |
-
scene.remove(enemy.model);
|
555 |
-
enemies = enemies.filter(e => e !== enemy);
|
556 |
-
console.log('Enemy destroyed, remaining enemies:', enemies.length);
|
557 |
-
}
|
558 |
-
}
|
559 |
-
});
|
560 |
-
|
561 |
-
if (bullets[i] && bullets[i].position.distanceTo(camera.position) > 1000) {
|
562 |
-
scene.remove(bullets[i]);
|
563 |
-
bullets.splice(i, 1);
|
564 |
-
}
|
565 |
-
}
|
566 |
-
}
|
567 |
-
|
568 |
-
function updateEnemyBullets() {
|
569 |
-
for (let i = enemyBullets.length - 1; i >= 0; i--) {
|
570 |
-
if (!enemyBullets[i]) continue;
|
571 |
-
|
572 |
-
enemyBullets[i].position.add(enemyBullets[i].velocity);
|
573 |
-
|
574 |
-
if (enemyBullets[i].position.distanceTo(camera.position) < 3) {
|
575 |
-
playerHealth -= 10;
|
576 |
-
updateHealthBar();
|
577 |
-
scene.remove(enemyBullets[i]);
|
578 |
-
enemyBullets.splice(i, 1);
|
579 |
-
|
580 |
-
if (playerHealth <= 0) {
|
581 |
-
gameOver(false);
|
582 |
-
}
|
583 |
-
continue;
|
584 |
-
}
|
585 |
-
|
586 |
-
if (enemyBullets[i].position.distanceTo(camera.position) > 1000) {
|
587 |
-
scene.remove(enemyBullets[i]);
|
588 |
-
enemyBullets.splice(i, 1);
|
589 |
-
}
|
590 |
-
}
|
591 |
-
}
|
592 |
-
|
593 |
-
function updateEnemies() {
|
594 |
-
const currentTime = Date.now();
|
595 |
-
|
596 |
-
enemies.forEach(enemy => {
|
597 |
-
if (!enemy || !enemy.model) {
|
598 |
-
console.warn('Invalid enemy detected');
|
599 |
-
return;
|
600 |
-
}
|
601 |
-
|
602 |
-
// μ μ νμ¬ μμΉμμ νλ μ΄μ΄ λ°©ν₯μΌλ‘ ν₯νλ λ²‘ν° κ³μ°
|
603 |
-
const direction = new THREE.Vector3();
|
604 |
-
direction.subVectors(camera.position, enemy.model.position);
|
605 |
-
direction.y = 0; // yμΆ μ΄λ μ ν
|
606 |
-
direction.normalize();
|
607 |
-
|
608 |
-
// μλ‘μ΄ μμΉ κ³μ°
|
609 |
-
const newPosition = enemy.model.position.clone()
|
610 |
-
.add(direction.multiplyScalar(enemy.speed));
|
611 |
-
newPosition.y = ENEMY_GROUND_HEIGHT;
|
612 |
-
enemy.model.position.copy(newPosition);
|
613 |
-
|
614 |
-
// μ μ΄ νλ μ΄μ΄λ₯Ό λ°λΌλ³΄λλ‘ μ€μ
|
615 |
-
enemy.model.lookAt(new THREE.Vector3(
|
616 |
-
camera.position.x,
|
617 |
-
enemy.model.position.y,
|
618 |
-
camera.position.z
|
619 |
-
));
|
620 |
-
|
621 |
-
// 곡격 λ‘μ§
|
622 |
-
const distanceToPlayer = enemy.model.position.distanceTo(camera.position);
|
623 |
-
if (distanceToPlayer < ENEMY_CONFIG.ATTACK_RANGE &&
|
624 |
-
currentTime - enemy.lastAttackTime > ENEMY_CONFIG.ATTACK_INTERVAL) {
|
625 |
-
|
626 |
-
console.log('Enemy attacking!');
|
627 |
-
enemyBullets.push(createEnemyBullet(enemy));
|
628 |
-
enemy.lastAttackTime = currentTime;
|
629 |
-
}
|
630 |
-
});
|
631 |
-
}
|
632 |
-
|
633 |
-
function checkGameStatus() {
|
634 |
-
if (enemies.length === 0 && currentStage < 5) {
|
635 |
-
currentStage++;
|
636 |
-
document.getElementById('stage').style.display = 'block';
|
637 |
-
document.getElementById('stage').textContent = `Stage ${currentStage}`;
|
638 |
-
setTimeout(() => {
|
639 |
-
document.getElementById('stage').style.display = 'none';
|
640 |
-
loadEnemies();
|
641 |
-
}, 2000);
|
642 |
-
}
|
643 |
-
}
|
644 |
-
|
645 |
-
function gameOver(won) {
|
646 |
-
isGameOver = true;
|
647 |
-
controls.unlock();
|
648 |
-
soundSystem.audioContext.suspend();
|
649 |
-
alert(won ? 'Mission Complete!' : 'Game Over!');
|
650 |
-
location.reload();
|
651 |
-
}
|
652 |
-
|
653 |
-
function gameLoop() {
|
654 |
-
requestAnimationFrame(gameLoop);
|
655 |
-
|
656 |
-
const time = performance.now();
|
657 |
-
const delta = (time - lastTime) / 1000;
|
658 |
-
lastTime = time;
|
659 |
-
|
660 |
-
if (controls.isLocked && !isGameOver) {
|
661 |
-
updateMovement();
|
662 |
-
updateBullets();
|
663 |
-
updateEnemies();
|
664 |
-
updateEnemyBullets();
|
665 |
-
updateHelicopterHUD();
|
666 |
-
checkGameStatus();
|
667 |
-
}
|
668 |
-
|
669 |
-
renderer.render(scene, camera);
|
670 |
-
}
|
671 |
-
|
672 |
-
// κ²μ μμ
|
673 |
-
window.addEventListener('load', async () => {
|
674 |
-
try {
|
675 |
-
await init();
|
676 |
-
console.log('Game started');
|
677 |
-
console.log('Active enemies:', enemies.length);
|
678 |
-
gameLoop();
|
679 |
-
} catch (error) {
|
680 |
-
console.error('Game initialization error:', error);
|
681 |
-
alert('Error loading game. Please check console for details.');
|
682 |
-
}
|
683 |
-
});
|
684 |
-
|
685 |
-
// λλ²κΉ
μ μν μ μ μ κ·Ό
|
686 |
-
window.debugGame = {
|
687 |
-
scene,
|
688 |
-
camera,
|
689 |
-
enemies,
|
690 |
-
soundSystem,
|
691 |
-
reloadEnemies: loadEnemies
|
692 |
-
};
|
|
|
5 |
// κ²μ μμ
|
6 |
const GAME_DURATION = 180;
|
7 |
const MAP_SIZE = 2000;
|
8 |
+
const HELICOPTER_HEIGHT = 30; // 30mλ‘ μ‘°μ
|
9 |
+
const ENEMY_GROUND_HEIGHT = 0;
|
10 |
+
const ENEMY_SCALE = 10; // ν¬κΈ° λν μ¦κ°
|
11 |
const MAX_HEALTH = 1000;
|
12 |
const ENEMY_MOVE_SPEED = 0.1;
|
13 |
const ENEMY_MODELS = [
|
|
|
33 |
let isGameOver = false;
|
34 |
let lastTime = performance.now();
|
35 |
|
36 |
+
// μ΄μ리 μμ±κΈ° ν΄λμ€
|
37 |
+
class GunSoundGenerator {
|
38 |
+
constructor(audioContext) {
|
39 |
+
this.audioContext = audioContext;
|
40 |
+
}
|
41 |
+
|
42 |
+
createGunshot() {
|
43 |
+
// λ©μΈ μ€μ€λ μ΄ν° (μ΄μ리μ μ£Όμ μ£Όνμ)
|
44 |
+
const mainOsc = this.audioContext.createOscillator();
|
45 |
+
mainOsc.type = 'square';
|
46 |
+
mainOsc.frequency.setValueAtTime(100, this.audioContext.currentTime);
|
47 |
+
mainOsc.frequency.exponentialRampToValueAtTime(1, this.audioContext.currentTime + 0.1);
|
48 |
+
|
49 |
+
// λ
Έμ΄μ¦ μμ±
|
50 |
+
const noiseBuffer = this.createNoiseBuffer();
|
51 |
+
const noise = this.audioContext.createBufferSource();
|
52 |
+
noise.buffer = noiseBuffer;
|
53 |
+
|
54 |
+
// κ²μΈ λ
Έλλ€
|
55 |
+
const mainGain = this.audioContext.createGain();
|
56 |
+
const noiseGain = this.audioContext.createGain();
|
57 |
+
|
58 |
+
// λ³Όλ₯¨ μ벨λ‘ν μ€μ
|
59 |
+
mainGain.gain.setValueAtTime(1, this.audioContext.currentTime);
|
60 |
+
mainGain.gain.exponentialRampToValueAtTime(0.01, this.audioContext.currentTime + 0.1);
|
61 |
+
|
62 |
+
noiseGain.gain.setValueAtTime(1, this.audioContext.currentTime);
|
63 |
+
noiseGain.gain.exponentialRampToValueAtTime(0.01, this.audioContext.currentTime + 0.05);
|
64 |
+
|
65 |
+
// μ°κ²°
|
66 |
+
mainOsc.connect(mainGain);
|
67 |
+
noise.connect(noiseGain);
|
68 |
+
mainGain.connect(this.audioContext.destination);
|
69 |
+
noiseGain.connect(this.audioContext.destination);
|
70 |
+
|
71 |
+
// μ¬μ
|
72 |
+
mainOsc.start();
|
73 |
+
noise.start();
|
74 |
+
mainOsc.stop(this.audioContext.currentTime + 0.1);
|
75 |
+
noise.stop(this.audioContext.currentTime + 0.1);
|
76 |
+
}
|
77 |
+
|
78 |
+
createNoiseBuffer() {
|
79 |
+
const bufferSize = this.audioContext.sampleRate * 0.1;
|
80 |
+
const buffer = this.audioContext.createBuffer(1, bufferSize, this.audioContext.sampleRate);
|
81 |
+
const output = buffer.getChannelData(0);
|
82 |
+
|
83 |
+
for (let i = 0; i < bufferSize; i++) {
|
84 |
+
output[i] = Math.random() * 2 - 1;
|
85 |
+
}
|
86 |
+
|
87 |
+
return buffer;
|
88 |
+
}
|
89 |
+
}
|
90 |
+
|
91 |
// μ¬μ΄λ μμ€ν
|
92 |
class SoundSystem {
|
93 |
constructor() {
|
94 |
this.audioContext = new (window.AudioContext || window.webkitAudioContext)();
|
95 |
+
this.gunSoundGenerator = new GunSoundGenerator(this.audioContext);
|
96 |
this.buffers = new Map();
|
97 |
this.pools = new Map();
|
98 |
}
|
|
|
122 |
this.pools.set(name, pool);
|
123 |
}
|
124 |
|
125 |
+
playGunshot() {
|
126 |
+
this.gunSoundGenerator.createGunshot();
|
127 |
+
}
|
128 |
+
|
129 |
play(name) {
|
130 |
const pool = this.pools.get(name);
|
131 |
const buffer = this.buffers.get(name);
|
|
|
165 |
|
166 |
async function initSounds() {
|
167 |
try {
|
|
|
168 |
await soundSystem.loadSound('explosion', 'explosion.wav');
|
169 |
console.log('All sounds loaded successfully');
|
170 |
} catch (error) {
|
|
|
228 |
}
|
229 |
}
|
230 |
|
231 |
+
function createEnemyOutline(model) {
|
232 |
+
const outlineMaterial = new THREE.MeshBasicMaterial({
|
233 |
+
color: 0xff0000,
|
234 |
+
side: THREE.BackSide
|
235 |
+
});
|
236 |
|
237 |
+
const outlineMesh = model.clone();
|
238 |
+
outlineMesh.traverse((node) => {
|
239 |
+
if (node.isMesh) {
|
240 |
+
node.material = outlineMaterial;
|
241 |
+
node.scale.multiplyScalar(1.05);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
242 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
243 |
});
|
244 |
+
|
245 |
+
model.add(outlineMesh);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
246 |
}
|
247 |
|
248 |
async function loadEnemies() {
|
|
|
267 |
const tempMaterial = new THREE.MeshPhongMaterial({
|
268 |
color: 0xff0000,
|
269 |
transparent: true,
|
270 |
+
opacity: 0.8,
|
271 |
+
emissive: 0xff0000,
|
272 |
+
emissiveIntensity: 0.5
|
273 |
});
|
274 |
const tempEnemy = new THREE.Mesh(tempGeometry, tempMaterial);
|
275 |
tempEnemy.position.copy(position);
|
|
|
277 |
tempEnemy.receiveShadow = true;
|
278 |
scene.add(tempEnemy);
|
279 |
|
280 |
+
// μμ μ μ λΉ μΆκ°
|
281 |
+
const tempLight = new THREE.PointLight(0xff0000, 1, 20);
|
282 |
+
tempLight.position.set(0, 5, 0);
|
283 |
+
tempEnemy.add(tempLight);
|
284 |
+
|
285 |
const enemy = {
|
286 |
model: tempEnemy,
|
287 |
health: 100,
|
|
|
308 |
node.receiveShadow = true;
|
309 |
node.material.metalness = 0.2;
|
310 |
node.material.roughness = 0.8;
|
311 |
+
node.material.emissive = new THREE.Color(0x444444);
|
312 |
+
node.material.emissiveIntensity = 0.5;
|
313 |
}
|
314 |
});
|
315 |
|
316 |
+
// μμλΌμΈ ν¨κ³Ό μΆκ°
|
317 |
+
createEnemyOutline(model);
|
318 |
+
|
319 |
+
// μ μ£Όλ³μ λΉ μΆκ°
|
320 |
+
const enemyLight = new THREE.PointLight(0xff0000, 1, 20);
|
321 |
+
enemyLight.position.set(0, 5, 0);
|
322 |
+
model.add(enemyLight);
|
323 |
+
|
324 |
scene.remove(tempEnemy);
|
325 |
scene.add(model);
|
326 |
enemy.model = model;
|
|
|
346 |
}
|
347 |
}
|
348 |
|
349 |
+
function createTerrain() {
|
350 |
+
return new Promise((resolve) => {
|
351 |
+
const geometry = new THREE.PlaneGeometry(MAP_SIZE, MAP_SIZE, 200, 200);
|
352 |
+
const material = new THREE.MeshStandardMaterial({
|
353 |
+
color: 0xD2B48C,
|
354 |
+
roughness: 0.8,
|
355 |
+
metalness: 0.2
|
356 |
+
});
|
357 |
+
|
358 |
+
const vertices = geometry.attributes.position.array;
|
359 |
+
for (let i = 0; i < vertices.length; i += 3) {
|
360 |
+
vertices[i + 2] = Math.sin(vertices[i] * 0.01) * Math.cos(vertices[i + 1] * 0.01) * 20;
|
361 |
+
}
|
362 |
+
|
363 |
+
geometry.attributes.position.needsUpdate = true;
|
364 |
+
geometry.computeVertexNormals();
|
365 |
+
|
366 |
+
const terrain = new THREE.Mesh(geometry, material);
|
367 |
+
terrain.rotation.x = -Math.PI / 2;
|
368 |
+
terrain.receiveShadow = true;
|
369 |
+
scene.add(terrain);
|
370 |
+
|
371 |
+
addObstacles();
|
372 |
+
resolve();
|
373 |
+
});
|
374 |
+
}
|
375 |
+
|
376 |
+
function addObstacles() {
|
377 |
+
const rockGeometry = new THREE.DodecahedronGeometry(10);
|
378 |
+
const rockMaterial = new THREE.MeshStandardMaterial({
|
379 |
+
color: 0x8B4513,
|
380 |
+
roughness: 0.9
|
381 |
+
});
|
382 |
+
|
383 |
+
for (let i = 0; i < 100; i++) {
|
384 |
+
const rock = new THREE.Mesh(rockGeometry, rockMaterial);
|
385 |
+
rock.position.set(
|
386 |
+
(Math.random() - 0.5) * MAP_SIZE * 0.9,
|
387 |
+
Math.random() * 10,
|
388 |
+
(Math.random() - 0.5) * MAP_SIZE * 0.9
|
389 |
+
);
|
390 |
+
rock.rotation.set(
|
391 |
+
Math.random() * Math.PI,
|
392 |
+
Math.random() * Math.PI,
|
393 |
+
Math.random() * Math.PI
|
394 |
+
);
|
395 |
+
rock.castShadow = true;
|
396 |
+
rock.receiveShadow = true;
|
397 |
+
scene.add(rock);
|
398 |
+
}
|
399 |
+
}
|
400 |
+
|
401 |
function createExplosion(position) {
|
402 |
const particleCount = 30;
|
403 |
const particles = [];
|
|
|
408 |
new THREE.MeshBasicMaterial({
|
409 |
color: 0xff4400,
|
410 |
transparent: true,
|
411 |
+
opacity: 1,
|
412 |
+
emissive: 0xff4400,
|
413 |
+
emissiveIntensity: 1
|
414 |
})
|
415 |
);
|
416 |
|
|
|
423 |
|
424 |
particles.push(particle);
|
425 |
scene.add(particle);
|
426 |
+
|
427 |
+
// νλ° μ§μ μ μΌμμ μΈ λΉ μΆκ°
|
428 |
+
const explosionLight = new THREE.PointLight(0xff4400, 2, 20);
|
429 |
+
explosionLight.position.copy(position);
|
430 |
+
scene.add(explosionLight);
|
431 |
+
setTimeout(() => scene.remove(explosionLight), 100);
|
432 |
}
|
433 |
|
434 |
function animateExplosion() {
|
|
|
455 |
function onClick() {
|
456 |
if (!controls.isLocked) {
|
457 |
controls.lock();
|
|
|
458 |
soundSystem.audioContext.resume();
|
459 |
} else if (ammo > 0) {
|
460 |
shoot();
|
|
|
495 |
const bullet = createBullet();
|
496 |
bullets.push(bullet);
|
497 |
|
498 |
+
soundSystem.playGunshot();
|
499 |
+
|
500 |
+
// μ΄κ΅¬ νμΌ ν¨κ³Ό
|
501 |
+
const muzzleFlash = new THREE.PointLight(0xffff00, 3, 10);
|
502 |
+
muzzleFlash.position.copy(camera.position);
|
503 |
+
scene.add(muzzleFlash);
|
504 |
+
setTimeout(() => scene.remove(muzzleFlash), 50);
|
505 |
+
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|