cutechicken commited on
Commit
fe8a7bc
ยท
verified ยท
1 Parent(s): 24645b3

Update game.js

Browse files
Files changed (1) hide show
  1. game.js +342 -517
game.js CHANGED
@@ -1382,15 +1382,6 @@ class EnemyFighter {
1382
 
1383
  // ์ดˆ๊ธฐ ๋ชฉํ‘œ ์„ค์ •
1384
  this.selectNewPatrolTarget();
1385
- // AIM-9 ๋ฏธ์‚ฌ์ผ ์‹œ์Šคํ…œ
1386
- this.aim9Missiles = 4; // ์ ์€ 4๋ฐœ์˜ AIM-9 ๋ณด์œ 
1387
- this.firedMissiles = [];
1388
- this.currentWeapon = 'MG'; // ๊ธฐ๋ณธ ๋ฌด๊ธฐ
1389
- this.lockTarget = null;
1390
- this.lockProgress = 0;
1391
- this.lastLockTime = 0;
1392
- this.lastMissileFireTime = 0; // ๋งˆ์ง€๋ง‰ ๋ฏธ์‚ฌ์ผ ๋ฐœ์‚ฌ ์‹œ๊ฐ„
1393
- }
1394
  }
1395
 
1396
  async initialize(loader) {
@@ -1439,11 +1430,11 @@ class EnemyFighter {
1439
  this.isLoaded = true;
1440
  }
1441
 
1442
- // EnemyFighter ํด๋ž˜์Šค์˜ update ๋ฉ”์„œ๋“œ - ๋ฏธ์‚ฌ์ผ ๊ธฐ๋Šฅ ์ถ”๊ฐ€
1443
  update(playerPosition, deltaTime) {
1444
  if (!this.mesh || !this.isLoaded) return;
1445
 
1446
- // ํšŒํ”ผ ํƒ€์ด๋จธ ์—…๋ฐ์ดํŠธ
1447
  if (this.temporaryEvadeMode && this.evadeTimer > 0) {
1448
  this.evadeTimer -= deltaTime;
1449
  if (this.evadeTimer <= 0) {
@@ -1453,39 +1444,7 @@ update(playerPosition, deltaTime) {
1453
 
1454
  const distanceToPlayer = this.position.distanceTo(playerPosition);
1455
 
1456
- // ํ”Œ๋ ˆ์ด์–ด์˜ AIM-9 ๋ฏธ์‚ฌ์ผ ๊ฐ์ง€ ๋ฐ ํšŒํ”ผ
1457
- if (this.playerFighter && this.playerFighter.firedMissiles) {
1458
- for (const missile of this.playerFighter.firedMissiles) {
1459
- if (!missile || !missile.target) continue;
1460
-
1461
- // ์ด ์ ๊ธฐ๋ฅผ ํƒ€๊ฒŸ์œผ๋กœ ํ•˜๋Š” ๋ฏธ์‚ฌ์ผ ๊ฐ์ง€
1462
- if (missile.target === this && missile.position) {
1463
- const missileDistance = this.position.distanceTo(missile.position);
1464
-
1465
- // 1500m ์ด๋‚ด์˜ ๋ฏธ์‚ฌ์ผ ๊ฐ์ง€ ์‹œ ๊ธด๊ธ‰ ํšŒํ”ผ
1466
- if (missileDistance < 1500) {
1467
- this.aiState = 'evade';
1468
- this.temporaryEvadeMode = true;
1469
- this.evadeTimer = 3.0; // 3์ดˆ๊ฐ„ ํšŒํ”ผ ๊ธฐ๋™
1470
-
1471
- // ๋ฏธ์‚ฌ์ผ ๋ฐ˜๋Œ€ ๋ฐฉํ–ฅ์œผ๋กœ ํšŒํ”ผ
1472
- const evadeDirection = this.position.clone().sub(missile.position).normalize();
1473
- this.targetPosition = this.position.clone().add(evadeDirection.multiplyScalar(2000));
1474
-
1475
- // ๊ณ ๋„ ๋ณ€ํ™” ์ถ”๊ฐ€
1476
- if (this.position.y > 3000) {
1477
- this.targetPosition.y -= 1000; // ๊ธ‰ํ•˜๊ฐ•
1478
- } else {
1479
- this.targetPosition.y += 1000; // ๊ธ‰์ƒ์Šน
1480
- }
1481
-
1482
- break; // ํ•˜๋‚˜์˜ ๋ฏธ์‚ฌ์ผ๋งŒ ํšŒํ”ผ
1483
- }
1484
- }
1485
- }
1486
- }
1487
-
1488
- // ์ƒํƒœ ๊ฒฐ์ •
1489
  if (this.temporaryEvadeMode) {
1490
  this.aiState = 'evade';
1491
  } else if (distanceToPlayer <= 3000) {
@@ -1516,48 +1475,31 @@ update(playerPosition, deltaTime) {
1516
 
1517
  // ํƒ„ํ™˜ ์—…๋ฐ์ดํŠธ
1518
  this.updateBullets(deltaTime);
1519
-
1520
- // AIM-9 ๋ฏธ์‚ฌ์ผ ์—…๋ฐ์ดํŠธ
1521
- this.updateMissiles(deltaTime);
1522
-
1523
- // ๋ฝ์˜จ ์—…๋ฐ์ดํŠธ (์ „ํˆฌ ์ƒํƒœ์—์„œ๋งŒ)
1524
- if (this.aiState === 'combat' && this.playerFighter) {
1525
- this.updateLockOn(deltaTime);
1526
- }
1527
  }
1528
-
1529
- executePatrol(deltaTime) {
1530
- // ๋ชฉํ‘œ ์ง€์ ๊นŒ์ง€์˜ ๊ฑฐ๋ฆฌ ํ™•์ธ
1531
- if (!this.targetPosition || this.position.distanceTo(this.targetPosition) < 500) {
1532
- this.selectNewPatrolTarget();
1533
- }
1534
 
1535
- // ๋ชฉํ‘œ๋ฅผ ํ–ฅํ•ด ๋ถ€๋“œ๋Ÿฝ๊ฒŒ ํšŒ์ „
1536
- this.smoothTurnToTarget(this.targetPosition, deltaTime);
1537
-
1538
- // ์†๋„ ์œ ์ง€ (500-750kt) - ํ•ญ์ƒ ์ตœ์†Œ ์†๋„ ์ด์ƒ ์œ ์ง€
1539
- if (this.speed < this.minSpeed) {
1540
- this.speed = this.minSpeed;
1541
- }
1542
- this.speed = THREE.MathUtils.clamp(this.speed, this.minSpeed, this.maxSpeed);
1543
- }
1544
-
1545
- executeCombat(playerPosition, deltaTime) {
1546
- const distance = this.position.distanceTo(playerPosition);
1547
-
1548
- // ๋ฌด๊ธฐ ์„ ํƒ ๋กœ์ง
1549
- if (distance > 1500 && distance < 5000 && this.aim9Missiles > 0) {
1550
- // ์ค‘๊ฑฐ๋ฆฌ์—์„œ AIM-9 ์‚ฌ์šฉ
1551
- this.currentWeapon = 'AIM9';
1552
- } else {
1553
- // ๊ทผ๊ฑฐ๋ฆฌ ๋˜๋Š” ๋ฏธ์‚ฌ์ผ ์—†์„ ๋•Œ MG ์‚ฌ์šฉ
1554
- this.currentWeapon = 'MG';
1555
  }
1556
 
1557
- // ํ”Œ๋ ˆ์ด์–ด๋ฅผ ํ–ฅํ•ด ํšŒ์ „
1558
- this.smoothTurnToTarget(playerPosition, deltaTime);
1559
-
1560
- if (this.currentWeapon === 'MG') {
 
 
 
1561
  // ์กฐ์ค€ ์ •ํ™•๋„ ํ™•์ธ
1562
  const aimAccuracy = this.calculateAimAccuracy(playerPosition);
1563
 
@@ -1576,74 +1518,122 @@ executeCombat(playerPosition, deltaTime) {
1576
  // ๋ฐœ์‚ฌํ•  ์ˆ˜ ์—†์œผ๋ฉด ์—ฐ๋ฐœ ์นด์šดํ„ฐ ๋ฆฌ์…‹
1577
  this.burstCounter = 0;
1578
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1579
  }
1580
- // AIM-9 ๋ชจ๋“œ์—์„œ๋Š” ๋ฝ์˜จ๋งŒ ์‹œ๋„ (๋ฐœ์‚ฌ๋Š” updateLockOn์—์„œ ์ฒ˜๋ฆฌ)
1581
-
1582
- // ๊ฑฐ๋ฆฌ์— ๋”ฐ๋ผ ๊ธฐ๋™ ๊ฒฐ์ •
1583
- if (distance < 150) {
1584
- // 150m ์ด๋‚ด๋ฉด ํšŒํ”ผ ๊ธฐ๋™ ์‹œ์ž‘
1585
- this.selectEvadeTarget();
1586
- // ์ผ์‹œ์  ํšŒํ”ผ ๋ชจ๋“œ ํ™œ์„ฑํ™”
1587
- this.temporaryEvadeMode = true;
1588
- this.evadeTimer = 1.0;
1589
- } else if (distance < 500) {
1590
- // 500m ์ด๋‚ด๋ฉด ์ธก๋ฉด ํšŒํ”ผ
1591
- this.selectEvadeTarget();
1592
- } else if (distance > 2000 && this.currentWeapon === 'MG') {
1593
- // MG ๋ชจ๋“œ์—์„œ๋งŒ ์ ‘๊ทผ
1594
- this.targetPosition = playerPosition.clone();
1595
- }
1596
- }
1597
-
1598
- // ์ƒˆ๋กœ์šด ๋ฉ”์„œ๋“œ: ์ถฉ๋Œ ์˜ˆ์ธก
1599
- checkCollisionPrediction(deltaTime) {
1600
- if (!this.nearbyEnemies) return;
1601
-
1602
- // 2์ดˆ ํ›„ ์˜ˆ์ƒ ์œ„์น˜ ๊ณ„์‚ฐ
1603
- const predictTime = 2.0;
1604
- const forward = new THREE.Vector3(0, 0, 1).applyEuler(this.rotation);
1605
- this.predictedPosition.copy(this.position).add(forward.multiplyScalar(this.speed * predictTime));
1606
 
1607
- this.nearbyEnemies.forEach(enemy => {
1608
- if (enemy === this || !enemy.position) return;
 
1609
 
1610
- // ๋‹ค๋ฅธ ์ ๊ธฐ์˜ ์˜ˆ์ƒ ์œ„์น˜
1611
- const enemyForward = new THREE.Vector3(0, 0, 1).applyEuler(enemy.rotation);
1612
- const enemyPredicted = enemy.position.clone().add(enemyForward.multiplyScalar(enemy.speed * predictTime));
1613
-
1614
- // ์˜ˆ์ƒ ๊ฑฐ๋ฆฌ
1615
- const predictedDistance = this.predictedPosition.distanceTo(enemyPredicted);
1616
 
1617
- // 150m ์ด๋‚ด๋กœ ์ ‘๊ทผ ์˜ˆ์ƒ ์‹œ ์‚ฌ์ „ ํšŒํ”ผ
1618
- if (predictedDistance < 150) {
1619
- // ์˜ˆ๋ฐฉ์  ํšŒํ”ผ ๋ฐฉํ–ฅ ์„ค์ •
1620
- const avoidDir = this.predictedPosition.clone().sub(enemyPredicted).normalize();
 
 
1621
 
1622
- // ์ˆ˜์ง ๋ถ„๋ฆฌ ์ถ”๊ฐ€
1623
- if (this.position.y > enemy.position.y) {
1624
- avoidDir.y += 0.3;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1625
  } else {
1626
- avoidDir.y -= 0.3;
1627
  }
1628
 
1629
- this.avoidanceVector.add(avoidDir.multiplyScalar(1.5));
 
 
 
 
 
 
 
 
 
 
 
 
1630
  }
1631
- });
1632
- }
1633
-
1634
- // ์ƒˆ๋กœ์šด ๋ฉ”์„œ๋“œ: ๊ธด๊ธ‰ ํšŒํ”ผ ์‹คํ–‰
1635
- executeEmergencyEvade(deltaTime) {
1636
- // ํšŒํ”ผ ๋ฒกํ„ฐ๊ฐ€ ์žˆ์œผ๋ฉด ๊ทธ ๋ฐฉํ–ฅ์œผ๋กœ, ์—†์œผ๋ฉด ๊ธ‰์ƒ์Šน
1637
- if (this.avoidanceVector.length() > 0) {
1638
- const evadeDirection = this.avoidanceVector.clone().normalize();
1639
 
1640
- // ๊ธ‰์„ ํšŒ
1641
- const targetYaw = Math.atan2(evadeDirection.x, evadeDirection.z);
1642
- const targetPitch = Math.asin(-evadeDirection.y) * 0.5; // ํ”ผ์น˜๋Š” ์ ˆ๋ฐ˜๋งŒ
 
 
1643
 
1644
- // ๋น ๋ฅธ ํšŒ์ „ ์†๋„
1645
- const emergencyTurnSpeed = this.turnSpeed * 2.0;
1646
- const maxTurnRate = emergencyTurnSpeed * deltaTime;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1647
 
1648
  // Yaw ํšŒ์ „
1649
  const yawDiff = this.normalizeAngle(targetYaw - this.rotation.y);
@@ -1653,181 +1643,132 @@ executeEmergencyEvade(deltaTime) {
1653
  this.rotation.y = targetYaw;
1654
  }
1655
 
1656
- // Pitch ํšŒ์ „
1657
- const pitchDiff = targetPitch - this.rotation.x;
1658
- if (Math.abs(pitchDiff) > maxTurnRate * 0.7) {
1659
- this.rotation.x += Math.sign(pitchDiff) * maxTurnRate * 0.7;
1660
  } else {
1661
  this.rotation.x = targetPitch;
1662
  }
1663
 
1664
- // ๊ธ‰์„ ํšŒ ์‹œ ์•ฝ๊ฐ„์˜ ๋กค ์ถ”๊ฐ€
1665
- this.rotation.z = Math.sign(yawDiff) * Math.min(Math.abs(yawDiff), Math.PI / 6);
1666
- } else {
1667
- // ๊ธฐ๋ณธ ํšŒํ”ผ: ๊ธ‰์ƒ์Šน
1668
- this.rotation.x = -Math.PI / 6; // 30๋„ ์ƒ์Šน
1669
- }
1670
-
1671
- // ํšŒํ”ผ ์ค‘์—๋Š” ์ตœ๋Œ€ ์†๋„
1672
- this.speed = this.maxSpeed;
1673
- }
1674
-
1675
- smoothTurnToTarget(targetPos, deltaTime, isEmergency = false) {
1676
- // ๊ธฐ์กด ๋กœ์ง (ํ›„ํ‡ด๊ฐ€ ์•„๋‹ ๋•Œ)
1677
- const direction = targetPos.clone().sub(this.position);
1678
- direction.y *= 0.5;
1679
- direction.normalize();
1680
-
1681
- // ์ถฉ๋Œ ํšŒํ”ผ ๋ฒกํ„ฐ ์ ์šฉ (ํšŒํ”ผ๊ฐ€ ์šฐ์„ )
1682
- if (this.avoidanceVector.length() > 0) {
1683
- const avoidanceStrength = this.temporaryEvadeMode ? 1.0 : 0.5;
1684
- direction.add(this.avoidanceVector.multiplyScalar(avoidanceStrength));
1685
- direction.normalize();
1686
- }
1687
-
1688
- // ๋ชฉํ‘œ ํšŒ์ „ ๊ณ„์‚ฐ
1689
- const targetYaw = Math.atan2(direction.x, direction.z);
1690
- const targetPitch = Math.asin(-direction.y);
1691
-
1692
- // ๋ถ€๋“œ๋Ÿฌ์šด ํšŒ์ „ (์ตœ๋Œ€ ํšŒ์ „ ์†๋„ ์ œํ•œ)
1693
- const turnSpeed = isEmergency ? this.turnSpeed * 2.0 : (this.temporaryEvadeMode ? this.turnSpeed * 1.5 : this.turnSpeed);
1694
- const maxTurnRate = turnSpeed * deltaTime;
1695
-
1696
- // Yaw ํšŒ์ „
1697
- const yawDiff = this.normalizeAngle(targetYaw - this.rotation.y);
1698
- if (Math.abs(yawDiff) > maxTurnRate) {
1699
- this.rotation.y += Math.sign(yawDiff) * maxTurnRate;
1700
- } else {
1701
- this.rotation.y = targetYaw;
1702
- }
1703
-
1704
- // Pitch ํšŒ์ „ (์ œํ•œ์ )
1705
- const maxPitchRate = maxTurnRate * 0.7;
1706
- if (Math.abs(targetPitch - this.rotation.x) > maxPitchRate) {
1707
- this.rotation.x += Math.sign(targetPitch - this.rotation.x) * maxPitchRate;
1708
- } else {
1709
- this.rotation.x = targetPitch;
1710
- }
1711
-
1712
- // Pitch ์ œํ•œ (ยฑ40๋„)
1713
- const maxPitchAngle = Math.PI * 40 / 180;
1714
- this.rotation.x = THREE.MathUtils.clamp(this.rotation.x, -maxPitchAngle, maxPitchAngle);
1715
-
1716
- // ๋กค ์ž๋™ ๊ณ„์‚ฐ (์„ ํšŒ ์‹œ)
1717
- if (!this.temporaryEvadeMode) {
1718
- this.rotation.z = -yawDiff * 0.5;
1719
- this.rotation.z = THREE.MathUtils.clamp(this.rotation.z, -Math.PI / 4, Math.PI / 4);
1720
  }
1721
- }
1722
-
1723
- calculateAvoidance() {
1724
- this.avoidanceVector.set(0, 0, 0);
1725
-
1726
- if (!this.nearbyEnemies) return;
1727
 
1728
- let avoidCount = 0;
1729
- let criticalAvoidance = false;
1730
-
1731
- this.nearbyEnemies.forEach(enemy => {
1732
- if (enemy === this || !enemy.position) return;
1733
 
1734
- const distance = this.position.distanceTo(enemy.position);
 
1735
 
1736
- // 100m ๋ฏธ๋งŒ: ๊ธด๊ธ‰ ํšŒํ”ผ
1737
- if (distance < 100 && distance > 0) {
1738
- criticalAvoidance = true;
1739
 
1740
- // ๊ฐ•ํ•œ ๋ฐ˜๋ฐœ๋ ฅ
1741
- const avoidDir = this.position.clone().sub(enemy.position).normalize();
1742
- const strength = 2.0; // ๋งค์šฐ ๊ฐ•ํ•œ ํšŒํ”ผ
1743
- this.avoidanceVector.add(avoidDir.multiplyScalar(strength));
1744
 
1745
- // ๊ณ ๋„ ์ฐจ์ด ์ถ”๊ฐ€ (์œ„/์•„๋ž˜๋กœ ๋ถ„์‚ฐ)
1746
- if (this.position.y > enemy.position.y) {
1747
- this.avoidanceVector.y += 0.5; // ์œ„๋กœ
1748
- } else {
1749
- this.avoidanceVector.y -= 0.5; // ์•„๋ž˜๋กœ
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1750
  }
 
 
 
 
 
1751
 
1752
- avoidCount++;
1753
- }
1754
- // 100-300m: ์˜ˆ๋ฐฉ์  ํšŒํ”ผ
1755
- else if (distance < 300) {
1756
- // ๋ฐ˜๋Œ€ ๋ฐฉํ–ฅ์œผ๋กœ ํšŒํ”ผ
1757
- const avoidDir = this.position.clone().sub(enemy.position).normalize();
1758
- const strength = (300 - distance) / 200; // 100-300m ๋ฒ”์œ„์—์„œ ๊ฐ•๋„ ๊ณ„์‚ฐ
1759
- this.avoidanceVector.add(avoidDir.multiplyScalar(strength));
1760
- avoidCount++;
1761
- }
1762
- });
1763
-
1764
- if (avoidCount > 0) {
1765
- this.avoidanceVector.divideScalar(avoidCount);
1766
- this.avoidanceVector.normalize();
1767
-
1768
- // ๊ธด๊ธ‰ ํšŒํ”ผ ์‹œ ๋” ๊ฐ•ํ•œ ํšŒํ”ผ๋ ฅ ์ ์šฉ
1769
- if (criticalAvoidance) {
1770
- this.avoidanceVector.multiplyScalar(2.0);
1771
- // ์ผ์‹œ์ ์œผ๋กœ ์ „ํˆฌ ์ƒํƒœ ํ•ด์ œ
1772
- this.temporaryEvadeMode = true;
1773
- this.evadeTimer = 2.0; // 2์ดˆ ๋™์•ˆ ํšŒํ”ผ ์šฐ์„ 
1774
  }
1775
  }
1776
- }
1777
-
1778
- updatePhysics(deltaTime) {
1779
- if (!this.mesh) return;
1780
 
1781
- // ์†๋„ ๋ฒกํ„ฐ ๊ณ„์‚ฐ (ํ•ญ์ƒ ์ „์ง„)
1782
- const forward = new THREE.Vector3(0, 0, 1);
1783
- forward.applyEuler(this.rotation);
1784
-
1785
- // ์†๋„ ์œ ์ง€ (500-750kt, m/s๋กœ ๋ณ€ํ™˜) - ๊ฐ•์ œ๋กœ ์ตœ์†Œ ์†๋„ ์œ ์ง€
1786
- if (this.speed < this.minSpeed) {
1787
- this.speed = this.minSpeed;
1788
- }
1789
- this.speed = THREE.MathUtils.clamp(this.speed, this.minSpeed, this.maxSpeed);
1790
-
1791
- // ์†๋„ ๋ฒกํ„ฐ ์ƒ์„ฑ - ํ•ญ์ƒ ์ „์ง„
1792
- this.velocity = forward.multiplyScalar(this.speed);
1793
-
1794
- // ์œ„์น˜ ์—…๋ฐ์ดํŠธ - ํ•ญ์ƒ ์ด๋™
1795
- this.position.add(this.velocity.clone().multiplyScalar(deltaTime));
1796
-
1797
- // ๊ณ ๋„ ์ œํ•œ
1798
- if (this.position.y < 500) {
1799
- this.position.y = 500;
1800
- this.rotation.x = -0.2; // ์ƒ์Šน
1801
- } else if (this.position.y > 8000) {
1802
- this.position.y = 8000;
1803
- this.rotation.x = 0.2; // ํ•˜๊ฐ•
1804
- }
1805
-
1806
- // ๋งต ๊ฒฝ๊ณ„ ์ฒ˜๋ฆฌ
1807
- const mapLimit = GAME_CONSTANTS.MAP_SIZE / 2;
1808
- const boundaryBuffer = mapLimit * 0.9;
1809
-
1810
- if (Math.abs(this.position.x) > boundaryBuffer || Math.abs(this.position.z) > boundaryBuffer) {
1811
- // ๋งต ์ค‘์•™์„ ํ–ฅํ•ด ํšŒ์ „
1812
- const centerDirection = new THREE.Vector3(-this.position.x, 0, -this.position.z).normalize();
1813
- const targetYaw = Math.atan2(centerDirection.x, centerDirection.z);
1814
- this.rotation.y = targetYaw;
1815
- this.selectNewPatrolTarget(); // ์ƒˆ๋กœ์šด ๋ชฉํ‘œ ์„ ํƒ
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1816
  }
1817
 
1818
- // ํ•˜๋“œ ๋ฆฌ๋ฏธํŠธ
1819
- this.position.x = THREE.MathUtils.clamp(this.position.x, -mapLimit, mapLimit);
1820
- this.position.z = THREE.MathUtils.clamp(this.position.z, -mapLimit, mapLimit);
1821
-
1822
- // ๋ฉ”์‹œ ์—…๋ฐ์ดํŠธ
1823
- this.mesh.position.copy(this.position);
1824
- this.mesh.rotation.x = this.rotation.x;
1825
- this.mesh.rotation.y = this.rotation.y + 3 * Math.PI / 2; // ํ”Œ๋ ˆ์ด์–ด์™€ ๋™์ผํ•œ ํšŒ์ „ ์˜คํ”„์…‹
1826
- this.mesh.rotation.z = this.rotation.z;
1827
- }
1828
-
1829
- fireWeapon() {
1830
- if (this.currentWeapon === 'MG') {
1831
  const now = Date.now();
1832
 
1833
  // 0.1์ดˆ์— 1๋ฐœ์”ฉ, 10๋ฐœ ์—ฐ๋ฐœ
@@ -1849,227 +1790,119 @@ fireWeapon() {
1849
  }
1850
  }
1851
  }
1852
- // AIM-9๋Š” updateLockOn์—์„œ ์ž๋™ ๋ฐœ์‚ฌ
1853
- }
1854
-
1855
- shoot() {
1856
- // ํƒ„ํ™˜ ์ƒ์„ฑ (ํ”Œ๋ ˆ์ด์–ด์™€ ๋™์ผํ•œ ํฌ๊ธฐ)
1857
- const bulletGeometry = new THREE.CylinderGeometry(1.0, 1.0, 16, 8);
1858
- const bulletMaterial = new THREE.MeshBasicMaterial({
1859
- color: 0xff0000,
1860
- });
1861
- const bullet = new THREE.Mesh(bulletGeometry, bulletMaterial);
1862
-
1863
- // ๊ธฐ์ˆ˜ ๋์—์„œ๋งŒ ๋ฐœ์‚ฌ - ์ •ํ™•ํžˆ ์ „๋ฐฉ ์ค‘์•™์—์„œ
1864
- const muzzleOffset = new THREE.Vector3(0, 0, 10); // X=0, Y=0์œผ๋กœ ์ค‘์•™ ๊ณ ์ •
1865
- muzzleOffset.applyEuler(this.rotation);
1866
- bullet.position.copy(this.position).add(muzzleOffset);
1867
-
1868
- // ํƒ„ํ™˜์„ ๋ฐœ์‚ฌ ๋ฐฉํ–ฅ์œผ๋กœ ํšŒ์ „
1869
- bullet.rotation.copy(this.rotation);
1870
- bullet.rotateX(Math.PI / 2); // ์‹ค๋ฆฐ๋”๊ฐ€ Z์ถ• ๋ฐฉํ–ฅ์„ ํ–ฅํ•˜๋„๋ก
1871
-
1872
- // ํƒ„ํ™˜ ์ดˆ๊ธฐ ์œ„์น˜ ์ €์žฅ
1873
- bullet.startPosition = bullet.position.clone();
1874
-
1875
- // ํƒ„ํ™˜ ์†๋„ (ํ”Œ๋ ˆ์ด์–ด์™€ ๋™์ผํ•˜๊ฒŒ 1500) - ์ •ํ™•ํžˆ ์ „๋ฐฉ์œผ๋กœ
1876
- const direction = new THREE.Vector3(0, 0, 1);
1877
- direction.applyEuler(this.rotation);
1878
- bullet.velocity = direction.multiplyScalar(1500);
1879
 
1880
- this.scene.add(bullet);
1881
- this.bullets.push(bullet);
1882
-
1883
- // ์‚ฌ์šด๋“œ ์žฌ์ƒ
1884
- if (this.playerFighter) {
1885
- const distanceToPlayer = this.position.distanceTo(this.playerFighter.position);
1886
- if (distanceToPlayer < 3000) {
1887
- try {
1888
- const audio = new Audio('sounds/MGLAUNCH.ogg');
1889
- const volumeMultiplier = 1 - (distanceToPlayer / 3000);
1890
- audio.volume = 0.5 * volumeMultiplier;
1891
- audio.play().catch(e => {});
1892
- } catch (e) {}
1893
- }
1894
- }
1895
- }
1896
-
1897
- updateBullets(deltaTime) {
1898
- for (let i = this.bullets.length - 1; i >= 0; i--) {
1899
- const bullet = this.bullets[i];
1900
- bullet.position.add(bullet.velocity.clone().multiplyScalar(deltaTime));
1901
-
1902
- // ์ง€๋ฉด ์ถฉ๋Œ
1903
- if (bullet.position.y <= 0) {
1904
- if (window.gameInstance) {
1905
- window.gameInstance.createGroundImpactEffect(bullet.position);
1906
- }
1907
- this.scene.remove(bullet);
1908
- this.bullets.splice(i, 1);
1909
- continue;
1910
- }
1911
 
1912
- // ๊ฑฐ๋ฆฌ ์ œํ•œ (ํ”Œ๋ ˆ์ด์–ด์™€ ๋™์ผํ•˜๊ฒŒ 6000m)
1913
- if (bullet.position.distanceTo(bullet.startPosition) > 6000 ||
1914
- bullet.position.y > GAME_CONSTANTS.MAX_ALTITUDE + 500) {
1915
- this.scene.remove(bullet);
1916
- this.bullets.splice(i, 1);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1917
  }
1918
  }
1919
- }
1920
-
1921
- // ์ ๊ธฐ์šฉ AIM-9 ๋ฝ์˜จ ์‹œ์Šคํ…œ
1922
- updateLockOn(deltaTime) {
1923
- if (!this.playerFighter || this.currentWeapon !== 'AIM9') {
1924
- this.lockTarget = null;
1925
- this.lockProgress = 0;
1926
- return;
1927
- }
1928
-
1929
- const now = Date.now();
1930
- if (now - this.lastLockTime < 1000) return;
1931
-
1932
- const distance = this.position.distanceTo(this.playerFighter.position);
1933
 
1934
- // ๊ฑฐ๋ฆฌ์™€ ๊ฐ๋„ ์ฒดํฌ
1935
- if (distance < GAME_CONSTANTS.AIM9_LOCK_RANGE) {
1936
- const toPlayer = this.playerFighter.position.clone().sub(this.position).normalize();
1937
- const forward = new THREE.Vector3(0, 0, 1).applyEuler(this.rotation);
1938
- const dotProduct = forward.dot(toPlayer);
1939
- const angle = Math.acos(Math.max(-1, Math.min(1, dotProduct)));
1940
-
1941
- // 15๋„ ์ด๋‚ด์— ์žˆ์œผ๋ฉด ๋ฝ์˜จ
1942
- if (angle < Math.PI / 12) {
1943
- if (this.lockTarget === this.playerFighter) {
1944
- this.lockProgress++;
1945
- this.lastLockTime = now;
1946
-
1947
- // ๋ฝ์˜จ ์‹œ์ž‘ ์‹œ ํ”Œ๋ ˆ์ด์–ด์—๊ฒŒ ๊ฒฝ๊ณ 
1948
- if (this.lockProgress === 1 && window.gameInstance) {
1949
- window.gameInstance.onEnemyLockStart(this);
1950
- }
1951
-
1952
- // ๋ฝ์˜จ ์™„๋ฃŒ ์‹œ ๋ฏธ์‚ฌ์ผ ๋ฐœ์‚ฌ
1953
- if (this.lockProgress >= GAME_CONSTANTS.AIM9_LOCK_REQUIRED) {
1954
- this.fireMissile();
1955
- }
1956
- } else {
1957
- // ์ƒˆ๋กœ์šด ๋ฝ์˜จ ์‹œ์ž‘
1958
- this.lockTarget = this.playerFighter;
1959
- this.lockProgress = 1;
1960
- this.lastLockTime = now;
1961
-
1962
  if (window.gameInstance) {
1963
- window.gameInstance.onEnemyLockStart(this);
1964
  }
 
 
 
1965
  }
1966
- } else {
1967
- // ํƒ€๊ฒŸ ์žƒ์Œ
1968
- if (this.lockTarget && window.gameInstance) {
1969
- window.gameInstance.onEnemyLockLost(this);
 
 
1970
  }
1971
- this.lockTarget = null;
1972
- this.lockProgress = 0;
1973
  }
1974
- } else {
1975
- // ๋ฒ”์œ„ ๋ฐ–
1976
- if (this.lockTarget && window.gameInstance) {
1977
- window.gameInstance.onEnemyLockLost(this);
1978
- }
1979
- this.lockTarget = null;
1980
- this.lockProgress = 0;
1981
  }
1982
- }
1983
-
1984
- // ์ ๊ธฐ ๋ฏธ์‚ฌ์ผ ๋ฐœ์‚ฌ
1985
- fireMissile() {
1986
- if (this.aim9Missiles <= 0 || !this.lockTarget) return;
1987
-
1988
- const now = Date.now();
1989
- // ๋ฏธ์‚ฌ์ผ ๋ฐœ์‚ฌ ๊ฐ„๊ฒฉ ์ตœ์†Œ 5์ดˆ
1990
- if (now - this.lastMissileFireTime < 5000) return;
1991
-
1992
- this.aim9Missiles--;
1993
- this.lastMissileFireTime = now;
1994
 
1995
- // ๋‚ ๊ฐœ ์œ„์น˜์—์„œ ๋ฐœ์‚ฌ
1996
- const isLeftWing = Math.random() < 0.5;
1997
- const wingOffset = new THREE.Vector3(isLeftWing ? -6 : 6, -0.5, 1);
1998
- wingOffset.applyEuler(this.rotation);
1999
- const missileStartPos = this.position.clone().add(wingOffset);
2000
-
2001
- // ์ ๊ธฐ์šฉ AIM-9 ๋ฏธ์‚ฌ์ผ ์ƒ์„ฑ
2002
- const missile = new EnemyAIM9Missile(this.scene, missileStartPos, this.lockTarget, this.rotation.clone());
2003
- this.firedMissiles.push(missile);
2004
-
2005
- // ๋ฝ์˜จ ์ดˆ๊ธฐํ™”
2006
- this.lockTarget = null;
2007
- this.lockProgress = 0;
2008
-
2009
- // ํ”Œ๋ ˆ์ด์–ด์—๊ฒŒ ๋ฏธ์‚ฌ์ผ ๋ฐœ์‚ฌ ๊ฒฝ๊ณ 
2010
- if (window.gameInstance) {
2011
- window.gameInstance.onEnemyMissileLaunch(this, missile);
2012
- }
2013
- }
2014
-
2015
- // ๋ฏธ์‚ฌ์ผ ์—…๋ฐ์ดํŠธ
2016
- updateMissiles(deltaTime) {
2017
- for (let i = this.firedMissiles.length - 1; i >= 0; i--) {
2018
- const missile = this.firedMissiles[i];
2019
- const result = missile.update(deltaTime, this.position);
2020
-
2021
- if (result === 'hit' || result === 'expired') {
2022
- this.firedMissiles.splice(i, 1);
2023
- }
2024
  }
2025
- }
2026
-
2027
- calculateAimAccuracy(target) {
2028
- const toTarget = target.clone().sub(this.position).normalize();
2029
- const forward = new THREE.Vector3(0, 0, 1).applyEuler(this.rotation);
2030
- const dotProduct = forward.dot(toTarget);
2031
- return Math.acos(Math.max(-1, Math.min(1, dotProduct)));
2032
- }
2033
-
2034
- selectNewPatrolTarget() {
2035
- const mapLimit = GAME_CONSTANTS.MAP_SIZE / 2 * 0.7;
2036
 
2037
- // ๋” ๋‹ค์–‘ํ•œ ๊ณ ๋„ ์„ ํƒ
2038
- const minAltitude = 1000;
2039
- const maxAltitude = 6000;
 
 
 
 
 
 
 
 
 
 
2040
 
2041
- this.targetPosition = new THREE.Vector3(
2042
- (Math.random() - 0.5) * 2 * mapLimit,
2043
- minAltitude + Math.random() * (maxAltitude - minAltitude),
2044
- (Math.random() - 0.5) * 2 * mapLimit
2045
- );
2046
- }
2047
-
2048
- selectEvadeTarget() {
2049
- // ํ˜„์žฌ ์œ„์น˜์—์„œ ๋žœ๋ค ๋ฐฉํ–ฅ์œผ๋กœ ํšŒํ”ผ
2050
- const evadeDistance = 1000 + Math.random() * 1000;
2051
- const evadeAngle = Math.random() * Math.PI * 2;
 
 
 
 
 
 
2052
 
2053
- this.targetPosition = new THREE.Vector3(
2054
- this.position.x + Math.cos(evadeAngle) * evadeDistance,
2055
- this.position.y + (Math.random() - 0.5) * 500,
2056
- this.position.z + Math.sin(evadeAngle) * evadeDistance
2057
- );
2058
 
2059
- // ๋งต ๊ฒฝ๊ณ„ ํ™•์ธ
2060
- const mapLimit = GAME_CONSTANTS.MAP_SIZE / 2 * 0.8;
2061
- this.targetPosition.x = THREE.MathUtils.clamp(this.targetPosition.x, -mapLimit, mapLimit);
2062
- this.targetPosition.z = THREE.MathUtils.clamp(this.targetPosition.z, -mapLimit, mapLimit);
2063
- this.targetPosition.y = THREE.MathUtils.clamp(this.targetPosition.y, 1000, 6000);
2064
- }
2065
-
2066
- normalizeAngle(angle) {
2067
- while (angle > Math.PI) angle -= Math.PI * 2;
2068
- while (angle < -Math.PI) angle += Math.PI * 2;
2069
- return angle;
2070
- }
2071
-
2072
- takeDamage(damage) {
2073
  console.log(`Enemy taking damage: ${damage}, Current health: ${this.health}`);
2074
  this.health -= damage;
2075
  console.log(`Enemy health after damage: ${this.health}`);
@@ -2077,22 +1910,14 @@ takeDamage(damage) {
2077
  console.log(`Enemy is dead: ${isDead}`);
2078
  return isDead;
2079
  }
2080
-
2081
- destroy() {
2082
- if (this.mesh) {
2083
- this.scene.remove(this.mesh);
2084
- this.bullets.forEach(bullet => this.scene.remove(bullet));
2085
- this.bullets = [];
2086
-
2087
- // ๋ฏธ์‚ฌ์ผ๋„ ์ œ๊ฑฐ
2088
- this.firedMissiles.forEach(missile => {
2089
- if (missile.destroy) {
2090
- missile.destroy();
2091
- }
2092
- });
2093
- this.firedMissiles = [];
2094
-
2095
- this.isLoaded = false;
2096
  }
2097
  }
2098
 
 
1382
 
1383
  // ์ดˆ๊ธฐ ๋ชฉํ‘œ ์„ค์ •
1384
  this.selectNewPatrolTarget();
 
 
 
 
 
 
 
 
 
1385
  }
1386
 
1387
  async initialize(loader) {
 
1430
  this.isLoaded = true;
1431
  }
1432
 
1433
+ // EnemyFighter ํด๋ž˜์Šค์˜ update ๋ฉ”์„œ๋“œ์—์„œ ๋ฏธ์‚ฌ์ผ ํšŒํ”ผ ๋กœ์ง ์ œ๊ฑฐ
1434
  update(playerPosition, deltaTime) {
1435
  if (!this.mesh || !this.isLoaded) return;
1436
 
1437
+ // ํšŒํ”ผ ํƒ€์ด๋จธ ์—…๋ฐ์ดํŠธ (๋ฏธ์‚ฌ์ผ ํšŒํ”ผ ์ œ์™ธ)
1438
  if (this.temporaryEvadeMode && this.evadeTimer > 0) {
1439
  this.evadeTimer -= deltaTime;
1440
  if (this.evadeTimer <= 0) {
 
1444
 
1445
  const distanceToPlayer = this.position.distanceTo(playerPosition);
1446
 
1447
+ // ์ƒํƒœ ๊ฒฐ์ • (๋ฏธ์‚ฌ์ผ ํƒ์ง€ ์ œ๊ฑฐ)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1448
  if (this.temporaryEvadeMode) {
1449
  this.aiState = 'evade';
1450
  } else if (distanceToPlayer <= 3000) {
 
1475
 
1476
  // ํƒ„ํ™˜ ์—…๋ฐ์ดํŠธ
1477
  this.updateBullets(deltaTime);
 
 
 
 
 
 
 
 
1478
  }
 
 
 
 
 
 
1479
 
1480
+ executePatrol(deltaTime) {
1481
+ // ๋ชฉํ‘œ ์ง€์ ๊นŒ์ง€์˜ ๊ฑฐ๋ฆฌ ํ™•์ธ
1482
+ if (!this.targetPosition || this.position.distanceTo(this.targetPosition) < 500) {
1483
+ this.selectNewPatrolTarget();
1484
+ }
1485
+
1486
+ // ๋ชฉํ‘œ๋ฅผ ํ–ฅํ•ด ๋ถ€๋“œ๋Ÿฝ๊ฒŒ ํšŒ์ „
1487
+ this.smoothTurnToTarget(this.targetPosition, deltaTime);
1488
+
1489
+ // ์†๋„ ์œ ์ง€ (500-750kt) - ํ•ญ์ƒ ์ตœ์†Œ ์†๋„ ์ด์ƒ ์œ ์ง€
1490
+ if (this.speed < this.minSpeed) {
1491
+ this.speed = this.minSpeed;
1492
+ }
1493
+ this.speed = THREE.MathUtils.clamp(this.speed, this.minSpeed, this.maxSpeed);
 
 
 
 
 
 
1494
  }
1495
 
1496
+ executeCombat(playerPosition, deltaTime) {
1497
+ const distance = this.position.distanceTo(playerPosition);
1498
+
1499
+
1500
+ // ํ”Œ๋ ˆ์ด์–ด๋ฅผ ํ–ฅํ•ด ํšŒ์ „
1501
+ this.smoothTurnToTarget(playerPosition, deltaTime);
1502
+
1503
  // ์กฐ์ค€ ์ •ํ™•๋„ ํ™•์ธ
1504
  const aimAccuracy = this.calculateAimAccuracy(playerPosition);
1505
 
 
1518
  // ๋ฐœ์‚ฌํ•  ์ˆ˜ ์—†์œผ๋ฉด ์—ฐ๋ฐœ ์นด์šดํ„ฐ ๋ฆฌ์…‹
1519
  this.burstCounter = 0;
1520
  }
1521
+
1522
+ // ๊ฑฐ๋ฆฌ์— ๋”ฐ๋ผ ๊ธฐ๋™ ๊ฒฐ์ •
1523
+ if (distance < 150) {
1524
+ // 150m ์ด๋‚ด๋ฉด ํšŒํ”ผ ๊ธฐ๋™ ์‹œ์ž‘
1525
+ this.selectEvadeTarget();
1526
+ // ์ผ์‹œ์  ํšŒํ”ผ ๋ชจ๋“œ ํ™œ์„ฑํ™”
1527
+ this.temporaryEvadeMode = true;
1528
+ this.evadeTimer = 1.0;
1529
+ } else if (distance < 500) {
1530
+ // 500m ์ด๋‚ด๋ฉด ์ธก๋ฉด ํšŒํ”ผ
1531
+ this.selectEvadeTarget();
1532
+ } else if (distance > 2000) {
1533
+ // ๋ฉ€๋ฉด ์ ‘๊ทผ
1534
+ this.targetPosition = playerPosition.clone();
1535
+ }
1536
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1537
 
1538
+ // ์ƒˆ๋กœ์šด ๋ฉ”์„œ๋“œ: ์ถฉ๋Œ ์˜ˆ์ธก
1539
+ checkCollisionPrediction(deltaTime) {
1540
+ if (!this.nearbyEnemies) return;
1541
 
1542
+ // 2์ดˆ ํ›„ ์˜ˆ์ƒ ์œ„์น˜ ๊ณ„์‚ฐ
1543
+ const predictTime = 2.0;
1544
+ const forward = new THREE.Vector3(0, 0, 1).applyEuler(this.rotation);
1545
+ this.predictedPosition.copy(this.position).add(forward.multiplyScalar(this.speed * predictTime));
 
 
1546
 
1547
+ this.nearbyEnemies.forEach(enemy => {
1548
+ if (enemy === this || !enemy.position) return;
1549
+
1550
+ // ๋‹ค๋ฅธ ์ ๊ธฐ์˜ ์˜ˆ์ƒ ์œ„์น˜
1551
+ const enemyForward = new THREE.Vector3(0, 0, 1).applyEuler(enemy.rotation);
1552
+ const enemyPredicted = enemy.position.clone().add(enemyForward.multiplyScalar(enemy.speed * predictTime));
1553
 
1554
+ // ์˜ˆ์ƒ ๊ฑฐ๋ฆฌ
1555
+ const predictedDistance = this.predictedPosition.distanceTo(enemyPredicted);
1556
+
1557
+ // 150m ์ด๋‚ด๋กœ ์ ‘๊ทผ ์˜ˆ์ƒ ์‹œ ์‚ฌ์ „ ํšŒํ”ผ
1558
+ if (predictedDistance < 150) {
1559
+ // ์˜ˆ๋ฐฉ์  ํšŒํ”ผ ๋ฐฉํ–ฅ ์„ค์ •
1560
+ const avoidDir = this.predictedPosition.clone().sub(enemyPredicted).normalize();
1561
+
1562
+ // ์ˆ˜์ง ๋ถ„๋ฆฌ ์ถ”๊ฐ€
1563
+ if (this.position.y > enemy.position.y) {
1564
+ avoidDir.y += 0.3;
1565
+ } else {
1566
+ avoidDir.y -= 0.3;
1567
+ }
1568
+
1569
+ this.avoidanceVector.add(avoidDir.multiplyScalar(1.5));
1570
+ }
1571
+ });
1572
+ }
1573
+
1574
+ // ์ƒˆ๋กœ์šด ๋ฉ”์„œ๋“œ: ๊ธด๊ธ‰ ํšŒํ”ผ ์‹คํ–‰
1575
+ executeEmergencyEvade(deltaTime) {
1576
+ // ํšŒํ”ผ ๋ฒกํ„ฐ๊ฐ€ ์žˆ์œผ๋ฉด ๊ทธ ๋ฐฉํ–ฅ์œผ๋กœ, ์—†์œผ๋ฉด ๊ธ‰์ƒ์Šน
1577
+ if (this.avoidanceVector.length() > 0) {
1578
+ const evadeDirection = this.avoidanceVector.clone().normalize();
1579
+
1580
+ // ๊ธ‰์„ ํšŒ
1581
+ const targetYaw = Math.atan2(evadeDirection.x, evadeDirection.z);
1582
+ const targetPitch = Math.asin(-evadeDirection.y) * 0.5; // ํ”ผ์น˜๋Š” ์ ˆ๋ฐ˜๋งŒ
1583
+
1584
+ // ๋น ๋ฅธ ํšŒ์ „ ์†๋„
1585
+ const emergencyTurnSpeed = this.turnSpeed * 2.0;
1586
+ const maxTurnRate = emergencyTurnSpeed * deltaTime;
1587
+
1588
+ // Yaw ํšŒ์ „
1589
+ const yawDiff = this.normalizeAngle(targetYaw - this.rotation.y);
1590
+ if (Math.abs(yawDiff) > maxTurnRate) {
1591
+ this.rotation.y += Math.sign(yawDiff) * maxTurnRate;
1592
  } else {
1593
+ this.rotation.y = targetYaw;
1594
  }
1595
 
1596
+ // Pitch ํšŒ์ „
1597
+ const pitchDiff = targetPitch - this.rotation.x;
1598
+ if (Math.abs(pitchDiff) > maxTurnRate * 0.7) {
1599
+ this.rotation.x += Math.sign(pitchDiff) * maxTurnRate * 0.7;
1600
+ } else {
1601
+ this.rotation.x = targetPitch;
1602
+ }
1603
+
1604
+ // ๊ธ‰์„ ํšŒ ์‹œ ์•ฝ๊ฐ„์˜ ๋กค ์ถ”๊ฐ€
1605
+ this.rotation.z = Math.sign(yawDiff) * Math.min(Math.abs(yawDiff), Math.PI / 6);
1606
+ } else {
1607
+ // ๊ธฐ๋ณธ ํšŒํ”ผ: ๊ธ‰์ƒ์Šน
1608
+ this.rotation.x = -Math.PI / 6; // 30๋„ ์ƒ์Šน
1609
  }
 
 
 
 
 
 
 
 
1610
 
1611
+ // ํšŒํ”ผ ์ค‘์—๋Š” ์ตœ๋Œ€ ์†๋„
1612
+ this.speed = this.maxSpeed;
1613
+ }
1614
+
1615
+ smoothTurnToTarget(targetPos, deltaTime, isEmergency = false) {
1616
 
1617
+
1618
+ // ๊ธฐ์กด ๋กœ์ง (ํ›„ํ‡ด๊ฐ€ ์•„๋‹ ๋•Œ)
1619
+ const direction = targetPos.clone().sub(this.position);
1620
+ direction.y *= 0.5;
1621
+ direction.normalize();
1622
+
1623
+ // ์ถฉ๋Œ ํšŒํ”ผ ๋ฒกํ„ฐ ์ ์šฉ (ํšŒํ”ผ๊ฐ€ ์šฐ์„ )
1624
+ if (this.avoidanceVector.length() > 0) {
1625
+ const avoidanceStrength = this.temporaryEvadeMode ? 1.0 : 0.5;
1626
+ direction.add(this.avoidanceVector.multiplyScalar(avoidanceStrength));
1627
+ direction.normalize();
1628
+ }
1629
+
1630
+ // ๋ชฉํ‘œ ํšŒ์ „ ๊ณ„์‚ฐ
1631
+ const targetYaw = Math.atan2(direction.x, direction.z);
1632
+ const targetPitch = Math.asin(-direction.y);
1633
+
1634
+ // ๋ถ€๋“œ๋Ÿฌ์šด ํšŒ์ „ (์ตœ๋Œ€ ํšŒ์ „ ์†๋„ ์ œํ•œ)
1635
+ const turnSpeed = isEmergency ? this.turnSpeed * 2.0 : (this.temporaryEvadeMode ? this.turnSpeed * 1.5 : this.turnSpeed);
1636
+ const maxTurnRate = turnSpeed * deltaTime;
1637
 
1638
  // Yaw ํšŒ์ „
1639
  const yawDiff = this.normalizeAngle(targetYaw - this.rotation.y);
 
1643
  this.rotation.y = targetYaw;
1644
  }
1645
 
1646
+ // Pitch ํšŒ์ „ (์ œํ•œ์ )
1647
+ const maxPitchRate = maxTurnRate * 0.7;
1648
+ if (Math.abs(targetPitch - this.rotation.x) > maxPitchRate) {
1649
+ this.rotation.x += Math.sign(targetPitch - this.rotation.x) * maxPitchRate;
1650
  } else {
1651
  this.rotation.x = targetPitch;
1652
  }
1653
 
1654
+ // Pitch ์ œํ•œ (ยฑ40๋„)
1655
+ const maxPitchAngle = Math.PI * 40 / 180;
1656
+ this.rotation.x = THREE.MathUtils.clamp(this.rotation.x, -maxPitchAngle, maxPitchAngle);
1657
+
1658
+ // ๋กค ์ž๋™ ๊ณ„์‚ฐ (์„ ํšŒ ์‹œ)
1659
+ if (!this.temporaryEvadeMode) {
1660
+ this.rotation.z = -yawDiff * 0.5;
1661
+ this.rotation.z = THREE.MathUtils.clamp(this.rotation.z, -Math.PI / 4, Math.PI / 4);
1662
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1663
  }
 
 
 
 
 
 
1664
 
1665
+ calculateAvoidance() {
1666
+ this.avoidanceVector.set(0, 0, 0);
1667
+
1668
+ if (!this.nearbyEnemies) return;
 
1669
 
1670
+ let avoidCount = 0;
1671
+ let criticalAvoidance = false;
1672
 
1673
+ this.nearbyEnemies.forEach(enemy => {
1674
+ if (enemy === this || !enemy.position) return;
 
1675
 
1676
+ const distance = this.position.distanceTo(enemy.position);
 
 
 
1677
 
1678
+ // 100m ๋ฏธ๋งŒ: ๊ธด๊ธ‰ ํšŒํ”ผ
1679
+ if (distance < 100 && distance > 0) {
1680
+ criticalAvoidance = true;
1681
+
1682
+ // ๊ฐ•ํ•œ ๋ฐ˜๋ฐœ๋ ฅ
1683
+ const avoidDir = this.position.clone().sub(enemy.position).normalize();
1684
+ const strength = 2.0; // ๋งค์šฐ ๊ฐ•ํ•œ ํšŒํ”ผ
1685
+ this.avoidanceVector.add(avoidDir.multiplyScalar(strength));
1686
+
1687
+ // ๊ณ ๋„ ์ฐจ์ด ์ถ”๊ฐ€ (์œ„/์•„๋ž˜๋กœ ๋ถ„์‚ฐ)
1688
+ if (this.position.y > enemy.position.y) {
1689
+ this.avoidanceVector.y += 0.5; // ์œ„๋กœ
1690
+ } else {
1691
+ this.avoidanceVector.y -= 0.5; // ์•„๋ž˜๋กœ
1692
+ }
1693
+
1694
+ avoidCount++;
1695
+ }
1696
+ // 100-300m: ์˜ˆ๋ฐฉ์  ํšŒํ”ผ
1697
+ else if (distance < 300) {
1698
+ // ๋ฐ˜๋Œ€ ๋ฐฉํ–ฅ์œผ๋กœ ํšŒํ”ผ
1699
+ const avoidDir = this.position.clone().sub(enemy.position).normalize();
1700
+ const strength = (300 - distance) / 200; // 100-300m ๋ฒ”์œ„์—์„œ ๊ฐ•๋„ ๊ณ„์‚ฐ
1701
+ this.avoidanceVector.add(avoidDir.multiplyScalar(strength));
1702
+ avoidCount++;
1703
  }
1704
+ });
1705
+
1706
+ if (avoidCount > 0) {
1707
+ this.avoidanceVector.divideScalar(avoidCount);
1708
+ this.avoidanceVector.normalize();
1709
 
1710
+ // ๊ธด๊ธ‰ ํšŒํ”ผ ์‹œ ๋” ๊ฐ•ํ•œ ํšŒํ”ผ๋ ฅ ์ ์šฉ
1711
+ if (criticalAvoidance) {
1712
+ this.avoidanceVector.multiplyScalar(2.0);
1713
+ // ์ผ์‹œ์ ์œผ๋กœ ์ „ํˆฌ ์ƒํƒœ ํ•ด์ œ
1714
+ this.temporaryEvadeMode = true;
1715
+ this.evadeTimer = 2.0; // 2์ดˆ ๋™์•ˆ ํšŒํ”ผ ์šฐ์„ 
1716
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1717
  }
1718
  }
 
 
 
 
1719
 
1720
+ updatePhysics(deltaTime) {
1721
+ if (!this.mesh) return;
1722
+
1723
+ // ์†๋„ ๋ฒกํ„ฐ ๊ณ„์‚ฐ (ํ•ญ์ƒ ์ „์ง„)
1724
+ const forward = new THREE.Vector3(0, 0, 1);
1725
+ forward.applyEuler(this.rotation);
1726
+
1727
+ // ์†๋„ ์œ ์ง€ (500-750kt, m/s๋กœ ๋ณ€ํ™˜) - ๊ฐ•์ œ๋กœ ์ตœ์†Œ ์†๋„ ์œ ์ง€
1728
+ if (this.speed < this.minSpeed) {
1729
+ this.speed = this.minSpeed;
1730
+ }
1731
+ this.speed = THREE.MathUtils.clamp(this.speed, this.minSpeed, this.maxSpeed);
1732
+
1733
+ // ์†๋„ ๋ฒกํ„ฐ ์ƒ์„ฑ - ํ•ญ์ƒ ์ „์ง„
1734
+ this.velocity = forward.multiplyScalar(this.speed);
1735
+
1736
+ // ์œ„์น˜ ์—…๋ฐ์ดํŠธ - ํ•ญ์ƒ ์ด๋™
1737
+ this.position.add(this.velocity.clone().multiplyScalar(deltaTime));
1738
+
1739
+ // ๊ณ ๋„ ์ œํ•œ
1740
+ if (this.position.y < 500) {
1741
+ this.position.y = 500;
1742
+ this.rotation.x = -0.2; // ์ƒ์Šน
1743
+ } else if (this.position.y > 8000) {
1744
+ this.position.y = 8000;
1745
+ this.rotation.x = 0.2; // ํ•˜๊ฐ•
1746
+ }
1747
+
1748
+ // ๋งต ๊ฒฝ๊ณ„ ์ฒ˜๋ฆฌ
1749
+ const mapLimit = GAME_CONSTANTS.MAP_SIZE / 2;
1750
+ const boundaryBuffer = mapLimit * 0.9;
1751
+
1752
+ if (Math.abs(this.position.x) > boundaryBuffer || Math.abs(this.position.z) > boundaryBuffer) {
1753
+ // ๋งต ์ค‘์•™์„ ํ–ฅํ•ด ํšŒ์ „
1754
+ const centerDirection = new THREE.Vector3(-this.position.x, 0, -this.position.z).normalize();
1755
+ const targetYaw = Math.atan2(centerDirection.x, centerDirection.z);
1756
+ this.rotation.y = targetYaw;
1757
+ this.selectNewPatrolTarget(); // ์ƒˆ๋กœ์šด ๋ชฉํ‘œ ์„ ํƒ
1758
+ }
1759
+
1760
+ // ํ•˜๋“œ ๋ฆฌ๋ฏธํŠธ
1761
+ this.position.x = THREE.MathUtils.clamp(this.position.x, -mapLimit, mapLimit);
1762
+ this.position.z = THREE.MathUtils.clamp(this.position.z, -mapLimit, mapLimit);
1763
+
1764
+ // ๋ฉ”์‹œ ์—…๋ฐ์ดํŠธ
1765
+ this.mesh.position.copy(this.position);
1766
+ this.mesh.rotation.x = this.rotation.x;
1767
+ this.mesh.rotation.y = this.rotation.y + 3 * Math.PI / 2; // ํ”Œ๋ ˆ์ด์–ด์™€ ๋™์ผํ•œ ํšŒ์ „ ์˜คํ”„์…‹
1768
+ this.mesh.rotation.z = this.rotation.z;
1769
  }
1770
 
1771
+ fireWeapon() {
 
 
 
 
 
 
 
 
 
 
 
 
1772
  const now = Date.now();
1773
 
1774
  // 0.1์ดˆ์— 1๋ฐœ์”ฉ, 10๋ฐœ ์—ฐ๋ฐœ
 
1790
  }
1791
  }
1792
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1793
 
1794
+ shoot() {
1795
+ // ํƒ„ํ™˜ ์ƒ์„ฑ (ํ”Œ๋ ˆ์ด์–ด์™€ ๋™์ผํ•œ ํฌ๊ธฐ)
1796
+ const bulletGeometry = new THREE.CylinderGeometry(1.0, 1.0, 16, 8);
1797
+ const bulletMaterial = new THREE.MeshBasicMaterial({
1798
+ color: 0xff0000,
1799
+ });
1800
+ const bullet = new THREE.Mesh(bulletGeometry, bulletMaterial);
1801
+
1802
+ // ๊ธฐ์ˆ˜ ๋์—์„œ๋งŒ ๋ฐœ์‚ฌ - ์ •ํ™•ํžˆ ์ „๋ฐฉ ์ค‘์•™์—์„œ
1803
+ const muzzleOffset = new THREE.Vector3(0, 0, 10); // X=0, Y=0์œผ๋กœ ์ค‘์•™ ๊ณ ์ •
1804
+ muzzleOffset.applyEuler(this.rotation);
1805
+ bullet.position.copy(this.position).add(muzzleOffset);
1806
+
1807
+ // ํƒ„ํ™˜์„ ๋ฐœ์‚ฌ ๋ฐฉํ–ฅ์œผ๋กœ ํšŒ์ „
1808
+ bullet.rotation.copy(this.rotation);
1809
+ bullet.rotateX(Math.PI / 2); // ์‹ค๋ฆฐ๋”๊ฐ€ Z์ถ• ๋ฐฉํ–ฅ์„ ํ–ฅํ•˜๋„๋ก
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1810
 
1811
+ // ํƒ„ํ™˜ ์ดˆ๊ธฐ ์œ„์น˜ ์ €์žฅ
1812
+ bullet.startPosition = bullet.position.clone();
1813
+
1814
+ // ํƒ„ํ™˜ ์†๋„ (ํ”Œ๋ ˆ์ด์–ด์™€ ๋™์ผํ•˜๊ฒŒ 1500) - ์ •ํ™•ํžˆ ์ „๋ฐฉ์œผ๋กœ
1815
+ const direction = new THREE.Vector3(0, 0, 1);
1816
+ direction.applyEuler(this.rotation);
1817
+ bullet.velocity = direction.multiplyScalar(1500);
1818
+
1819
+ this.scene.add(bullet);
1820
+ this.bullets.push(bullet);
1821
+
1822
+ // ์‚ฌ์šด๋“œ ์žฌ์ƒ
1823
+ if (this.playerFighter) {
1824
+ const distanceToPlayer = this.position.distanceTo(this.playerFighter.position);
1825
+ if (distanceToPlayer < 3000) {
1826
+ try {
1827
+ const audio = new Audio('sounds/MGLAUNCH.ogg');
1828
+ const volumeMultiplier = 1 - (distanceToPlayer / 3000);
1829
+ audio.volume = 0.5 * volumeMultiplier;
1830
+ audio.play().catch(e => {});
1831
+ } catch (e) {}
1832
+ }
1833
  }
1834
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1835
 
1836
+ updateBullets(deltaTime) {
1837
+ for (let i = this.bullets.length - 1; i >= 0; i--) {
1838
+ const bullet = this.bullets[i];
1839
+ bullet.position.add(bullet.velocity.clone().multiplyScalar(deltaTime));
1840
+
1841
+ // ์ง€๋ฉด ์ถฉ๋Œ
1842
+ if (bullet.position.y <= 0) {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1843
  if (window.gameInstance) {
1844
+ window.gameInstance.createGroundImpactEffect(bullet.position);
1845
  }
1846
+ this.scene.remove(bullet);
1847
+ this.bullets.splice(i, 1);
1848
+ continue;
1849
  }
1850
+
1851
+ // ๊ฑฐ๋ฆฌ ์ œํ•œ (ํ”Œ๋ ˆ์ด์–ด์™€ ๋™์ผํ•˜๊ฒŒ 6000m)
1852
+ if (bullet.position.distanceTo(bullet.startPosition) > 6000 ||
1853
+ bullet.position.y > GAME_CONSTANTS.MAX_ALTITUDE + 500) {
1854
+ this.scene.remove(bullet);
1855
+ this.bullets.splice(i, 1);
1856
  }
 
 
1857
  }
 
 
 
 
 
 
 
1858
  }
 
 
 
 
 
 
 
 
 
 
 
 
1859
 
1860
+ calculateAimAccuracy(target) {
1861
+ const toTarget = target.clone().sub(this.position).normalize();
1862
+ const forward = new THREE.Vector3(0, 0, 1).applyEuler(this.rotation);
1863
+ const dotProduct = forward.dot(toTarget);
1864
+ return Math.acos(Math.max(-1, Math.min(1, dotProduct)));
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1865
  }
 
 
 
 
 
 
 
 
 
 
 
1866
 
1867
+ selectNewPatrolTarget() {
1868
+ const mapLimit = GAME_CONSTANTS.MAP_SIZE / 2 * 0.7;
1869
+
1870
+ // ๋” ๋‹ค์–‘ํ•œ ๊ณ ๋„ ์„ ํƒ
1871
+ const minAltitude = 1000;
1872
+ const maxAltitude = 6000;
1873
+
1874
+ this.targetPosition = new THREE.Vector3(
1875
+ (Math.random() - 0.5) * 2 * mapLimit,
1876
+ minAltitude + Math.random() * (maxAltitude - minAltitude),
1877
+ (Math.random() - 0.5) * 2 * mapLimit
1878
+ );
1879
+ }
1880
 
1881
+ selectEvadeTarget() {
1882
+ // ํ˜„์žฌ ์œ„์น˜์—์„œ ๋žœ๋ค ๋ฐฉํ–ฅ์œผ๋กœ ํšŒํ”ผ
1883
+ const evadeDistance = 1000 + Math.random() * 1000;
1884
+ const evadeAngle = Math.random() * Math.PI * 2;
1885
+
1886
+ this.targetPosition = new THREE.Vector3(
1887
+ this.position.x + Math.cos(evadeAngle) * evadeDistance,
1888
+ this.position.y + (Math.random() - 0.5) * 500,
1889
+ this.position.z + Math.sin(evadeAngle) * evadeDistance
1890
+ );
1891
+
1892
+ // ๋งต ๊ฒฝ๊ณ„ ํ™•์ธ
1893
+ const mapLimit = GAME_CONSTANTS.MAP_SIZE / 2 * 0.8;
1894
+ this.targetPosition.x = THREE.MathUtils.clamp(this.targetPosition.x, -mapLimit, mapLimit);
1895
+ this.targetPosition.z = THREE.MathUtils.clamp(this.targetPosition.z, -mapLimit, mapLimit);
1896
+ this.targetPosition.y = THREE.MathUtils.clamp(this.targetPosition.y, 1000, 6000);
1897
+ }
1898
 
1899
+ normalizeAngle(angle) {
1900
+ while (angle > Math.PI) angle -= Math.PI * 2;
1901
+ while (angle < -Math.PI) angle += Math.PI * 2;
1902
+ return angle;
1903
+ }
1904
 
1905
+ takeDamage(damage) {
 
 
 
 
 
 
 
 
 
 
 
 
 
1906
  console.log(`Enemy taking damage: ${damage}, Current health: ${this.health}`);
1907
  this.health -= damage;
1908
  console.log(`Enemy health after damage: ${this.health}`);
 
1910
  console.log(`Enemy is dead: ${isDead}`);
1911
  return isDead;
1912
  }
1913
+
1914
+ destroy() {
1915
+ if (this.mesh) {
1916
+ this.scene.remove(this.mesh);
1917
+ this.bullets.forEach(bullet => this.scene.remove(bullet));
1918
+ this.bullets = [];
1919
+ this.isLoaded = false;
1920
+ }
 
 
 
 
 
 
 
 
1921
  }
1922
  }
1923