cutechicken commited on
Commit
3ec7eb8
ยท
verified ยท
1 Parent(s): a148823

Update game.js

Browse files
Files changed (1) hide show
  1. game.js +326 -294
game.js CHANGED
@@ -1010,301 +1010,333 @@ class Fighter {
1010
  }
1011
  }
1012
 
1013
- // AIM-9 ๋ฏธ์‚ฌ์ผ ํด๋ž˜์Šค
1014
  class AIM9Missile {
1015
- constructor(scene, position, target, rotation) {
1016
- this.scene = scene;
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();
1034
- const yawQuat = new THREE.Quaternion();
1035
- const rollQuat = new THREE.Quaternion();
1036
-
1037
- pitchQuat.setFromAxisAngle(new THREE.Vector3(1, 0, 0), this.rotation.x);
1038
- yawQuat.setFromAxisAngle(new THREE.Vector3(0, 1, 0), this.rotation.y);
1039
- rollQuat.setFromAxisAngle(new THREE.Vector3(0, 0, 1), this.rotation.z);
1040
-
1041
- quaternion.multiply(rollQuat);
1042
- quaternion.multiply(pitchQuat);
1043
- quaternion.multiply(yawQuat);
1044
-
1045
- const initialDirection = new THREE.Vector3(0, 0, 1);
1046
- initialDirection.applyQuaternion(quaternion);
1047
- this.velocity = initialDirection.multiplyScalar(this.speed);
1048
-
1049
- // ๋ฏธ์‚ฌ์ผ ์†Œ๋ฆฌ
1050
- this.swingAudio = null;
1051
- this.initializeAudio();
1052
-
1053
- this.createMissile();
1054
- }
1055
-
1056
- initializeAudio() {
1057
- try {
1058
- this.swingAudio = new Audio('sounds/missileswing.ogg');
1059
- this.swingAudio.volume = 0.3;
1060
- this.swingAudio.loop = true;
1061
- this.swingAudio.play().catch(e => {});
1062
- } catch (e) {
1063
- console.log('Missile swing audio failed:', e);
1064
- }
1065
- }
1066
-
1067
- createMissile() {
1068
- // ์ฆ‰์‹œ ํด๋ฐฑ ๋ชจ๋ธ ์ƒ์„ฑ (๋น„๋™๊ธฐ ๋กœ๋”ฉ ๋Œ€์‹ )
1069
- this.createFallbackMissile();
1070
-
1071
- this.mesh.position.copy(this.position);
1072
- this.scene.add(this.mesh);
1073
-
1074
- // ๋‚˜์ค‘์— GLB ๋ชจ๋ธ ๋กœ๋“œ ์‹œ๋„ (์„ ํƒ์‚ฌํ•ญ)
1075
- const loader = new GLTFLoader();
1076
- loader.load('models/aim-9.glb',
1077
- (result) => {
1078
- // ๋ชจ๋ธ ๋กœ๋“œ ์„ฑ๊ณต ์‹œ ๊ต์ฒด
1079
- if (this.mesh && this.mesh.parent) {
1080
- this.scene.remove(this.mesh);
1081
- this.mesh = result.scene;
1082
- this.mesh.scale.set(0.75, 0.75, 0.75);
1083
- this.mesh.position.copy(this.position);
1084
- this.scene.add(this.mesh);
1085
- console.log('AIM-9 model loaded and replaced');
1086
- }
1087
- },
1088
- undefined,
1089
- (error) => {
1090
- console.log('AIM-9 model not found, keeping fallback');
1091
- }
1092
- );
1093
- }
1094
-
1095
- createFallbackMissile() {
1096
- // ํด๋ฐฑ ๋ฏธ์‚ฌ์ผ ๋ชจ๋ธ
1097
- const group = new THREE.Group();
1098
-
1099
- // ๋ฏธ์‚ฌ์ผ ๋ณธ์ฒด
1100
- const bodyGeometry = new THREE.CylinderGeometry(0.3, 0.4, 4, 8);
1101
- const bodyMaterial = new THREE.MeshLambertMaterial({ color: 0x808080 });
1102
- const body = new THREE.Mesh(bodyGeometry, bodyMaterial);
1103
- body.rotation.x = Math.PI / 2;
1104
- group.add(body);
1105
-
1106
- // ํƒ„๋‘
1107
- const noseGeometry = new THREE.ConeGeometry(0.3, 1, 8);
1108
- const noseMaterial = new THREE.MeshLambertMaterial({ color: 0x404040 });
1109
- const nose = new THREE.Mesh(noseGeometry, noseMaterial);
1110
- nose.position.z = 2.5;
1111
- nose.rotation.x = -Math.PI / 2;
1112
- group.add(nose);
1113
-
1114
- // ๋‚ ๊ฐœ
1115
- const finGeometry = new THREE.BoxGeometry(2, 0.1, 0.5);
1116
- const finMaterial = new THREE.MeshLambertMaterial({ color: 0x606060 });
1117
-
1118
- for (let i = 0; i < 4; i++) {
1119
- const fin = new THREE.Mesh(finGeometry, finMaterial);
1120
- fin.position.z = -1.5;
1121
- fin.rotation.z = (Math.PI / 2) * i;
1122
- group.add(fin);
1123
- }
1124
-
1125
- // ๋ถˆ๊ฝƒ ํšจ๊ณผ
1126
- const flameGeometry = new THREE.ConeGeometry(0.4, 1.5, 8);
1127
- const flameMaterial = new THREE.MeshBasicMaterial({
1128
- color: 0xff4400,
1129
- transparent: true,
1130
- opacity: 0.8
1131
- });
1132
- const flame = new THREE.Mesh(flameGeometry, flameMaterial);
1133
- flame.position.z = -2.5;
1134
- flame.rotation.x = Math.PI / 2;
1135
- group.add(flame);
1136
-
1137
- this.mesh = group;
1138
- this.mesh.scale.set(2.25, 2.25, 2.25); // 50% ๋” ํฌ๊ฒŒ (1.5 -> 2.25)
1139
- this.isLoaded = true;
1140
- }
1141
-
1142
- update(deltaTime, playerPosition) {
1143
- if (!this.mesh || !this.target || !this.target.position) {
1144
- this.destroy();
1145
- return 'expired';
1146
- }
1147
-
1148
- this.lifeTime -= deltaTime;
1149
- if (this.lifeTime <= 0) {
1150
- this.destroy();
1151
- return 'expired';
1152
- }
1153
-
1154
- // ํƒ€๊ฒŸ ์ถ”์ 
1155
- const toTarget = this.target.position.clone().sub(this.position);
1156
- const distance = toTarget.length();
1157
-
1158
- // ๋ช…์ค‘ ์ฒดํฌ
1159
- if (distance < 30) {
1160
- // ๋ช…์ค‘!
1161
- this.onHit();
1162
- return 'hit';
1163
- }
1164
-
1165
- // ํ˜ธ๋ฐ ๋กœ์ง
1166
- toTarget.normalize();
1167
- const currentDirection = this.velocity.clone().normalize();
1168
-
1169
- // ๋ถ€๋“œ๋Ÿฌ์šด ๋ฐฉํ–ฅ ์ „ํ™˜
1170
- const newDirection = new THREE.Vector3();
1171
- newDirection.lerpVectors(currentDirection, toTarget, deltaTime * this.turnRate);
1172
- newDirection.normalize();
1173
-
1174
- this.velocity = newDirection.multiplyScalar(this.speed);
1175
-
1176
- // ์œ„์น˜ ์—…๋ฐ์ดํŠธ
1177
- this.position.add(this.velocity.clone().multiplyScalar(deltaTime));
1178
- this.mesh.position.copy(this.position);
1179
-
1180
- // ๋ฏธ์‚ฌ์ผ ํšŒ์ „
1181
- const lookAtTarget = this.position.clone().add(this.velocity);
1182
- this.mesh.lookAt(lookAtTarget);
1183
-
1184
- // ์ถ”๋ ฅ ์—ฐ๊ธฐ ์ƒ์„ฑ
1185
- this.smokeEmitTime += deltaTime;
1186
- if (this.smokeEmitTime >= 0.02) { // 0.02์ดˆ๋งˆ๋‹ค ์—ฐ๊ธฐ ์ƒ์„ฑ
1187
- this.smokeEmitTime = 0;
1188
- this.createSmokeParticle();
1189
- }
1190
-
1191
- // ์—ฐ๊ธฐ ํŒŒํ‹ฐํด ์—…๋ฐ์ดํŠธ
1192
- for (let i = this.smokeTrail.length - 1; i >= 0; i--) {
1193
- const smoke = this.smokeTrail[i];
1194
- smoke.life -= deltaTime;
1195
-
1196
- if (smoke.life <= 0) {
1197
- this.scene.remove(smoke.mesh);
1198
- this.smokeTrail.splice(i, 1);
1199
- } else {
1200
- // ์—ฐ๊ธฐ ํ™•์‚ฐ ๋ฐ ํŽ˜์ด๋“œ
1201
- smoke.mesh.scale.multiplyScalar(1.02);
1202
- smoke.mesh.material.opacity = smoke.life / 3.0;
1203
-
1204
- // ์•ฝ๊ฐ„์˜ ์ƒ์Šน ํšจ๊ณผ
1205
- smoke.mesh.position.y += deltaTime * 2;
1206
- }
1207
- }
1208
-
1209
- // ์‚ฌ์šด๋“œ ๋ณผ๋ฅจ ์กฐ์ • (ํ”Œ๋ ˆ์ด์–ด์™€์˜ ๊ฑฐ๋ฆฌ์— ๋”ฐ๋ผ)
1210
- if (this.swingAudio && playerPosition) {
1211
- const distanceToPlayer = this.position.distanceTo(playerPosition);
1212
- if (distanceToPlayer < 200) {
1213
- this.swingAudio.volume = 0.3 * (1 - distanceToPlayer / 200);
1214
- } else {
1215
- this.swingAudio.volume = 0;
1216
- }
1217
- }
1218
-
1219
- // ์ง€๋ฉด ์ถฉ๋Œ
1220
- if (this.position.y <= 0) {
1221
- this.destroy();
1222
- return 'expired';
1223
- }
1224
-
1225
- return 'flying';
1226
- }
1227
-
1228
- createSmokeParticle() {
1229
- // ๋ฏธ์‚ฌ์ผ ๋’ค์ชฝ ์œ„์น˜ ๊ณ„์‚ฐ
1230
- const backward = this.velocity.clone().normalize().multiplyScalar(-3);
1231
- const smokePos = this.position.clone().add(backward);
1232
-
1233
- // ํ•˜์–€ ์—ฐ๊ธฐ ํŒŒํ‹ฐํด ์ƒ์„ฑ
1234
- const smokeGeometry = new THREE.SphereGeometry(
1235
- 1 + Math.random() * 1.5, // ํฌ๊ธฐ ๋ณ€ํ™”
1236
- 6,
1237
- 6
1238
- );
1239
- const smokeMaterial = new THREE.MeshBasicMaterial({
1240
- color: 0xffffff, // ํ•˜์–€์ƒ‰
1241
- transparent: true,
1242
- opacity: 0.8
1243
- });
1244
-
1245
- const smoke = new THREE.Mesh(smokeGeometry, smokeMaterial);
1246
- smoke.position.copy(smokePos);
1247
-
1248
- // ์•ฝ๊ฐ„์˜ ๋žœ๋ค ์˜คํ”„์…‹
1249
- smoke.position.x += (Math.random() - 0.5) * 1;
1250
- smoke.position.y += (Math.random() - 0.5) * 1;
1251
- smoke.position.z += (Math.random() - 0.5) * 1;
1252
-
1253
- this.scene.add(smoke);
1254
-
1255
- this.smokeTrail.push({
1256
- mesh: smoke,
1257
- life: 3.0 // 3์ดˆ ๋™์•ˆ ์ง€์†
1258
- });
1259
-
1260
- // ์—ฐ๊ธฐ ํŒŒํ‹ฐํด ์ œํ•œ (์„ฑ๋Šฅ์„ ์œ„ํ•ด)
1261
- if (this.smokeTrail.length > 150) {
1262
- const oldSmoke = this.smokeTrail.shift();
1263
- this.scene.remove(oldSmoke.mesh);
1264
- }
1265
- }
1266
-
1267
- onHit() {
1268
- // ๋ช…์ค‘ ํšจ๊ณผ
1269
- if (window.gameInstance) {
1270
- window.gameInstance.createExplosionEffect(this.position);
1271
- }
1272
-
1273
- // ๋ช…์ค‘์Œ
1274
- try {
1275
- const hitSound = new Audio('sounds/missilehit.ogg');
1276
- hitSound.volume = 0.8;
1277
- hitSound.play().catch(e => {});
1278
- } catch (e) {
1279
- console.log('Missile hit sound failed:', e);
1280
- }
1281
-
1282
- // ํƒ€๊ฒŸ์—๊ฒŒ ํ”ผํ•ด
1283
- if (this.target.takeDamage) {
1284
- this.target.takeDamage(GAME_CONSTANTS.AIM9_DAMAGE);
1285
- }
1286
-
1287
- this.destroy();
1288
- }
1289
-
1290
- destroy() {
1291
- if (this.mesh) {
1292
- this.scene.remove(this.mesh);
1293
- }
1294
-
1295
- // ์—ฐ๊ธฐ ํŒŒํ‹ฐํด ์ •๋ฆฌ
1296
- this.smokeTrail.forEach(smoke => {
1297
- if (smoke.mesh) {
1298
- this.scene.remove(smoke.mesh);
1299
- }
1300
- });
1301
- this.smokeTrail = [];
1302
-
1303
- if (this.swingAudio) {
1304
- this.swingAudio.pause();
1305
- this.swingAudio = null;
1306
- }
1307
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1308
  }
1309
 
1310
  // ์  ์ „ํˆฌ๊ธฐ ํด๋ž˜์Šค - ์™„์ „ํžˆ ์žฌ์„ค๊ณ„
 
1010
  }
1011
  }
1012
 
1013
+ javascript// AIM-9 ๋ฏธ์‚ฌ์ผ ํด๋ž˜์Šค
1014
  class AIM9Missile {
1015
+ constructor(scene, position, target, rotation) {
1016
+ this.scene = scene;
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 = 30; // 30์ดˆ๋กœ ์ฆ๊ฐ€ (๋” ์˜ค๋ž˜ ์ถ”์ )
1024
+ this.turnRate = 6.0; // ํšŒ์ „์œจ 2๋ฐฐ ์ฆ๊ฐ€ (๋” ๋น ๋ฅธ ์ถ”์ )
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();
1034
+ const yawQuat = new THREE.Quaternion();
1035
+ const rollQuat = new THREE.Quaternion();
1036
+
1037
+ pitchQuat.setFromAxisAngle(new THREE.Vector3(1, 0, 0), this.rotation.x);
1038
+ yawQuat.setFromAxisAngle(new THREE.Vector3(0, 1, 0), this.rotation.y);
1039
+ rollQuat.setFromAxisAngle(new THREE.Vector3(0, 0, 1), this.rotation.z);
1040
+
1041
+ quaternion.multiply(rollQuat);
1042
+ quaternion.multiply(pitchQuat);
1043
+ quaternion.multiply(yawQuat);
1044
+
1045
+ const initialDirection = new THREE.Vector3(0, 0, 1);
1046
+ initialDirection.applyQuaternion(quaternion);
1047
+ this.velocity = initialDirection.multiplyScalar(this.speed);
1048
+
1049
+ // ๋ฏธ์‚ฌ์ผ ์†Œ๋ฆฌ
1050
+ this.swingAudio = null;
1051
+ this.swingAudioPlayed = false; // ํ•œ ๋ฒˆ๋งŒ ์žฌ์ƒํ•˜๊ธฐ ์œ„ํ•œ ํ”Œ๋ž˜๊ทธ
1052
+ this.initializeAudio();
1053
+
1054
+ this.createMissile();
1055
+ }
1056
+
1057
+ initializeAudio() {
1058
+ // missileswing.ogg๋Š” update ๋ฉ”์„œ๋“œ์—์„œ ํ•œ ๋ฒˆ๋งŒ ์žฌ์ƒ
1059
+ // ์—ฌ๊ธฐ์„œ๋Š” ์ดˆ๊ธฐํ™”ํ•˜์ง€ ์•Š์Œ
1060
+ }
1061
+
1062
+ createMissile() {
1063
+ // ์ฆ‰์‹œ ํด๋ฐฑ ๋ชจ๋ธ ์ƒ์„ฑ (๋น„๋™๊ธฐ ๋กœ๋”ฉ ๋Œ€์‹ )
1064
+ this.createFallbackMissile();
1065
+
1066
+ this.mesh.position.copy(this.position);
1067
+ this.scene.add(this.mesh);
1068
+
1069
+ // ๋‚˜์ค‘์— GLB ๋ชจ๋ธ ๋กœ๋“œ ์‹œ๋„ (์„ ํƒ์‚ฌํ•ญ)
1070
+ const loader = new GLTFLoader();
1071
+ loader.load('models/aim-9.glb',
1072
+ (result) => {
1073
+ // ๋ชจ๋ธ ๋กœ๋“œ ์„ฑ๊ณต ์‹œ ๊ต์ฒด
1074
+ if (this.mesh && this.mesh.parent) {
1075
+ this.scene.remove(this.mesh);
1076
+ this.mesh = result.scene;
1077
+ this.mesh.scale.set(0.75, 0.75, 0.75);
1078
+ this.mesh.position.copy(this.position);
1079
+ this.scene.add(this.mesh);
1080
+ console.log('AIM-9 model loaded and replaced');
1081
+ }
1082
+ },
1083
+ undefined,
1084
+ (error) => {
1085
+ console.log('AIM-9 model not found, keeping fallback');
1086
+ }
1087
+ );
1088
+ }
1089
+
1090
+ createFallbackMissile() {
1091
+ // ํด๋ฐฑ ๋ฏธ์‚ฌ์ผ ๋ชจ๋ธ
1092
+ const group = new THREE.Group();
1093
+
1094
+ // ๋ฏธ์‚ฌ์ผ ๋ณธ์ฒด
1095
+ const bodyGeometry = new THREE.CylinderGeometry(0.3, 0.4, 4, 8);
1096
+ const bodyMaterial = new THREE.MeshLambertMaterial({ color: 0x808080 });
1097
+ const body = new THREE.Mesh(bodyGeometry, bodyMaterial);
1098
+ body.rotation.x = Math.PI / 2;
1099
+ group.add(body);
1100
+
1101
+ // ํƒ„๋‘
1102
+ const noseGeometry = new THREE.ConeGeometry(0.3, 1, 8);
1103
+ const noseMaterial = new THREE.MeshLambertMaterial({ color: 0x404040 });
1104
+ const nose = new THREE.Mesh(noseGeometry, noseMaterial);
1105
+ nose.position.z = 2.5;
1106
+ nose.rotation.x = -Math.PI / 2;
1107
+ group.add(nose);
1108
+
1109
+ // ๋‚ ๊ฐœ
1110
+ const finGeometry = new THREE.BoxGeometry(2, 0.1, 0.5);
1111
+ const finMaterial = new THREE.MeshLambertMaterial({ color: 0x606060 });
1112
+
1113
+ for (let i = 0; i < 4; i++) {
1114
+ const fin = new THREE.Mesh(finGeometry, finMaterial);
1115
+ fin.position.z = -1.5;
1116
+ fin.rotation.z = (Math.PI / 2) * i;
1117
+ group.add(fin);
1118
+ }
1119
+
1120
+ // ๋ถˆ๊ฝƒ ํšจ๊ณผ
1121
+ const flameGeometry = new THREE.ConeGeometry(0.4, 1.5, 8);
1122
+ const flameMaterial = new THREE.MeshBasicMaterial({
1123
+ color: 0xff4400,
1124
+ transparent: true,
1125
+ opacity: 0.8
1126
+ });
1127
+ const flame = new THREE.Mesh(flameGeometry, flameMaterial);
1128
+ flame.position.z = -2.5;
1129
+ flame.rotation.x = Math.PI / 2;
1130
+ group.add(flame);
1131
+
1132
+ this.mesh = group;
1133
+ this.mesh.scale.set(2.25, 2.25, 2.25); // 50% ๋” ํฌ๊ฒŒ (1.5 -> 2.25)
1134
+ this.isLoaded = true;
1135
+ }
1136
+
1137
+ update(deltaTime, playerPosition) {
1138
+ if (!this.mesh || !this.target || !this.target.position) {
1139
+ this.destroy();
1140
+ return 'expired';
1141
+ }
1142
+
1143
+ this.lifeTime -= deltaTime;
1144
+ if (this.lifeTime <= 0) {
1145
+ this.destroy();
1146
+ return 'expired';
1147
+ }
1148
+
1149
+ // missileswing.ogg๋ฅผ ํ•œ ๋ฒˆ๋งŒ ์žฌ์ƒ
1150
+ if (!this.swingAudioPlayed && this.lifeTime < 29.5) { // ๋ฐœ์‚ฌ 0.5์ดˆ ํ›„ ์žฌ๏ฟฝ๏ฟฝ
1151
+ try {
1152
+ this.swingAudio = new Audio('sounds/missileswing.ogg');
1153
+ this.swingAudio.volume = 0.5;
1154
+ this.swingAudio.play().catch(e => {});
1155
+ this.swingAudioPlayed = true;
1156
+ } catch (e) {
1157
+ console.log('Missile swing audio failed:', e);
1158
+ }
1159
+ }
1160
+
1161
+ // ํƒ€๊ฒŸ ์ถ”์  - ํ–ฅ์ƒ๋œ ํ˜ธ๋ฐ ์•Œ๊ณ ๋ฆฌ์ฆ˜
1162
+ const toTarget = this.target.position.clone().sub(this.position);
1163
+ const distance = toTarget.length();
1164
+
1165
+ // ๋ช…์ค‘ ์ฒดํฌ - ๋ฒ”์œ„ ์ฆ๊ฐ€
1166
+ if (distance < 50) { // 30์—์„œ 50์œผ๋กœ ์ฆ๊ฐ€
1167
+ // ๋ช…์ค‘!
1168
+ this.onHit();
1169
+ return 'hit';
1170
+ }
1171
+
1172
+ // ์˜ˆ์ธก ์‚ฌ๊ฒฉ: ํƒ€๊ฒŸ์˜ ๋ฏธ๋ž˜ ์œ„์น˜ ์˜ˆ์ธก
1173
+ if (this.target.velocity) {
1174
+ const timeToImpact = distance / this.speed;
1175
+ const predictedPosition = this.target.position.clone().add(
1176
+ this.target.velocity.clone().multiplyScalar(timeToImpact * 0.5)
1177
+ );
1178
+ toTarget.copy(predictedPosition.sub(this.position));
1179
+ }
1180
+
1181
+ toTarget.normalize();
1182
+ const currentDirection = this.velocity.clone().normalize();
1183
+
1184
+ // ๋น„๋ก€ ํ•ญ๋ฒ• ์œ ๋„ (Proportional Navigation)
1185
+ const closing_velocity = this.velocity.length();
1186
+ const line_of_sight_rate = currentDirection.clone().cross(toTarget);
1187
+ const navigation_constant = 4.0; // N = 4 (์ผ๋ฐ˜์ ์ธ ๋ฏธ์‚ฌ์ผ ์ƒ์ˆ˜)
1188
+
1189
+ // ๊ฐ€์†๋„ ๋ช…๋ น ๊ณ„์‚ฐ
1190
+ const commanded_acceleration = line_of_sight_rate.multiplyScalar(
1191
+ navigation_constant * closing_velocity
1192
+ );
1193
+
1194
+ // ๋ถ€๋“œ๋Ÿฌ์šด ๋ฐฉํ–ฅ ์ „ํ™˜ with ๋น„๋ก€ ํ•ญ๋ฒ•
1195
+ const newDirection = currentDirection.clone();
1196
+ const maxTurnRate = this.turnRate * deltaTime;
1197
+
1198
+ // ๊ฐ€์†๋„๋ฅผ ๋ฐฉํ–ฅ ๋ณ€ํ™”๋กœ ๋ณ€ํ™˜
1199
+ const deltaDirection = commanded_acceleration.multiplyScalar(deltaTime / this.speed);
1200
+ newDirection.add(deltaDirection);
1201
+
1202
+ // ์ง์ ‘ ์ถ”์ ๋„ ํ˜ผํ•ฉ (๊ทผ๊ฑฐ๋ฆฌ์—์„œ ๋” ์ •ํ™•ํ•œ ์ถ”์ )
1203
+ if (distance < 500) {
1204
+ const directWeight = 1.0 - (distance / 500);
1205
+ newDirection.lerp(toTarget, directWeight * maxTurnRate);
1206
+ }
1207
+
1208
+ newDirection.normalize();
1209
+
1210
+ // ์†๋„ ์—…๋ฐ์ดํŠธ - ๊ฐ€์† ์ถ”๊ฐ€
1211
+ if (this.lifeTime > 25) {
1212
+ // ์ดˆ๊ธฐ 5์ดˆ๊ฐ„ ๊ฐ€์†
1213
+ this.speed = Math.min(this.speed + deltaTime * 200, 1543.2); // ์ตœ๋Œ€ 3000kt
1214
+ }
1215
+
1216
+ this.velocity = newDirection.multiplyScalar(this.speed);
1217
+
1218
+ // ์œ„์น˜ ์—…๋ฐ์ดํŠธ
1219
+ this.position.add(this.velocity.clone().multiplyScalar(deltaTime));
1220
+ this.mesh.position.copy(this.position);
1221
+
1222
+ // ๋ฏธ์‚ฌ์ผ ํšŒ์ „
1223
+ const lookAtTarget = this.position.clone().add(this.velocity);
1224
+ this.mesh.lookAt(lookAtTarget);
1225
+
1226
+ // ์ถ”๋ ฅ ์—ฐ๊ธฐ ์ƒ์„ฑ
1227
+ this.smokeEmitTime += deltaTime;
1228
+ if (this.smokeEmitTime >= 0.02) { // 0.02์ดˆ๋งˆ๋‹ค ์—ฐ๊ธฐ ์ƒ์„ฑ
1229
+ this.smokeEmitTime = 0;
1230
+ this.createSmokeParticle();
1231
+ }
1232
+
1233
+ // ์—ฐ๊ธฐ ํŒŒํ‹ฐํด ์—…๋ฐ์ดํŠธ
1234
+ for (let i = this.smokeTrail.length - 1; i >= 0; i--) {
1235
+ const smoke = this.smokeTrail[i];
1236
+ smoke.life -= deltaTime;
1237
+
1238
+ if (smoke.life <= 0) {
1239
+ this.scene.remove(smoke.mesh);
1240
+ this.smokeTrail.splice(i, 1);
1241
+ } else {
1242
+ // ์—ฐ๊ธฐ ํ™•์‚ฐ ๋ฐ ํŽ˜์ด๋“œ
1243
+ smoke.mesh.scale.multiplyScalar(1.02);
1244
+ smoke.mesh.material.opacity = smoke.life / 3.0;
1245
+
1246
+ // ์•ฝ๊ฐ„์˜ ์ƒ์Šน ํšจ๊ณผ
1247
+ smoke.mesh.position.y += deltaTime * 2;
1248
+ }
1249
+ }
1250
+
1251
+ // ์ง€๋ฉด ์ถฉ๋Œ
1252
+ if (this.position.y <= 0) {
1253
+ this.destroy();
1254
+ return 'expired';
1255
+ }
1256
+
1257
+ return 'flying';
1258
+ }
1259
+
1260
+ createSmokeParticle() {
1261
+ // ๋ฏธ์‚ฌ์ผ ๋’ค์ชฝ ์œ„์น˜ ๊ณ„์‚ฐ
1262
+ const backward = this.velocity.clone().normalize().multiplyScalar(-3);
1263
+ const smokePos = this.position.clone().add(backward);
1264
+
1265
+ // ํ•˜์–€ ์—ฐ๊ธฐ ํŒŒํ‹ฐํด ์ƒ์„ฑ
1266
+ const smokeGeometry = new THREE.SphereGeometry(
1267
+ 1 + Math.random() * 2.5, // ํฌ๊ธฐ ๋ณ€ํ™”
1268
+ 6,
1269
+ 6
1270
+ );
1271
+ const smokeMaterial = new THREE.MeshBasicMaterial({
1272
+ color: 0xffffff, // ํ•˜์–€์ƒ‰
1273
+ transparent: true,
1274
+ opacity: 0.8
1275
+ });
1276
+
1277
+ const smoke = new THREE.Mesh(smokeGeometry, smokeMaterial);
1278
+ smoke.position.copy(smokePos);
1279
+
1280
+ // ์•ฝ๊ฐ„์˜ ๋žœ๋ค ์˜คํ”„์…‹
1281
+ smoke.position.x += (Math.random() - 0.5) * 1;
1282
+ smoke.position.y += (Math.random() - 0.5) * 1;
1283
+ smoke.position.z += (Math.random() - 0.5) * 1;
1284
+
1285
+ this.scene.add(smoke);
1286
+
1287
+ this.smokeTrail.push({
1288
+ mesh: smoke,
1289
+ life: 3.0 // 3์ดˆ ๋™์•ˆ ์ง€์†
1290
+ });
1291
+
1292
+ // ์—ฐ๊ธฐ ํŒŒํ‹ฐํด ์ œํ•œ (์„ฑ๋Šฅ์„ ์œ„ํ•ด)
1293
+ if (this.smokeTrail.length > 150) {
1294
+ const oldSmoke = this.smokeTrail.shift();
1295
+ this.scene.remove(oldSmoke.mesh);
1296
+ }
1297
+ }
1298
+
1299
+ onHit() {
1300
+ // ๋ช…์ค‘ ํšจ๊ณผ
1301
+ if (window.gameInstance) {
1302
+ window.gameInstance.createExplosionEffect(this.position);
1303
+ }
1304
+
1305
+ // ๋ช…์ค‘์Œ
1306
+ try {
1307
+ const hitSound = new Audio('sounds/missilehit.ogg');
1308
+ hitSound.volume = 0.8;
1309
+ hitSound.play().catch(e => {});
1310
+ } catch (e) {
1311
+ console.log('Missile hit sound failed:', e);
1312
+ }
1313
+
1314
+ // ํƒ€๊ฒŸ์—๊ฒŒ ํ”ผํ•ด
1315
+ if (this.target.takeDamage) {
1316
+ this.target.takeDamage(GAME_CONSTANTS.AIM9_DAMAGE);
1317
+ }
1318
+
1319
+ this.destroy();
1320
+ }
1321
+
1322
+ destroy() {
1323
+ if (this.mesh) {
1324
+ this.scene.remove(this.mesh);
1325
+ }
1326
+
1327
+ // ์—ฐ๊ธฐ ํŒŒํ‹ฐํด ์ •๋ฆฌ
1328
+ this.smokeTrail.forEach(smoke => {
1329
+ if (smoke.mesh) {
1330
+ this.scene.remove(smoke.mesh);
1331
+ }
1332
+ });
1333
+ this.smokeTrail = [];
1334
+
1335
+ if (this.swingAudio) {
1336
+ this.swingAudio.pause();
1337
+ this.swingAudio = null;
1338
+ }
1339
+ }
1340
  }
1341
 
1342
  // ์  ์ „ํˆฌ๊ธฐ ํด๋ž˜์Šค - ์™„์ „ํžˆ ์žฌ์„ค๊ณ„