cutechicken commited on
Commit
6ab16ab
ยท
verified ยท
1 Parent(s): 43f1890

Update game.js

Browse files
Files changed (1) hide show
  1. game.js +371 -719
game.js CHANGED
@@ -792,7 +792,7 @@ class Fighter {
792
  }
793
  }
794
 
795
- // ์  ์ „ํˆฌ๊ธฐ ํด๋ž˜์Šค - ํญ๋ฐœ ์ฒ˜๋ฆฌ ์ˆ˜์ •
796
  class EnemyFighter {
797
  constructor(scene, position) {
798
  this.mesh = null;
@@ -838,9 +838,6 @@ class EnemyFighter {
838
  this.nearbyEnemies = [];
839
  this.avoidanceVector = new THREE.Vector3();
840
 
841
- // ํŒŒ๊ดด ์ƒํƒœ ์ถ”๊ฐ€
842
- this.isDestroyed = false;
843
-
844
  // ์ดˆ๊ธฐ ๋ชฉํ‘œ ์„ค์ •
845
  this.selectNewPatrolTarget();
846
  }
@@ -892,7 +889,7 @@ class EnemyFighter {
892
  }
893
 
894
  update(playerPosition, deltaTime) {
895
- if (!this.mesh || !this.isLoaded || this.isDestroyed) return;
896
 
897
  // ํšŒํ”ผ ํƒ€์ด๋จธ ์—…๋ฐ์ดํŠธ
898
  if (this.temporaryEvadeMode && this.evadeTimer > 0) {
@@ -908,6 +905,7 @@ class EnemyFighter {
908
  if (distanceToPlayer < 100) {
909
  this.isRetreating = true;
910
  this.aiState = 'retreat';
 
911
  console.log(`Enemy retreating! Distance: ${distanceToPlayer.toFixed(1)}m`);
912
  }
913
 
@@ -920,6 +918,7 @@ class EnemyFighter {
920
  // ์ƒํƒœ ๊ฒฐ์ • - ํ›„ํ‡ด๊ฐ€ ์ตœ์šฐ์„ 
921
  if (this.isRetreating) {
922
  this.aiState = 'retreat';
 
923
  } else if (this.temporaryEvadeMode) {
924
  this.aiState = 'evade';
925
  } else if (distanceToPlayer <= 3000) {
@@ -957,6 +956,7 @@ class EnemyFighter {
957
  this.updateBullets(deltaTime);
958
  }
959
 
 
960
  executeRetreat(playerPosition, deltaTime) {
961
  // ํ”Œ๋ ˆ์ด์–ด๋กœ๋ถ€ํ„ฐ ๋ฉ€์–ด์ง€๋Š” ๋ฐฉํ–ฅ ๊ณ„์‚ฐ
962
  const retreatDirection = this.position.clone().sub(playerPosition).normalize();
@@ -1294,7 +1294,7 @@ class EnemyFighter {
1294
  }
1295
 
1296
  updatePhysics(deltaTime) {
1297
- if (!this.mesh || this.isDestroyed) return;
1298
 
1299
  // ์†๋„ ๋ฒกํ„ฐ ๊ณ„์‚ฐ (ํ•ญ์ƒ ์ „์ง„)
1300
  const forward = new THREE.Vector3(0, 0, 1);
@@ -1482,330 +1482,17 @@ class EnemyFighter {
1482
 
1483
  takeDamage(damage) {
1484
  this.health -= damage;
1485
-
1486
- // ์ฒด๋ ฅ์ด 0 ์ดํ•˜๊ฐ€ ๋˜๋ฉด true ๋ฐ˜ํ™˜ (ํŒŒ๊ดด๋จ)
1487
- if (this.health <= 0) {
1488
- this.isDestroyed = true;
1489
- return true;
1490
- }
1491
- return false;
1492
  }
1493
 
1494
  destroy() {
1495
- console.log('EnemyFighter destroy() called');
1496
-
1497
- if (this.isDestroyed) {
1498
- console.log('Already destroyed, skipping');
1499
- return;
1500
- }
1501
-
1502
- // ํŒŒ๊ดด ์ƒํƒœ ์„ค์ •
1503
- this.isDestroyed = true;
1504
-
1505
- // ๋ฉ”์‹œ๊ฐ€ ์žˆ๋Š”์ง€ ํ™•์ธํ•˜๊ณ  ์ œ๊ฑฐ
1506
- if (this.mesh) {
1507
- console.log('Removing enemy mesh from scene');
1508
-
1509
- // GLB ๋ชจ๋ธ์€ ๋ณต์žกํ•œ ๊ตฌ์กฐ๋ฅผ ๊ฐ€์งˆ ์ˆ˜ ์žˆ์œผ๋ฏ€๋กœ ์ „์ฒด ๊ณ„์ธต๊ตฌ์กฐ๋ฅผ ์ˆœํšŒํ•˜๋ฉฐ ์ œ๊ฑฐ
1510
- this.mesh.traverse((child) => {
1511
- if (child.isMesh) {
1512
- console.log('Disposing mesh:', child.name || 'unnamed');
1513
-
1514
- // geometry ์ œ๊ฑฐ
1515
- if (child.geometry) {
1516
- child.geometry.dispose();
1517
- }
1518
-
1519
- // material ์ œ๊ฑฐ
1520
- if (child.material) {
1521
- if (Array.isArray(child.material)) {
1522
- child.material.forEach(mat => {
1523
- if (mat.dispose) mat.dispose();
1524
- });
1525
- } else {
1526
- if (child.material.dispose) child.material.dispose();
1527
- }
1528
- }
1529
- }
1530
- });
1531
-
1532
- // ์”ฌ์—์„œ ๋ฉ”์‹œ ์ œ๊ฑฐ - ์—ฌ๋Ÿฌ ๋ฐฉ๋ฒ• ์‹œ๋„
1533
- if (this.scene) {
1534
  this.scene.remove(this.mesh);
1535
- }
1536
-
1537
- // parent๊ฐ€ ์žˆ๋‹ค๋ฉด parent์—์„œ๋„ ์ œ๊ฑฐ
1538
- if (this.mesh.parent) {
1539
- console.log('Mesh still has parent, forcing removal from parent');
1540
- this.mesh.parent.remove(this.mesh);
1541
- }
1542
-
1543
- // ๋ฉ”์‹œ๋ฅผ ๋น„ํ™œ์„ฑํ™”
1544
- this.mesh.visible = false;
1545
-
1546
- // ๋ฉ”์‹œ ์ฐธ์กฐ ์™„์ „ํžˆ ์ œ๊ฑฐ
1547
- this.mesh = null;
1548
- }
1549
-
1550
- // ๋‚จ์€ ํƒ„ํ™˜๋“ค ์ œ๊ฑฐ
1551
- console.log(`Removing ${this.bullets.length} enemy bullets`);
1552
- this.bullets.forEach(bullet => {
1553
- // ํƒ„ํ™˜์˜ geometry์™€ material๋„ ์ •๋ฆฌ
1554
- if (bullet.geometry) bullet.geometry.dispose();
1555
- if (bullet.material) bullet.material.dispose();
1556
-
1557
- this.scene.remove(bullet);
1558
-
1559
- // ํƒ„ํ™˜์ด ์‹ค์ œ๋กœ ์ œ๊ฑฐ๋˜์—ˆ๋Š”์ง€ ํ™•์ธ
1560
- if (bullet.parent) {
1561
- bullet.parent.remove(bullet);
1562
- }
1563
- });
1564
- this.bullets = [];
1565
-
1566
- this.isLoaded = false;
1567
- console.log('EnemyFighter destroyed successfully');
1568
- }
1569
-
1570
- // Game ํด๋ž˜์Šค์˜ checkCollisions ๋ฉ”์„œ๋“œ ์ˆ˜์ •
1571
- checkCollisions() {
1572
- // ํ”Œ๋ ˆ์ด์–ด ํƒ„ํ™˜ vs ์ ๊ธฐ ์ถฉ๋Œ
1573
- for (let i = this.fighter.bullets.length - 1; i >= 0; i--) {
1574
- const bullet = this.fighter.bullets[i];
1575
-
1576
- for (let j = this.enemies.length - 1; j >= 0; j--) {
1577
- const enemy = this.enemies[j];
1578
-
1579
- // ์ด๋ฏธ ํŒŒ๊ดด๋œ ์ ์€ ๊ฑด๋„ˆ๋›ฐ๊ธฐ
1580
- if (!enemy.mesh || !enemy.isLoaded || enemy.isDestroyed) continue;
1581
-
1582
- const distance = bullet.position.distanceTo(enemy.position);
1583
- if (distance < 90) {
1584
- console.log(`Hit detected! Distance: ${distance}, Enemy health: ${enemy.health}`);
1585
-
1586
- // ํžˆํŠธ ํ‘œ์‹œ ์ถ”๊ฐ€
1587
- this.showHitMarker(enemy.position);
1588
- // ํ”ผ๊ฒฉ ์ดํŽ™ํŠธ ์ถ”๊ฐ€
1589
- this.createHitEffect(enemy.position);
1590
-
1591
- // ํƒ„ํ™˜ ์ œ๊ฑฐ
1592
- this.scene.remove(bullet);
1593
- this.fighter.bullets.splice(i, 1);
1594
-
1595
- // ์ ๊ธฐ์— ๋ฐ๋ฏธ์ง€ ์ž…ํžˆ๊ธฐ
1596
- const isDestroyed = enemy.takeDamage(GAME_CONSTANTS.BULLET_DAMAGE);
1597
- console.log(`Enemy damaged. New health: ${enemy.health}, Destroyed: ${isDestroyed}`);
1598
-
1599
- if (isDestroyed) {
1600
- console.log('Enemy destroyed! Creating explosion effect...');
1601
-
1602
- // ํญ๋ฐœ ํšจ๊ณผ ์ƒ์„ฑ - ์œ„์น˜๋ฅผ ๋ณต์‚ฌํ•ด์„œ ์ „๋‹ฌ
1603
- const explosionPosition = enemy.position.clone();
1604
- console.log('Explosion position:', explosionPosition);
1605
-
1606
- // ํญ๋ฐœ ํšจ๊ณผ ์ฆ‰์‹œ ์ƒ์„ฑ
1607
- this.createExplosionEffect(explosionPosition);
1608
-
1609
- // ์ ๊ธฐ ๋ฉ”์‹œ๋ฅผ ๋จผ์ € ์ˆจ๊ธฐ๊ธฐ
1610
- if (enemy.mesh) {
1611
- enemy.mesh.visible = false;
1612
- }
1613
-
1614
- // ์ ๊ธฐ ์ œ๊ฑฐ
1615
- enemy.destroy();
1616
-
1617
- // ๋ฐฐ์—ด์—์„œ ์ œ๊ฑฐ
1618
- this.enemies.splice(j, 1);
1619
- this.score += 100;
1620
-
1621
- console.log(`Enemy removed. Remaining enemies: ${this.enemies.length}`);
1622
- }
1623
- break;
1624
- }
1625
- }
1626
- }
1627
-
1628
- // ์  ํƒ„ํ™˜ vs ํ”Œ๋ ˆ์ด์–ด ์ถฉ๋Œ
1629
- this.enemies.forEach(enemy => {
1630
- // ํŒŒ๊ดด๋œ ์ ์€ ์ฒ˜๋ฆฌํ•˜์ง€ ์•Š์Œ
1631
- if (enemy.isDestroyed) return;
1632
-
1633
- for (let index = enemy.bullets.length - 1; index >= 0; index--) {
1634
- const bullet = enemy.bullets[index];
1635
- const distance = bullet.position.distanceTo(this.fighter.position);
1636
- if (distance < 100) {
1637
- // ํ”Œ๋ ˆ์ด์–ด ํ”ผ๊ฒฉ ์ดํŽ™ํŠธ
1638
- this.createHitEffect(this.fighter.position);
1639
-
1640
- // ํƒ„ํ™˜ ์ œ๊ฑฐ
1641
- this.scene.remove(bullet);
1642
- enemy.bullets.splice(index, 1);
1643
-
1644
- if (this.fighter.takeDamage(GAME_CONSTANTS.BULLET_DAMAGE)) {
1645
- // ํ”Œ๋ ˆ์ด์–ด ํŒŒ๊ดด ์‹œ ํญ๋ฐœ ํšจ๊ณผ ์ถ”๊ฐ€
1646
- this.createExplosionEffect(this.fighter.position);
1647
-
1648
- this.endGame(false);
1649
- }
1650
- }
1651
- }
1652
- });
1653
- }
1654
-
1655
- // EnemyFighter์˜ update ๋ฉ”์„œ๋“œ์—๋„ ์ถ”๊ฐ€ ๋ณดํ˜ธ ์žฅ์น˜
1656
- update(playerPosition, deltaTime) {
1657
- // ํŒŒ๊ดด๋˜์—ˆ๊ฑฐ๋‚˜ ๋ฉ”์‹œ๊ฐ€ ์—†์œผ๋ฉด ์•„๋ฌด๊ฒƒ๋„ ํ•˜์ง€ ์•Š์Œ
1658
- if (!this.mesh || !this.isLoaded || this.isDestroyed || !this.mesh.visible) return;
1659
-
1660
- // ... ๋‚˜๋จธ์ง€ update ๋กœ์ง
1661
-
1662
- // ํšŒํ”ผ ํƒ€์ด๋จธ ์—…๋ฐ์ดํŠธ
1663
- if (this.temporaryEvadeMode && this.evadeTimer > 0) {
1664
- this.evadeTimer -= deltaTime;
1665
- if (this.evadeTimer <= 0) {
1666
- this.temporaryEvadeMode = false;
1667
- }
1668
- }
1669
-
1670
- const distanceToPlayer = this.position.distanceTo(playerPosition);
1671
-
1672
- // 100m ์ด๋‚ด๋ฉด ์ฆ‰์‹œ ํ›„ํ‡ด ๋ชจ๋“œ ํ™œ์„ฑํ™”
1673
- if (distanceToPlayer < 100) {
1674
- this.isRetreating = true;
1675
- this.aiState = 'retreat';
1676
- console.log(`Enemy retreating! Distance: ${distanceToPlayer.toFixed(1)}m`);
1677
- }
1678
-
1679
- // 250m ์ด์ƒ ๋–จ์–ด์ง€๋ฉด ํ›„ํ‡ด ๋ชจ๋“œ ํ•ด์ œ
1680
- if (this.isRetreating && distanceToPlayer >= this.retreatTargetDistance) {
1681
- this.isRetreating = false;
1682
- console.log(`Enemy retreat complete. Distance: ${distanceToPlayer.toFixed(1)}m`);
1683
- }
1684
-
1685
- // ์ƒํƒœ ๊ฒฐ์ • - ํ›„ํ‡ด๊ฐ€ ์ตœ์šฐ์„ 
1686
- if (this.isRetreating) {
1687
- this.aiState = 'retreat';
1688
- } else if (this.temporaryEvadeMode) {
1689
- this.aiState = 'evade';
1690
- } else if (distanceToPlayer <= 3000) {
1691
- this.aiState = 'combat';
1692
- } else {
1693
- this.aiState = 'patrol';
1694
- }
1695
-
1696
- // ์ถฉ๋Œ ํšŒํ”ผ ๊ณ„์‚ฐ (ํ›„ํ‡ด ์ค‘์ด ์•„๋‹ ๋•Œ๋งŒ)
1697
- if (!this.isRetreating) {
1698
- this.calculateAvoidance();
1699
- this.checkCollisionPrediction(deltaTime);
1700
- }
1701
-
1702
- // AI ํ–‰๋™ ์‹คํ–‰
1703
- switch (this.aiState) {
1704
- case 'patrol':
1705
- this.executePatrol(deltaTime);
1706
- break;
1707
- case 'combat':
1708
- this.executeCombat(playerPosition, deltaTime);
1709
- break;
1710
- case 'evade':
1711
- this.executeEmergencyEvade(deltaTime);
1712
- break;
1713
- case 'retreat':
1714
- this.executeRetreat(playerPosition, deltaTime);
1715
- break;
1716
- }
1717
-
1718
- // ๋ฌผ๋ฆฌ ์—…๋ฐ์ดํŠธ
1719
- this.updatePhysics(deltaTime);
1720
-
1721
- // ํƒ„ํ™˜ ์—…๋ฐ์ดํŠธ
1722
- this.updateBullets(deltaTime);
1723
- }
1724
-
1725
- // Game ํด๋ž˜์Šค์˜ checkCollisions ๋ฉ”์„œ๋“œ๋„ ์•ฝ๊ฐ„ ์ˆ˜์ •
1726
- checkCollisions() {
1727
- // ํ”Œ๋ ˆ์ด์–ด ํƒ„ํ™˜ vs ์ ๊ธฐ ์ถฉ๋Œ
1728
- for (let i = this.fighter.bullets.length - 1; i >= 0; i--) {
1729
- const bullet = this.fighter.bullets[i];
1730
-
1731
- for (let j = this.enemies.length - 1; j >= 0; j--) {
1732
- const enemy = this.enemies[j];
1733
-
1734
- // ์ด๋ฏธ ํŒŒ๊ดด๋œ ์ ์€ ๊ฑด๋„ˆ๋›ฐ๊ธฐ
1735
- if (!enemy.mesh || !enemy.isLoaded || enemy.isDestroyed) continue;
1736
-
1737
- const distance = bullet.position.distanceTo(enemy.position);
1738
- if (distance < 90) {
1739
- console.log(`Hit detected! Distance: ${distance}, Enemy health: ${enemy.health}`);
1740
-
1741
- // ํžˆํŠธ ํ‘œ์‹œ ์ถ”๊ฐ€
1742
- this.showHitMarker(enemy.position);
1743
- // ํ”ผ๊ฒฉ ์ดํŽ™ํŠธ ์ถ”๊ฐ€
1744
- this.createHitEffect(enemy.position);
1745
-
1746
- // ํƒ„ํ™˜ ์ œ๊ฑฐ
1747
- this.scene.remove(bullet);
1748
- this.fighter.bullets.splice(i, 1);
1749
-
1750
- // ์ ๊ธฐ์— ๋ฐ๋ฏธ์ง€ ์ž…ํžˆ๊ธฐ
1751
- const isDestroyed = enemy.takeDamage(GAME_CONSTANTS.BULLET_DAMAGE);
1752
- console.log(`Enemy damaged. New health: ${enemy.health}, Destroyed: ${isDestroyed}`);
1753
-
1754
- if (isDestroyed) {
1755
- console.log('Enemy destroyed! Creating explosion effect...');
1756
-
1757
- // ํญ๋ฐœ ํšจ๊ณผ ์ƒ์„ฑ - ์œ„์น˜๋ฅผ ๋ณต์‚ฌํ•ด์„œ ์ „๋‹ฌ
1758
- const explosionPosition = enemy.position.clone();
1759
- console.log('Explosion position:', explosionPosition);
1760
-
1761
- // ํญ๋ฐœ ํšจ๊ณผ ์ฆ‰์‹œ ์ƒ์„ฑ
1762
- this.createExplosionEffect(explosionPosition);
1763
-
1764
- // ์ ๊ธฐ ๋ฉ”์‹œ ์ œ๊ฑฐ (destroy ํ˜ธ์ถœ ์ „์— ๋ช…์‹œ์ ์œผ๋กœ ์ œ๊ฑฐ)
1765
- if (enemy.mesh && enemy.mesh.parent) {
1766
- console.log('Explicitly removing enemy mesh before destroy');
1767
- enemy.mesh.parent.remove(enemy.mesh);
1768
- }
1769
-
1770
- // ์ ๊ธฐ ์ œ๊ฑฐ
1771
- enemy.destroy();
1772
-
1773
- // ๋ฐฐ์—ด์—์„œ ์ œ๊ฑฐ
1774
- this.enemies.splice(j, 1);
1775
- this.score += 100;
1776
-
1777
- console.log(`Enemy removed. Remaining enemies: ${this.enemies.length}`);
1778
- }
1779
- break;
1780
- }
1781
  }
1782
  }
1783
-
1784
- // ์  ํƒ„ํ™˜ vs ํ”Œ๋ ˆ์ด์–ด ์ถฉ๋Œ
1785
- this.enemies.forEach(enemy => {
1786
- // ํŒŒ๊ดด๋œ ์ ์€ ์ฒ˜๋ฆฌํ•˜์ง€ ์•Š์Œ
1787
- if (enemy.isDestroyed) return;
1788
-
1789
- for (let index = enemy.bullets.length - 1; index >= 0; index--) {
1790
- const bullet = enemy.bullets[index];
1791
- const distance = bullet.position.distanceTo(this.fighter.position);
1792
- if (distance < 100) {
1793
- // ํ”Œ๋ ˆ์ด์–ด ํ”ผ๊ฒฉ ์ดํŽ™ํŠธ
1794
- this.createHitEffect(this.fighter.position);
1795
-
1796
- // ํƒ„ํ™˜ ์ œ๊ฑฐ
1797
- this.scene.remove(bullet);
1798
- enemy.bullets.splice(index, 1);
1799
-
1800
- if (this.fighter.takeDamage(GAME_CONSTANTS.BULLET_DAMAGE)) {
1801
- // ํ”Œ๋ ˆ์ด์–ด ํŒŒ๊ดด ์‹œ ํญ๋ฐœ ํšจ๊ณผ ์ถ”๊ฐ€
1802
- this.createExplosionEffect(this.fighter.position);
1803
-
1804
- this.endGame(false);
1805
- }
1806
- }
1807
- }
1808
- });
1809
  }
1810
 
1811
  // ๋ฉ”์ธ ๊ฒŒ์ž„ ํด๋ž˜์Šค
@@ -2070,78 +1757,82 @@ class Game {
2070
  }
2071
 
2072
  setupEventListeners() {
2073
- document.addEventListener('keydown', (event) => {
2074
- if (this.isGameOver) return;
2075
-
2076
- if (!this.isStarted) return;
2077
-
2078
- switch(event.code) {
2079
- case 'KeyW':
2080
- this.keys.w = true;
2081
- console.log('W key pressed - Accelerating');
2082
- break;
2083
- case 'KeyA':
2084
- this.keys.a = true;
2085
- console.log('A key pressed - Turning left');
2086
- break;
2087
- case 'KeyS':
2088
- this.keys.s = true;
2089
- console.log('S key pressed - Decelerating');
2090
- break;
2091
- case 'KeyD':
2092
- this.keys.d = true;
2093
- console.log('D key pressed - Turning right');
2094
- break;
2095
- case 'KeyF':
2096
- this.keys.f = true;
2097
- break;
2098
- }
2099
- });
 
2100
 
2101
- document.addEventListener('keyup', (event) => {
2102
- if (this.isGameOver) return;
2103
-
2104
- if (!this.isStarted) return;
2105
-
2106
- switch(event.code) {
2107
- case 'KeyW': this.keys.w = false; break;
2108
- case 'KeyA': this.keys.a = false; break;
2109
- case 'KeyS': this.keys.s = false; break;
2110
- case 'KeyD': this.keys.d = false; break;
2111
- case 'KeyF': this.keys.f = false; break;
2112
- }
2113
- });
 
2114
 
2115
- document.addEventListener('mousemove', (event) => {
2116
- if (!document.pointerLockElement || this.isGameOver || !this.isStarted) return;
2117
-
2118
- const deltaX = event.movementX || 0;
2119
- const deltaY = event.movementY || 0;
2120
-
2121
- this.fighter.updateMouseInput(deltaX, deltaY);
2122
- });
 
2123
 
2124
- document.addEventListener('mousedown', (event) => {
2125
- if (!document.pointerLockElement || this.isGameOver || !this.isStarted) return;
2126
-
2127
- if (event.button === 0) {
2128
- this.fighter.isMouseDown = true;
2129
- this.lastShootTime = 0;
2130
- }
2131
- });
 
2132
 
2133
- document.addEventListener('mouseup', (event) => {
2134
- if (event.button === 0) {
2135
- this.fighter.isMouseDown = false;
2136
- }
2137
- });
2138
 
2139
- window.addEventListener('resize', () => {
2140
- this.camera.aspect = window.innerWidth / window.innerHeight;
2141
- this.camera.updateProjectionMatrix();
2142
- this.renderer.setSize(window.innerWidth, window.innerHeight);
2143
- });
2144
- }
2145
 
2146
  startGame() {
2147
  if (!this.isLoaded) {
@@ -2806,88 +2497,72 @@ class Game {
2806
  animateImpact();
2807
 
2808
  // ์ž‘์€ ์ถฉ๋Œ์Œ
2809
- try {
2810
- const impactSound = new Audio('sounds/hit.ogg');
2811
- impactSound.volume = 0.2;
2812
- impactSound.play().catch(e => {
2813
- console.log('Impact sound not found or failed to play');
2814
- });
2815
- } catch (e) {
2816
- console.log('Impact sound error:', e);
2817
- }
2818
  }
2819
-
2820
  checkCollisions() {
2821
- // ํ”Œ๋ ˆ์ด์–ด ํƒ„ํ™˜ vs ์ ๊ธฐ ์ถฉ๋Œ
2822
- for (let i = this.fighter.bullets.length - 1; i >= 0; i--) {
2823
- const bullet = this.fighter.bullets[i];
 
 
 
 
2824
 
2825
- for (let j = this.enemies.length - 1; j >= 0; j--) {
2826
- const enemy = this.enemies[j];
2827
- if (!enemy.mesh || !enemy.isLoaded) continue;
 
 
 
2828
 
2829
- const distance = bullet.position.distanceTo(enemy.position);
2830
- if (distance < 90) {
2831
- console.log(`Hit detected! Distance: ${distance}, Enemy health: ${enemy.health}`);
2832
-
2833
- // ํžˆํŠธ ํ‘œ์‹œ ์ถ”๊ฐ€
2834
- this.showHitMarker(enemy.position);
2835
- // ํ”ผ๊ฒฉ ์ดํŽ™ํŠธ ์ถ”๊ฐ€
2836
- this.createHitEffect(enemy.position);
2837
-
2838
- // ํƒ„ํ™˜ ์ œ๊ฑฐ
2839
- this.scene.remove(bullet);
2840
- this.fighter.bullets.splice(i, 1);
2841
-
2842
- // ์ ๊ธฐ์— ๋ฐ๋ฏธ์ง€ ์ž…ํžˆ๊ธฐ
2843
- const isDestroyed = enemy.takeDamage(GAME_CONSTANTS.BULLET_DAMAGE);
2844
- console.log(`Enemy damaged. New health: ${enemy.health}, Destroyed: ${isDestroyed}`);
2845
 
2846
- if (isDestroyed) {
2847
- console.log('Enemy destroyed! Creating explosion effect...');
2848
-
2849
- // ํญ๋ฐœ ํšจ๊ณผ ์ƒ์„ฑ - ์œ„์น˜๋ฅผ ๋ณต์‚ฌํ•ด์„œ ์ „๋‹ฌ
2850
- const explosionPosition = enemy.position.clone();
2851
- console.log('Explosion position:', explosionPosition);
2852
-
2853
- // ํญ๋ฐœ ํšจ๊ณผ ์ฆ‰์‹œ ์ƒ์„ฑ
2854
- this.createExplosionEffect(explosionPosition);
2855
-
2856
- // ์ ๊ธฐ ์ œ๊ฑฐ
2857
- enemy.destroy();
2858
- this.enemies.splice(j, 1);
2859
- this.score += 100;
2860
-
2861
- console.log(`Enemy removed. Remaining enemies: ${this.enemies.length}`);
2862
- }
2863
- break;
2864
  }
 
2865
  }
2866
  }
2867
-
2868
- // ์  ํƒ„ํ™˜ vs ํ”Œ๋ ˆ์ด์–ด ์ถฉ๋Œ
2869
- this.enemies.forEach(enemy => {
2870
- for (let index = enemy.bullets.length - 1; index >= 0; index--) {
2871
- const bullet = enemy.bullets[index];
2872
- const distance = bullet.position.distanceTo(this.fighter.position);
2873
- if (distance < 100) {
2874
- // ํ”Œ๋ ˆ์ด์–ด ํ”ผ๊ฒฉ ์ดํŽ™ํŠธ
2875
- this.createHitEffect(this.fighter.position);
2876
-
2877
- // ํƒ„ํ™˜ ์ œ๊ฑฐ
2878
- this.scene.remove(bullet);
2879
- enemy.bullets.splice(index, 1);
 
 
 
 
 
2880
 
2881
- if (this.fighter.takeDamage(GAME_CONSTANTS.BULLET_DAMAGE)) {
2882
- // ํ”Œ๋ ˆ์ด์–ด ํŒŒ๊ดด ์‹œ ํญ๋ฐœ ํšจ๊ณผ ์ถ”๊ฐ€
2883
- this.createExplosionEffect(this.fighter.position);
2884
-
2885
- this.endGame(false);
2886
- }
2887
  }
2888
  }
2889
- });
2890
- }
 
2891
 
2892
  createHitEffect(position) {
2893
  // ํ”ผ๊ฒฉ ํŒŒํ‹ฐํด ํšจ๊ณผ ์ƒ์„ฑ
@@ -2988,192 +2663,169 @@ class Game {
2988
  }
2989
 
2990
  createExplosionEffect(position) {
2991
- console.log('createExplosionEffect called with position:', position);
2992
-
2993
- // ์œ„์น˜๊ฐ€ ์œ ํšจํ•œ์ง€ ํ™•์ธ
2994
- if (!position || typeof position.x === 'undefined') {
2995
- console.error('Invalid position for explosion effect');
2996
- return;
2997
- }
2998
 
2999
- // ํญ๋ฐœ์Œ์„ ๊ฐ€์žฅ ๋จผ์ € ์žฌ์ƒ (์‹œ๊ฐํšจ๊ณผ๋ณด๋‹ค ์šฐ์„ )
3000
- try {
3001
- const explosionSound = new Audio('sounds/bang.ogg');
3002
- explosionSound.volume = 1.0; // ์ตœ๋Œ€ ์Œ๋Ÿ‰
3003
-
3004
- // ํ”Œ๋ ˆ์ด์–ด์™€์˜ ๊ฑฐ๋ฆฌ์— ๋”ฐ๋ฅธ ๋ณผ๋ฅจ ์กฐ์ •
3005
- if (this.fighter && this.fighter.position) {
3006
- const distanceToPlayer = position.distanceTo(this.fighter.position);
3007
- const maxAudibleDistance = 5000;
3008
- if (distanceToPlayer < maxAudibleDistance) {
3009
- explosionSound.volume = Math.max(0.3, 1.0 - (distanceToPlayer / maxAudibleDistance));
3010
- console.log(`Explosion sound volume: ${explosionSound.volume} (distance: ${distanceToPlayer})`);
3011
- }
3012
- }
3013
-
3014
- // ์ฆ‰์‹œ ์žฌ์ƒ ์‹œ๋„
3015
- const playPromise = explosionSound.play();
3016
- if (playPromise !== undefined) {
3017
- playPromise
3018
- .then(() => console.log('Explosion sound played successfully'))
3019
- .catch(e => console.error('Explosion sound failed:', e));
3020
- }
3021
- } catch (e) {
3022
- console.error('Explosion sound error:', e);
3023
  }
3024
-
3025
- // ๋ฉ”์ธ ํญ๋ฐœ ํ”Œ๋ž˜์‹œ
3026
- const explosionGeometry = new THREE.SphereGeometry(50, 16, 16);
3027
- const explosionMaterial = new THREE.MeshBasicMaterial({
3028
- color: 0xffaa00,
3029
- emissive: 0xffaa00,
3030
- emissiveIntensity: 3.0,
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3031
  transparent: true,
3032
  opacity: 1.0
3033
  });
3034
 
3035
- const explosion = new THREE.Mesh(explosionGeometry, explosionMaterial);
3036
- explosion.position.copy(position);
3037
- this.scene.add(explosion);
3038
- console.log('Explosion mesh added to scene');
3039
-
3040
- // ํญ๋ฐœ ํŒŒํŽธ๋“ค
3041
- const debrisCount = 30;
3042
- const debris = [];
3043
-
3044
- for (let i = 0; i < debrisCount; i++) {
3045
- const debrisGeometry = new THREE.BoxGeometry(
3046
- 2 + Math.random() * 4,
3047
- 2 + Math.random() * 4,
3048
- 2 + Math.random() * 4
3049
- );
3050
- const debrisMaterial = new THREE.MeshBasicMaterial({
3051
- color: Math.random() > 0.5 ? 0xff6600 : 0x333333,
3052
- transparent: true,
3053
- opacity: 1.0
3054
- });
3055
-
3056
- const debrisPiece = new THREE.Mesh(debrisGeometry, debrisMaterial);
3057
- debrisPiece.position.copy(position);
3058
-
3059
- // ๋žœ๋ค ์†๋„์™€ ํšŒ์ „
3060
- debrisPiece.velocity = new THREE.Vector3(
3061
- (Math.random() - 0.5) * 200,
3062
- (Math.random() - 0.5) * 200,
3063
- (Math.random() - 0.5) * 200
3064
- );
3065
- debrisPiece.rotationSpeed = new THREE.Vector3(
3066
- Math.random() * 10,
3067
- Math.random() * 10,
3068
- Math.random() * 10
3069
- );
3070
- debrisPiece.life = 2.0;
3071
-
3072
- this.scene.add(debrisPiece);
3073
- debris.push(debrisPiece);
3074
- }
3075
 
3076
- // ์—ฐ๊ธฐ ํšจ๊ณผ
3077
- const smokeCount = 10;
3078
- const smoke = [];
 
 
 
 
 
 
 
 
 
3079
 
3080
- for (let i = 0; i < smokeCount; i++) {
3081
- const smokeGeometry = new THREE.SphereGeometry(10 + Math.random() * 20, 8, 8);
3082
- const smokeMaterial = new THREE.MeshBasicMaterial({
3083
- color: 0x222222,
3084
- transparent: true,
3085
- opacity: 0.8
3086
- });
3087
-
3088
- const smokePuff = new THREE.Mesh(smokeGeometry, smokeMaterial);
3089
- smokePuff.position.copy(position);
3090
- smokePuff.position.add(new THREE.Vector3(
3091
- (Math.random() - 0.5) * 20,
3092
- (Math.random() - 0.5) * 20,
3093
- (Math.random() - 0.5) * 20
3094
- ));
3095
-
3096
- smokePuff.velocity = new THREE.Vector3(
3097
- (Math.random() - 0.5) * 50,
3098
- Math.random() * 50 + 20,
3099
- (Math.random() - 0.5) * 50
3100
- );
3101
- smokePuff.life = 3.0;
3102
-
3103
- this.scene.add(smokePuff);
3104
- smoke.push(smokePuff);
3105
- }
3106
 
3107
- console.log(`Explosion effect created with ${debrisCount} debris and ${smokeCount} smoke particles`);
 
 
 
 
 
 
 
 
 
 
 
 
 
3108
 
3109
- // ์• ๋‹ˆ๋ฉ”์ด์…˜
3110
- const animateExplosion = () => {
3111
- let allDead = true;
3112
-
3113
- // ๋ฉ”์ธ ํญ๋ฐœ ์• ๋‹ˆ๋ฉ”์ด์…˜
3114
- if (explosion.material.opacity > 0) {
3115
- explosion.material.opacity -= 0.02;
3116
- explosion.scale.multiplyScalar(1.08);
 
 
 
 
 
 
 
 
 
 
 
 
3117
  allDead = false;
3118
- } else if (explosion.parent) {
3119
- this.scene.remove(explosion);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3120
  }
3121
-
3122
- // ํŒŒํŽธ ์• ๋‹ˆ๋ฉ”์ด์…˜
3123
- debris.forEach(piece => {
3124
- if (piece.life > 0) {
3125
- allDead = false;
3126
- piece.life -= 0.02;
3127
-
3128
- // ์œ„์น˜ ์—…๋ฐ์ดํŠธ
3129
- piece.position.add(piece.velocity.clone().multiplyScalar(0.02));
3130
-
3131
- // ์ค‘๋ ฅ
3132
- piece.velocity.y -= 3;
3133
-
3134
- // ํšŒ์ „
3135
- piece.rotation.x += piece.rotationSpeed.x * 0.02;
3136
- piece.rotation.y += piece.rotationSpeed.y * 0.02;
3137
- piece.rotation.z += piece.rotationSpeed.z * 0.02;
3138
-
3139
- // ํŽ˜์ด๋“œ ์•„์›ƒ
3140
- piece.material.opacity = piece.life / 2;
3141
- } else if (piece.parent) {
3142
- this.scene.remove(piece);
3143
- }
3144
- });
3145
-
3146
- // ์—ฐ๊ธฐ ์• ๋‹ˆ๋ฉ”์ด์…˜
3147
- smoke.forEach(puff => {
3148
- if (puff.life > 0) {
3149
- allDead = false;
3150
- puff.life -= 0.02;
3151
-
3152
- // ์œ„์น˜ ์—…๋ฐ์ดํŠธ
3153
- puff.position.add(puff.velocity.clone().multiplyScalar(0.02));
3154
-
3155
- // ์ƒ์Šน ๊ฐ์†
3156
- puff.velocity.y *= 0.98;
3157
- puff.velocity.x *= 0.98;
3158
- puff.velocity.z *= 0.98;
3159
-
3160
- // ํ™•์‚ฐ
3161
- puff.scale.multiplyScalar(1.02);
3162
-
3163
- // ํŽ˜์ด๋“œ ์•„์›ƒ
3164
- puff.material.opacity = (puff.life / 3) * 0.8;
3165
- } else if (puff.parent) {
3166
- this.scene.remove(puff);
3167
- }
3168
- });
3169
-
3170
- if (!allDead) {
3171
- requestAnimationFrame(animateExplosion);
3172
  }
3173
- };
3174
 
3175
- animateExplosion();
3176
- }
 
 
 
 
 
3177
 
3178
  showHitMarker(position) {
3179
  // ํžˆํŠธ ๋งˆ์ปค div ์ƒ์„ฑ
@@ -3226,101 +2878,101 @@ class Game {
3226
  }
3227
 
3228
  animate() {
3229
- if (this.isGameOver) return;
 
 
 
 
 
 
 
 
 
 
 
 
3230
 
3231
- this.animationFrameId = requestAnimationFrame(() => this.animate());
 
3232
 
3233
- const currentTime = performance.now();
3234
- const deltaTime = Math.min((currentTime - this.lastTime) / 1000, 0.1);
3235
- this.lastTime = currentTime;
3236
 
3237
- if (this.isLoaded && this.fighter.isLoaded && this.isStarted) {
3238
- // ํ‚ค ์ƒํƒœ ๋””๋ฒ„๊น…
3239
- if (this.keys.w || this.keys.s || this.keys.a || this.keys.d) {
3240
- console.log('animate() - Keys state:', this.keys);
3241
- }
3242
-
3243
- // Fํ‚ค ์ƒํƒœ๋ฅผ Fighter์— ์ „๋‹ฌ
3244
- this.fighter.escapeKeyPressed = this.keys.f;
3245
-
3246
- // ์ปจํŠธ๋กค ์—…๋ฐ์ดํŠธ - ๋ฐ˜๋“œ์‹œ ๋ฌผ๋ฆฌ ์—…๋ฐ์ดํŠธ ์ „์— ํ˜ธ์ถœ
3247
- this.fighter.updateControls(this.keys, deltaTime);
3248
-
3249
- // ๋ฌผ๋ฆฌ ์—…๋ฐ์ดํŠธ
3250
- this.fighter.updatePhysics(deltaTime);
3251
-
3252
- // ํƒ„ํ™˜ ์—…๋ฐ์ดํŠธ
3253
- this.fighter.updateBullets(this.scene, deltaTime, this);
3254
-
3255
- // ๋งˆ์šฐ์Šค ๋ˆ„๋ฆ„ ์ƒํƒœ์ผ ๋•Œ ์—ฐ์† ๋ฐœ์‚ฌ
3256
- if (this.fighter.isMouseDown) {
3257
- const currentShootTime = Date.now();
3258
- if (!this.lastShootTime || currentShootTime - this.lastShootTime >= 100) {
3259
- this.fighter.shoot(this.scene);
3260
- this.lastShootTime = currentShootTime;
3261
- }
3262
- }
3263
-
3264
- // ์ ๊ธฐ ์—…๋ฐ์ดํŠธ
3265
- this.enemies.forEach(enemy => {
3266
- enemy.nearbyEnemies = this.enemies;
3267
- });
3268
-
3269
- this.enemies.forEach(enemy => {
3270
- enemy.update(this.fighter.position, deltaTime);
3271
- });
3272
-
3273
- // ์ถฉ๋Œ ์ฒดํฌ
3274
- this.checkCollisions();
3275
-
3276
- // ๊ฒŒ์ž„ ์ข…๋ฃŒ ์กฐ๊ฑด ์ฒดํฌ
3277
- if (this.fighter.health <= 0) {
3278
- if (this.fighter.position.y <= 0) {
3279
- this.endGame(false, "GROUND COLLISION");
3280
- } else {
3281
- this.endGame(false);
3282
- }
3283
- return;
3284
  }
3285
-
3286
- // UI ์—…๋ฐ์ดํŠธ
3287
- this.updateUI();
3288
- this.updateRadar();
3289
-
3290
- // ์ ์ด ๋ชจ๋‘ ์ œ๊ฑฐ๋˜์—ˆ๋Š”์ง€ ์ฒดํฌ
3291
- if (this.enemies.length === 0) {
3292
- this.endGame(true);
 
 
 
 
 
 
 
 
 
 
 
 
3293
  }
3294
- } else if (this.isLoaded && this.fighter.isLoaded) {
3295
- // ๊ฒŒ์ž„์ด ์‹œ์ž‘๋˜์ง€ ์•Š์•˜์„ ๋•Œ๋„ ๋ฌผ๋ฆฌ๋Š” ์—…๋ฐ์ดํŠธ (์นด๋ฉ”๋ผ ์›€์ง์ž„์„ ์œ„ํ•ด)
3296
- this.fighter.updatePhysics(deltaTime);
3297
- }
3298
-
3299
- // ๊ตฌ๋ฆ„ ์• ๋‹ˆ๋ฉ”์ด์…˜
3300
- if (this.clouds) {
3301
- this.clouds.forEach(cloud => {
3302
- cloud.userData.time += deltaTime;
3303
- cloud.position.x += cloud.userData.driftSpeed;
3304
- cloud.position.y = cloud.userData.initialY +
3305
- Math.sin(cloud.userData.time * cloud.userData.floatSpeed) * 20;
3306
-
3307
- const mapLimit = GAME_CONSTANTS.MAP_SIZE / 2;
3308
- if (cloud.position.x > mapLimit) cloud.position.x = -mapLimit;
3309
- if (cloud.position.x < -mapLimit) cloud.position.x = mapLimit;
3310
- });
3311
  }
3312
 
3313
- // ์นด๋ฉ”๋ผ ์—…๋ฐ์ดํŠธ
3314
- if (this.fighter.isLoaded) {
3315
- const targetCameraPos = this.fighter.getCameraPosition();
3316
- const targetCameraTarget = this.fighter.getCameraTarget();
3317
-
3318
- this.camera.position.lerp(targetCameraPos, this.fighter.cameraLag);
3319
- this.camera.lookAt(targetCameraTarget);
3320
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3321
 
3322
- this.renderer.render(this.scene, this.camera);
 
3323
  }
 
 
 
3324
 
3325
  endGame(victory = false, reason = "") {
3326
  this.isGameOver = true;
 
792
  }
793
  }
794
 
795
+ // ์  ์ „ํˆฌ๊ธฐ ํด๋ž˜์Šค - ์™„์ „ํžˆ ์žฌ์„ค๊ณ„
796
  class EnemyFighter {
797
  constructor(scene, position) {
798
  this.mesh = null;
 
838
  this.nearbyEnemies = [];
839
  this.avoidanceVector = new THREE.Vector3();
840
 
 
 
 
841
  // ์ดˆ๊ธฐ ๋ชฉํ‘œ ์„ค์ •
842
  this.selectNewPatrolTarget();
843
  }
 
889
  }
890
 
891
  update(playerPosition, deltaTime) {
892
+ if (!this.mesh || !this.isLoaded) return;
893
 
894
  // ํšŒํ”ผ ํƒ€์ด๋จธ ์—…๋ฐ์ดํŠธ
895
  if (this.temporaryEvadeMode && this.evadeTimer > 0) {
 
905
  if (distanceToPlayer < 100) {
906
  this.isRetreating = true;
907
  this.aiState = 'retreat';
908
+ // ํ›„ํ‡ด ์‹œ์ž‘ ์‹œ ๋กœ๊ทธ
909
  console.log(`Enemy retreating! Distance: ${distanceToPlayer.toFixed(1)}m`);
910
  }
911
 
 
918
  // ์ƒํƒœ ๊ฒฐ์ • - ํ›„ํ‡ด๊ฐ€ ์ตœ์šฐ์„ 
919
  if (this.isRetreating) {
920
  this.aiState = 'retreat';
921
+ // ํ›„ํ‡ด ์ค‘์—๋Š” ๋‹ค๋ฅธ ์ƒํƒœ๋กœ ๋ณ€๊ฒฝ ๋ถˆ๊ฐ€
922
  } else if (this.temporaryEvadeMode) {
923
  this.aiState = 'evade';
924
  } else if (distanceToPlayer <= 3000) {
 
956
  this.updateBullets(deltaTime);
957
  }
958
 
959
+ // ์ƒˆ๋กœ์šด ๋ฉ”์„œ๋“œ: ํ›„ํ‡ด ์‹คํ–‰
960
  executeRetreat(playerPosition, deltaTime) {
961
  // ํ”Œ๋ ˆ์ด์–ด๋กœ๋ถ€ํ„ฐ ๋ฉ€์–ด์ง€๋Š” ๋ฐฉํ–ฅ ๊ณ„์‚ฐ
962
  const retreatDirection = this.position.clone().sub(playerPosition).normalize();
 
1294
  }
1295
 
1296
  updatePhysics(deltaTime) {
1297
+ if (!this.mesh) return;
1298
 
1299
  // ์†๋„ ๋ฒกํ„ฐ ๊ณ„์‚ฐ (ํ•ญ์ƒ ์ „์ง„)
1300
  const forward = new THREE.Vector3(0, 0, 1);
 
1482
 
1483
  takeDamage(damage) {
1484
  this.health -= damage;
1485
+ return this.health <= 0;
 
 
 
 
 
 
1486
  }
1487
 
1488
  destroy() {
1489
+ if (this.mesh) {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1490
  this.scene.remove(this.mesh);
1491
+ this.bullets.forEach(bullet => this.scene.remove(bullet));
1492
+ this.bullets = [];
1493
+ this.isLoaded = false;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1494
  }
1495
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1496
  }
1497
 
1498
  // ๋ฉ”์ธ ๊ฒŒ์ž„ ํด๋ž˜์Šค
 
1757
  }
1758
 
1759
  setupEventListeners() {
1760
+ document.addEventListener('keydown', (event) => {
1761
+ if (this.isGameOver) return;
1762
+
1763
+ // gameStarted ๋Œ€์‹  this.isStarted ์‚ฌ์šฉ
1764
+ if (!this.isStarted) return;
1765
+
1766
+ switch(event.code) {
1767
+ case 'KeyW':
1768
+ this.keys.w = true;
1769
+ console.log('W key pressed - Accelerating');
1770
+ break;
1771
+ case 'KeyA':
1772
+ this.keys.a = true;
1773
+ console.log('A key pressed - Turning left');
1774
+ break;
1775
+ case 'KeyS':
1776
+ this.keys.s = true;
1777
+ console.log('S key pressed - Decelerating');
1778
+ break;
1779
+ case 'KeyD':
1780
+ this.keys.d = true;
1781
+ console.log('D key pressed - Turning right');
1782
+ break;
1783
+ case 'KeyF':
1784
+ this.keys.f = true;
1785
+ break;
1786
+ }
1787
+ });
1788
 
1789
+ document.addEventListener('keyup', (event) => {
1790
+ if (this.isGameOver) return;
1791
+
1792
+ // gameStarted ๋Œ€์‹  this.isStarted ์‚ฌ์šฉ
1793
+ if (!this.isStarted) return;
1794
+
1795
+ switch(event.code) {
1796
+ case 'KeyW': this.keys.w = false; break;
1797
+ case 'KeyA': this.keys.a = false; break;
1798
+ case 'KeyS': this.keys.s = false; break;
1799
+ case 'KeyD': this.keys.d = false; break;
1800
+ case 'KeyF': this.keys.f = false; break;
1801
+ }
1802
+ });
1803
 
1804
+ document.addEventListener('mousemove', (event) => {
1805
+ // ์—ฌ๊ธฐ๋„ gameStarted๋ฅผ this.isStarted๋กœ ๋ณ€๊ฒฝ
1806
+ if (!document.pointerLockElement || this.isGameOver || !this.isStarted) return;
1807
+
1808
+ const deltaX = event.movementX || 0;
1809
+ const deltaY = event.movementY || 0;
1810
+
1811
+ this.fighter.updateMouseInput(deltaX, deltaY);
1812
+ });
1813
 
1814
+ document.addEventListener('mousedown', (event) => {
1815
+ // ์—ฌ๊ธฐ๋„ gameStarted๋ฅผ this.isStarted๋กœ ๋ณ€๊ฒฝ
1816
+ if (!document.pointerLockElement || this.isGameOver || !this.isStarted) return;
1817
+
1818
+ if (event.button === 0) {
1819
+ this.fighter.isMouseDown = true;
1820
+ this.lastShootTime = 0;
1821
+ }
1822
+ });
1823
 
1824
+ document.addEventListener('mouseup', (event) => {
1825
+ if (event.button === 0) {
1826
+ this.fighter.isMouseDown = false;
1827
+ }
1828
+ });
1829
 
1830
+ window.addEventListener('resize', () => {
1831
+ this.camera.aspect = window.innerWidth / window.innerHeight;
1832
+ this.camera.updateProjectionMatrix();
1833
+ this.renderer.setSize(window.innerWidth, window.innerHeight);
1834
+ });
1835
+ }
1836
 
1837
  startGame() {
1838
  if (!this.isLoaded) {
 
2497
  animateImpact();
2498
 
2499
  // ์ž‘์€ ์ถฉ๋Œ์Œ
2500
+ try {
2501
+ const impactSound = new Audio('sounds/hit.ogg');
2502
+ impactSound.volume = 0.2;
2503
+ impactSound.play().catch(e => {
2504
+ console.log('Impact sound not found or failed to play');
2505
+ });
2506
+ } catch (e) {
2507
+ console.log('Impact sound error:', e);
 
2508
  }
2509
+ }
2510
  checkCollisions() {
2511
+ // ํ”Œ๋ ˆ์ด์–ด ํƒ„ํ™˜ vs ์ ๊ธฐ ์ถฉ๋Œ
2512
+ for (let i = this.fighter.bullets.length - 1; i >= 0; i--) {
2513
+ const bullet = this.fighter.bullets[i];
2514
+
2515
+ for (let j = this.enemies.length - 1; j >= 0; j--) {
2516
+ const enemy = this.enemies[j];
2517
+ if (!enemy.mesh || !enemy.isLoaded) continue;
2518
 
2519
+ const distance = bullet.position.distanceTo(enemy.position);
2520
+ if (distance < 90) {
2521
+ // ํžˆํŠธ ํ‘œ์‹œ ์ถ”๊ฐ€
2522
+ this.showHitMarker(enemy.position);
2523
+ // ํ”ผ๊ฒฉ ์ดํŽ™ํŠธ ์ถ”๊ฐ€
2524
+ this.createHitEffect(enemy.position);
2525
 
2526
+ // ํƒ„ํ™˜ ์ œ๊ฑฐ๋Š” ์ดํŽ™ํŠธ ์ƒ์„ฑ ํ›„์—
2527
+ this.scene.remove(bullet);
2528
+ this.fighter.bullets.splice(i, 1);
2529
+
2530
+ if (enemy.takeDamage(GAME_CONSTANTS.BULLET_DAMAGE)) {
2531
+ // ์ ๊ธฐ ํŒŒ๊ดด ์‹œ ํญ๋ฐœ ํšจ๊ณผ ์ถ”๊ฐ€ - ์œ„์น˜ ํ™•์ธ
2532
+ this.createExplosionEffect(enemy.position);
 
 
 
 
 
 
 
 
 
2533
 
2534
+ enemy.destroy();
2535
+ this.enemies.splice(j, 1);
2536
+ this.score += 100;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2537
  }
2538
+ break;
2539
  }
2540
  }
2541
+ }
2542
+
2543
+ // ์  ํƒ„ํ™˜ vs ํ”Œ๋ ˆ์ด์–ด ์ถฉ๋Œ
2544
+ this.enemies.forEach(enemy => {
2545
+ for (let index = enemy.bullets.length - 1; index >= 0; index--) {
2546
+ const bullet = enemy.bullets[index];
2547
+ const distance = bullet.position.distanceTo(this.fighter.position);
2548
+ if (distance < 100) {
2549
+ // ํ”Œ๋ ˆ์ด์–ด ํ”ผ๊ฒฉ ์ดํŽ™ํŠธ
2550
+ this.createHitEffect(this.fighter.position);
2551
+
2552
+ // ํƒ„ํ™˜ ์ œ๊ฑฐ
2553
+ this.scene.remove(bullet);
2554
+ enemy.bullets.splice(index, 1);
2555
+
2556
+ if (this.fighter.takeDamage(GAME_CONSTANTS.BULLET_DAMAGE)) {
2557
+ // ํ”Œ๋ ˆ์ด์–ด ํŒŒ๊ดด ์‹œ ํญ๋ฐœ ํšจ๊ณผ ์ถ”๊ฐ€
2558
+ this.createExplosionEffect(this.fighter.position);
2559
 
2560
+ this.endGame(false);
 
 
 
 
 
2561
  }
2562
  }
2563
+ }
2564
+ });
2565
+ }
2566
 
2567
  createHitEffect(position) {
2568
  // ํ”ผ๊ฒฉ ํŒŒํ‹ฐํด ํšจ๊ณผ ์ƒ์„ฑ
 
2663
  }
2664
 
2665
  createExplosionEffect(position) {
2666
+ // ํญ๋ฐœ์Œ์„ ๊ฐ€์žฅ ๋จผ์ € ์žฌ์ƒ (์‹œ๊ฐํšจ๊ณผ๋ณด๋‹ค ์šฐ์„ )
2667
+ try {
2668
+ const explosionSound = new Audio('sounds/bang.ogg');
2669
+ explosionSound.volume = 1.0; // ์ตœ๋Œ€ ์Œ๋Ÿ‰
 
 
 
2670
 
2671
+ // ์ฆ‰์‹œ ์žฌ์ƒ ์‹œ๋„
2672
+ const playPromise = explosionSound.play();
2673
+ if (playPromise !== undefined) {
2674
+ playPromise.catch(e => console.log('Explosion sound failed:', e));
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2675
  }
2676
+ } catch (e) {
2677
+ console.log('Explosion sound error:', e);
2678
+ }
2679
+
2680
+ // ๋ฉ”์ธ ํญ๋ฐœ ํ”Œ๋ž˜์‹œ
2681
+ const explosionGeometry = new THREE.SphereGeometry(50, 16, 16);
2682
+ const explosionMaterial = new THREE.MeshBasicMaterial({
2683
+ color: 0xffaa00,
2684
+ emissive: 0xffaa00,
2685
+ emissiveIntensity: 3.0,
2686
+ transparent: true,
2687
+ opacity: 1.0
2688
+ });
2689
+
2690
+ const explosion = new THREE.Mesh(explosionGeometry, explosionMaterial);
2691
+ explosion.position.copy(position);
2692
+ this.scene.add(explosion);
2693
+
2694
+ // ํญ๋ฐœ ํŒŒํŽธ๋“ค
2695
+ const debrisCount = 30;
2696
+ const debris = [];
2697
+
2698
+ for (let i = 0; i < debrisCount; i++) {
2699
+ const debrisGeometry = new THREE.BoxGeometry(
2700
+ 2 + Math.random() * 4,
2701
+ 2 + Math.random() * 4,
2702
+ 2 + Math.random() * 4
2703
+ );
2704
+ const debrisMaterial = new THREE.MeshBasicMaterial({
2705
+ color: Math.random() > 0.5 ? 0xff6600 : 0x333333,
2706
  transparent: true,
2707
  opacity: 1.0
2708
  });
2709
 
2710
+ const debrisPiece = new THREE.Mesh(debrisGeometry, debrisMaterial);
2711
+ debrisPiece.position.copy(position);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2712
 
2713
+ // ๋žœ๋ค ์†๋„์™€ ํšŒ์ „
2714
+ debrisPiece.velocity = new THREE.Vector3(
2715
+ (Math.random() - 0.5) * 200,
2716
+ (Math.random() - 0.5) * 200,
2717
+ (Math.random() - 0.5) * 200
2718
+ );
2719
+ debrisPiece.rotationSpeed = new THREE.Vector3(
2720
+ Math.random() * 10,
2721
+ Math.random() * 10,
2722
+ Math.random() * 10
2723
+ );
2724
+ debrisPiece.life = 2.0;
2725
 
2726
+ this.scene.add(debrisPiece);
2727
+ debris.push(debrisPiece);
2728
+ }
2729
+
2730
+ // ์—ฐ๊ธฐ ํšจ๊ณผ
2731
+ const smokeCount = 10;
2732
+ const smoke = [];
2733
+
2734
+ for (let i = 0; i < smokeCount; i++) {
2735
+ const smokeGeometry = new THREE.SphereGeometry(10 + Math.random() * 20, 8, 8);
2736
+ const smokeMaterial = new THREE.MeshBasicMaterial({
2737
+ color: 0x222222,
2738
+ transparent: true,
2739
+ opacity: 0.8
2740
+ });
 
 
 
 
 
 
 
 
 
 
 
2741
 
2742
+ const smokePuff = new THREE.Mesh(smokeGeometry, smokeMaterial);
2743
+ smokePuff.position.copy(position);
2744
+ smokePuff.position.add(new THREE.Vector3(
2745
+ (Math.random() - 0.5) * 20,
2746
+ (Math.random() - 0.5) * 20,
2747
+ (Math.random() - 0.5) * 20
2748
+ ));
2749
+
2750
+ smokePuff.velocity = new THREE.Vector3(
2751
+ (Math.random() - 0.5) * 50,
2752
+ Math.random() * 50 + 20,
2753
+ (Math.random() - 0.5) * 50
2754
+ );
2755
+ smokePuff.life = 3.0;
2756
 
2757
+ this.scene.add(smokePuff);
2758
+ smoke.push(smokePuff);
2759
+ }
2760
+
2761
+ // ์• ๋‹ˆ๋ฉ”์ด์…˜
2762
+ const animateExplosion = () => {
2763
+ let allDead = true;
2764
+
2765
+ // ๋ฉ”์ธ ํญ๋ฐœ ์• ๋‹ˆ๋ฉ”์ด์…˜
2766
+ if (explosion.material.opacity > 0) {
2767
+ explosion.material.opacity -= 0.02;
2768
+ explosion.scale.multiplyScalar(1.08);
2769
+ allDead = false;
2770
+ } else if (explosion.parent) {
2771
+ this.scene.remove(explosion);
2772
+ }
2773
+
2774
+ // ํŒŒํŽธ ์• ๋‹ˆ๋ฉ”์ด์…˜
2775
+ debris.forEach(piece => {
2776
+ if (piece.life > 0) {
2777
  allDead = false;
2778
+ piece.life -= 0.02;
2779
+
2780
+ // ์œ„์น˜ ์—…๋ฐ์ดํŠธ
2781
+ piece.position.add(piece.velocity.clone().multiplyScalar(0.02));
2782
+
2783
+ // ์ค‘๋ ฅ
2784
+ piece.velocity.y -= 3;
2785
+
2786
+ // ํšŒ์ „
2787
+ piece.rotation.x += piece.rotationSpeed.x * 0.02;
2788
+ piece.rotation.y += piece.rotationSpeed.y * 0.02;
2789
+ piece.rotation.z += piece.rotationSpeed.z * 0.02;
2790
+
2791
+ // ํŽ˜์ด๋“œ ์•„์›ƒ
2792
+ piece.material.opacity = piece.life / 2;
2793
+ } else if (piece.parent) {
2794
+ this.scene.remove(piece);
2795
  }
2796
+ });
2797
+
2798
+ // ์—ฐ๊ธฐ ์• ๋‹ˆ๋ฉ”์ด์…˜
2799
+ smoke.forEach(puff => {
2800
+ if (puff.life > 0) {
2801
+ allDead = false;
2802
+ puff.life -= 0.02;
2803
+
2804
+ // ์œ„์น˜ ์—…๋ฐ์ดํŠธ
2805
+ puff.position.add(puff.velocity.clone().multiplyScalar(0.02));
2806
+
2807
+ // ์ƒ์Šน ๊ฐ์†
2808
+ puff.velocity.y *= 0.98;
2809
+ puff.velocity.x *= 0.98;
2810
+ puff.velocity.z *= 0.98;
2811
+
2812
+ // ํ™•์‚ฐ
2813
+ puff.scale.multiplyScalar(1.02);
2814
+
2815
+ // ํŽ˜์ด๋“œ ์•„์›ƒ
2816
+ puff.material.opacity = (puff.life / 3) * 0.8;
2817
+ } else if (puff.parent) {
2818
+ this.scene.remove(puff);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2819
  }
2820
+ });
2821
 
2822
+ if (!allDead) {
2823
+ requestAnimationFrame(animateExplosion);
2824
+ }
2825
+ };
2826
+
2827
+ animateExplosion();
2828
+ }
2829
 
2830
  showHitMarker(position) {
2831
  // ํžˆํŠธ ๋งˆ์ปค div ์ƒ์„ฑ
 
2878
  }
2879
 
2880
  animate() {
2881
+ if (this.isGameOver) return;
2882
+
2883
+ this.animationFrameId = requestAnimationFrame(() => this.animate());
2884
+
2885
+ const currentTime = performance.now();
2886
+ const deltaTime = Math.min((currentTime - this.lastTime) / 1000, 0.1);
2887
+ this.lastTime = currentTime;
2888
+
2889
+ if (this.isLoaded && this.fighter.isLoaded && this.isStarted) {
2890
+ // ํ‚ค ์ƒํƒœ ๋””๋ฒ„๊น…
2891
+ if (this.keys.w || this.keys.s || this.keys.a || this.keys.d) {
2892
+ console.log('animate() - Keys state:', this.keys);
2893
+ }
2894
 
2895
+ // Fํ‚ค ์ƒํƒœ๋ฅผ Fighter์— ์ „๋‹ฌ
2896
+ this.fighter.escapeKeyPressed = this.keys.f;
2897
 
2898
+ // ์ปจํŠธ๋กค ์—…๋ฐ์ดํŠธ - ๋ฐ˜๋“œ์‹œ ๋ฌผ๋ฆฌ ์—…๋ฐ์ดํŠธ ์ „์— ํ˜ธ์ถœ
2899
+ this.fighter.updateControls(this.keys, deltaTime);
 
2900
 
2901
+ // ๋ฌผ๋ฆฌ ์—…๋ฐ์ดํŠธ
2902
+ this.fighter.updatePhysics(deltaTime);
2903
+
2904
+ // ํƒ„ํ™˜ ์—…๋ฐ์ดํŠธ
2905
+ this.fighter.updateBullets(this.scene, deltaTime, this);
2906
+
2907
+ // ๋งˆ์šฐ์Šค ๋ˆ„๋ฆ„ ์ƒํƒœ์ผ ๋•Œ ์—ฐ์† ๋ฐœ์‚ฌ
2908
+ if (this.fighter.isMouseDown) {
2909
+ const currentShootTime = Date.now();
2910
+ if (!this.lastShootTime || currentShootTime - this.lastShootTime >= 100) {
2911
+ this.fighter.shoot(this.scene);
2912
+ this.lastShootTime = currentShootTime;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2913
  }
2914
+ }
2915
+
2916
+ // ์ ๊ธฐ ์—…๋ฐ์ดํŠธ
2917
+ this.enemies.forEach(enemy => {
2918
+ enemy.nearbyEnemies = this.enemies;
2919
+ });
2920
+
2921
+ this.enemies.forEach(enemy => {
2922
+ enemy.update(this.fighter.position, deltaTime);
2923
+ });
2924
+
2925
+ // ์ถฉ๋Œ ์ฒดํฌ
2926
+ this.checkCollisions();
2927
+
2928
+ // ๊ฒŒ์ž„ ์ข…๋ฃŒ ์กฐ๊ฑด ์ฒดํฌ
2929
+ if (this.fighter.health <= 0) {
2930
+ if (this.fighter.position.y <= 0) {
2931
+ this.endGame(false, "GROUND COLLISION");
2932
+ } else {
2933
+ this.endGame(false);
2934
  }
2935
+ return;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2936
  }
2937
 
2938
+ // UI ์—…๋ฐ์ดํŠธ
2939
+ this.updateUI();
2940
+ this.updateRadar();
2941
+
2942
+ // ์ ์ด ๋ชจ๋‘ ์ œ๊ฑฐ๋˜์—ˆ๋Š”์ง€ ์ฒดํฌ
2943
+ if (this.enemies.length === 0) {
2944
+ this.endGame(true);
2945
  }
2946
+ } else if (this.isLoaded && this.fighter.isLoaded) {
2947
+ // ๊ฒŒ์ž„์ด ์‹œ์ž‘๋˜์ง€ ์•Š์•˜์„ ๋•Œ๋„ ๋ฌผ๋ฆฌ๋Š” ์—…๋ฐ์ดํŠธ (์นด๋ฉ”๋ผ ์›€์ง์ž„์„ ์œ„ํ•ด)
2948
+ this.fighter.updatePhysics(deltaTime);
2949
+ }
2950
+
2951
+ // ๊ตฌ๋ฆ„ ์• ๋‹ˆ๋ฉ”์ด์…˜
2952
+ if (this.clouds) {
2953
+ this.clouds.forEach(cloud => {
2954
+ cloud.userData.time += deltaTime;
2955
+ cloud.position.x += cloud.userData.driftSpeed;
2956
+ cloud.position.y = cloud.userData.initialY +
2957
+ Math.sin(cloud.userData.time * cloud.userData.floatSpeed) * 20;
2958
+
2959
+ const mapLimit = GAME_CONSTANTS.MAP_SIZE / 2;
2960
+ if (cloud.position.x > mapLimit) cloud.position.x = -mapLimit;
2961
+ if (cloud.position.x < -mapLimit) cloud.position.x = mapLimit;
2962
+ });
2963
+ }
2964
+
2965
+ // ์นด๋ฉ”๋ผ ์—…๋ฐ์ดํŠธ
2966
+ if (this.fighter.isLoaded) {
2967
+ const targetCameraPos = this.fighter.getCameraPosition();
2968
+ const targetCameraTarget = this.fighter.getCameraTarget();
2969
 
2970
+ this.camera.position.lerp(targetCameraPos, this.fighter.cameraLag);
2971
+ this.camera.lookAt(targetCameraTarget);
2972
  }
2973
+
2974
+ this.renderer.render(this.scene, this.camera);
2975
+ }
2976
 
2977
  endGame(victory = false, reason = "") {
2978
  this.isGameOver = true;