Spaces:
Running
Running
Update game.js
Browse files
game.js
CHANGED
|
@@ -807,106 +807,127 @@ class Fighter {
|
|
| 807 |
}
|
| 808 |
|
| 809 |
shoot(scene) {
|
| 810 |
-
|
| 811 |
-
|
| 812 |
-
|
| 813 |
-
|
| 814 |
-
|
| 815 |
-
|
| 816 |
-
|
| 817 |
-
|
| 818 |
-
|
| 819 |
-
|
| 820 |
-
|
| 821 |
-
|
| 822 |
-
|
| 823 |
-
|
| 824 |
-
|
| 825 |
-
|
| 826 |
-
|
| 827 |
-
|
| 828 |
-
|
| 829 |
-
|
| 830 |
-
|
| 831 |
-
|
| 832 |
-
|
| 833 |
-
|
| 834 |
-
|
| 835 |
-
|
| 836 |
-
|
| 837 |
-
|
| 838 |
-
|
| 839 |
-
|
| 840 |
-
|
| 841 |
-
|
| 842 |
-
|
| 843 |
-
|
| 844 |
-
|
| 845 |
-
|
| 846 |
-
|
| 847 |
-
|
| 848 |
-
|
| 849 |
-
|
| 850 |
-
|
| 851 |
-
|
| 852 |
-
|
| 853 |
-
|
| 854 |
-
|
| 855 |
-
|
| 856 |
-
|
| 857 |
-
|
| 858 |
-
|
| 859 |
-
|
| 860 |
-
|
| 861 |
-
|
| 862 |
-
|
| 863 |
-
|
| 864 |
-
|
| 865 |
-
|
| 866 |
-
|
| 867 |
-
|
| 868 |
-
|
| 869 |
-
|
| 870 |
-
|
| 871 |
-
|
| 872 |
-
|
| 873 |
-
|
| 874 |
-
|
| 875 |
-
|
| 876 |
-
|
| 877 |
-
|
| 878 |
-
|
| 879 |
-
|
| 880 |
-
|
| 881 |
-
|
| 882 |
-
|
| 883 |
-
|
| 884 |
-
|
| 885 |
-
|
| 886 |
-
|
| 887 |
-
|
| 888 |
-
|
| 889 |
-
|
| 890 |
-
|
| 891 |
-
|
| 892 |
-
|
| 893 |
-
|
| 894 |
-
|
| 895 |
-
|
| 896 |
-
|
| 897 |
-
|
| 898 |
-
|
| 899 |
-
|
| 900 |
-
|
| 901 |
-
|
| 902 |
-
|
| 903 |
-
|
| 904 |
-
|
| 905 |
-
|
| 906 |
-
|
| 907 |
-
|
| 908 |
-
|
| 909 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 910 |
|
| 911 |
updateBullets(scene, deltaTime, gameInstance) {
|
| 912 |
for (let i = this.bullets.length - 1; i >= 0; i--) {
|
|
@@ -996,13 +1017,17 @@ class AIM9Missile {
|
|
| 996 |
this.position = position.clone();
|
| 997 |
this.target = target;
|
| 998 |
this.rotation = rotation.clone();
|
| 999 |
-
this.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();
|
|
@@ -1045,7 +1070,7 @@ class AIM9Missile {
|
|
| 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.
|
| 1049 |
this.isLoaded = true;
|
| 1050 |
} catch (error) {
|
| 1051 |
console.log('AIM-9 model not found, using fallback');
|
|
@@ -1099,7 +1124,7 @@ class AIM9Missile {
|
|
| 1099 |
group.add(flame);
|
| 1100 |
|
| 1101 |
this.mesh = group;
|
| 1102 |
-
this.mesh.scale.set(
|
| 1103 |
this.isLoaded = true;
|
| 1104 |
}
|
| 1105 |
|
|
@@ -1145,6 +1170,31 @@ class AIM9Missile {
|
|
| 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);
|
|
@@ -1164,6 +1214,45 @@ class AIM9Missile {
|
|
| 1164 |
return 'flying';
|
| 1165 |
}
|
| 1166 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1167 |
onHit() {
|
| 1168 |
// 명중 효과
|
| 1169 |
if (window.gameInstance) {
|
|
@@ -1192,6 +1281,14 @@ class AIM9Missile {
|
|
| 1192 |
this.scene.remove(this.mesh);
|
| 1193 |
}
|
| 1194 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1195 |
if (this.swingAudio) {
|
| 1196 |
this.swingAudio.pause();
|
| 1197 |
this.swingAudio = null;
|
|
|
|
| 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 isLeftWing = Math.random() < 0.5;
|
| 890 |
+
const wingOffset = new THREE.Vector3(isLeftWing ? -8 : 8, -1, 2); // 날개 위치
|
| 891 |
+
|
| 892 |
+
// 전투기의 회전을 적용
|
| 893 |
+
const quaternion = new THREE.Quaternion();
|
| 894 |
+
const pitchQuat = new THREE.Quaternion();
|
| 895 |
+
const yawQuat = new THREE.Quaternion();
|
| 896 |
+
const rollQuat = new THREE.Quaternion();
|
| 897 |
+
|
| 898 |
+
pitchQuat.setFromAxisAngle(new THREE.Vector3(1, 0, 0), this.rotation.x);
|
| 899 |
+
yawQuat.setFromAxisAngle(new THREE.Vector3(0, 1, 0), this.rotation.y);
|
| 900 |
+
rollQuat.setFromAxisAngle(new THREE.Vector3(0, 0, 1), this.rotation.z);
|
| 901 |
+
|
| 902 |
+
quaternion.multiply(rollQuat);
|
| 903 |
+
quaternion.multiply(pitchQuat);
|
| 904 |
+
quaternion.multiply(yawQuat);
|
| 905 |
+
|
| 906 |
+
wingOffset.applyQuaternion(quaternion);
|
| 907 |
+
const missileStartPos = this.position.clone().add(wingOffset);
|
| 908 |
+
|
| 909 |
+
// 미사일 생성
|
| 910 |
+
const missile = new AIM9Missile(scene, missileStartPos, this.lockTarget, this.rotation.clone());
|
| 911 |
+
this.firedMissiles.push(missile);
|
| 912 |
+
|
| 913 |
+
// 락온 초기화
|
| 914 |
+
this.lockTarget = null;
|
| 915 |
+
this.lockProgress = 0;
|
| 916 |
+
if (this.lockAudios.locking && !this.lockAudios.locking.paused) {
|
| 917 |
+
this.lockAudios.locking.pause();
|
| 918 |
+
this.lockAudios.locking.currentTime = 0;
|
| 919 |
+
}
|
| 920 |
+
|
| 921 |
+
// 발사음
|
| 922 |
+
try {
|
| 923 |
+
const missileSound = new Audio('sounds/missile.ogg');
|
| 924 |
+
missileSound.volume = 0.7;
|
| 925 |
+
missileSound.play().catch(e => {});
|
| 926 |
+
} catch (e) {
|
| 927 |
+
console.log('Missile sound failed:', e);
|
| 928 |
+
}
|
| 929 |
+
}
|
| 930 |
+
}
|
| 931 |
|
| 932 |
updateBullets(scene, deltaTime, gameInstance) {
|
| 933 |
for (let i = this.bullets.length - 1; i >= 0; i--) {
|
|
|
|
| 1017 |
this.position = position.clone();
|
| 1018 |
this.target = target;
|
| 1019 |
this.rotation = rotation.clone();
|
| 1020 |
+
this.speed = 1028.8; // 2000kt를 m/s로 변환
|
| 1021 |
this.mesh = null;
|
| 1022 |
this.isLoaded = false;
|
| 1023 |
this.lifeTime = 20; // 20초 후 자폭
|
| 1024 |
this.turnRate = 3.0; // 초당 회전 속도 (라디안)
|
| 1025 |
this.startPosition = position.clone();
|
| 1026 |
|
| 1027 |
+
// 추력 연기 파티클
|
| 1028 |
+
this.smokeTrail = [];
|
| 1029 |
+
this.smokeEmitTime = 0;
|
| 1030 |
+
|
| 1031 |
// 미사일 발사 방향 설정
|
| 1032 |
const quaternion = new THREE.Quaternion();
|
| 1033 |
const pitchQuat = new THREE.Quaternion();
|
|
|
|
| 1070 |
const loader = new GLTFLoader();
|
| 1071 |
const result = await loader.loadAsync('models/aim-9.glb');
|
| 1072 |
this.mesh = result.scene;
|
| 1073 |
+
this.mesh.scale.set(0.75, 0.75, 0.75); // 50% 더 크게 (0.5 -> 0.75)
|
| 1074 |
this.isLoaded = true;
|
| 1075 |
} catch (error) {
|
| 1076 |
console.log('AIM-9 model not found, using fallback');
|
|
|
|
| 1124 |
group.add(flame);
|
| 1125 |
|
| 1126 |
this.mesh = group;
|
| 1127 |
+
this.mesh.scale.set(2.25, 2.25, 2.25); // 50% 더 크게 (1.5 -> 2.25)
|
| 1128 |
this.isLoaded = true;
|
| 1129 |
}
|
| 1130 |
|
|
|
|
| 1170 |
const lookAtTarget = this.position.clone().add(this.velocity);
|
| 1171 |
this.mesh.lookAt(lookAtTarget);
|
| 1172 |
|
| 1173 |
+
// 추력 연기 생성
|
| 1174 |
+
this.smokeEmitTime += deltaTime;
|
| 1175 |
+
if (this.smokeEmitTime >= 0.02) { // 0.02초마다 연기 생성
|
| 1176 |
+
this.smokeEmitTime = 0;
|
| 1177 |
+
this.createSmokeParticle();
|
| 1178 |
+
}
|
| 1179 |
+
|
| 1180 |
+
// 연기 파티클 업데이트
|
| 1181 |
+
for (let i = this.smokeTrail.length - 1; i >= 0; i--) {
|
| 1182 |
+
const smoke = this.smokeTrail[i];
|
| 1183 |
+
smoke.life -= deltaTime;
|
| 1184 |
+
|
| 1185 |
+
if (smoke.life <= 0) {
|
| 1186 |
+
this.scene.remove(smoke.mesh);
|
| 1187 |
+
this.smokeTrail.splice(i, 1);
|
| 1188 |
+
} else {
|
| 1189 |
+
// 연기 확산 및 페이드
|
| 1190 |
+
smoke.mesh.scale.multiplyScalar(1.02);
|
| 1191 |
+
smoke.mesh.material.opacity = smoke.life / 3.0;
|
| 1192 |
+
|
| 1193 |
+
// 약간의 상승 효과
|
| 1194 |
+
smoke.mesh.position.y += deltaTime * 2;
|
| 1195 |
+
}
|
| 1196 |
+
}
|
| 1197 |
+
|
| 1198 |
// 사운드 볼륨 조정 (플레이어와의 거리에 따라)
|
| 1199 |
if (this.swingAudio && playerPosition) {
|
| 1200 |
const distanceToPlayer = this.position.distanceTo(playerPosition);
|
|
|
|
| 1214 |
return 'flying';
|
| 1215 |
}
|
| 1216 |
|
| 1217 |
+
createSmokeParticle() {
|
| 1218 |
+
// 미사일 뒤쪽 위치 계산
|
| 1219 |
+
const backward = this.velocity.clone().normalize().multiplyScalar(-3);
|
| 1220 |
+
const smokePos = this.position.clone().add(backward);
|
| 1221 |
+
|
| 1222 |
+
// 하얀 연기 파티클 생성
|
| 1223 |
+
const smokeGeometry = new THREE.SphereGeometry(
|
| 1224 |
+
1 + Math.random() * 1.5, // 크기 변화
|
| 1225 |
+
6,
|
| 1226 |
+
6
|
| 1227 |
+
);
|
| 1228 |
+
const smokeMaterial = new THREE.MeshBasicMaterial({
|
| 1229 |
+
color: 0xffffff, // 하얀색
|
| 1230 |
+
transparent: true,
|
| 1231 |
+
opacity: 0.8
|
| 1232 |
+
});
|
| 1233 |
+
|
| 1234 |
+
const smoke = new THREE.Mesh(smokeGeometry, smokeMaterial);
|
| 1235 |
+
smoke.position.copy(smokePos);
|
| 1236 |
+
|
| 1237 |
+
// 약간의 랜덤 오프셋
|
| 1238 |
+
smoke.position.x += (Math.random() - 0.5) * 1;
|
| 1239 |
+
smoke.position.y += (Math.random() - 0.5) * 1;
|
| 1240 |
+
smoke.position.z += (Math.random() - 0.5) * 1;
|
| 1241 |
+
|
| 1242 |
+
this.scene.add(smoke);
|
| 1243 |
+
|
| 1244 |
+
this.smokeTrail.push({
|
| 1245 |
+
mesh: smoke,
|
| 1246 |
+
life: 3.0 // 3초 동안 지속
|
| 1247 |
+
});
|
| 1248 |
+
|
| 1249 |
+
// 연기 파티클 제한 (성능을 위해)
|
| 1250 |
+
if (this.smokeTrail.length > 150) {
|
| 1251 |
+
const oldSmoke = this.smokeTrail.shift();
|
| 1252 |
+
this.scene.remove(oldSmoke.mesh);
|
| 1253 |
+
}
|
| 1254 |
+
}
|
| 1255 |
+
|
| 1256 |
onHit() {
|
| 1257 |
// 명중 효과
|
| 1258 |
if (window.gameInstance) {
|
|
|
|
| 1281 |
this.scene.remove(this.mesh);
|
| 1282 |
}
|
| 1283 |
|
| 1284 |
+
// 연기 파티클 정리
|
| 1285 |
+
this.smokeTrail.forEach(smoke => {
|
| 1286 |
+
if (smoke.mesh) {
|
| 1287 |
+
this.scene.remove(smoke.mesh);
|
| 1288 |
+
}
|
| 1289 |
+
});
|
| 1290 |
+
this.smokeTrail = [];
|
| 1291 |
+
|
| 1292 |
if (this.swingAudio) {
|
| 1293 |
this.swingAudio.pause();
|
| 1294 |
this.swingAudio = null;
|