cutechicken commited on
Commit
02c0df6
ยท
verified ยท
1 Parent(s): b796cbc

Update game.js

Browse files
Files changed (1) hide show
  1. game.js +506 -87
game.js CHANGED
@@ -71,6 +71,11 @@ class Fighter {
71
  this.lastShootTime = 0;
72
  this.isMouseDown = false; // ๋งˆ์šฐ์Šค ๋ˆ„๋ฆ„ ์ƒํƒœ ์ถ”์ 
73
  this.gunfireAudios = []; // ๊ธฐ๊ด€์ด ์†Œ๋ฆฌ ๋ฐฐ์—ด (์ตœ๋Œ€ 5๊ฐœ)
 
 
 
 
 
74
 
75
  // AIM-9 ๋ฏธ์‚ฌ์ผ ์‹œ์Šคํ…œ
76
  this.aim9Missiles = GAME_CONSTANTS.AIM9_COUNT;
@@ -537,7 +542,7 @@ class Fighter {
537
  this.targetRoll *= 0.95;
538
  }
539
 
540
- // ์›Œ์ฌ๋” ์Šคํƒ€์ผ: ์š” ํšŒ์ „์ด ์ฃผ๋„์ , ๋กค์€ ๋ณด์กฐ์ 
541
  let bankTurnRate = 0;
542
  if (Math.abs(this.rotation.z) > 0.3) { // ๋กค์ด ์ถฉ๋ถ„ํžˆ ํด ๋•Œ๋งŒ
543
  const bankAngle = this.rotation.z;
@@ -850,10 +855,18 @@ class Fighter {
850
  this.updateWarningAudios();
851
 
852
  // ์—”์ง„ ์†Œ๋ฆฌ ๋ณผ๋ฅจ์„ ์Šค๋กœํ‹€์— ์—ฐ๋™
853
- if (this.warningAudios.normal && !this.warningAudios.normal.paused) {
854
- this.warningAudios.normal.volume = 0.3 + this.throttle * 0.4; // 0.3~0.7
855
- }
856
- }
 
 
 
 
 
 
 
 
857
 
858
  shoot(scene) {
859
  if (this.currentWeapon === 'MG') {
@@ -977,6 +990,65 @@ class Fighter {
977
  }
978
  }
979
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
980
 
981
  updateBullets(scene, deltaTime, gameInstance) {
982
  for (let i = this.bullets.length - 1; i >= 0; i--) {
@@ -1059,6 +1131,162 @@ class Fighter {
1059
  }
1060
  }
1061
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1062
  // AIM-9 ๋ฏธ์‚ฌ์ผ ํด๋ž˜์Šค
1063
  class AIM9Missile {
1064
  constructor(scene, position, target, rotation) {
@@ -1179,17 +1407,70 @@ class AIM9Missile {
1179
  group.add(flame);
1180
 
1181
  this.mesh = group;
1182
- this.mesh.scale.set(2.25, 2.25, 2.25); // 50% ๋” ํฌ๊ฒŒ (1.5 -> 2.25)
1183
  this.isLoaded = true;
1184
  }
1185
 
1186
- // AIM-9 ๋ฏธ์‚ฌ์ผ ํด๋ž˜์Šค - update ๋ฉ”์„œ๋“œ ์ˆ˜์ •
1187
  update(deltaTime, playerPosition) {
1188
  if (!this.mesh || !this.target || !this.target.position) {
1189
  this.destroy();
1190
  return 'expired';
1191
  }
1192
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1193
  this.lifeTime -= deltaTime;
1194
  if (this.lifeTime <= 0) {
1195
  this.destroy();
@@ -1298,7 +1579,7 @@ update(deltaTime, playerPosition) {
1298
 
1299
  // ํ•˜์–€ ์—ฐ๊ธฐ ํŒŒํ‹ฐํด ์ƒ์„ฑ
1300
  const smokeGeometry = new THREE.SphereGeometry(
1301
- 1 + Math.random() * 2.5, // ํฌ๊ธฐ ๋ณ€ํ™”
1302
  6,
1303
  6
1304
  );
@@ -1469,7 +1750,7 @@ class EnemyAIM9Missile {
1469
  group.add(flame);
1470
 
1471
  this.mesh = group;
1472
- this.mesh.scale.set(2.25, 2.25, 2.25);
1473
  this.mesh.position.copy(this.position);
1474
  this.scene.add(this.mesh);
1475
  this.isLoaded = true;
@@ -1561,7 +1842,7 @@ class EnemyAIM9Missile {
1561
 
1562
  // ํ•˜์–€ ์—ฐ๊ธฐ ํŒŒํ‹ฐํด ์ƒ์„ฑ
1563
  const smokeGeometry = new THREE.SphereGeometry(
1564
- 1 + Math.random() * 2.5,
1565
  6,
1566
  6
1567
  );
@@ -1623,6 +1904,7 @@ class EnemyAIM9Missile {
1623
  }
1624
 
1625
 
 
1626
  // ์  ์ „ํˆฌ๊ธฐ ํด๋ž˜์Šค - ์™„์ „ํžˆ ์žฌ์„ค๊ณ„
1627
  class EnemyFighter {
1628
  constructor(scene, position) {
@@ -1674,6 +1956,12 @@ class EnemyFighter {
1674
  this.lastMissileFireTime = 0;
1675
  this.isLocking = false;
1676
 
 
 
 
 
 
 
1677
  // ์ดˆ๊ธฐ ๋ชฉํ‘œ ์„ค์ •
1678
  this.selectNewPatrolTarget();
1679
  }
@@ -1724,61 +2012,82 @@ class EnemyFighter {
1724
  this.isLoaded = true;
1725
  }
1726
 
1727
- // EnemyFighter ํด๋ž˜์Šค์˜ update ๋ฉ”์„œ๋“œ์—์„œ ๋ฏธ์‚ฌ์ผ ํšŒํ”ผ ๋กœ์ง ์ œ๊ฑฐ
1728
- update(playerPosition, deltaTime) {
1729
- if (!this.mesh || !this.isLoaded) return;
1730
-
1731
- // ํšŒํ”ผ ํƒ€์ด๋จธ ์—…๋ฐ์ดํŠธ (๋ฏธ์‚ฌ์ผ ํšŒํ”ผ ์ œ์™ธ)
1732
- if (this.temporaryEvadeMode && this.evadeTimer > 0) {
1733
- this.evadeTimer -= deltaTime;
1734
- if (this.evadeTimer <= 0) {
1735
- this.temporaryEvadeMode = false;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1736
  }
1737
  }
1738
 
1739
- const distanceToPlayer = this.position.distanceTo(playerPosition);
1740
-
1741
- // ์ƒํƒœ ๊ฒฐ์ • (๋ฏธ์‚ฌ์ผ ํƒ์ง€ ์ œ๊ฑฐ)
1742
- if (this.temporaryEvadeMode) {
1743
- this.aiState = 'evade';
1744
- } else if (distanceToPlayer <= 3000) {
1745
- this.aiState = 'combat';
1746
- } else {
1747
- this.aiState = 'patrol';
1748
- }
1749
-
1750
- // ์ถฉ๋Œ ํšŒํ”ผ ๊ณ„์‚ฐ (๋‹ค๋ฅธ ์ ๊ธฐ์™€์˜ ์ถฉ๋Œ๋งŒ)
1751
- this.calculateAvoidance();
1752
- this.checkCollisionPrediction(deltaTime);
1753
-
1754
- // AI ํ–‰๋™ ์‹คํ–‰
1755
- switch (this.aiState) {
1756
- case 'patrol':
1757
- this.executePatrol(deltaTime);
1758
- break;
1759
- case 'combat':
1760
- this.executeCombat(playerPosition, deltaTime);
1761
- break;
1762
- case 'evade':
1763
- this.executeEmergencyEvade(deltaTime);
1764
- break;
1765
- }
1766
-
1767
- // ๋ฌผ๋ฆฌ ์—…๋ฐ์ดํŠธ
1768
- this.updatePhysics(deltaTime);
1769
-
1770
- // ํƒ„ํ™˜ ์—…๋ฐ์ดํŠธ
1771
- this.updateBullets(deltaTime);
1772
-
1773
- // ๋ฏธ์‚ฌ์ผ ์—…๋ฐ์ดํŠธ
1774
- this.updateMissiles(deltaTime);
1775
-
1776
- // ๋ฝ์˜จ ์‹œ์Šคํ…œ ์—…๋ฐ์ดํŠธ
1777
- if (this.playerFighter) {
1778
- this.updateLockOn(deltaTime);
1779
- }
1780
- }
1781
-
1782
  executePatrol(deltaTime) {
1783
  // ๋ชฉํ‘œ ์ง€์ ๊นŒ์ง€์˜ ๊ฑฐ๋ฆฌ ํ™•์ธ
1784
  if (!this.targetPosition || this.position.distanceTo(this.targetPosition) < 500) {
@@ -1914,8 +2223,6 @@ update(playerPosition, deltaTime) {
1914
  }
1915
 
1916
  smoothTurnToTarget(targetPos, deltaTime, isEmergency = false) {
1917
-
1918
-
1919
  // ๊ธฐ์กด ๋กœ์ง (ํ›„ํ‡ด๊ฐ€ ์•„๋‹ ๋•Œ)
1920
  const direction = targetPos.clone().sub(this.position);
1921
  direction.y *= 0.5;
@@ -2288,6 +2595,35 @@ update(playerPosition, deltaTime) {
2288
  }
2289
  }
2290
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2291
  calculateAimAccuracy(target) {
2292
  const toTarget = target.clone().sub(this.position).normalize();
2293
  const forward = new THREE.Vector3(0, 0, 1).applyEuler(this.rotation);
@@ -2333,14 +2669,14 @@ update(playerPosition, deltaTime) {
2333
  return angle;
2334
  }
2335
 
2336
- takeDamage(damage) {
2337
- console.log(`Enemy taking damage: ${damage}, Current health: ${this.health}`);
2338
- this.health -= damage;
2339
- console.log(`Enemy health after damage: ${this.health}`);
2340
- const isDead = this.health <= 0;
2341
- console.log(`Enemy is dead: ${isDead}`);
2342
- return isDead;
2343
- }
2344
 
2345
  destroy() {
2346
  if (this.mesh) {
@@ -2349,6 +2685,8 @@ update(playerPosition, deltaTime) {
2349
  this.bullets = [];
2350
  this.firedMissiles.forEach(missile => missile.destroy());
2351
  this.firedMissiles = [];
 
 
2352
  this.isLoaded = false;
2353
  }
2354
  }
@@ -2643,6 +2981,10 @@ class Game {
2643
  break;
2644
  case 'KeyF':
2645
  this.keys.f = true;
 
 
 
 
2646
  break;
2647
  case 'KeyR':
2648
  if (!event.repeat) { // ํ‚ค๋ฅผ ๋ˆ„๋ฅด๊ณ  ์žˆ์„ ๋•Œ ๋ฐ˜๋ณต ๋ฐฉ์ง€
@@ -2805,7 +3147,84 @@ class Game {
2805
  // ๊ฒฝ๊ณ ์Œ ์—…๋ฐ์ดํŠธ
2806
  this.fighter.updateWarningAudios();
2807
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2808
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2809
  updateUI() {
2810
  if (this.fighter.isLoaded) {
2811
  const speedKnots = Math.round(this.fighter.speed * 1.94384);
@@ -2833,21 +3252,21 @@ class Game {
2833
  if (timeElement) timeElement.textContent = `Time: ${this.gameTime}s`;
2834
 
2835
  // ๋ฌด๊ธฐ ํ‘œ์‹œ ์—…๋ฐ์ดํŠธ
2836
- if (ammoElement) {
2837
- if (this.fighter.currentWeapon === 'MG') {
2838
- ammoElement.textContent = `20MM MG: ${this.fighter.ammo}`;
2839
- ammoElement.style.color = '#0f0';
2840
- } else {
2841
- ammoElement.textContent = `AIM-9: ${this.fighter.aim9Missiles}`;
2842
- ammoElement.style.color = '#ff0000';
2843
-
2844
- // ๋ฝ์˜จ ์ƒํƒœ ํ‘œ์‹œ
2845
- if (this.fighter.lockTarget && this.fighter.lockProgress > 0) {
2846
- const lockStatus = this.fighter.lockProgress >= GAME_CONSTANTS.AIM9_LOCK_REQUIRED ? 'LOCKED' : `LOCKING ${this.fighter.lockProgress}/3`;
2847
- ammoElement.textContent += ` [${lockStatus}]`;
2848
- }
2849
- }
2850
- }
2851
 
2852
  if (gameStatsElement) {
2853
  gameStatsElement.innerHTML = `
 
71
  this.lastShootTime = 0;
72
  this.isMouseDown = false; // ๋งˆ์šฐ์Šค ๋ˆ„๋ฆ„ ์ƒํƒœ ์ถ”์ 
73
  this.gunfireAudios = []; // ๊ธฐ๊ด€์ด ์†Œ๋ฆฌ ๋ฐฐ์—ด (์ตœ๋Œ€ 5๊ฐœ)
74
+
75
+ // ํ”Œ๋ ˆ์–ด ์‹œ์Šคํ…œ
76
+ this.flareCount = 3; // ํ”Œ๋ ˆ์–ด 3ํšŒ ์‚ฌ์šฉ ๊ฐ€๋Šฅ
77
+ this.deployedFlares = []; // ๋ฐฐ์น˜๋œ ํ”Œ๋ ˆ์–ด ๋ฐฐ์—ด
78
+ this.lastFlareTime = 0; // ๋งˆ์ง€๋ง‰ ํ”Œ๋ ˆ์–ด ์‚ฌ์šฉ ์‹œ๊ฐ„
79
 
80
  // AIM-9 ๋ฏธ์‚ฌ์ผ ์‹œ์Šคํ…œ
81
  this.aim9Missiles = GAME_CONSTANTS.AIM9_COUNT;
 
542
  this.targetRoll *= 0.95;
543
  }
544
 
545
+ // ์š” ํšŒ์ „์ด ์ฃผ๋„์ , ๋กค์€ ๋ณด์กฐ์ 
546
  let bankTurnRate = 0;
547
  if (Math.abs(this.rotation.z) > 0.3) { // ๋กค์ด ์ถฉ๋ถ„ํžˆ ํด ๋•Œ๋งŒ
548
  const bankAngle = this.rotation.z;
 
855
  this.updateWarningAudios();
856
 
857
  // ์—”์ง„ ์†Œ๋ฆฌ ๋ณผ๋ฅจ์„ ์Šค๋กœํ‹€์— ์—ฐ๋™
858
+ if (this.warningAudios.normal && !this.warningAudios.normal.paused) {
859
+ this.warningAudios.normal.volume = 0.3 + this.throttle * 0.4; // 0.3~0.7
860
+ }
861
+
862
+ // ํ”Œ๋ ˆ์–ด ์—…๋ฐ์ดํŠธ
863
+ for (let i = this.deployedFlares.length - 1; i >= 0; i--) {
864
+ const flare = this.deployedFlares[i];
865
+ if (!flare.update(deltaTime)) {
866
+ this.deployedFlares.splice(i, 1);
867
+ }
868
+ }
869
+ }
870
 
871
  shoot(scene) {
872
  if (this.currentWeapon === 'MG') {
 
990
  }
991
  }
992
  }
993
+ deployFlares() {
994
+ if (this.flareCount <= 0) return;
995
+
996
+ const now = Date.now();
997
+ if (now - this.lastFlareTime < 2000) return; // 2์ดˆ ์ฟจ๋‹ค์šด
998
+
999
+ this.flareCount--;
1000
+ this.lastFlareTime = now;
1001
+
1002
+ // ๊ผฌ๋ฆฌ ๋ถ€๋ถ„ ์œ„์น˜ ๊ณ„์‚ฐ
1003
+ const tailOffset = new THREE.Vector3(0, 0, -15);
1004
+ const quaternion = new THREE.Quaternion();
1005
+ const pitchQuat = new THREE.Quaternion();
1006
+ const yawQuat = new THREE.Quaternion();
1007
+ const rollQuat = new THREE.Quaternion();
1008
+
1009
+ pitchQuat.setFromAxisAngle(new THREE.Vector3(1, 0, 0), this.rotation.x);
1010
+ yawQuat.setFromAxisAngle(new THREE.Vector3(0, 1, 0), this.rotation.y);
1011
+ rollQuat.setFromAxisAngle(new THREE.Vector3(0, 0, 1), this.rotation.z);
1012
+
1013
+ quaternion.multiply(rollQuat);
1014
+ quaternion.multiply(pitchQuat);
1015
+ quaternion.multiply(yawQuat);
1016
+
1017
+ tailOffset.applyQuaternion(quaternion);
1018
+ const flareStartPos = this.position.clone().add(tailOffset);
1019
+
1020
+ // ํ›„๋ฐฉ ๋ฐฉํ–ฅ
1021
+ const backwardDirection = new THREE.Vector3(0, 0, -1);
1022
+ backwardDirection.applyQuaternion(quaternion);
1023
+
1024
+ // ์–‘์ชฝ์œผ๋กœ 2๊ฐœ์”ฉ, ์ด 4๊ฐœ ํ”Œ๋ ˆ์–ด ๋ฐœ์‚ฌ
1025
+ for (let i = 0; i < 4; i++) {
1026
+ const isLeftSide = i < 2;
1027
+ const flare = new Flare(window.gameInstance.scene, flareStartPos, backwardDirection, isLeftSide);
1028
+ this.deployedFlares.push(flare);
1029
+
1030
+ // ๋ชจ๋“  ๋ฏธ์‚ฌ์ผ์—๊ฒŒ ํ”Œ๋ ˆ์–ด ์•Œ๋ฆผ
1031
+ if (window.gameInstance) {
1032
+ window.gameInstance.notifyMissilesOfFlare(flare);
1033
+ }
1034
+ }
1035
+
1036
+ // ํ”Œ๋ ˆ์–ด ๋ฐœ์‚ฌ์Œ
1037
+ try {
1038
+ const flareSound = new Audio('sounds/flare.ogg');
1039
+ flareSound.volume = 0.6;
1040
+ flareSound.play().catch(e => {
1041
+ console.log('Flare sound not found, using alternative');
1042
+ // ๋Œ€์ฒด ์†Œ๋ฆฌ (๊ธฐ์กด ์‚ฌ์šด๋“œ ํ™œ์šฉ)
1043
+ const altSound = new Audio('sounds/missile.ogg');
1044
+ altSound.volume = 0.3;
1045
+ altSound.playbackRate = 2.0; // ๋น ๋ฅด๊ฒŒ ์žฌ์ƒ
1046
+ altSound.play().catch(e => {});
1047
+ });
1048
+ } catch (e) {}
1049
+
1050
+ console.log(`Flares deployed! Remaining: ${this.flareCount}`);
1051
+ }
1052
 
1053
  updateBullets(scene, deltaTime, gameInstance) {
1054
  for (let i = this.bullets.length - 1; i >= 0; i--) {
 
1131
  }
1132
  }
1133
 
1134
+ // ํ”Œ๋ ˆ์–ด ํด๋ž˜์Šค
1135
+ class Flare {
1136
+ constructor(scene, position, direction, isLeftSide) {
1137
+ this.scene = scene;
1138
+ this.position = position.clone();
1139
+ this.velocity = direction.clone().multiplyScalar(200); // 200m/s ์†๋„
1140
+
1141
+ // ์ขŒ์šฐ๋กœ ๋ถ„์‚ฐ
1142
+ const sideOffset = isLeftSide ? -1 : 1;
1143
+ const perpendicular = new THREE.Vector3(sideOffset * 50, -20, (Math.random() - 0.5) * 30);
1144
+ this.velocity.add(perpendicular);
1145
+
1146
+ this.lifeTime = 5.0; // 5์ดˆ ์ง€์†
1147
+ this.mesh = null;
1148
+ this.createFlare();
1149
+
1150
+ // ์—ด ์ถ”์  ํŠน์„ฑ
1151
+ this.heatSignature = 1.0; // ์ดˆ๊ธฐ ์—ด ์‹ ํ˜ธ
1152
+ }
1153
+
1154
+ createFlare() {
1155
+ // ํ”Œ๋ ˆ์–ด ๋ฉ”์ธ ๋ชธ์ฒด
1156
+ const group = new THREE.Group();
1157
+
1158
+ // ๋ฐ์€ ์ค‘์‹ฌ๋ถ€
1159
+ const coreGeometry = new THREE.SphereGeometry(0.5, 6, 6);
1160
+ const coreMaterial = new THREE.MeshBasicMaterial({
1161
+ color: 0xffffff,
1162
+ transparent: true,
1163
+ opacity: 1.0
1164
+ });
1165
+ const core = new THREE.Mesh(coreGeometry, coreMaterial);
1166
+ group.add(core);
1167
+
1168
+ // ์—ด ๊ด‘์› ํšจ๊ณผ
1169
+ const glowGeometry = new THREE.SphereGeometry(2, 8, 8);
1170
+ const glowMaterial = new THREE.MeshBasicMaterial({
1171
+ color: 0xff6600,
1172
+ transparent: true,
1173
+ opacity: 0.6
1174
+ });
1175
+ const glow = new THREE.Mesh(glowGeometry, glowMaterial);
1176
+ group.add(glow);
1177
+
1178
+ // ์™ธ๋ถ€ ๊ด‘์›
1179
+ const outerGlowGeometry = new THREE.SphereGeometry(4, 8, 8);
1180
+ const outerGlowMaterial = new THREE.MeshBasicMaterial({
1181
+ color: 0xff3300,
1182
+ transparent: true,
1183
+ opacity: 0.3
1184
+ });
1185
+ const outerGlow = new THREE.Mesh(outerGlowGeometry, outerGlowMaterial);
1186
+ group.add(outerGlow);
1187
+
1188
+ this.mesh = group;
1189
+ this.mesh.position.copy(this.position);
1190
+ this.scene.add(this.mesh);
1191
+
1192
+ // ์—ฐ๊ธฐ ํŠธ๋ ˆ์ผ
1193
+ this.smokeTrail = [];
1194
+ this.smokeEmitTime = 0;
1195
+ }
1196
+
1197
+ update(deltaTime) {
1198
+ this.lifeTime -= deltaTime;
1199
+ if (this.lifeTime <= 0) {
1200
+ this.destroy();
1201
+ return false;
1202
+ }
1203
+
1204
+ // ์—ด ์‹ ํ˜ธ ๊ฐ์†Œ
1205
+ this.heatSignature = Math.max(0, this.heatSignature - deltaTime * 0.15);
1206
+
1207
+ // ์ค‘๋ ฅ ํšจ๊ณผ
1208
+ this.velocity.y -= 50 * deltaTime; // ์ค‘๋ ฅ ๊ฐ€์†๋„
1209
+
1210
+ // ์œ„์น˜ ์—…๋ฐ์ดํŠธ
1211
+ this.position.add(this.velocity.clone().multiplyScalar(deltaTime));
1212
+ this.mesh.position.copy(this.position);
1213
+
1214
+ // ๋ฐ๊ธฐ ๊ฐ์†Œ
1215
+ const fadeRatio = this.lifeTime / 5.0;
1216
+ this.mesh.children[0].material.opacity = fadeRatio;
1217
+ this.mesh.children[1].material.opacity = fadeRatio * 0.6;
1218
+ this.mesh.children[2].material.opacity = fadeRatio * 0.3;
1219
+
1220
+ // ์—ฐ๊ธฐ ์ƒ์„ฑ
1221
+ this.smokeEmitTime += deltaTime;
1222
+ if (this.smokeEmitTime >= 0.05) {
1223
+ this.smokeEmitTime = 0;
1224
+ this.createSmoke();
1225
+ }
1226
+
1227
+ // ์—ฐ๊ธฐ ์—…๋ฐ์ดํŠธ
1228
+ for (let i = this.smokeTrail.length - 1; i >= 0; i--) {
1229
+ const smoke = this.smokeTrail[i];
1230
+ smoke.life -= deltaTime;
1231
+
1232
+ if (smoke.life <= 0) {
1233
+ this.scene.remove(smoke.mesh);
1234
+ this.smokeTrail.splice(i, 1);
1235
+ } else {
1236
+ smoke.mesh.scale.multiplyScalar(1.05);
1237
+ smoke.mesh.material.opacity = smoke.life / 2.0 * 0.5;
1238
+ }
1239
+ }
1240
+
1241
+ // ์ง€๋ฉด ์ถฉ๋Œ
1242
+ if (this.position.y <= 0) {
1243
+ this.destroy();
1244
+ return false;
1245
+ }
1246
+
1247
+ return true;
1248
+ }
1249
+
1250
+ createSmoke() {
1251
+ const smokeGeometry = new THREE.SphereGeometry(1 + Math.random() * 1, 6, 6);
1252
+ const smokeMaterial = new THREE.MeshBasicMaterial({
1253
+ color: 0xcccccc,
1254
+ transparent: true,
1255
+ opacity: 0.5
1256
+ });
1257
+
1258
+ const smoke = new THREE.Mesh(smokeGeometry, smokeMaterial);
1259
+ smoke.position.copy(this.position);
1260
+ smoke.position.add(new THREE.Vector3(
1261
+ (Math.random() - 0.5) * 2,
1262
+ (Math.random() - 0.5) * 2,
1263
+ (Math.random() - 0.5) * 2
1264
+ ));
1265
+
1266
+ this.scene.add(smoke);
1267
+ this.smokeTrail.push({
1268
+ mesh: smoke,
1269
+ life: 2.0
1270
+ });
1271
+
1272
+ // ์—ฐ๊ธฐ ์ œํ•œ
1273
+ if (this.smokeTrail.length > 30) {
1274
+ const oldSmoke = this.smokeTrail.shift();
1275
+ this.scene.remove(oldSmoke.mesh);
1276
+ }
1277
+ }
1278
+
1279
+ destroy() {
1280
+ if (this.mesh) {
1281
+ this.scene.remove(this.mesh);
1282
+ }
1283
+ this.smokeTrail.forEach(smoke => {
1284
+ this.scene.remove(smoke.mesh);
1285
+ });
1286
+ this.smokeTrail = [];
1287
+ }
1288
+ }
1289
+
1290
  // AIM-9 ๋ฏธ์‚ฌ์ผ ํด๋ž˜์Šค
1291
  class AIM9Missile {
1292
  constructor(scene, position, target, rotation) {
 
1407
  group.add(flame);
1408
 
1409
  this.mesh = group;
1410
+ this.mesh.scale.set(3.375, 3.375, 3.375); // 50% ๋” ํฌ๊ฒŒ (1.5 -> 2.25)
1411
  this.isLoaded = true;
1412
  }
1413
 
1414
+ // AIM-9 ๋ฏธ์‚ฌ์ผ ํด๋ž˜์Šค - update ๋ฉ”์„œ๋“œ ์ˆ˜์ •
1415
  update(deltaTime, playerPosition) {
1416
  if (!this.mesh || !this.target || !this.target.position) {
1417
  this.destroy();
1418
  return 'expired';
1419
  }
1420
 
1421
+ // *** ํ”Œ๋ ˆ์–ด ํƒ์ง€ ๋กœ์ง ์ถ”๊ฐ€ ์‹œ์ž‘ ***
1422
+ // ํ”Œ๋ ˆ์–ด ํƒ์ง€ ๋ฐ ๋ชฉํ‘œ ๋ณ€๊ฒฝ
1423
+ if (window.gameInstance && !this.isTargetingFlare) {
1424
+ const nearestFlare = window.gameInstance.findNearestFlare(this.position);
1425
+ if (nearestFlare) {
1426
+ const flareDistance = this.position.distanceTo(nearestFlare.position);
1427
+ if (flareDistance < 500 && nearestFlare.heatSignature > 0.3) {
1428
+ // ํ”Œ๋ ˆ์–ด๋กœ ๋ชฉํ‘œ ๋ณ€๊ฒฝ
1429
+ console.log('Missile redirected to flare!');
1430
+ this.target = nearestFlare;
1431
+ this.isTargetingFlare = true;
1432
+ }
1433
+ }
1434
+ }
1435
+
1436
+ // ํ”Œ๋ ˆ์–ด๋ฅผ ์ถ”์  ์ค‘์ด๊ณ  ํ”Œ๋ ˆ์–ด๊ฐ€ ์†Œ๋ฉธํ–ˆ๋‹ค๋ฉด
1437
+ if (this.isTargetingFlare && (!this.target.mesh || this.target.lifeTime <= 0)) {
1438
+ // ์ง์„ ์œผ๋กœ ๊ณ„์† ๋น„ํ–‰
1439
+ this.position.add(this.velocity.clone().multiplyScalar(deltaTime));
1440
+ this.mesh.position.copy(this.position);
1441
+
1442
+ this.lifeTime -= deltaTime;
1443
+ if (this.lifeTime <= 0 || this.position.y <= 0) {
1444
+ this.destroy();
1445
+ return 'expired';
1446
+ }
1447
+
1448
+ // ์—ฐ๊ธฐ๋Š” ๊ณ„์† ์ƒ์„ฑ
1449
+ this.smokeEmitTime += deltaTime;
1450
+ if (this.smokeEmitTime >= 0.02) {
1451
+ this.smokeEmitTime = 0;
1452
+ this.createSmokeParticle();
1453
+ }
1454
+
1455
+ // ์—ฐ๊ธฐ ํŒŒํ‹ฐํด ์—…๋ฐ์ดํŠธ
1456
+ for (let i = this.smokeTrail.length - 1; i >= 0; i--) {
1457
+ const smoke = this.smokeTrail[i];
1458
+ smoke.life -= deltaTime;
1459
+
1460
+ if (smoke.life <= 0) {
1461
+ this.scene.remove(smoke.mesh);
1462
+ this.smokeTrail.splice(i, 1);
1463
+ } else {
1464
+ smoke.mesh.scale.multiplyScalar(1.02);
1465
+ smoke.mesh.material.opacity = smoke.life / 3.0;
1466
+ smoke.mesh.position.y += deltaTime * 2;
1467
+ }
1468
+ }
1469
+
1470
+ return 'flying';
1471
+ }
1472
+ // *** ํ”Œ๋ ˆ์–ด ํƒ์ง€ ๋กœ์ง ์ถ”๊ฐ€ ๋ ***
1473
+
1474
  this.lifeTime -= deltaTime;
1475
  if (this.lifeTime <= 0) {
1476
  this.destroy();
 
1579
 
1580
  // ํ•˜์–€ ์—ฐ๊ธฐ ํŒŒํ‹ฐํด ์ƒ์„ฑ
1581
  const smokeGeometry = new THREE.SphereGeometry(
1582
+ 1 + Math.random() * 3.5, // ํฌ๊ธฐ ๋ณ€ํ™”
1583
  6,
1584
  6
1585
  );
 
1750
  group.add(flame);
1751
 
1752
  this.mesh = group;
1753
+ this.mesh.scale.set(3.375, 3.375, 3.375);
1754
  this.mesh.position.copy(this.position);
1755
  this.scene.add(this.mesh);
1756
  this.isLoaded = true;
 
1842
 
1843
  // ํ•˜์–€ ์—ฐ๊ธฐ ํŒŒํ‹ฐํด ์ƒ์„ฑ
1844
  const smokeGeometry = new THREE.SphereGeometry(
1845
+ 1 + Math.random() * 3.5,
1846
  6,
1847
  6
1848
  );
 
1904
  }
1905
 
1906
 
1907
+ // ์  ์ „ํˆฌ๊ธฐ ํด๋ž˜์Šค - ์™„์ „ํžˆ ์žฌ์„ค๊ณ„
1908
  // ์  ์ „ํˆฌ๊ธฐ ํด๋ž˜์Šค - ์™„์ „ํžˆ ์žฌ์„ค๊ณ„
1909
  class EnemyFighter {
1910
  constructor(scene, position) {
 
1956
  this.lastMissileFireTime = 0;
1957
  this.isLocking = false;
1958
 
1959
+ // ํ”Œ๋ ˆ์–ด ์‹œ์Šคํ…œ
1960
+ this.flareCount = 1; // ํ”Œ๋ ˆ์–ด 1ํšŒ๋งŒ ์‚ฌ์šฉ ๊ฐ€๋Šฅ
1961
+ this.deployedFlares = [];
1962
+ this.lastFlareTime = 0;
1963
+ this.hasUsedFlare = false;
1964
+
1965
  // ์ดˆ๊ธฐ ๋ชฉํ‘œ ์„ค์ •
1966
  this.selectNewPatrolTarget();
1967
  }
 
2012
  this.isLoaded = true;
2013
  }
2014
 
2015
+ update(playerPosition, deltaTime) {
2016
+ if (!this.mesh || !this.isLoaded) return;
2017
+
2018
+ // ํ”Œ๋ ˆ์–ด ์—…๋ฐ์ดํŠธ ์ถ”๊ฐ€
2019
+ for (let i = this.deployedFlares.length - 1; i >= 0; i--) {
2020
+ const flare = this.deployedFlares[i];
2021
+ if (!flare.update(deltaTime)) {
2022
+ this.deployedFlares.splice(i, 1);
2023
+ }
2024
+ }
2025
+
2026
+ // ๋ฏธ์‚ฌ์ผ ์œ„ํ˜‘ ๊ฐ์ง€ ๋ฐ ํ”Œ๋ ˆ์–ด ์‚ฌ์šฉ
2027
+ if (!this.hasUsedFlare && this.flareCount > 0 && window.gameInstance) {
2028
+ const incomingMissiles = window.gameInstance.findIncomingMissilesForEnemy(this);
2029
+ if (incomingMissiles.length > 0) {
2030
+ const nearestMissile = incomingMissiles[0];
2031
+ const missileDistance = this.position.distanceTo(nearestMissile.position);
2032
+
2033
+ if (missileDistance < 500) {
2034
+ this.deployFlares();
2035
+ this.hasUsedFlare = true;
2036
+ }
2037
+ }
2038
+ }
2039
+
2040
+ // ํšŒํ”ผ ํƒ€์ด๋จธ ์—…๋ฐ์ดํŠธ (๋ฏธ์‚ฌ์ผ ํšŒํ”ผ ์ œ์™ธ)
2041
+ if (this.temporaryEvadeMode && this.evadeTimer > 0) {
2042
+ this.evadeTimer -= deltaTime;
2043
+ if (this.evadeTimer <= 0) {
2044
+ this.temporaryEvadeMode = false;
2045
+ }
2046
+ }
2047
+
2048
+ const distanceToPlayer = this.position.distanceTo(playerPosition);
2049
+
2050
+ // ์ƒํƒœ ๊ฒฐ์ • (๋ฏธ์‚ฌ์ผ ํƒ์ง€ ์ œ๊ฑฐ)
2051
+ if (this.temporaryEvadeMode) {
2052
+ this.aiState = 'evade';
2053
+ } else if (distanceToPlayer <= 3000) {
2054
+ this.aiState = 'combat';
2055
+ } else {
2056
+ this.aiState = 'patrol';
2057
+ }
2058
+
2059
+ // ์ถฉ๋Œ ํšŒํ”ผ ๊ณ„์‚ฐ (๋‹ค๋ฅธ ์ ๊ธฐ์™€์˜ ์ถฉ๋Œ๋งŒ)
2060
+ this.calculateAvoidance();
2061
+ this.checkCollisionPrediction(deltaTime);
2062
+
2063
+ // AI ํ–‰๋™ ์‹คํ–‰
2064
+ switch (this.aiState) {
2065
+ case 'patrol':
2066
+ this.executePatrol(deltaTime);
2067
+ break;
2068
+ case 'combat':
2069
+ this.executeCombat(playerPosition, deltaTime);
2070
+ break;
2071
+ case 'evade':
2072
+ this.executeEmergencyEvade(deltaTime);
2073
+ break;
2074
+ }
2075
+
2076
+ // ๋ฌผ๋ฆฌ ์—…๋ฐ์ดํŠธ
2077
+ this.updatePhysics(deltaTime);
2078
+
2079
+ // ํƒ„ํ™˜ ์—…๋ฐ์ดํŠธ
2080
+ this.updateBullets(deltaTime);
2081
+
2082
+ // ๋ฏธ์‚ฌ์ผ ์—…๋ฐ์ดํŠธ
2083
+ this.updateMissiles(deltaTime);
2084
+
2085
+ // ๋ฝ์˜จ ์‹œ์Šคํ…œ ์—…๋ฐ์ดํŠธ
2086
+ if (this.playerFighter) {
2087
+ this.updateLockOn(deltaTime);
2088
  }
2089
  }
2090
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2091
  executePatrol(deltaTime) {
2092
  // ๋ชฉํ‘œ ์ง€์ ๊นŒ์ง€์˜ ๊ฑฐ๋ฆฌ ํ™•์ธ
2093
  if (!this.targetPosition || this.position.distanceTo(this.targetPosition) < 500) {
 
2223
  }
2224
 
2225
  smoothTurnToTarget(targetPos, deltaTime, isEmergency = false) {
 
 
2226
  // ๊ธฐ์กด ๋กœ์ง (ํ›„ํ‡ด๊ฐ€ ์•„๋‹ ๋•Œ)
2227
  const direction = targetPos.clone().sub(this.position);
2228
  direction.y *= 0.5;
 
2595
  }
2596
  }
2597
 
2598
+ // ์ ๊ธฐ์šฉ ํ”Œ๋ ˆ์–ด ๋ฐฐ์น˜ ๋ฉ”์„œ๋“œ ์ถ”๊ฐ€
2599
+ deployFlares() {
2600
+ if (this.flareCount <= 0) return;
2601
+
2602
+ this.flareCount--;
2603
+
2604
+ // ๊ผฌ๋ฆฌ ๋ถ€๋ถ„ ์œ„์น˜ ๊ณ„์‚ฐ
2605
+ const tailOffset = new THREE.Vector3(0, 0, -10);
2606
+ tailOffset.applyEuler(this.rotation);
2607
+ const flareStartPos = this.position.clone().add(tailOffset);
2608
+
2609
+ // ํ›„๋ฐฉ ๋ฐฉํ–ฅ
2610
+ const backwardDirection = new THREE.Vector3(0, 0, -1);
2611
+ backwardDirection.applyEuler(this.rotation);
2612
+
2613
+ // 4๊ฐœ ํ”Œ๋ ˆ์–ด ๋ฐœ์‚ฌ
2614
+ for (let i = 0; i < 4; i++) {
2615
+ const isLeftSide = i < 2;
2616
+ const flare = new Flare(this.scene, flareStartPos, backwardDirection, isLeftSide);
2617
+ this.deployedFlares.push(flare);
2618
+
2619
+ if (window.gameInstance) {
2620
+ window.gameInstance.notifyMissilesOfFlare(flare);
2621
+ }
2622
+ }
2623
+
2624
+ console.log('Enemy deployed flares!');
2625
+ }
2626
+
2627
  calculateAimAccuracy(target) {
2628
  const toTarget = target.clone().sub(this.position).normalize();
2629
  const forward = new THREE.Vector3(0, 0, 1).applyEuler(this.rotation);
 
2669
  return angle;
2670
  }
2671
 
2672
+ takeDamage(damage) {
2673
+ console.log(`Enemy taking damage: ${damage}, Current health: ${this.health}`);
2674
+ this.health -= damage;
2675
+ console.log(`Enemy health after damage: ${this.health}`);
2676
+ const isDead = this.health <= 0;
2677
+ console.log(`Enemy is dead: ${isDead}`);
2678
+ return isDead;
2679
+ }
2680
 
2681
  destroy() {
2682
  if (this.mesh) {
 
2685
  this.bullets = [];
2686
  this.firedMissiles.forEach(missile => missile.destroy());
2687
  this.firedMissiles = [];
2688
+ this.deployedFlares.forEach(flare => flare.destroy());
2689
+ this.deployedFlares = [];
2690
  this.isLoaded = false;
2691
  }
2692
  }
 
2981
  break;
2982
  case 'KeyF':
2983
  this.keys.f = true;
2984
+ // Fํ‚ค ๋ˆ„๋ฅผ ๋•Œ ํ”Œ๋ ˆ์–ด ๋ฐœ์‚ฌ
2985
+ if (!event.repeat && this.fighter) {
2986
+ this.fighter.deployFlares();
2987
+ }
2988
  break;
2989
  case 'KeyR':
2990
  if (!event.repeat) { // ํ‚ค๋ฅผ ๋ˆ„๋ฅด๊ณ  ์žˆ์„ ๋•Œ ๋ฐ˜๋ณต ๋ฐฉ์ง€
 
3147
  // ๊ฒฝ๊ณ ์Œ ์—…๋ฐ์ดํŠธ
3148
  this.fighter.updateWarningAudios();
3149
  }
3150
+ notifyMissilesOfFlare(flare) {
3151
+ // ๋ชจ๋“  ํ™œ์„ฑ ๋ฏธ์‚ฌ์ผ์—๊ฒŒ ํ”Œ๋ ˆ์–ด ์กด์žฌ ์•Œ๋ฆผ
3152
+ const allMissiles = [];
3153
+
3154
+ // ํ”Œ๋ ˆ์ด์–ด ๋ฏธ์‚ฌ์ผ
3155
+ this.fighter.firedMissiles.forEach(missile => {
3156
+ allMissiles.push(missile);
3157
+ });
3158
+
3159
+ // ์  ๋ฏธ์‚ฌ์ผ
3160
+ this.enemies.forEach(enemy => {
3161
+ enemy.firedMissiles.forEach(missile => {
3162
+ allMissiles.push(missile);
3163
+ });
3164
+ });
3165
+
3166
+ // ๊ฐ ๋ฏธ์‚ฌ์ผ์ด ํ”Œ๋ ˆ์–ด๋ฅผ ๊ฐ์ง€ํ•˜๋„๋ก
3167
+ allMissiles.forEach(missile => {
3168
+ if (missile && missile.position) {
3169
+ const distance = missile.position.distanceTo(flare.position);
3170
+ if (distance < 500) {
3171
+ // ๋ฏธ์‚ฌ์ผ์ด ํ”Œ๋ ˆ์–ด๋ฅผ ๊ฐ์ง€
3172
+ missile.detectedFlare = flare;
3173
+ }
3174
+ }
3175
+ });
3176
+ }
3177
 
3178
+ findNearestFlare(position) {
3179
+ let nearestFlare = null;
3180
+ let nearestDistance = Infinity;
3181
+
3182
+ // ํ”Œ๋ ˆ์ด์–ด ํ”Œ๋ ˆ์–ด
3183
+ this.fighter.deployedFlares.forEach(flare => {
3184
+ if (flare.heatSignature > 0.3) {
3185
+ const distance = position.distanceTo(flare.position);
3186
+ if (distance < nearestDistance) {
3187
+ nearestDistance = distance;
3188
+ nearestFlare = flare;
3189
+ }
3190
+ }
3191
+ });
3192
+
3193
+ // ์  ํ”Œ๋ ˆ์–ด
3194
+ this.enemies.forEach(enemy => {
3195
+ enemy.deployedFlares.forEach(flare => {
3196
+ if (flare.heatSignature > 0.3) {
3197
+ const distance = position.distanceTo(flare.position);
3198
+ if (distance < nearestDistance) {
3199
+ nearestDistance = distance;
3200
+ nearestFlare = flare;
3201
+ }
3202
+ }
3203
+ });
3204
+ });
3205
+
3206
+ return nearestFlare;
3207
+ }
3208
+
3209
+ findIncomingMissilesForEnemy(enemy) {
3210
+ const incomingMissiles = [];
3211
+
3212
+ // ํ”Œ๋ ˆ์ด์–ด๊ฐ€ ๋ฐœ์‚ฌํ•œ ๋ฏธ์‚ฌ์ผ ์ค‘ ์ด ์ ์„ ์ถ”์ ํ•˜๋Š” ๊ฒƒ
3213
+ this.fighter.firedMissiles.forEach(missile => {
3214
+ if (missile.target === enemy) {
3215
+ incomingMissiles.push(missile);
3216
+ }
3217
+ });
3218
+
3219
+ // ๊ฑฐ๋ฆฌ์ˆœ์œผ๋กœ ์ •๋ ฌ
3220
+ incomingMissiles.sort((a, b) => {
3221
+ const distA = enemy.position.distanceTo(a.position);
3222
+ const distB = enemy.position.distanceTo(b.position);
3223
+ return distA - distB;
3224
+ });
3225
+
3226
+ return incomingMissiles;
3227
+ }
3228
  updateUI() {
3229
  if (this.fighter.isLoaded) {
3230
  const speedKnots = Math.round(this.fighter.speed * 1.94384);
 
3252
  if (timeElement) timeElement.textContent = `Time: ${this.gameTime}s`;
3253
 
3254
  // ๋ฌด๊ธฐ ํ‘œ์‹œ ์—…๋ฐ์ดํŠธ
3255
+ if (ammoElement) {
3256
+ if (this.fighter.currentWeapon === 'MG') {
3257
+ ammoElement.innerHTML = `20MM MG: ${this.fighter.ammo}<br>FLARES: ${this.fighter.flareCount}`;
3258
+ ammoElement.style.color = '#0f0';
3259
+ } else {
3260
+ ammoElement.innerHTML = `AIM-9: ${this.fighter.aim9Missiles}<br>FLARES: ${this.fighter.flareCount}`;
3261
+ ammoElement.style.color = '#ff0000';
3262
+
3263
+ // ๋ฝ์˜จ ์ƒํƒœ ํ‘œ์‹œ
3264
+ if (this.fighter.lockTarget && this.fighter.lockProgress > 0) {
3265
+ const lockStatus = this.fighter.lockProgress >= GAME_CONSTANTS.AIM9_LOCK_REQUIRED ? 'LOCKED' : `LOCKING ${this.fighter.lockProgress}/3`;
3266
+ ammoElement.innerHTML = `AIM-9: ${this.fighter.aim9Missiles}<br>FLARES: ${this.fighter.flareCount}<br>[${lockStatus}]`;
3267
+ }
3268
+ }
3269
+ }
3270
 
3271
  if (gameStatsElement) {
3272
  gameStatsElement.innerHTML = `