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;
|