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

Update game.js

Browse files
Files changed (1) hide show
  1. game.js +517 -342
game.js CHANGED
@@ -1382,6 +1382,15 @@ class EnemyFighter {
1382
 
1383
  // ์ดˆ๊ธฐ ๋ชฉํ‘œ ์„ค์ •
1384
  this.selectNewPatrolTarget();
 
 
 
 
 
 
 
 
 
1385
  }
1386
 
1387
  async initialize(loader) {
@@ -1430,11 +1439,11 @@ class EnemyFighter {
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,7 +1453,39 @@ update(playerPosition, deltaTime) {
1444
 
1445
  const distanceToPlayer = this.position.distanceTo(playerPosition);
1446
 
1447
- // ์ƒํƒœ ๊ฒฐ์ • (๋ฏธ์‚ฌ์ผ ํƒ์ง€ ์ œ๊ฑฐ)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1448
  if (this.temporaryEvadeMode) {
1449
  this.aiState = 'evade';
1450
  } else if (distanceToPlayer <= 3000) {
@@ -1475,31 +1516,48 @@ update(playerPosition, deltaTime) {
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,122 +1576,74 @@ update(playerPosition, deltaTime) {
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,132 +1653,181 @@ update(playerPosition, deltaTime) {
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,119 +1849,227 @@ update(playerPosition, deltaTime) {
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,14 +2077,22 @@ update(playerPosition, deltaTime) {
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
 
 
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
  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
 
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
 
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
  // ๋ฐœ์‚ฌํ•  ์ˆ˜ ์—†์œผ๋ฉด ์—ฐ๋ฐœ ์นด์šดํ„ฐ ๋ฆฌ์…‹
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
  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
  }
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
  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