cutechicken commited on
Commit
5efbc2d
ยท
verified ยท
1 Parent(s): dfdd037

Update game.js

Browse files
Files changed (1) hide show
  1. game.js +807 -346
game.js CHANGED
@@ -20,7 +20,12 @@ const GAME_CONSTANTS = {
20
  MISSILE_COUNT: 6,
21
  AMMO_COUNT: 940, // 940๋ฐœ๋กœ ๋ณ€๊ฒฝ
22
  BULLET_DAMAGE: 50, // ๋ฐœ๋‹น 50 ๋ฐ๋ฏธ์ง€
23
- MAX_HEALTH: 1000 // ์ฒด๋ ฅ 1000
 
 
 
 
 
24
  };
25
 
26
  // ์ „ํˆฌ๊ธฐ ํด๋ž˜์Šค
@@ -64,6 +69,19 @@ class Fighter {
64
  this.isMouseDown = false; // ๋งˆ์šฐ์Šค ๋ˆ„๋ฆ„ ์ƒํƒœ ์ถ”์ 
65
  this.gunfireAudios = []; // ๊ธฐ๊ด€์ด ์†Œ๋ฆฌ ๋ฐฐ์—ด (์ตœ๋Œ€ 5๊ฐœ)
66
 
 
 
 
 
 
 
 
 
 
 
 
 
 
67
  // ์Šคํ†จ ํƒˆ์ถœ์„ ์œ„ํ•œ Fํ‚ค ์ƒํƒœ
68
  this.escapeKeyPressed = false;
69
  this.stallEscapeProgress = 0; // ์Šคํ†จ ํƒˆ์ถœ ์ง„ํ–‰๋„ (0~2์ดˆ)
@@ -142,6 +160,19 @@ class Fighter {
142
  }
143
  }
144
 
 
 
 
 
 
 
 
 
 
 
 
 
 
145
  startEngineSound() {
146
  // ์—”์ง„ ์†Œ๋ฆฌ ์‹œ์ž‘
147
  if (this.warningAudios.normal) {
@@ -190,6 +221,12 @@ class Fighter {
190
  audio.currentTime = 0;
191
  }
192
  });
 
 
 
 
 
 
193
  }
194
 
195
  async initialize(scene, loader) {
@@ -252,6 +289,127 @@ class Fighter {
252
 
253
  console.log('Fallback ์ „ํˆฌ๊ธฐ ๋ชจ๋ธ ์ƒ์„ฑ ์™„๋ฃŒ');
254
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
255
 
256
  updateMouseInput(deltaX, deltaY) {
257
  // Over-G ์ƒํƒœ์—์„œ ์Šคํ†จ์ด ํ•ด์ œ๋˜์ง€ ์•Š์•˜์œผ๋ฉด ํ”ผ์น˜ ์กฐ์ž‘ ๋ถˆ๊ฐ€
@@ -649,73 +807,104 @@ class Fighter {
649
  }
650
 
651
  shoot(scene) {
652
- // ํƒ„์•ฝ์ด ์—†์œผ๋ฉด ๋ฐœ์‚ฌํ•˜์ง€ ์•Š์Œ
653
- if (this.ammo <= 0) return;
654
-
655
- this.ammo--;
656
-
657
- // ์ง์„  ๋ชจ์–‘์˜ ํƒ„ํ™˜ (100% ๋” ํฌ๊ฒŒ)
658
- const bulletGeometry = new THREE.CylinderGeometry(1.0, 1.0, 16, 8); // ๋ฐ˜์ง€๋ฆ„ 0.75โ†’1.0, ๊ธธ์ด 12โ†’16
659
- const bulletMaterial = new THREE.MeshBasicMaterial({
660
- color: 0xffff00,
661
- });
662
- const bullet = new THREE.Mesh(bulletGeometry, bulletMaterial);
663
-
664
- // ๊ธฐ์ˆ˜ ๋์—์„œ ๋ฐœ์‚ฌ (์ฟผํ„ฐ๋‹ˆ์–ธ ์‚ฌ์šฉ)
665
- const muzzleOffset = new THREE.Vector3(0, 0, 10);
666
-
667
- // ์ „ํˆฌ๊ธฐ์™€ ๋™์ผํ•œ ์ฟผํ„ฐ๋‹ˆ์–ธ ์ƒ์„ฑ
668
- const quaternion = new THREE.Quaternion();
669
- const pitchQuat = new THREE.Quaternion();
670
- const yawQuat = new THREE.Quaternion();
671
- const rollQuat = new THREE.Quaternion();
672
-
673
- pitchQuat.setFromAxisAngle(new THREE.Vector3(1, 0, 0), this.rotation.x);
674
- yawQuat.setFromAxisAngle(new THREE.Vector3(0, 1, 0), this.rotation.y);
675
- rollQuat.setFromAxisAngle(new THREE.Vector3(0, 0, 1), this.rotation.z);
676
-
677
- quaternion.multiply(rollQuat);
678
- quaternion.multiply(pitchQuat);
679
- quaternion.multiply(yawQuat);
680
-
681
- muzzleOffset.applyQuaternion(quaternion);
682
- bullet.position.copy(this.position).add(muzzleOffset);
683
-
684
- // ํƒ„ํ™˜์„ ๋ฐœ์‚ฌ ๋ฐฉํ–ฅ์œผ๋กœ ํšŒ์ „
685
- bullet.quaternion.copy(quaternion);
686
- // ์‹ค๋ฆฐ๋”๊ฐ€ Z์ถ• ๋ฐฉํ–ฅ์„ ํ–ฅํ•˜๋„๋ก ์ถ”๊ฐ€ ํšŒ์ „
687
- const cylinderRotation = new THREE.Quaternion();
688
- cylinderRotation.setFromAxisAngle(new THREE.Vector3(1, 0, 0), Math.PI / 2);
689
- bullet.quaternion.multiply(cylinderRotation);
690
-
691
- // ํƒ„ํ™˜ ์ดˆ๊ธฐ ์œ„์น˜ ์ €์žฅ
692
- bullet.startPosition = bullet.position.clone();
693
-
694
- const bulletSpeed = 1500; // 1000์—์„œ 1500์œผ๋กœ ์ฆ๊ฐ€ (50% ๋น ๋ฅด๊ฒŒ)
695
- const direction = new THREE.Vector3(0, 0, 1);
696
- direction.applyQuaternion(quaternion);
697
- bullet.velocity = direction.multiplyScalar(bulletSpeed);
698
-
699
- scene.add(bullet);
700
- this.bullets.push(bullet);
701
-
702
- // m134.ogg ์†Œ๋ฆฌ ์žฌ์ƒ - ์ค‘์ฒฉ ์ œํ•œ ํ•ด์ œ, ๋žœ๋ค ํ”ผ์น˜
703
- try {
704
- const audio = new Audio('sounds/m134.ogg');
705
- audio.volume = 0.15; // 0.3์—์„œ 0.15๋กœ ๊ฐ์†Œ (50% ์ค„์ž„)
706
-
707
- // -2 ~ +2 ์‚ฌ์ด์˜ ๋žœ๋ค ํ”ผ์น˜ (playbackRate๋กœ ์‹œ๋ฎฌ๋ ˆ์ด์…˜)
708
- const randomPitch = 0.8 + Math.random() * 0.4; // 0.8 ~ 1.2 ๋ฒ”์œ„
709
- audio.playbackRate = randomPitch;
710
 
711
- audio.play().catch(e => console.log('Gunfire sound failed to play'));
712
 
713
- // ์žฌ์ƒ ์™„๋ฃŒ ์‹œ ๋ฉ”๋ชจ๋ฆฌ ์ •๋ฆฌ๋ฅผ ์œ„ํ•ด ์ฐธ์กฐ ์ œ๊ฑฐ
714
- audio.addEventListener('ended', () => {
715
- audio.remove();
 
716
  });
717
- } catch (e) {
718
- console.log('Audio error:', e);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
719
  }
720
  }
721
 
@@ -745,6 +934,16 @@ class Fighter {
745
  this.bullets.splice(i, 1);
746
  }
747
  }
 
 
 
 
 
 
 
 
 
 
748
  }
749
 
750
  takeDamage(damage) {
@@ -790,6 +989,216 @@ class Fighter {
790
  }
791
  }
792
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
793
  // ์  ์ „ํˆฌ๊ธฐ ํด๋ž˜์Šค - ์™„์ „ํžˆ ์žฌ์„ค๊ณ„
794
  class EnemyFighter {
795
  constructor(scene, position) {
@@ -1402,7 +1811,7 @@ class Game {
1402
  this.bgm = null;
1403
  this.bgmPlaying = false;
1404
 
1405
- this.keys = { w: false, a: false, s: false, d: false, f: false };
1406
  this.isStarted = false;
1407
 
1408
  this.setupScene();
@@ -1634,82 +2043,89 @@ class Game {
1634
  }
1635
 
1636
  setupEventListeners() {
1637
- document.addEventListener('keydown', (event) => {
1638
- if (this.isGameOver) return;
1639
-
1640
- // gameStarted ๋Œ€์‹  this.isStarted ์‚ฌ์šฉ
1641
- if (!this.isStarted) return;
1642
-
1643
- switch(event.code) {
1644
- case 'KeyW':
1645
- this.keys.w = true;
1646
- console.log('W key pressed - Accelerating');
1647
- break;
1648
- case 'KeyA':
1649
- this.keys.a = true;
1650
- console.log('A key pressed - Turning left');
1651
- break;
1652
- case 'KeyS':
1653
- this.keys.s = true;
1654
- console.log('S key pressed - Decelerating');
1655
- break;
1656
- case 'KeyD':
1657
- this.keys.d = true;
1658
- console.log('D key pressed - Turning right');
1659
- break;
1660
- case 'KeyF':
1661
- this.keys.f = true;
1662
- break;
1663
- }
1664
- });
 
 
 
 
 
 
1665
 
1666
- document.addEventListener('keyup', (event) => {
1667
- if (this.isGameOver) return;
1668
-
1669
- // gameStarted ๋Œ€์‹  this.isStarted ์‚ฌ์šฉ
1670
- if (!this.isStarted) return;
1671
-
1672
- switch(event.code) {
1673
- case 'KeyW': this.keys.w = false; break;
1674
- case 'KeyA': this.keys.a = false; break;
1675
- case 'KeyS': this.keys.s = false; break;
1676
- case 'KeyD': this.keys.d = false; break;
1677
- case 'KeyF': this.keys.f = false; break;
1678
- }
1679
- });
 
1680
 
1681
- document.addEventListener('mousemove', (event) => {
1682
- // ์—ฌ๊ธฐ๋„ gameStarted๋ฅผ this.isStarted๋กœ ๋ณ€๊ฒฝ
1683
- if (!document.pointerLockElement || this.isGameOver || !this.isStarted) return;
1684
-
1685
- const deltaX = event.movementX || 0;
1686
- const deltaY = event.movementY || 0;
1687
-
1688
- this.fighter.updateMouseInput(deltaX, deltaY);
1689
- });
1690
 
1691
- document.addEventListener('mousedown', (event) => {
1692
- // ์—ฌ๊ธฐ๋„ gameStarted๋ฅผ this.isStarted๋กœ ๋ณ€๊ฒฝ
1693
- if (!document.pointerLockElement || this.isGameOver || !this.isStarted) return;
1694
-
1695
- if (event.button === 0) {
1696
- this.fighter.isMouseDown = true;
1697
- this.lastShootTime = 0;
1698
- }
1699
- });
1700
 
1701
- document.addEventListener('mouseup', (event) => {
1702
- if (event.button === 0) {
1703
- this.fighter.isMouseDown = false;
1704
- }
1705
- });
1706
 
1707
- window.addEventListener('resize', () => {
1708
- this.camera.aspect = window.innerWidth / window.innerHeight;
1709
- this.camera.updateProjectionMatrix();
1710
- this.renderer.setSize(window.innerWidth, window.innerHeight);
1711
- });
1712
- }
1713
 
1714
  startGame() {
1715
  if (!this.isLoaded) {
@@ -1796,7 +2212,23 @@ class Game {
1796
 
1797
  if (scoreElement) scoreElement.textContent = `Score: ${this.score}`;
1798
  if (timeElement) timeElement.textContent = `Time: ${this.gameTime}s`;
1799
- if (ammoElement) ammoElement.textContent = `20MM MG: ${this.fighter.ammo}`;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1800
 
1801
  if (gameStatsElement) {
1802
  gameStatsElement.innerHTML = `
@@ -1898,8 +2330,21 @@ class Game {
1898
  if (isInCrosshair) {
1899
  marker.classList.add('in-crosshair');
1900
 
1901
- // 2000m ์ด๋‚ด๋ฉด ๋ฝ์˜จ
1902
- if (distance < 2000) {
 
 
 
 
 
 
 
 
 
 
 
 
 
1903
  marker.classList.add('locked');
1904
  }
1905
 
@@ -1912,6 +2357,18 @@ class Game {
1912
  const targetInfo = document.createElement('div');
1913
  targetInfo.className = 'target-info';
1914
  targetInfo.textContent = `${Math.round(distance)}m`;
 
 
 
 
 
 
 
 
 
 
 
 
1915
  marker.appendChild(targetInfo);
1916
  }
1917
 
@@ -2370,146 +2827,147 @@ class Game {
2370
  animateImpact();
2371
 
2372
  // ์ž‘์€ ์ถฉ๋Œ์Œ
2373
- try {
2374
- const impactSound = new Audio('sounds/hit.ogg');
2375
- impactSound.volume = 0.2;
2376
- impactSound.play().catch(e => {
2377
- console.log('Impact sound not found or failed to play');
2378
- });
2379
- } catch (e) {
2380
- console.log('Impact sound error:', e);
2381
- }
2382
- }
2383
- checkCollisions() {
2384
- // ํ”Œ๋ ˆ์ด์–ด์™€ ์ ๊ธฐ์˜ ์ง์ ‘ ์ถฉ๋Œ ์ฒดํฌ (์ตœ์šฐ์„ )
2385
- for (let i = this.enemies.length - 1; i >= 0; i--) {
2386
- const enemy = this.enemies[i];
2387
- if (!enemy.mesh || !enemy.isLoaded) continue;
2388
-
2389
- const distance = this.fighter.position.distanceTo(enemy.position);
2390
- // ์ง์ ‘ ์ถฉ๋Œ ํŒ์ • (80m ์ด๋‚ด)
2391
- if (distance < 80) {
2392
- console.log('Direct collision detected! Distance:', distance);
2393
-
2394
- // ์–‘์ชฝ ๋ชจ๋‘์˜ ์œ„์น˜ ์ €์žฅ (์ถฉ๋Œ ์ค‘๊ฐ„ ์ง€์ )
2395
- const collisionPoint = this.fighter.position.clone().add(enemy.position).divideScalar(2);
2396
- const playerExplosionPos = this.fighter.position.clone();
2397
- const enemyExplosionPos = enemy.position.clone();
2398
-
2399
- // ๋” ํฐ ํญ๋ฐœ์Œ ์žฌ์ƒ (์‹œ๊ฐ ํšจ๊ณผ๋ณด๋‹ค ๋จผ์ €)
2400
- try {
2401
- const collisionSound = new Audio('sounds/bang.ogg');
2402
- collisionSound.volume = 1.0;
2403
- collisionSound.play().catch(e => {});
2404
-
2405
- // ๋‘ ๋ฒˆ์งธ ํญ๋ฐœ์Œ (์•ฝ๊ฐ„์˜ ๋”œ๋ ˆ์ด)
2406
- setTimeout(() => {
2407
- const secondExplosion = new Audio('sounds/bang.ogg');
2408
- secondExplosion.volume = 0.8;
2409
- secondExplosion.play().catch(e => {});
2410
- }, 100);
2411
- } catch (e) {}
2412
-
2413
- // 1. ์ถฉ๋Œ ์ง€์ ์— ํฐ ํญ๋ฐœ ํšจ๊ณผ
2414
- this.createExplosionEffect(collisionPoint);
2415
-
2416
- // 2. ์–‘์ชฝ ์œ„์น˜์—๋„ ํญ๋ฐœ ํšจ๊ณผ
2417
- setTimeout(() => {
2418
- this.createExplosionEffect(playerExplosionPos);
2419
- this.createExplosionEffect(enemyExplosionPos);
2420
- }, 50);
2421
-
2422
- // 3. ์ ๊ธฐ ์ œ๊ฑฐ
2423
- enemy.destroy();
2424
- this.enemies.splice(i, 1);
2425
- this.score += 200; // ์ถฉ๋Œ ํ‚ฌ์€ ๋ณด๋„ˆ์Šค ์ ์ˆ˜
2426
-
2427
- // 4. ํ”Œ๋ ˆ์ด์–ด ํŒŒ๊ดด
2428
- this.fighter.health = 0;
2429
-
2430
- // 5. ๊ฒŒ์ž„ ์ข…๋ฃŒ
2431
- setTimeout(() => {
2432
- this.endGame(false, "COLLISION WITH ENEMY");
2433
- }, 100); // ํญ๋ฐœ ํšจ๊ณผ๊ฐ€ ๋ณด์ด๋„๋ก ์•ฝ๊ฐ„์˜ ๋”œ๋ ˆ์ด
2434
-
2435
- return; // ์ถฉ๋Œ ์ฒ˜๋ฆฌ ํ›„ ์ฆ‰์‹œ ์ข…๋ฃŒ
2436
  }
2437
  }
2438
 
2439
- // ํ”Œ๋ ˆ์ด์–ด ํƒ„ํ™˜ vs ์ ๊ธฐ ์ถฉ๋Œ
2440
- for (let i = this.fighter.bullets.length - 1; i >= 0; i--) {
2441
- const bullet = this.fighter.bullets[i];
2442
-
2443
- for (let j = this.enemies.length - 1; j >= 0; j--) {
2444
- const enemy = this.enemies[j];
2445
  if (!enemy.mesh || !enemy.isLoaded) continue;
2446
 
2447
- const distance = bullet.position.distanceTo(enemy.position);
2448
- if (distance < 90) {
2449
- // ์ ๊ธฐ ์œ„์น˜๋ฅผ ๋ฏธ๋ฆฌ ์ €์žฅ (์ค‘์š”!)
2450
- const explosionPosition = enemy.position.clone();
2451
 
2452
- // ํžˆํŠธ ํ‘œ์‹œ ์ถ”๊ฐ€
2453
- this.showHitMarker(explosionPosition);
2454
- // ํ”ผ๊ฒฉ ์ดํŽ™ํŠธ ์ถ”๊ฐ€
2455
- this.createHitEffect(explosionPosition);
2456
 
2457
- // ํƒ„ํ™˜ ์ œ๊ฑฐ
2458
- this.scene.remove(bullet);
2459
- this.fighter.bullets.splice(i, 1);
 
 
 
 
 
 
 
 
 
 
 
 
 
2460
 
2461
- // ๋ฐ๋ฏธ์ง€ ์ ์šฉ ๋ฐ ํŒŒ๊ดด ํ™•์ธ
2462
- if (enemy.takeDamage(GAME_CONSTANTS.BULLET_DAMAGE)) {
2463
- // ๋””๋ฒ„๊น…์šฉ ๋กœ๊ทธ ์ถ”๊ฐ€
2464
- console.log('Enemy destroyed! Creating explosion at:', explosionPosition);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2465
 
2466
- // ์ ๊ธฐ ํŒŒ๊ดด ์‹œ ํญ๋ฐœ ํšจ๊ณผ ์ถ”๊ฐ€ - ์ €์žฅ๋œ ์œ„์น˜ ์‚ฌ์šฉ
2467
- this.createExplosionEffect(explosionPosition);
 
 
2468
 
2469
- // ์ ๊ธฐ ์ œ๊ฑฐ - ์ฒด๋ ฅ์ด 0 ์ดํ•˜์ผ ๋•Œ๋งŒ ์ œ๊ฑฐ
2470
- enemy.destroy();
2471
- this.enemies.splice(j, 1);
2472
- this.score += 100;
2473
 
2474
- // ์ถ”๊ฐ€ ๋””๋ฒ„๊น…
2475
- console.log('Enemies remaining:', this.enemies.length);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2476
  }
2477
- // else ๋ธ”๋ก ์ œ๊ฑฐ - ์ ์ด ํŒŒ๊ดด๋˜์ง€ ์•Š์•˜์œผ๋ฉด ์•„๋ฌด๊ฒƒ๋„ ํ•˜์ง€ ์•Š์Œ
2478
-
2479
- break; // ํ•˜๋‚˜์˜ ํƒ„ํ™˜์€ ํ•˜๋‚˜์˜ ์ ๋งŒ ๋งž์ถœ ์ˆ˜ ์žˆ์Œ
2480
  }
2481
  }
2482
- }
2483
-
2484
- // ์  ํƒ„ํ™˜ vs ํ”Œ๋ ˆ์ด์–ด ์ถฉ๋Œ
2485
- this.enemies.forEach(enemy => {
2486
- for (let index = enemy.bullets.length - 1; index >= 0; index--) {
2487
- const bullet = enemy.bullets[index];
2488
- const distance = bullet.position.distanceTo(this.fighter.position);
2489
- if (distance < 100) {
2490
- // ํ”Œ๋ ˆ์ด์–ด ์œ„์น˜ ์ €์žฅ
2491
- const playerPosition = this.fighter.position.clone();
2492
-
2493
- // ํ”Œ๋ ˆ์ด์–ด ํ”ผ๊ฒฉ ์ดํŽ™ํŠธ
2494
- this.createHitEffect(playerPosition);
2495
-
2496
- // ํƒ„ํ™˜ ์ œ๊ฑฐ
2497
- this.scene.remove(bullet);
2498
- enemy.bullets.splice(index, 1);
2499
-
2500
- if (this.fighter.takeDamage(GAME_CONSTANTS.BULLET_DAMAGE)) {
2501
- console.log('Player destroyed! Creating explosion');
2502
- // ํ”Œ๋ ˆ์ด์–ด ํŒŒ๊ดด ์‹œ ํญ๋ฐœ ํšจ๊ณผ ์ถ”๊ฐ€
2503
- this.createExplosionEffect(playerPosition);
2504
 
2505
- this.endGame(false);
 
 
 
 
 
 
 
 
 
 
 
 
 
2506
  }
2507
  }
2508
- }
2509
- });
2510
- }
2511
 
2512
- createHitEffect(position) {
2513
  // ํ”ผ๊ฒฉ ํŒŒํ‹ฐํด ํšจ๊ณผ ์ƒ์„ฑ
2514
  const particleCount = 15;
2515
  const particles = [];
@@ -2830,101 +3288,104 @@ createHitEffect(position) {
2830
  }
2831
 
2832
  animate() {
2833
- if (this.isGameOver) return;
2834
-
2835
- this.animationFrameId = requestAnimationFrame(() => this.animate());
2836
-
2837
- const currentTime = performance.now();
2838
- const deltaTime = Math.min((currentTime - this.lastTime) / 1000, 0.1);
2839
- this.lastTime = currentTime;
2840
-
2841
- if (this.isLoaded && this.fighter.isLoaded && this.isStarted) {
2842
- // ํ‚ค ์ƒํƒœ ๋””๋ฒ„๊น…
2843
- if (this.keys.w || this.keys.s || this.keys.a || this.keys.d) {
2844
- console.log('animate() - Keys state:', this.keys);
2845
- }
2846
-
2847
- // Fํ‚ค ์ƒํƒœ๋ฅผ Fighter์— ์ „๋‹ฌ
2848
- this.fighter.escapeKeyPressed = this.keys.f;
2849
 
2850
- // ์ปจํŠธ๋กค ์—…๋ฐ์ดํŠธ - ๋ฐ˜๋“œ์‹œ ๋ฌผ๋ฆฌ ์—…๋ฐ์ดํŠธ ์ „์— ํ˜ธ์ถœ
2851
- this.fighter.updateControls(this.keys, deltaTime);
2852
 
2853
- // ๋ฌผ๋ฆฌ ์—…๋ฐ์ดํŠธ
2854
- this.fighter.updatePhysics(deltaTime);
 
2855
 
2856
- // ํƒ„ํ™˜ ์—…๋ฐ์ดํŠธ
2857
- this.fighter.updateBullets(this.scene, deltaTime, this);
2858
-
2859
- // ๋งˆ์šฐ์Šค ๋ˆ„๋ฆ„ ์ƒํƒœ์ผ ๋•Œ ์—ฐ์† ๋ฐœ์‚ฌ
2860
- if (this.fighter.isMouseDown) {
2861
- const currentShootTime = Date.now();
2862
- if (!this.lastShootTime || currentShootTime - this.lastShootTime >= 100) {
2863
- this.fighter.shoot(this.scene);
2864
- this.lastShootTime = currentShootTime;
2865
  }
2866
- }
2867
-
2868
- // ์ ๊ธฐ ์—…๋ฐ์ดํŠธ
2869
- this.enemies.forEach(enemy => {
2870
- enemy.nearbyEnemies = this.enemies;
2871
- });
2872
-
2873
- this.enemies.forEach(enemy => {
2874
- enemy.update(this.fighter.position, deltaTime);
2875
- });
2876
-
2877
- // ์ถฉ๋Œ ์ฒดํฌ
2878
- this.checkCollisions();
2879
-
2880
- // ๊ฒŒ์ž„ ์ข…๋ฃŒ ์กฐ๊ฑด ์ฒดํฌ
2881
- if (this.fighter.health <= 0) {
2882
- if (this.fighter.position.y <= 0) {
2883
- this.endGame(false, "GROUND COLLISION");
2884
- } else {
2885
- this.endGame(false);
 
 
 
2886
  }
2887
- return;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2888
  }
2889
 
2890
- // UI ์—…๋ฐ์ดํŠธ
2891
- this.updateUI();
2892
- this.updateRadar();
 
 
 
 
 
 
 
 
 
 
2893
 
2894
- // ์ ์ด ๋ชจ๋‘ ์ œ๊ฑฐ๋˜์—ˆ๋Š”์ง€ ์ฒดํฌ
2895
- if (this.enemies.length === 0) {
2896
- this.endGame(true);
 
 
 
 
2897
  }
2898
- } else if (this.isLoaded && this.fighter.isLoaded) {
2899
- // ๊ฒŒ์ž„์ด ์‹œ์ž‘๋˜์ง€ ์•Š์•˜์„ ๋•Œ๋„ ๋ฌผ๋ฆฌ๋Š” ์—…๋ฐ๏ฟฝ๏ฟฝํŠธ (์นด๋ฉ”๋ผ ์›€์ง์ž„์„ ์œ„ํ•ด)
2900
- this.fighter.updatePhysics(deltaTime);
2901
- }
2902
-
2903
- // ๊ตฌ๋ฆ„ ์• ๋‹ˆ๋ฉ”์ด์…˜
2904
- if (this.clouds) {
2905
- this.clouds.forEach(cloud => {
2906
- cloud.userData.time += deltaTime;
2907
- cloud.position.x += cloud.userData.driftSpeed;
2908
- cloud.position.y = cloud.userData.initialY +
2909
- Math.sin(cloud.userData.time * cloud.userData.floatSpeed) * 20;
2910
-
2911
- const mapLimit = GAME_CONSTANTS.MAP_SIZE / 2;
2912
- if (cloud.position.x > mapLimit) cloud.position.x = -mapLimit;
2913
- if (cloud.position.x < -mapLimit) cloud.position.x = mapLimit;
2914
- });
2915
- }
2916
-
2917
- // ์นด๋ฉ”๋ผ ์—…๋ฐ์ดํŠธ
2918
- if (this.fighter.isLoaded) {
2919
- const targetCameraPos = this.fighter.getCameraPosition();
2920
- const targetCameraTarget = this.fighter.getCameraTarget();
2921
 
2922
- this.camera.position.lerp(targetCameraPos, this.fighter.cameraLag);
2923
- this.camera.lookAt(targetCameraTarget);
2924
  }
2925
-
2926
- this.renderer.render(this.scene, this.camera);
2927
- }
2928
 
2929
  endGame(victory = false, reason = "") {
2930
  this.isGameOver = true;
 
20
  MISSILE_COUNT: 6,
21
  AMMO_COUNT: 940, // 940๋ฐœ๋กœ ๋ณ€๊ฒฝ
22
  BULLET_DAMAGE: 50, // ๋ฐœ๋‹น 50 ๋ฐ๋ฏธ์ง€
23
+ MAX_HEALTH: 1000, // ์ฒด๋ ฅ 1000
24
+ AIM9_COUNT: 8, // AIM-9 ๋ฏธ์‚ฌ์ผ 8๋ฐœ
25
+ AIM9_DAMAGE: 500, // AIM-9 ๋ฏธ์‚ฌ์ผ ํ”ผํ•ด 500
26
+ AIM9_SPEED: 514.4, // 1000kt๋ฅผ m/s๋กœ ๋ณ€ํ™˜
27
+ AIM9_LOCK_RANGE: 6000, // ๋ฝ์˜จ ๊ฑฐ๋ฆฌ 6000m
28
+ AIM9_LOCK_REQUIRED: 3 // 3๋ฒˆ ๋ฝ์˜จ ํ•„์š”
29
  };
30
 
31
  // ์ „ํˆฌ๊ธฐ ํด๋ž˜์Šค
 
69
  this.isMouseDown = false; // ๋งˆ์šฐ์Šค ๋ˆ„๋ฆ„ ์ƒํƒœ ์ถ”์ 
70
  this.gunfireAudios = []; // ๊ธฐ๊ด€์ด ์†Œ๋ฆฌ ๋ฐฐ์—ด (์ตœ๋Œ€ 5๊ฐœ)
71
 
72
+ // AIM-9 ๋ฏธ์‚ฌ์ผ ์‹œ์Šคํ…œ
73
+ this.aim9Missiles = GAME_CONSTANTS.AIM9_COUNT;
74
+ this.firedMissiles = []; // ๋ฐœ์‚ฌ๋œ ๋ฏธ์‚ฌ์ผ ๋ฐฐ์—ด
75
+ this.currentWeapon = 'MG'; // 'MG' ๋˜๋Š” 'AIM9'
76
+ this.lockTarget = null; // ํ˜„์žฌ ๋ฝ์˜จ ์ค‘์ธ ํƒ€๊ฒŸ
77
+ this.lockProgress = 0; // ๋ฝ์˜จ ์ง„ํ–‰๋„ (0~3)
78
+ this.lastLockTime = 0; // ๋งˆ์ง€๋ง‰ ๋ฝ์˜จ ์‹œ๋„ ์‹œ๊ฐ„
79
+ this.lockAudios = {
80
+ locking: null,
81
+ locked: null
82
+ };
83
+ this.initializeLockAudios();
84
+
85
  // ์Šคํ†จ ํƒˆ์ถœ์„ ์œ„ํ•œ Fํ‚ค ์ƒํƒœ
86
  this.escapeKeyPressed = false;
87
  this.stallEscapeProgress = 0; // ์Šคํ†จ ํƒˆ์ถœ ์ง„ํ–‰๋„ (0~2์ดˆ)
 
160
  }
161
  }
162
 
163
+ initializeLockAudios() {
164
+ try {
165
+ this.lockAudios.locking = new Audio('sounds/lockon.ogg');
166
+ this.lockAudios.locking.volume = 0.5;
167
+ this.lockAudios.locking.loop = true;
168
+
169
+ this.lockAudios.locked = new Audio('sounds/lockondone.ogg');
170
+ this.lockAudios.locked.volume = 0.7;
171
+ } catch (e) {
172
+ console.log('Lock audio initialization failed:', e);
173
+ }
174
+ }
175
+
176
  startEngineSound() {
177
  // ์—”์ง„ ์†Œ๋ฆฌ ์‹œ์ž‘
178
  if (this.warningAudios.normal) {
 
221
  audio.currentTime = 0;
222
  }
223
  });
224
+
225
+ // ๋ฝ์˜จ ์†Œ๋ฆฌ๋„ ์ •์ง€
226
+ if (this.lockAudios.locking && !this.lockAudios.locking.paused) {
227
+ this.lockAudios.locking.pause();
228
+ this.lockAudios.locking.currentTime = 0;
229
+ }
230
  }
231
 
232
  async initialize(scene, loader) {
 
289
 
290
  console.log('Fallback ์ „ํˆฌ๊ธฐ ๋ชจ๋ธ ์ƒ์„ฑ ์™„๋ฃŒ');
291
  }
292
+
293
+ switchWeapon() {
294
+ if (this.currentWeapon === 'MG') {
295
+ this.currentWeapon = 'AIM9';
296
+ // ๋ฌด๊ธฐ ์ „ํ™˜ ์‹œ ๋ฝ์˜จ ์ดˆ๊ธฐํ™”
297
+ this.lockTarget = null;
298
+ this.lockProgress = 0;
299
+ if (this.lockAudios.locking && !this.lockAudios.locking.paused) {
300
+ this.lockAudios.locking.pause();
301
+ this.lockAudios.locking.currentTime = 0;
302
+ }
303
+ } else {
304
+ this.currentWeapon = 'MG';
305
+ // MG๋กœ ์ „ํ™˜ ์‹œ ๋ฝ์˜จ ํ•ด์ œ
306
+ this.lockTarget = null;
307
+ this.lockProgress = 0;
308
+ if (this.lockAudios.locking && !this.lockAudios.locking.paused) {
309
+ this.lockAudios.locking.pause();
310
+ this.lockAudios.locking.currentTime = 0;
311
+ }
312
+ }
313
+ }
314
+
315
+ updateLockOn(enemies, deltaTime) {
316
+ // AIM-9 ๋ชจ๋“œ๊ฐ€ ์•„๋‹ˆ๋ฉด ๋ฝ์˜จ ํ•˜์ง€ ์•Š์Œ
317
+ if (this.currentWeapon !== 'AIM9') {
318
+ this.lockTarget = null;
319
+ this.lockProgress = 0;
320
+ return;
321
+ }
322
+
323
+ const now = Date.now();
324
+
325
+ // 1์ดˆ์— ํ•œ ๋ฒˆ์”ฉ ๋ฝ์˜จ ์ฒ˜๋ฆฌ
326
+ if (now - this.lastLockTime < 1000) {
327
+ return;
328
+ }
329
+
330
+ // ํ˜„์žฌ ํฌ๋กœ์Šคํ—ค์–ด ๋‚ด์˜ ์  ์ฐพ๊ธฐ
331
+ let closestEnemy = null;
332
+ let closestDistance = GAME_CONSTANTS.AIM9_LOCK_RANGE;
333
+
334
+ enemies.forEach(enemy => {
335
+ if (!enemy.mesh || !enemy.isLoaded) return;
336
+
337
+ const distance = this.position.distanceTo(enemy.position);
338
+ if (distance > GAME_CONSTANTS.AIM9_LOCK_RANGE) return;
339
+
340
+ // ์ ์ด ํฌ๋กœ์Šคํ—ค์–ด ๋‚ด์— ์žˆ๋Š”์ง€ ํ™•์ธ
341
+ const toEnemy = enemy.position.clone().sub(this.position).normalize();
342
+ const forward = new THREE.Vector3(0, 0, 1);
343
+
344
+ // ์ฟผํ„ฐ๋‹ˆ์–ธ์„ ์‚ฌ์šฉํ•˜์—ฌ ์ „๋ฐฉ ๋ฒกํ„ฐ ๊ณ„์‚ฐ
345
+ const quaternion = new THREE.Quaternion();
346
+ const pitchQuat = new THREE.Quaternion();
347
+ const yawQuat = new THREE.Quaternion();
348
+ const rollQuat = new THREE.Quaternion();
349
+
350
+ pitchQuat.setFromAxisAngle(new THREE.Vector3(1, 0, 0), this.rotation.x);
351
+ yawQuat.setFromAxisAngle(new THREE.Vector3(0, 1, 0), this.rotation.y);
352
+ rollQuat.setFromAxisAngle(new THREE.Vector3(0, 0, 1), this.rotation.z);
353
+
354
+ quaternion.multiply(rollQuat);
355
+ quaternion.multiply(pitchQuat);
356
+ quaternion.multiply(yawQuat);
357
+
358
+ forward.applyQuaternion(quaternion);
359
+
360
+ const dotProduct = forward.dot(toEnemy);
361
+ const angle = Math.acos(Math.max(-1, Math.min(1, dotProduct)));
362
+
363
+ // ํฌ๋กœ์Šคํ—ค์–ด ๋ฒ”์œ„ ๋‚ด (์•ฝ 10๋„)
364
+ if (angle < Math.PI / 18 && distance < closestDistance) {
365
+ closestEnemy = enemy;
366
+ closestDistance = distance;
367
+ }
368
+ });
369
+
370
+ // ๋ฝ์˜จ ๋Œ€์ƒ ์—…๋ฐ์ดํŠธ
371
+ if (closestEnemy) {
372
+ if (this.lockTarget === closestEnemy) {
373
+ // ๊ฐ™์€ ํƒ€๊ฒŸ์— ๊ณ„์† ๋ฝ์˜จ ์ค‘
374
+ this.lockProgress++;
375
+ this.lastLockTime = now;
376
+
377
+ // ๋ฝ์˜จ ์†Œ๋ฆฌ ์žฌ์ƒ
378
+ if (this.lockProgress < GAME_CONSTANTS.AIM9_LOCK_REQUIRED) {
379
+ if (this.lockAudios.locking && this.lockAudios.locking.paused) {
380
+ this.lockAudios.locking.play().catch(e => {});
381
+ }
382
+ } else {
383
+ // ๋ฝ์˜จ ์™„๋ฃŒ
384
+ if (this.lockAudios.locking && !this.lockAudios.locking.paused) {
385
+ this.lockAudios.locking.pause();
386
+ this.lockAudios.locking.currentTime = 0;
387
+ }
388
+ if (this.lockAudios.locked) {
389
+ this.lockAudios.locked.play().catch(e => {});
390
+ }
391
+ }
392
+ } else {
393
+ // ์ƒˆ๋กœ์šด ํƒ€๊ฒŸ
394
+ this.lockTarget = closestEnemy;
395
+ this.lockProgress = 1;
396
+ this.lastLockTime = now;
397
+
398
+ // ๋ฝ์˜จ ์†Œ๋ฆฌ ์‹œ์ž‘
399
+ if (this.lockAudios.locking && this.lockAudios.locking.paused) {
400
+ this.lockAudios.locking.play().catch(e => {});
401
+ }
402
+ }
403
+ } else {
404
+ // ํƒ€๊ฒŸ ์žƒ์Œ
405
+ this.lockTarget = null;
406
+ this.lockProgress = 0;
407
+ if (this.lockAudios.locking && !this.lockAudios.locking.paused) {
408
+ this.lockAudios.locking.pause();
409
+ this.lockAudios.locking.currentTime = 0;
410
+ }
411
+ }
412
+ }
413
 
414
  updateMouseInput(deltaX, deltaY) {
415
  // Over-G ์ƒํƒœ์—์„œ ์Šคํ†จ์ด ํ•ด์ œ๋˜์ง€ ์•Š์•˜์œผ๋ฉด ํ”ผ์น˜ ์กฐ์ž‘ ๋ถˆ๊ฐ€
 
807
  }
808
 
809
  shoot(scene) {
810
+ if (this.currentWeapon === 'MG') {
811
+ // ๊ธฐ์กด MG ๋ฐœ์‚ฌ ๋กœ์ง
812
+ // ํƒ„์•ฝ์ด ์—†์œผ๋ฉด ๋ฐœ์‚ฌํ•˜์ง€ ์•Š์Œ
813
+ if (this.ammo <= 0) return;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
814
 
815
+ this.ammo--;
816
 
817
+ // ์ง์„  ๋ชจ์–‘์˜ ํƒ„ํ™˜ (100% ๋” ํฌ๊ฒŒ)
818
+ const bulletGeometry = new THREE.CylinderGeometry(1.0, 1.0, 16, 8); // ๋ฐ˜์ง€๋ฆ„ 0.75โ†’1.0, ๊ธธ์ด 12โ†’16
819
+ const bulletMaterial = new THREE.MeshBasicMaterial({
820
+ color: 0xffff00,
821
  });
822
+ const bullet = new THREE.Mesh(bulletGeometry, bulletMaterial);
823
+
824
+ // ๊ธฐ์ˆ˜ ๋์—์„œ ๋ฐœ์‚ฌ (์ฟผํ„ฐ๋‹ˆ์–ธ ์‚ฌ์šฉ)
825
+ const muzzleOffset = new THREE.Vector3(0, 0, 10);
826
+
827
+ // ์ „ํˆฌ๊ธฐ์™€ ๋™์ผํ•œ ์ฟผํ„ฐ๋‹ˆ์–ธ ์ƒ์„ฑ
828
+ const quaternion = new THREE.Quaternion();
829
+ const pitchQuat = new THREE.Quaternion();
830
+ const yawQuat = new THREE.Quaternion();
831
+ const rollQuat = new THREE.Quaternion();
832
+
833
+ pitchQuat.setFromAxisAngle(new THREE.Vector3(1, 0, 0), this.rotation.x);
834
+ yawQuat.setFromAxisAngle(new THREE.Vector3(0, 1, 0), this.rotation.y);
835
+ rollQuat.setFromAxisAngle(new THREE.Vector3(0, 0, 1), this.rotation.z);
836
+
837
+ quaternion.multiply(rollQuat);
838
+ quaternion.multiply(pitchQuat);
839
+ quaternion.multiply(yawQuat);
840
+
841
+ muzzleOffset.applyQuaternion(quaternion);
842
+ bullet.position.copy(this.position).add(muzzleOffset);
843
+
844
+ // ํƒ„ํ™˜์„ ๋ฐœ์‚ฌ ๋ฐฉํ–ฅ์œผ๋กœ ํšŒ์ „
845
+ bullet.quaternion.copy(quaternion);
846
+ // ์‹ค๋ฆฐ๋”๊ฐ€ Z์ถ• ๋ฐฉํ–ฅ์„ ํ–ฅํ•˜๋„๋ก ์ถ”๊ฐ€ ํšŒ์ „
847
+ const cylinderRotation = new THREE.Quaternion();
848
+ cylinderRotation.setFromAxisAngle(new THREE.Vector3(1, 0, 0), Math.PI / 2);
849
+ bullet.quaternion.multiply(cylinderRotation);
850
+
851
+ // ํƒ„ํ™˜ ์ดˆ๊ธฐ ์œ„์น˜ ์ €์žฅ
852
+ bullet.startPosition = bullet.position.clone();
853
+
854
+ const bulletSpeed = 1500; // 1000์—์„œ 1500์œผ๋กœ ์ฆ๊ฐ€ (50% ๋น ๋ฅด๊ฒŒ)
855
+ const direction = new THREE.Vector3(0, 0, 1);
856
+ direction.applyQuaternion(quaternion);
857
+ bullet.velocity = direction.multiplyScalar(bulletSpeed);
858
+
859
+ scene.add(bullet);
860
+ this.bullets.push(bullet);
861
+
862
+ // m134.ogg ์†Œ๋ฆฌ ์žฌ์ƒ - ์ค‘์ฒฉ ์ œํ•œ ํ•ด์ œ, ๋žœ๋ค ํ”ผ์น˜
863
+ try {
864
+ const audio = new Audio('sounds/m134.ogg');
865
+ audio.volume = 0.15; // 0.3์—์„œ 0.15๋กœ ๊ฐ์†Œ (50% ์ค„์ž„)
866
+
867
+ // -2 ~ +2 ์‚ฌ์ด์˜ ๋žœ๋ค ํ”ผ์น˜ (playbackRate๋กœ ์‹œ๋ฎฌ๋ ˆ์ด์…˜)
868
+ const randomPitch = 0.8 + Math.random() * 0.4; // 0.8 ~ 1.2 ๋ฒ”์œ„
869
+ audio.playbackRate = randomPitch;
870
+
871
+ audio.play().catch(e => console.log('Gunfire sound failed to play'));
872
+
873
+ // ์žฌ์ƒ ์™„๋ฃŒ ์‹œ ๋ฉ”๋ชจ๋ฆฌ ์ •๋ฆฌ๋ฅผ ์œ„ํ•ด ์ฐธ์กฐ ์ œ๊ฑฐ
874
+ audio.addEventListener('ended', () => {
875
+ audio.remove();
876
+ });
877
+ } catch (e) {
878
+ console.log('Audio error:', e);
879
+ }
880
+ } else if (this.currentWeapon === 'AIM9') {
881
+ // AIM-9 ๋ฏธ์‚ฌ์ผ ๋ฐœ์‚ฌ
882
+ if (this.aim9Missiles <= 0) return;
883
+ if (this.lockProgress < GAME_CONSTANTS.AIM9_LOCK_REQUIRED) return;
884
+ if (!this.lockTarget) return;
885
+
886
+ this.aim9Missiles--;
887
+
888
+ // ๋ฏธ์‚ฌ์ผ ์ƒ์„ฑ
889
+ const missile = new AIM9Missile(scene, this.position.clone(), this.lockTarget, this.rotation.clone());
890
+ this.firedMissiles.push(missile);
891
+
892
+ // ๋ฝ์˜จ ์ดˆ๊ธฐํ™”
893
+ this.lockTarget = null;
894
+ this.lockProgress = 0;
895
+ if (this.lockAudios.locking && !this.lockAudios.locking.paused) {
896
+ this.lockAudios.locking.pause();
897
+ this.lockAudios.locking.currentTime = 0;
898
+ }
899
+
900
+ // ๋ฐœ์‚ฌ์Œ
901
+ try {
902
+ const missileSound = new Audio('sounds/missile.ogg');
903
+ missileSound.volume = 0.7;
904
+ missileSound.play().catch(e => {});
905
+ } catch (e) {
906
+ console.log('Missile sound failed:', e);
907
+ }
908
  }
909
  }
910
 
 
934
  this.bullets.splice(i, 1);
935
  }
936
  }
937
+
938
+ // ๋ฏธ์‚ฌ์ผ ์—…๋ฐ์ดํŠธ
939
+ for (let i = this.firedMissiles.length - 1; i >= 0; i--) {
940
+ const missile = this.firedMissiles[i];
941
+ const result = missile.update(deltaTime, this.position);
942
+
943
+ if (result === 'hit' || result === 'expired') {
944
+ this.firedMissiles.splice(i, 1);
945
+ }
946
+ }
947
  }
948
 
949
  takeDamage(damage) {
 
989
  }
990
  }
991
 
992
+ // AIM-9 ๋ฏธ์‚ฌ์ผ ํด๋ž˜์Šค
993
+ class AIM9Missile {
994
+ constructor(scene, position, target, rotation) {
995
+ this.scene = scene;
996
+ this.position = position.clone();
997
+ this.target = target;
998
+ this.rotation = rotation.clone();
999
+ this.speed = GAME_CONSTANTS.AIM9_SPEED;
1000
+ this.mesh = null;
1001
+ this.isLoaded = false;
1002
+ this.lifeTime = 20; // 20์ดˆ ํ›„ ์žํญ
1003
+ this.turnRate = 3.0; // ์ดˆ๋‹น ํšŒ์ „ ์†๋„ (๋ผ๋””์•ˆ)
1004
+ this.startPosition = position.clone();
1005
+
1006
+ // ๋ฏธ์‚ฌ์ผ ๋ฐœ์‚ฌ ๋ฐฉํ–ฅ ์„ค์ •
1007
+ const quaternion = new THREE.Quaternion();
1008
+ const pitchQuat = new THREE.Quaternion();
1009
+ const yawQuat = new THREE.Quaternion();
1010
+ const rollQuat = new THREE.Quaternion();
1011
+
1012
+ pitchQuat.setFromAxisAngle(new THREE.Vector3(1, 0, 0), this.rotation.x);
1013
+ yawQuat.setFromAxisAngle(new THREE.Vector3(0, 1, 0), this.rotation.y);
1014
+ rollQuat.setFromAxisAngle(new THREE.Vector3(0, 0, 1), this.rotation.z);
1015
+
1016
+ quaternion.multiply(rollQuat);
1017
+ quaternion.multiply(pitchQuat);
1018
+ quaternion.multiply(yawQuat);
1019
+
1020
+ const initialDirection = new THREE.Vector3(0, 0, 1);
1021
+ initialDirection.applyQuaternion(quaternion);
1022
+ this.velocity = initialDirection.multiplyScalar(this.speed);
1023
+
1024
+ // ๋ฏธ์‚ฌ์ผ ์†Œ๋ฆฌ
1025
+ this.swingAudio = null;
1026
+ this.initializeAudio();
1027
+
1028
+ this.createMissile();
1029
+ }
1030
+
1031
+ initializeAudio() {
1032
+ try {
1033
+ this.swingAudio = new Audio('sounds/missileswing.ogg');
1034
+ this.swingAudio.volume = 0.3;
1035
+ this.swingAudio.loop = true;
1036
+ this.swingAudio.play().catch(e => {});
1037
+ } catch (e) {
1038
+ console.log('Missile swing audio failed:', e);
1039
+ }
1040
+ }
1041
+
1042
+ async createMissile() {
1043
+ try {
1044
+ // GLB ๋ชจ๋ธ ๋กœ๋“œ ์‹œ๋„
1045
+ const loader = new GLTFLoader();
1046
+ const result = await loader.loadAsync('models/aim-9.glb');
1047
+ this.mesh = result.scene;
1048
+ this.mesh.scale.set(0.5, 0.5, 0.5);
1049
+ this.isLoaded = true;
1050
+ } catch (error) {
1051
+ console.log('AIM-9 model not found, using fallback');
1052
+ this.createFallbackMissile();
1053
+ }
1054
+
1055
+ this.mesh.position.copy(this.position);
1056
+ this.scene.add(this.mesh);
1057
+ }
1058
+
1059
+ createFallbackMissile() {
1060
+ // ํด๋ฐฑ ๋ฏธ์‚ฌ์ผ ๋ชจ๋ธ
1061
+ const group = new THREE.Group();
1062
+
1063
+ // ๋ฏธ์‚ฌ์ผ ๋ณธ์ฒด
1064
+ const bodyGeometry = new THREE.CylinderGeometry(0.3, 0.4, 4, 8);
1065
+ const bodyMaterial = new THREE.MeshLambertMaterial({ color: 0x808080 });
1066
+ const body = new THREE.Mesh(bodyGeometry, bodyMaterial);
1067
+ body.rotation.x = Math.PI / 2;
1068
+ group.add(body);
1069
+
1070
+ // ํƒ„๋‘
1071
+ const noseGeometry = new THREE.ConeGeometry(0.3, 1, 8);
1072
+ const noseMaterial = new THREE.MeshLambertMaterial({ color: 0x404040 });
1073
+ const nose = new THREE.Mesh(noseGeometry, noseMaterial);
1074
+ nose.position.z = 2.5;
1075
+ nose.rotation.x = -Math.PI / 2;
1076
+ group.add(nose);
1077
+
1078
+ // ๋‚ ๊ฐœ
1079
+ const finGeometry = new THREE.BoxGeometry(2, 0.1, 0.5);
1080
+ const finMaterial = new THREE.MeshLambertMaterial({ color: 0x606060 });
1081
+
1082
+ for (let i = 0; i < 4; i++) {
1083
+ const fin = new THREE.Mesh(finGeometry, finMaterial);
1084
+ fin.position.z = -1.5;
1085
+ fin.rotation.z = (Math.PI / 2) * i;
1086
+ group.add(fin);
1087
+ }
1088
+
1089
+ // ๋ถˆ๊ฝƒ ํšจ๊ณผ
1090
+ const flameGeometry = new THREE.ConeGeometry(0.4, 1.5, 8);
1091
+ const flameMaterial = new THREE.MeshBasicMaterial({
1092
+ color: 0xff4400,
1093
+ transparent: true,
1094
+ opacity: 0.8
1095
+ });
1096
+ const flame = new THREE.Mesh(flameGeometry, flameMaterial);
1097
+ flame.position.z = -2.5;
1098
+ flame.rotation.x = Math.PI / 2;
1099
+ group.add(flame);
1100
+
1101
+ this.mesh = group;
1102
+ this.mesh.scale.set(1.5, 1.5, 1.5);
1103
+ this.isLoaded = true;
1104
+ }
1105
+
1106
+ update(deltaTime, playerPosition) {
1107
+ if (!this.mesh || !this.target || !this.target.position) {
1108
+ this.destroy();
1109
+ return 'expired';
1110
+ }
1111
+
1112
+ this.lifeTime -= deltaTime;
1113
+ if (this.lifeTime <= 0) {
1114
+ this.destroy();
1115
+ return 'expired';
1116
+ }
1117
+
1118
+ // ํƒ€๊ฒŸ ์ถ”์ 
1119
+ const toTarget = this.target.position.clone().sub(this.position);
1120
+ const distance = toTarget.length();
1121
+
1122
+ // ๋ช…์ค‘ ์ฒดํฌ
1123
+ if (distance < 30) {
1124
+ // ๋ช…์ค‘!
1125
+ this.onHit();
1126
+ return 'hit';
1127
+ }
1128
+
1129
+ // ํ˜ธ๋ฐ ๋กœ์ง
1130
+ toTarget.normalize();
1131
+ const currentDirection = this.velocity.clone().normalize();
1132
+
1133
+ // ๋ถ€๋“œ๋Ÿฌ์šด ๋ฐฉํ–ฅ ์ „ํ™˜
1134
+ const newDirection = new THREE.Vector3();
1135
+ newDirection.lerpVectors(currentDirection, toTarget, deltaTime * this.turnRate);
1136
+ newDirection.normalize();
1137
+
1138
+ this.velocity = newDirection.multiplyScalar(this.speed);
1139
+
1140
+ // ์œ„์น˜ ์—…๋ฐ์ดํŠธ
1141
+ this.position.add(this.velocity.clone().multiplyScalar(deltaTime));
1142
+ this.mesh.position.copy(this.position);
1143
+
1144
+ // ๋ฏธ์‚ฌ์ผ ํšŒ์ „
1145
+ const lookAtTarget = this.position.clone().add(this.velocity);
1146
+ this.mesh.lookAt(lookAtTarget);
1147
+
1148
+ // ์‚ฌ์šด๋“œ ๋ณผ๋ฅจ ์กฐ์ • (ํ”Œ๋ ˆ์ด์–ด์™€์˜ ๊ฑฐ๋ฆฌ์— ๋”ฐ๋ผ)
1149
+ if (this.swingAudio && playerPosition) {
1150
+ const distanceToPlayer = this.position.distanceTo(playerPosition);
1151
+ if (distanceToPlayer < 200) {
1152
+ this.swingAudio.volume = 0.3 * (1 - distanceToPlayer / 200);
1153
+ } else {
1154
+ this.swingAudio.volume = 0;
1155
+ }
1156
+ }
1157
+
1158
+ // ์ง€๋ฉด ์ถฉ๋Œ
1159
+ if (this.position.y <= 0) {
1160
+ this.destroy();
1161
+ return 'expired';
1162
+ }
1163
+
1164
+ return 'flying';
1165
+ }
1166
+
1167
+ onHit() {
1168
+ // ๋ช…์ค‘ ํšจ๊ณผ
1169
+ if (window.gameInstance) {
1170
+ window.gameInstance.createExplosionEffect(this.position);
1171
+ }
1172
+
1173
+ // ๋ช…์ค‘์Œ
1174
+ try {
1175
+ const hitSound = new Audio('sounds/missilehit.ogg');
1176
+ hitSound.volume = 0.8;
1177
+ hitSound.play().catch(e => {});
1178
+ } catch (e) {
1179
+ console.log('Missile hit sound failed:', e);
1180
+ }
1181
+
1182
+ // ํƒ€๊ฒŸ์—๊ฒŒ ํ”ผํ•ด
1183
+ if (this.target.takeDamage) {
1184
+ this.target.takeDamage(GAME_CONSTANTS.AIM9_DAMAGE);
1185
+ }
1186
+
1187
+ this.destroy();
1188
+ }
1189
+
1190
+ destroy() {
1191
+ if (this.mesh) {
1192
+ this.scene.remove(this.mesh);
1193
+ }
1194
+
1195
+ if (this.swingAudio) {
1196
+ this.swingAudio.pause();
1197
+ this.swingAudio = null;
1198
+ }
1199
+ }
1200
+ }
1201
+
1202
  // ์  ์ „ํˆฌ๊ธฐ ํด๋ž˜์Šค - ์™„์ „ํžˆ ์žฌ์„ค๊ณ„
1203
  class EnemyFighter {
1204
  constructor(scene, position) {
 
1811
  this.bgm = null;
1812
  this.bgmPlaying = false;
1813
 
1814
+ this.keys = { w: false, a: false, s: false, d: false, f: false, r: false };
1815
  this.isStarted = false;
1816
 
1817
  this.setupScene();
 
2043
  }
2044
 
2045
  setupEventListeners() {
2046
+ document.addEventListener('keydown', (event) => {
2047
+ if (this.isGameOver) return;
2048
+
2049
+ // gameStarted ๋Œ€์‹  this.isStarted ์‚ฌ์šฉ
2050
+ if (!this.isStarted) return;
2051
+
2052
+ switch(event.code) {
2053
+ case 'KeyW':
2054
+ this.keys.w = true;
2055
+ console.log('W key pressed - Accelerating');
2056
+ break;
2057
+ case 'KeyA':
2058
+ this.keys.a = true;
2059
+ console.log('A key pressed - Turning left');
2060
+ break;
2061
+ case 'KeyS':
2062
+ this.keys.s = true;
2063
+ console.log('S key pressed - Decelerating');
2064
+ break;
2065
+ case 'KeyD':
2066
+ this.keys.d = true;
2067
+ console.log('D key pressed - Turning right');
2068
+ break;
2069
+ case 'KeyF':
2070
+ this.keys.f = true;
2071
+ break;
2072
+ case 'KeyR':
2073
+ if (!event.repeat) { // ํ‚ค๋ฅผ ๋ˆ„๋ฅด๊ณ  ์žˆ์„ ๋•Œ ๋ฐ˜๋ณต ๋ฐฉ์ง€
2074
+ this.fighter.switchWeapon();
2075
+ console.log('R key pressed - Switching weapon to', this.fighter.currentWeapon);
2076
+ }
2077
+ break;
2078
+ }
2079
+ });
2080
 
2081
+ document.addEventListener('keyup', (event) => {
2082
+ if (this.isGameOver) return;
2083
+
2084
+ // gameStarted ๋Œ€์‹  this.isStarted ์‚ฌ์šฉ
2085
+ if (!this.isStarted) return;
2086
+
2087
+ switch(event.code) {
2088
+ case 'KeyW': this.keys.w = false; break;
2089
+ case 'KeyA': this.keys.a = false; break;
2090
+ case 'KeyS': this.keys.s = false; break;
2091
+ case 'KeyD': this.keys.d = false; break;
2092
+ case 'KeyF': this.keys.f = false; break;
2093
+ case 'KeyR': this.keys.r = false; break;
2094
+ }
2095
+ });
2096
 
2097
+ document.addEventListener('mousemove', (event) => {
2098
+ // ์—ฌ๊ธฐ๋„ gameStarted๋ฅผ this.isStarted๋กœ ๋ณ€๊ฒฝ
2099
+ if (!document.pointerLockElement || this.isGameOver || !this.isStarted) return;
2100
+
2101
+ const deltaX = event.movementX || 0;
2102
+ const deltaY = event.movementY || 0;
2103
+
2104
+ this.fighter.updateMouseInput(deltaX, deltaY);
2105
+ });
2106
 
2107
+ document.addEventListener('mousedown', (event) => {
2108
+ // ์—ฌ๊ธฐ๋„ gameStarted๋ฅผ this.isStarted๋กœ ๋ณ€๊ฒฝ
2109
+ if (!document.pointerLockElement || this.isGameOver || !this.isStarted) return;
2110
+
2111
+ if (event.button === 0) {
2112
+ this.fighter.isMouseDown = true;
2113
+ this.lastShootTime = 0;
2114
+ }
2115
+ });
2116
 
2117
+ document.addEventListener('mouseup', (event) => {
2118
+ if (event.button === 0) {
2119
+ this.fighter.isMouseDown = false;
2120
+ }
2121
+ });
2122
 
2123
+ window.addEventListener('resize', () => {
2124
+ this.camera.aspect = window.innerWidth / window.innerHeight;
2125
+ this.camera.updateProjectionMatrix();
2126
+ this.renderer.setSize(window.innerWidth, window.innerHeight);
2127
+ });
2128
+ }
2129
 
2130
  startGame() {
2131
  if (!this.isLoaded) {
 
2212
 
2213
  if (scoreElement) scoreElement.textContent = `Score: ${this.score}`;
2214
  if (timeElement) timeElement.textContent = `Time: ${this.gameTime}s`;
2215
+
2216
+ // ๋ฌด๊ธฐ ํ‘œ์‹œ ์—…๋ฐ์ดํŠธ
2217
+ if (ammoElement) {
2218
+ if (this.fighter.currentWeapon === 'MG') {
2219
+ ammoElement.textContent = `20MM MG: ${this.fighter.ammo}`;
2220
+ ammoElement.style.color = '#0f0';
2221
+ } else {
2222
+ ammoElement.textContent = `AIM-9: ${this.fighter.aim9Missiles}`;
2223
+ ammoElement.style.color = '#ff0000';
2224
+
2225
+ // ๋ฝ์˜จ ์ƒํƒœ ํ‘œ์‹œ
2226
+ if (this.fighter.lockTarget && this.fighter.lockProgress > 0) {
2227
+ const lockStatus = this.fighter.lockProgress >= GAME_CONSTANTS.AIM9_LOCK_REQUIRED ? 'LOCKED' : `LOCKING ${this.fighter.lockProgress}/3`;
2228
+ ammoElement.textContent += ` [${lockStatus}]`;
2229
+ }
2230
+ }
2231
+ }
2232
 
2233
  if (gameStatsElement) {
2234
  gameStatsElement.innerHTML = `
 
2330
  if (isInCrosshair) {
2331
  marker.classList.add('in-crosshair');
2332
 
2333
+ // AIM-9 ๋ชจ๋“œ์—์„œ ๋ฝ์˜จ ํ‘œ์‹œ
2334
+ if (this.fighter.currentWeapon === 'AIM9' && distance < GAME_CONSTANTS.AIM9_LOCK_RANGE) {
2335
+ if (this.fighter.lockTarget === enemy) {
2336
+ if (this.fighter.lockProgress >= GAME_CONSTANTS.AIM9_LOCK_REQUIRED) {
2337
+ marker.classList.add('locked');
2338
+ marker.style.border = '2px solid #ff0000';
2339
+ marker.style.boxShadow = '0 0 20px #ff0000';
2340
+ } else {
2341
+ // ๋ฝ์˜จ ์ค‘
2342
+ marker.style.border = '2px solid #ffff00';
2343
+ marker.style.boxShadow = '0 0 10px #ffff00';
2344
+ marker.style.animation = `target-pulse ${1.0 / this.fighter.lockProgress}s infinite`;
2345
+ }
2346
+ }
2347
+ } else if (distance < 2000) {
2348
  marker.classList.add('locked');
2349
  }
2350
 
 
2357
  const targetInfo = document.createElement('div');
2358
  targetInfo.className = 'target-info';
2359
  targetInfo.textContent = `${Math.round(distance)}m`;
2360
+
2361
+ // AIM-9 ๋ชจ๋“œ์—์„œ ๋ฝ์˜จ ์ •๋ณด ์ถ”๊ฐ€
2362
+ if (this.fighter.currentWeapon === 'AIM9' && this.fighter.lockTarget === enemy) {
2363
+ if (this.fighter.lockProgress >= GAME_CONSTANTS.AIM9_LOCK_REQUIRED) {
2364
+ targetInfo.textContent += ' LOCKED';
2365
+ targetInfo.style.color = '#ff0000';
2366
+ } else {
2367
+ targetInfo.textContent += ` LOCK ${this.fighter.lockProgress}/3`;
2368
+ targetInfo.style.color = '#ffff00';
2369
+ }
2370
+ }
2371
+
2372
  marker.appendChild(targetInfo);
2373
  }
2374
 
 
2827
  animateImpact();
2828
 
2829
  // ์ž‘์€ ์ถฉ๋Œ์Œ
2830
+ try {
2831
+ const impactSound = new Audio('sounds/hit.ogg');
2832
+ impactSound.volume = 0.2;
2833
+ impactSound.play().catch(e => {
2834
+ console.log('Impact sound not found or failed to play');
2835
+ });
2836
+ } catch (e) {
2837
+ console.log('Impact sound error:', e);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2838
  }
2839
  }
2840
 
2841
+ checkCollisions() {
2842
+ // ํ”Œ๋ ˆ์ด์–ด์™€ ์ ๊ธฐ์˜ ์ง์ ‘ ์ถฉ๋Œ ์ฒดํฌ (์ตœ์šฐ์„ )
2843
+ for (let i = this.enemies.length - 1; i >= 0; i--) {
2844
+ const enemy = this.enemies[i];
 
 
2845
  if (!enemy.mesh || !enemy.isLoaded) continue;
2846
 
2847
+ const distance = this.fighter.position.distanceTo(enemy.position);
2848
+ // ์ง์ ‘ ์ถฉ๋Œ ํŒ์ • (80m ์ด๋‚ด)
2849
+ if (distance < 80) {
2850
+ console.log('Direct collision detected! Distance:', distance);
2851
 
2852
+ // ์–‘์ชฝ ๋ชจ๋‘์˜ ์œ„์น˜ ์ €์žฅ (์ถฉ๋Œ ์ค‘๊ฐ„ ์ง€์ )
2853
+ const collisionPoint = this.fighter.position.clone().add(enemy.position).divideScalar(2);
2854
+ const playerExplosionPos = this.fighter.position.clone();
2855
+ const enemyExplosionPos = enemy.position.clone();
2856
 
2857
+ // ๋” ํฐ ํญ๋ฐœ์Œ ์žฌ์ƒ (์‹œ๊ฐ ํšจ๊ณผ๋ณด๋‹ค ๋จผ์ €)
2858
+ try {
2859
+ const collisionSound = new Audio('sounds/bang.ogg');
2860
+ collisionSound.volume = 1.0;
2861
+ collisionSound.play().catch(e => {});
2862
+
2863
+ // ๋‘ ๋ฒˆ์งธ ํญ๋ฐœ์Œ (์•ฝ๊ฐ„์˜ ๋”œ๋ ˆ์ด)
2864
+ setTimeout(() => {
2865
+ const secondExplosion = new Audio('sounds/bang.ogg');
2866
+ secondExplosion.volume = 0.8;
2867
+ secondExplosion.play().catch(e => {});
2868
+ }, 100);
2869
+ } catch (e) {}
2870
+
2871
+ // 1. ์ถฉ๋Œ ์ง€์ ์— ํฐ ํญ๋ฐœ ํšจ๊ณผ
2872
+ this.createExplosionEffect(collisionPoint);
2873
 
2874
+ // 2. ์–‘์ชฝ ์œ„์น˜์—๋„ ํญ๋ฐœ ํšจ๊ณผ
2875
+ setTimeout(() => {
2876
+ this.createExplosionEffect(playerExplosionPos);
2877
+ this.createExplosionEffect(enemyExplosionPos);
2878
+ }, 50);
2879
+
2880
+ // 3. ์ ๊ธฐ ์ œ๊ฑฐ
2881
+ enemy.destroy();
2882
+ this.enemies.splice(i, 1);
2883
+ this.score += 200; // ์ถฉ๋Œ ํ‚ฌ์€ ๋ณด๋„ˆ์Šค ์ ์ˆ˜
2884
+
2885
+ // 4. ํ”Œ๋ ˆ์ด์–ด ํŒŒ๊ดด
2886
+ this.fighter.health = 0;
2887
+
2888
+ // 5. ๊ฒŒ์ž„ ์ข…๋ฃŒ
2889
+ setTimeout(() => {
2890
+ this.endGame(false, "COLLISION WITH ENEMY");
2891
+ }, 100); // ํญ๋ฐœ ํšจ๊ณผ๊ฐ€ ๋ณด์ด๋„๋ก ์•ฝ๊ฐ„์˜ ๋”œ๋ ˆ์ด
2892
+
2893
+ return; // ์ถฉ๋Œ ์ฒ˜๋ฆฌ ํ›„ ์ฆ‰์‹œ ์ข…๋ฃŒ
2894
+ }
2895
+ }
2896
+
2897
+ // ํ”Œ๋ ˆ์ด์–ด ํƒ„ํ™˜ vs ์ ๊ธฐ ์ถฉ๋Œ
2898
+ for (let i = this.fighter.bullets.length - 1; i >= 0; i--) {
2899
+ const bullet = this.fighter.bullets[i];
2900
+
2901
+ for (let j = this.enemies.length - 1; j >= 0; j--) {
2902
+ const enemy = this.enemies[j];
2903
+ if (!enemy.mesh || !enemy.isLoaded) continue;
2904
+
2905
+ const distance = bullet.position.distanceTo(enemy.position);
2906
+ if (distance < 90) {
2907
+ // ์ ๊ธฐ ์œ„์น˜๋ฅผ ๋ฏธ๋ฆฌ ์ €์žฅ (์ค‘์š”!)
2908
+ const explosionPosition = enemy.position.clone();
2909
 
2910
+ // ํžˆํŠธ ํ‘œ์‹œ ์ถ”๊ฐ€
2911
+ this.showHitMarker(explosionPosition);
2912
+ // ํ”ผ๊ฒฉ ์ดํŽ™ํŠธ ์ถ”๊ฐ€
2913
+ this.createHitEffect(explosionPosition);
2914
 
2915
+ // ํƒ„ํ™˜ ์ œ๊ฑฐ
2916
+ this.scene.remove(bullet);
2917
+ this.fighter.bullets.splice(i, 1);
 
2918
 
2919
+ // ๋ฐ๋ฏธ์ง€ ์ ์šฉ ๋ฐ ํŒŒ๊ดด ํ™•์ธ
2920
+ if (enemy.takeDamage(GAME_CONSTANTS.BULLET_DAMAGE)) {
2921
+ // ๋””๋ฒ„๊น…์šฉ ๋กœ๊ทธ ์ถ”๊ฐ€
2922
+ console.log('Enemy destroyed! Creating explosion at:', explosionPosition);
2923
+
2924
+ // ์ ๊ธฐ ํŒŒ๊ดด ์‹œ ํญ๋ฐœ ํšจ๊ณผ ์ถ”๊ฐ€ - ์ €์žฅ๋œ ์œ„์น˜ ์‚ฌ์šฉ
2925
+ this.createExplosionEffect(explosionPosition);
2926
+
2927
+ // ์ ๊ธฐ ์ œ๊ฑฐ - ์ฒด๋ ฅ์ด 0 ์ดํ•˜์ผ ๋•Œ๋งŒ ์ œ๊ฑฐ
2928
+ enemy.destroy();
2929
+ this.enemies.splice(j, 1);
2930
+ this.score += 100;
2931
+
2932
+ // ์ถ”๊ฐ€ ๋””๋ฒ„๊น…
2933
+ console.log('Enemies remaining:', this.enemies.length);
2934
+ }
2935
+ // else ๋ธ”๋ก ์ œ๊ฑฐ - ์ ์ด ํŒŒ๊ดด๋˜์ง€ ์•Š์•˜์œผ๋ฉด ์•„๋ฌด๊ฒƒ๋„ ํ•˜์ง€ ์•Š์Œ
2936
+
2937
+ break; // ํ•˜๋‚˜์˜ ํƒ„ํ™˜์€ ํ•˜๋‚˜์˜ ์ ๋งŒ ๋งž์ถœ ์ˆ˜ ์žˆ์Œ
2938
  }
 
 
 
2939
  }
2940
  }
2941
+
2942
+ // ์  ํƒ„ํ™˜ vs ํ”Œ๋ ˆ์ด์–ด ์ถฉ๋Œ
2943
+ this.enemies.forEach(enemy => {
2944
+ for (let index = enemy.bullets.length - 1; index >= 0; index--) {
2945
+ const bullet = enemy.bullets[index];
2946
+ const distance = bullet.position.distanceTo(this.fighter.position);
2947
+ if (distance < 100) {
2948
+ // ํ”Œ๋ ˆ์ด์–ด ์œ„์น˜ ์ €์žฅ
2949
+ const playerPosition = this.fighter.position.clone();
 
 
 
 
 
 
 
 
 
 
 
 
 
2950
 
2951
+ // ํ”Œ๋ ˆ์ด์–ด ํ”ผ๊ฒฉ ์ดํŽ™ํŠธ
2952
+ this.createHitEffect(playerPosition);
2953
+
2954
+ // ํƒ„ํ™˜ ์ œ๊ฑฐ
2955
+ this.scene.remove(bullet);
2956
+ enemy.bullets.splice(index, 1);
2957
+
2958
+ if (this.fighter.takeDamage(GAME_CONSTANTS.BULLET_DAMAGE)) {
2959
+ console.log('Player destroyed! Creating explosion');
2960
+ // ํ”Œ๋ ˆ์ด์–ด ํŒŒ๊ดด ์‹œ ํญ๋ฐœ ํšจ๊ณผ ์ถ”๊ฐ€
2961
+ this.createExplosionEffect(playerPosition);
2962
+
2963
+ this.endGame(false);
2964
+ }
2965
  }
2966
  }
2967
+ });
2968
+ }
 
2969
 
2970
+ createHitEffect(position) {
2971
  // ํ”ผ๊ฒฉ ํŒŒํ‹ฐํด ํšจ๊ณผ ์ƒ์„ฑ
2972
  const particleCount = 15;
2973
  const particles = [];
 
3288
  }
3289
 
3290
  animate() {
3291
+ if (this.isGameOver) return;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3292
 
3293
+ this.animationFrameId = requestAnimationFrame(() => this.animate());
 
3294
 
3295
+ const currentTime = performance.now();
3296
+ const deltaTime = Math.min((currentTime - this.lastTime) / 1000, 0.1);
3297
+ this.lastTime = currentTime;
3298
 
3299
+ if (this.isLoaded && this.fighter.isLoaded && this.isStarted) {
3300
+ // ํ‚ค ์ƒํƒœ ๋””๋ฒ„๊น…
3301
+ if (this.keys.w || this.keys.s || this.keys.a || this.keys.d) {
3302
+ console.log('animate() - Keys state:', this.keys);
 
 
 
 
 
3303
  }
3304
+
3305
+ // Fํ‚ค ์ƒํƒœ๋ฅผ Fighter์— ์ „๋‹ฌ
3306
+ this.fighter.escapeKeyPressed = this.keys.f;
3307
+
3308
+ // ์ปจํŠธ๋กค ์—…๋ฐ์ดํŠธ - ๋ฐ˜๋“œ์‹œ ๋ฌผ๋ฆฌ ์—…๋ฐ์ดํŠธ ์ „์— ํ˜ธ์ถœ
3309
+ this.fighter.updateControls(this.keys, deltaTime);
3310
+
3311
+ // ๋ฝ์˜จ ์—…๋ฐ์ดํŠธ (AIM-9 ๋ชจ๋“œ์ผ ๋•Œ๋งŒ)
3312
+ this.fighter.updateLockOn(this.enemies, deltaTime);
3313
+
3314
+ // ๋ฌผ๋ฆฌ ์—…๋ฐ์ดํŠธ
3315
+ this.fighter.updatePhysics(deltaTime);
3316
+
3317
+ // ํƒ„ํ™˜ ์—…๋ฐ์ดํŠธ
3318
+ this.fighter.updateBullets(this.scene, deltaTime, this);
3319
+
3320
+ // ๋งˆ์šฐ์Šค ๋ˆ„๋ฆ„ ์ƒํƒœ์ผ ๋•Œ ์—ฐ์† ๋ฐœ์‚ฌ
3321
+ if (this.fighter.isMouseDown) {
3322
+ const currentShootTime = Date.now();
3323
+ if (!this.lastShootTime || currentShootTime - this.lastShootTime >= 100) {
3324
+ this.fighter.shoot(this.scene);
3325
+ this.lastShootTime = currentShootTime;
3326
+ }
3327
  }
3328
+
3329
+ // ์ ๊ธฐ ์—…๋ฐ์ดํŠธ
3330
+ this.enemies.forEach(enemy => {
3331
+ enemy.nearbyEnemies = this.enemies;
3332
+ });
3333
+
3334
+ this.enemies.forEach(enemy => {
3335
+ enemy.update(this.fighter.position, deltaTime);
3336
+ });
3337
+
3338
+ // ์ถฉ๋Œ ์ฒดํฌ
3339
+ this.checkCollisions();
3340
+
3341
+ // ๊ฒŒ์ž„ ์ข…๋ฃŒ ์กฐ๊ฑด ์ฒดํฌ
3342
+ if (this.fighter.health <= 0) {
3343
+ if (this.fighter.position.y <= 0) {
3344
+ this.endGame(false, "GROUND COLLISION");
3345
+ } else {
3346
+ this.endGame(false);
3347
+ }
3348
+ return;
3349
+ }
3350
+
3351
+ // UI ์—…๋ฐ์ดํŠธ
3352
+ this.updateUI();
3353
+ this.updateRadar();
3354
+
3355
+ // ์ ์ด ๋ชจ๋‘ ์ œ๊ฑฐ๋˜์—ˆ๋Š”์ง€ ์ฒดํฌ
3356
+ if (this.enemies.length === 0) {
3357
+ this.endGame(true);
3358
+ }
3359
+ } else if (this.isLoaded && this.fighter.isLoaded) {
3360
+ // ๊ฒŒ์ž„์ด ์‹œ์ž‘๋˜์ง€ ์•Š์•˜์„ ๋•Œ๋„ ๋ฌผ๋ฆฌ๋Š” ์—…๋ฐ์ดํŠธ (์นด๋ฉ”๋ผ ์›€์ง์ž„์„ ์œ„ํ•ด)
3361
+ this.fighter.updatePhysics(deltaTime);
3362
  }
3363
 
3364
+ // ๊ตฌ๋ฆ„ ์• ๋‹ˆ๋ฉ”์ด์…˜
3365
+ if (this.clouds) {
3366
+ this.clouds.forEach(cloud => {
3367
+ cloud.userData.time += deltaTime;
3368
+ cloud.position.x += cloud.userData.driftSpeed;
3369
+ cloud.position.y = cloud.userData.initialY +
3370
+ Math.sin(cloud.userData.time * cloud.userData.floatSpeed) * 20;
3371
+
3372
+ const mapLimit = GAME_CONSTANTS.MAP_SIZE / 2;
3373
+ if (cloud.position.x > mapLimit) cloud.position.x = -mapLimit;
3374
+ if (cloud.position.x < -mapLimit) cloud.position.x = mapLimit;
3375
+ });
3376
+ }
3377
 
3378
+ // ์นด๋ฉ”๋ผ ์—…๋ฐ์ดํŠธ
3379
+ if (this.fighter.isLoaded) {
3380
+ const targetCameraPos = this.fighter.getCameraPosition();
3381
+ const targetCameraTarget = this.fighter.getCameraTarget();
3382
+
3383
+ this.camera.position.lerp(targetCameraPos, this.fighter.cameraLag);
3384
+ this.camera.lookAt(targetCameraTarget);
3385
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3386
 
3387
+ this.renderer.render(this.scene, this.camera);
 
3388
  }
 
 
 
3389
 
3390
  endGame(victory = false, reason = "") {
3391
  this.isGameOver = true;