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