cutechicken commited on
Commit
ac418c8
ยท
verified ยท
1 Parent(s): d1802b2

Update game.js

Browse files
Files changed (1) hide show
  1. game.js +133 -178
game.js CHANGED
@@ -33,7 +33,17 @@ class Fighter {
33
  this.position = new THREE.Vector3(0, 2000, 0);
34
  this.velocity = new THREE.Vector3(0, 0, 350); // ์ดˆ๊ธฐ ์†๋„ 350kt
35
  this.acceleration = new THREE.Vector3(0, 0, 0);
36
- this.rotation = new THREE.Euler(0, 0, 0, 'YXZ'); // ์˜ค์ผ๋Ÿฌ ๊ฐ๋„ ์ˆœ์„œ๋ฅผ YXZ๋กœ ๋ณ€๊ฒฝํ•˜์—ฌ ์ง๋ฒŒ ๋ฝ ๋ฐฉ์ง€
 
 
 
 
 
 
 
 
 
 
37
 
38
  // ๋น„ํ–‰ ์ œ์–ด
39
  this.throttle = 0.7; // ์ดˆ๊ธฐ ์Šค๋กœํ‹€ 70%
@@ -51,11 +61,6 @@ class Fighter {
51
  this.mousePitch = 0;
52
  this.mouseRoll = 0;
53
 
54
- // ๋ถ€๋“œ๋Ÿฌ์šด ํšŒ์ „์„ ์œ„ํ•œ ๋ชฉํ‘œ๊ฐ’
55
- this.targetPitch = 0;
56
- this.targetRoll = 0;
57
- this.targetYaw = 0;
58
-
59
  // ๋ฌด๊ธฐ
60
  this.missiles = GAME_CONSTANTS.MISSILE_COUNT;
61
  this.ammo = GAME_CONSTANTS.AMMO_COUNT;
@@ -183,7 +188,11 @@ class Fighter {
183
  this.mesh = result.scene;
184
  this.mesh.position.copy(this.position);
185
  this.mesh.scale.set(2, 2, 2);
186
- this.mesh.rotation.y = Math.PI / 4;
 
 
 
 
187
 
188
  this.mesh.traverse((child) => {
189
  if (child.isMesh) {
@@ -242,320 +251,282 @@ class Fighter {
242
  // Over-G ์ƒํƒœ์—์„œ ์Šคํ†จ์ด ํ•ด์ œ๋˜์ง€ ์•Š์•˜์œผ๋ฉด ํ”ผ์น˜ ์กฐ์ž‘ ๋ถˆ๊ฐ€
243
  if (this.overG && this.overGTimer > 1.0 && this.stallWarning) {
244
  // ์š”(Yaw)๋งŒ ์ œํ•œ์ ์œผ๋กœ ํ—ˆ์šฉ
245
- const sensitivity = GAME_CONSTANTS.MOUSE_SENSITIVITY * 0.3; // ๊ฐ๋„ ๋Œ€ํญ ๊ฐ์†Œ
246
  this.targetYaw += deltaX * sensitivity * 0.3;
247
 
248
  // ํ”ผ์น˜๋Š” ์กฐ์ž‘ ๋ถˆ๊ฐ€
249
  // ๋กค๋„ ์ œํ•œ์ ์œผ๋กœ๋งŒ ํ—ˆ์šฉ
250
  const yawRate = deltaX * sensitivity * 0.3;
251
- this.targetRoll = -yawRate * 5; // ๋งค์šฐ ์ œํ•œ๋œ ๋กค
252
 
253
- return; // ์ถ”๊ฐ€ ์ฒ˜๋ฆฌ ์ค‘๋‹จ
254
  }
255
 
256
  const sensitivity = GAME_CONSTANTS.MOUSE_SENSITIVITY * 1.0;
257
 
258
- // ๋งˆ์šฐ์Šค Y์ถ•: ํ”ผ์น˜(๊ธฐ์ˆ˜ ์ƒํ•˜) - ํ•ญ์ƒ ๋™์ผํ•˜๊ฒŒ ์ž‘๋™
259
  this.targetPitch -= deltaY * sensitivity;
260
-
261
- // ๋งˆ์šฐ์Šค X์ถ•: ์š”(์ขŒ์šฐ ํšŒ์ „) - ํ•ญ์ƒ ๋™์ผํ•˜๊ฒŒ ์ž‘๋™
262
  this.targetYaw += deltaX * sensitivity * 0.8;
263
 
264
  // ์š” ํšŒ์ „์— ๋”ฐ๋ฅธ ์ž๋™ ๋กค (๋ฑ…ํฌ ํ„ด)
265
- // ์ขŒ์šฐ๋กœ ํšŒ์ „ํ•  ๋•Œ ์ž์—ฐ์Šค๋Ÿฝ๊ฒŒ ๋‚ ๊ฐœ๊ฐ€ ๊ธฐ์šธ์–ด์ง
266
  const yawRate = deltaX * sensitivity * 0.8;
267
- this.targetRoll = -yawRate * 15; // ์š” ํšŒ์ „๋Ÿ‰์— ๋น„๋ก€ํ•˜์—ฌ ๋กค ๋ฐœ์ƒ
268
 
269
  // ๊ฐ๋„ ์ œํ•œ
270
  const maxPitchAngle = Math.PI / 3; // 60๋„
271
- const maxRollAngle = Math.PI * 0.5; // 90๋„๋กœ ์ œํ•œ (์ž๋™ ๋กค)
272
 
273
  this.targetPitch = Math.max(-maxPitchAngle, Math.min(maxPitchAngle, this.targetPitch));
274
 
275
- // ์ž๋™ ๋กค์€ ์ œํ•œ๋œ ๋ฒ”์œ„ ๋‚ด์—์„œ๋งŒ ์ž‘๋™
276
  if (Math.abs(this.targetRoll) < maxRollAngle) {
277
- // ๋กค ๊ฐ๋„ ์ œํ•œ ์ ์šฉ
278
  this.targetRoll = Math.max(-maxRollAngle, Math.min(maxRollAngle, this.targetRoll));
279
  }
280
  }
281
 
282
  updateControls(keys, deltaTime) {
283
- // ๋””๋ฒ„๊น…์„ ์œ„ํ•œ ๋กœ๊ทธ
284
- if (keys.w || keys.s || keys.a || keys.d) {
285
- console.log('updateControls called:', {
286
- w: keys.w,
287
- a: keys.a,
288
- s: keys.s,
289
- d: keys.d,
290
- throttle_before: this.throttle,
291
- targetYaw_before: this.targetYaw,
292
- deltaTime: deltaTime
293
- });
294
- }
295
-
296
  // W/S: ์Šค๋กœํ‹€๋งŒ ์ œ์–ด (๊ฐ€์†/๊ฐ์†)
297
  if (keys.w) {
298
  this.throttle = Math.min(1.0, this.throttle + deltaTime * 0.5);
299
- console.log('W pressed - New throttle:', this.throttle);
300
  }
301
  if (keys.s) {
302
  this.throttle = Math.max(0.1, this.throttle - deltaTime * 0.5);
303
- console.log('S pressed - New throttle:', this.throttle);
304
  }
305
 
306
- // A/D: ๋ณด์กฐ ์š” ์ œ์–ด (๋Ÿฌ๋”) - ๋ฐ˜์‘์„ฑ ๊ฐœ์„ 
307
  if (keys.a) {
308
  this.targetYaw -= deltaTime * 1.2;
309
- console.log('A pressed - New targetYaw:', this.targetYaw);
310
  }
311
  if (keys.d) {
312
  this.targetYaw += deltaTime * 1.2;
313
- console.log('D pressed - New targetYaw:', this.targetYaw);
314
  }
315
  }
316
 
317
  updatePhysics(deltaTime) {
318
  if (!this.mesh) return;
319
 
320
- // ๋ถ€๋“œ๋Ÿฌ์šด ํšŒ์ „ ๋ณด๊ฐ„ - Yaw ํšŒ์ „ ์†๋„ ๊ฐœ์„ 
321
  const rotationSpeed = deltaTime * 2.0;
322
- const yawRotationSpeed = deltaTime * 3.0; // Yaw๋Š” ๋” ๋น ๋ฅด๊ฒŒ ๋ฐ˜์‘ํ•˜๋„๋ก ๋ณ„๋„ ์„ค์ •
323
 
324
- // ํ”ผ์น˜์™€ ๋กค์€ ์ง์ ‘ ๋ณด๊ฐ„
325
- this.rotation.x = THREE.MathUtils.lerp(this.rotation.x, this.targetPitch, rotationSpeed);
326
- this.rotation.z = THREE.MathUtils.lerp(this.rotation.z, this.targetRoll, rotationSpeed * 1.5); // ๋กค์€ ๋” ๋น ๋ฅด๊ฒŒ ๋ฐ˜์‘
327
 
328
- // Yaw ๋ณด๊ฐ„์„ ์œ„ํ•œ ๊ฐ๋„ ์ฐจ์ด ๊ณ„์‚ฐ (์ตœ๋‹จ ๊ฒฝ๋กœ)
 
329
  let yawDiff = this.targetYaw - this.rotation.y;
 
330
 
331
- // ๊ฐ๋„ ์ฐจ์ด๋ฅผ -ฯ€ ~ ฯ€ ๋ฒ”์œ„๋กœ ์ •๊ทœํ™” (์ตœ๋‹จ ํšŒ์ „ ๊ฒฝ๋กœ)
332
  while (yawDiff > Math.PI) yawDiff -= Math.PI * 2;
333
  while (yawDiff < -Math.PI) yawDiff += Math.PI * 2;
334
 
335
- // ๋ถ€๋“œ๋Ÿฌ์šด yaw ํšŒ์ „ ์ ์šฉ
336
- this.rotation.y += yawDiff * yawRotationSpeed;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
337
 
338
  // ๋กค ์ž๋™ ๋ณต๊ท€ ์‹œ์Šคํ…œ
339
- if (Math.abs(this.targetYaw - this.rotation.y) < 0.05) {
340
- // ์š” ํšŒ์ „์ด ๊ฑฐ์˜ ์—†์„ ๋•Œ ๋กค์„ 0์œผ๋กœ ๋ณต๊ท€
341
  this.targetRoll *= 0.95;
342
  }
343
 
344
- // ์›Œ์ฌ๋” ์Šคํƒ€์ผ: ์š” ํšŒ์ „์ด ์ฃผ๋„์ , ๋กค์€ ๋ณด์กฐ์ 
345
- // ๋กค์— ๋”ฐ๋ฅธ ์ถ”๊ฐ€ ์š” ํšŒ์ „์€ ์ œ๊ฑฐํ•˜๊ฑฐ๋‚˜ ์ตœ์†Œํ™”
346
  let bankTurnRate = 0;
347
- if (Math.abs(this.rotation.z) > 0.3) { // ๋กค์ด ์ถฉ๋ถ„ํžˆ ํด ๋•Œ๋งŒ
348
  const bankAngle = this.rotation.z;
349
- bankTurnRate = Math.sin(bankAngle) * deltaTime * 0.1; // ๋งค์šฐ ์ž‘์€ ์„ ํšŒ์œจ
350
  this.targetYaw += bankTurnRate;
351
  }
352
 
353
- // ํ˜„์‹ค์ ์ธ ์†๋„ ๊ณ„์‚ฐ
354
- const minSpeed = 0; // ์ตœ์†Œ ์†๋„ 0kt
355
- const maxSpeed = 600; // ์ตœ๋Œ€ ์†๋„ 600kt
356
  let targetSpeed = minSpeed + (maxSpeed - minSpeed) * this.throttle;
357
 
358
  // ํ”ผ์น˜ ๊ฐ๋„์— ๋”ฐ๋ฅธ ์†๋„ ๋ณ€ํ™”
359
  const pitchAngle = this.rotation.x;
360
  const pitchDegrees = Math.abs(pitchAngle) * (180 / Math.PI);
361
 
362
- // ๊ธฐ์ˆ˜๊ฐ€ ์œ„๋ฅผ ํ–ฅํ•˜๊ณ  ์žˆ์„ ๊ฒฝ์šฐ ๋น ๋ฅธ ์†๋„ ๊ฐ์†Œ
363
- if (pitchAngle < -0.1 && !this.stallWarning) { // ์Šคํ†จ์ด ์•„๋‹ ๋•Œ๋งŒ ์ƒ์Šน์œผ๋กœ ์ธํ•œ ๊ฐ์†
364
- const climbFactor = Math.abs(pitchAngle) / (Math.PI / 2); // 90๋„ ๊ธฐ์ค€
365
- if (pitchDegrees > 30) { // 30๋„ ์ด์ƒ์ผ ๋•Œ ๊ธ‰๊ฒฉํ•œ ๊ฐ์†
366
- targetSpeed *= Math.max(0, 1 - climbFactor * 1.5); // ์ตœ๋Œ€ 150% ๊ฐ์† (0kt๊นŒ์ง€)
367
  } else {
368
- targetSpeed *= (1 - climbFactor * 0.3); // ์ •์ƒ์ ์ธ ๊ฐ์†
369
  }
370
- } else if (pitchAngle > 0.1) { // ๊ธฐ์ˆ˜๊ฐ€ ์•„๋ž˜๋กœ (ํ•˜๊ฐ•) - ์Šคํ†จ ์ƒํƒœ์—์„œ๋„ ์ ์šฉ
371
  const diveFactor = pitchAngle / (Math.PI / 3);
372
- targetSpeed *= (1 + diveFactor * 0.4); // ํ•˜๊ฐ• ์‹œ ๊ฐ€์† ์ฆ๊ฐ€ (0.2 -> 0.4)
373
  }
374
 
375
- // G-Force ๊ณ„์‚ฐ ๊ฐœ์„ 
376
  const turnRate = Math.abs(bankTurnRate) * 100;
377
- const pitchRate = Math.abs(this.rotation.x - this.targetPitch) * 10;
378
 
379
- // ๊ณ ๋„์— ๋”ฐ๋ฅธ G-Force ์ฆ๊ฐ€ ๋ฐฐ์œจ ๊ณ„์‚ฐ
380
- const altitudeInKm = this.position.y / 1000; // ๋ฏธํ„ฐ๋ฅผ ํ‚ฌ๋กœ๋ฏธํ„ฐ๋กœ ๋ณ€ํ™˜
381
- const altitudeMultiplier = 1 + (altitudeInKm * 0.2); // 1km๋‹น 20% ์ฆ๊ฐ€
382
 
383
- // ์Šค๋กœํ‹€์— ๋”ฐ๋ฅธ G-Force ์ฆ๊ฐ€ ๋ฐฐ์œจ ๊ณ„์‚ฐ
384
- // THR 50% ์ดํ•˜: 0๋ฐฐ, THR 75%: 0.5๋ฐฐ, THR 100%: 1.0๋ฐฐ
385
  let throttleGMultiplier = 0;
386
  if (this.throttle > 0.5) {
387
- // 0.5 ~ 1.0 ๋ฒ”์œ„๋ฅผ 0 ~ 1.0์œผ๋กœ ๋งคํ•‘
388
  throttleGMultiplier = (this.throttle - 0.5) * 2.0;
389
  }
390
 
391
- // ๋น„์ •์ƒ์ ์ธ ์ž์„ธ์— ์˜ํ•œ G-Force ์ถ”๊ฐ€
392
  let abnormalG = 0;
393
 
394
- // ๋’ค์ง‘ํžŒ ์ƒํƒœ (๋กค์ด 90๋„ ์ด์ƒ)
395
  const isInverted = Math.abs(this.rotation.z) > Math.PI / 2;
 
396
  if (isInverted) {
397
  const baseG = 3.0 + Math.abs(Math.abs(this.rotation.z) - Math.PI / 2) * 2;
398
- abnormalG += baseG * altitudeMultiplier * (1 + throttleGMultiplier); // ์Šค๋กœํ‹€ ๋ฐฐ์œจ ์ถ”๊ฐ€
399
  }
400
 
401
- // ๋กค์ด ยฑ30๋„ ์ด์ƒ์ผ ๋•Œ ์ถ”๊ฐ€ G-Force
402
- const rollDegrees = Math.abs(this.rotation.z) * (180 / Math.PI);
403
  if (rollDegrees > 30) {
404
- // 30๋„ ์ดˆ๊ณผ๋ถ„๋‹น 0.1G ์ถ”๊ฐ€
405
  const extremeRollG = (rollDegrees - 30) * 0.1;
406
  abnormalG += extremeRollG * altitudeMultiplier * (1 + throttleGMultiplier);
407
  }
408
 
409
- // ํ”ผ์น˜ ๊ฐ๋„๊ฐ€ ยฑ40๋„ ์ด์ƒ์ผ ๋•Œ ์ถ”๊ฐ€ G-Force
410
  if (pitchDegrees >= 40) {
411
- // 40๋„ ์ด์ƒ์ผ ๋•Œ ๊ธ‰๊ฒฉํ•œ G-Force ์ฆ๊ฐ€
412
- const extremePitchG = (pitchDegrees - 40) * 0.15; // 40๋„ ์ดˆ๊ณผ๋ถ„๋‹น 0.15G ์ถ”๊ฐ€
413
  abnormalG += extremePitchG * altitudeMultiplier * (1 + throttleGMultiplier);
414
  }
415
 
416
- // ๊ธฐ์ˆ˜๊ฐ€ ๊ณ„์† ์œ„๋ฅผ ํ–ฅํ•˜๊ณ  ์žˆ๋Š” ๊ฒฝ์šฐ (ํ”ผ์น˜๊ฐ€ -30๋„ ์ดํ•˜)
417
  if (pitchAngle < -Math.PI / 6) {
418
  const baseG = 2.0 + Math.abs(pitchAngle + Math.PI / 6) * 3;
419
- abnormalG += baseG * altitudeMultiplier * (1 + throttleGMultiplier); // ์Šค๋กœํ‹€ ๋ฐฐ์œจ ์ถ”๊ฐ€
420
  }
421
 
422
- // ๊ธฐ์ˆ˜๊ฐ€ ๊ณผ๋„ํ•˜๊ฒŒ ์•„๋ž˜๋ฅผ ํ–ฅํ•˜๊ณ  ์žˆ๋Š” ๊ฒฝ์šฐ (ํ”ผ์น˜๊ฐ€ 60๋„ ์ด์ƒ)
423
  if (pitchAngle > Math.PI / 3) {
424
  const baseG = 2.0 + Math.abs(pitchAngle - Math.PI / 3) * 3;
425
- abnormalG += baseG * altitudeMultiplier * (1 + throttleGMultiplier); // ์Šค๋กœํ‹€ ๋ฐฐ์œจ ์ถ”๊ฐ€
426
  }
427
 
428
- // ๊ธ‰๊ฒฉํ•œ ๊ธฐ๋™์— ์˜ํ•œ G-Force
429
  const maneuverG = (turnRate + pitchRate + (Math.abs(this.rotation.z) * 3)) * (1 + throttleGMultiplier * 0.5);
430
 
431
- // ์ด G-Force ๊ณ„์‚ฐ
432
  this.gForce = 1.0 + maneuverG + abnormalG;
433
 
434
- // G-Force ํšŒ๋ณต ์กฐ๊ฑด ์ˆ˜์ •
435
- // 1. Over-G ์ƒํƒœ๊ฐ€ ์•„๋‹Œ ๊ฒฝ์šฐ์—๋งŒ ํšŒ๋ณต
436
- // 2. ํ”ผ์น˜๊ฐ€ ยฑ10๋„ ์ด๋‚ด์ผ ๋•Œ๋งŒ ํšŒ๋ณต
437
- // 3. ์Šคํ†จ ์ƒํƒœ๊ฐ€ ์•„๋‹ ๋•Œ๋งŒ ํšŒ๋ณต
438
  const isPitchNeutral = Math.abs(pitchDegrees) <= 10;
439
 
440
  if (!this.overG && isPitchNeutral && !isInverted && !this.stallWarning) {
441
- // ์Šค๋กœํ‹€์ด ๋†’์„์ˆ˜๋ก G-Force๊ฐ€ ์ฒœ์ฒœํžˆ ๊ฐ์†Œ
442
- const recoveryRate = 2.0 - throttleGMultiplier * 1.5; // THR 100%์ผ ๋•Œ 0.5, THR 50%์ผ ๋•Œ 2.0
443
  this.gForce = THREE.MathUtils.lerp(this.gForce, 1.0 + maneuverG, deltaTime * recoveryRate);
444
  } else if (this.overG) {
445
- // Over-G ์ƒํƒœ์—์„œ๋Š” ํ”ผ์น˜๊ฐ€ 0๋„ ๊ทผ์ฒ˜(ยฑ10๋„)๊ฐ€ ๋˜๊ณ  ์Šคํ†จ์ด ํšŒ๋ณต๋  ๋•Œ๊นŒ์ง€ ํšŒ๋ณตํ•˜์ง€ ์•Š์Œ
446
  if (!isPitchNeutral || this.stallWarning) {
447
- // ํ”ผ์น˜๊ฐ€ ์ค‘๋ฆฝ์ด ์•„๋‹ˆ๊ฑฐ๋‚˜ ์Šคํ†จ ์ƒํƒœ๋ฉด G-Force ์œ ์ง€ ๋˜๋Š” ์ฆ๊ฐ€๋งŒ ๊ฐ€๋Šฅ
448
  this.gForce = Math.max(this.gForce, 1.0 + maneuverG + abnormalG);
449
  } else {
450
- // ํ”ผ์น˜๊ฐ€ ์ค‘๋ฆฝ์ด๊ณ  ์Šคํ†จ์ด ์•„๋‹ ๋•Œ๋งŒ ๋งค์šฐ ์ฒœ์ฒœํžˆ ํšŒ๋ณต
451
- const overGRecoveryRate = 0.3 - throttleGMultiplier * 0.2; // Over-G ์ƒํƒœ๏ฟฝ๏ฟฝ๏ฟฝ์„œ๋Š” ๋” ๋А๋ฆฐ ํšŒ๋ณต
452
  this.gForce = THREE.MathUtils.lerp(this.gForce, 1.0 + maneuverG, deltaTime * overGRecoveryRate);
453
  }
454
  }
455
 
456
- // ์Šคํ†จ ์ƒํƒœ์—์„œ๋Š” Over-G๊ฐ€ ๊ฐ์†Œํ•˜์ง€ ์•Š๋„๋ก ์ถ”๊ฐ€ ์ฒ˜๋ฆฌ
457
  if (this.stallWarning && this.overG) {
458
- // ์Šคํ†จ ์ค‘์—๋Š” G-Force๋ฅผ 9.0 ์ด์ƒ์œผ๋กœ ์œ ์ง€
459
  this.gForce = Math.max(this.gForce, 9.0);
460
  }
461
 
462
  this.overG = this.gForce > this.maxGForce;
463
 
464
- // Over-G ํƒ€์ด๋จธ ์—…๋ฐ์ดํŠธ
465
  if (this.overG) {
466
  this.overGTimer += deltaTime;
467
-
468
- // 1.5์ดˆ ์ด์ƒ Over-G ์ƒํƒœ์ผ ๊ฒฝ์šฐ ์‹œ์•ผ ํ๋ฆผ ์‹œ์ž‘
469
  if (this.overGTimer > 1.5) {
470
- // ์†๋„ ๊ธ‰๊ฒฉํžˆ ๊ฐ์†Œ (2.5์ดˆ๋ถ€ํ„ฐ)
471
  if (this.overGTimer > 2.5) {
472
  targetSpeed *= Math.max(0.3, 1 - (this.overGTimer - 2.5) * 0.3);
473
  }
474
- // ์‹œ์•ผ ํ๋ฆผ ํšจ๊ณผ๋Š” UI์—์„œ ์ฒ˜๋ฆฌ (1.5์ดˆ๋ถ€ํ„ฐ)
475
  }
476
  } else {
477
- this.overGTimer = 0; // Over-G ์ƒํƒœ๊ฐ€ ์•„๋‹ˆ๋ฉด ํƒ€์ด๋จธ ๋ฆฌ์…‹
478
  }
479
 
480
- // ์Šคํ†จ ๊ฒฝ๊ณ : 300kt ์ดํ•˜์—์„œ ์Šคํ†จ ์œ„ํ—˜
481
- const speedKnots = this.speed * 1.94384; // m/s to knots
482
- const wasStalling = this.stallWarning;
483
 
484
- // ์Šคํ†จ ์ง„์ž… ์กฐ๊ฑด
485
  if (!this.stallWarning && speedKnots < GAME_CONSTANTS.STALL_SPEED) {
486
  this.stallWarning = true;
487
- this.stallEscapeProgress = 0; // ์Šคํ†จ ์ง„์ž… ์‹œ ์ง„ํ–‰๋„ ์ดˆ๊ธฐํ™”
488
  }
489
 
490
- // ์Šคํ†จ ํƒˆ์ถœ ์กฐ๊ฑด
491
  if (this.stallWarning) {
492
  if (this.escapeKeyPressed) {
493
- // Fํ‚ค๋ฅผ ๋ˆ„๋ฅด๊ณ  ์žˆ์œผ๋ฉด ์ง„ํ–‰๋„ ์ฆ๊ฐ€
494
  this.stallEscapeProgress += deltaTime;
495
-
496
- // 2์ดˆ ์ด์ƒ ๋ˆ„๋ฅด๋ฉด ์Šคํ†จ์—์„œ ํƒˆ์ถœ
497
  if (this.stallEscapeProgress >= 2.0 && speedKnots >= 350) {
498
  this.stallWarning = false;
499
  this.stallEscapeProgress = 0;
500
- // ์Šคํ†จ์—์„œ ๋ฒ—์–ด๋‚˜๋ฉด ์ž์„ธ๋ฅผ ์•ฝ๊ฐ„ ์•ˆ์ •ํ™”
501
  this.targetPitch = Math.max(-0.2, Math.min(0.2, this.targetPitch));
502
  }
503
  } else {
504
- // Fํ‚ค๋ฅผ ๋†“์œผ๋ฉด ์ง„ํ–‰๋„ ๊ฐ์†Œ
505
  this.stallEscapeProgress = Math.max(0, this.stallEscapeProgress - deltaTime * 2);
506
  }
507
  }
508
 
509
- // ์†๋„ ๋ณ€ํ™” ์ ์šฉ
510
  if (this.stallWarning) {
511
- // ์Šคํ†จ ์ƒํƒœ์—์„œ์˜ ์†๋„ ๋ณ€ํ™”
512
- if (pitchAngle > 0.1) { // ๊ธฐ์ˆ˜๊ฐ€ ์•„๋ž˜๋ฅผ ํ–ฅํ•  ๋•Œ
513
- // ๋‹ค์ด๋น™์œผ๋กœ ์ธํ•œ ์†๋„ ์ฆ๊ฐ€
514
- const diveSpeedGain = Math.min(pitchAngle * 300, 200); // ์ตœ๋Œ€ 200m/s ์ฆ๊ฐ€
515
  this.speed = Math.min(maxSpeed, this.speed + diveSpeedGain * deltaTime);
516
  } else {
517
- // ๊ธฐ์ˆ˜๊ฐ€ ์œ„๋ฅผ ํ–ฅํ•˜๊ฑฐ๋‚˜ ์ˆ˜ํ‰์ผ ๋•Œ๋Š” ์†๋„ ๊ฐ์†Œ
518
  this.speed = Math.max(0, this.speed - deltaTime * 100);
519
  }
520
  } else {
521
- // ์ •์ƒ ๋น„ํ–‰ ์‹œ ์†๋„ ๋ณ€ํ™”
522
  this.speed = THREE.MathUtils.lerp(this.speed, targetSpeed, deltaTime * 0.5);
523
  }
524
 
525
- // ์Šคํ†จ ์ƒํƒœ์—์„œ์˜ ๋ฌผ๋ฆฌ ํšจ๊ณผ
526
  if (this.stallWarning) {
527
- // ๋ฐ”๋‹ฅ์œผ๋กœ ์ถ”๋ฝํ•˜๋ฉฐ ๊ฐ€์†๋„๊ฐ€ ๋น ๋ฅด๊ฒŒ ๋ถ™์Œ
528
- this.targetPitch = Math.min(Math.PI / 2.5, this.targetPitch + deltaTime * 2.5); // ๊ธฐ์ˆ˜๊ฐ€ ๋” ๊ทน๋‹จ์ ์œผ๋กœ ์•„๋ž˜๋กœ (72๋„๊นŒ์ง€)
529
-
530
- // ์กฐ์ข… ๋ถˆ๋Šฅ ์ƒํƒœ - ๋” ์‹ฌํ•œ ํ”๋“ค๋ฆผ
531
- this.rotation.x += (Math.random() - 0.5) * deltaTime * 0.8;
532
- this.rotation.z += (Math.random() - 0.5) * deltaTime * 0.8;
533
-
534
- // ์ค‘๋ ฅ์— ์˜ํ•œ ๊ฐ€์†
535
- const gravityAcceleration = GAME_CONSTANTS.GRAVITY * deltaTime * 3.0; // 3๋ฐฐ ์ค‘๋ ฅ
 
 
 
536
  this.velocity.y -= gravityAcceleration;
537
  }
538
 
539
- // ์†๋„ ๋ฒกํ„ฐ ๊ณ„์‚ฐ
540
  const noseDirection = new THREE.Vector3(0, 0, 1);
541
- noseDirection.applyEuler(this.rotation);
542
 
543
  if (!this.stallWarning) {
544
- // ์ •์ƒ ๋น„ํ–‰ ์‹œ
545
  this.velocity = noseDirection.multiplyScalar(this.speed);
546
  } else {
547
- // ์Šคํ†จ ์‹œ์—๋Š” ์ค‘๋ ฅ์ด ์ฃผ๋„์ ์ด์ง€๋งŒ ๋‹ค์ด๋น™ ์†๋„๋„ ๋ฐ˜์˜
548
- this.velocity.x = noseDirection.x * this.speed * 0.5; // ์ „๋ฐฉ ์†๋„ ์ฆ๊ฐ€
549
  this.velocity.z = noseDirection.z * this.speed * 0.5;
550
- // y ์†๋„๋Š” ์œ„์—์„œ ์ค‘๋ ฅ์œผ๋กœ ์ฒ˜๋ฆฌ๋จ
551
  }
552
 
553
- // ์ •์ƒ ๋น„ํ–‰ ์‹œ ์ค‘๋ ฅ ํšจ๊ณผ
554
  if (!this.stallWarning) {
555
  const gravityEffect = GAME_CONSTANTS.GRAVITY * deltaTime * 0.15;
556
  this.velocity.y -= gravityEffect;
557
 
558
- // ์–‘๋ ฅ ํšจ๊ณผ (์†๋„์— ๋น„๋ก€)
559
  const liftFactor = (this.speed / maxSpeed) * 0.8;
560
  const lift = gravityEffect * liftFactor;
561
  this.velocity.y += lift;
@@ -569,7 +540,6 @@ class Fighter {
569
  this.position.y = GAME_CONSTANTS.MIN_ALTITUDE;
570
  this.health = 0;
571
 
572
- // ์ง€๋ฉด ์ถฉ๋Œ ์‹œ ํญ๋ฐœ ํšจ๊ณผ ์ถ”๊ฐ€
573
  if (window.gameInstance) {
574
  window.gameInstance.createExplosionEffect(this.position);
575
  }
@@ -577,7 +547,7 @@ class Fighter {
577
  return;
578
  }
579
 
580
- // ์ตœ๋Œ€ ๊ณ ๋„ ์ œํ•œ
581
  if (this.position.y > GAME_CONSTANTS.MAX_ALTITUDE) {
582
  this.position.y = GAME_CONSTANTS.MAX_ALTITUDE;
583
  this.altitudeWarning = true;
@@ -586,47 +556,43 @@ class Fighter {
586
  this.altitudeWarning = false;
587
  }
588
 
589
- // ๋งต ๊ฒฝ๊ณ„ ์ฒ˜๋ฆฌ
590
  const mapLimit = GAME_CONSTANTS.MAP_SIZE / 2;
591
  if (this.position.x > mapLimit) this.position.x = -mapLimit;
592
  if (this.position.x < -mapLimit) this.position.x = mapLimit;
593
  if (this.position.z > mapLimit) this.position.z = -mapLimit;
594
  if (this.position.z < -mapLimit) this.position.z = mapLimit;
595
 
596
- // ๋ฉ”์‹œ ์œ„์น˜ ๋ฐ ํšŒ์ „ ์—…๋ฐ์ดํŠธ
597
  this.mesh.position.copy(this.position);
598
- this.mesh.rotation.order = 'YXZ'; // ๋ฉ”์‹œ๋„ ๊ฐ™์€ ์˜ค์ผ๋Ÿฌ ์ˆœ์„œ ์‚ฌ์šฉ
599
- this.mesh.rotation.x = this.rotation.x;
600
- this.mesh.rotation.y = this.rotation.y + 3 * Math.PI / 2;
601
- this.mesh.rotation.z = this.rotation.z;
 
602
 
603
- // ๊ฒฝ๊ณ  ๊นœ๋นก์ž„ ํƒ€์ด๋จธ
604
  this.warningBlinkTimer += deltaTime;
605
  if (this.warningBlinkTimer >= 1.0) {
606
  this.warningBlinkTimer = 0;
607
  this.warningBlinkState = !this.warningBlinkState;
608
  }
609
 
610
- // ๊ณ ๋„ ๊ณ„์‚ฐ
611
  this.altitude = this.position.y;
612
 
613
- // ๊ฒฝ๊ณ ์Œ ์—…๋ฐ์ดํŠธ (์—”์ง„ ์†Œ๋ฆฌ๋Š” ๊ณ„์† ์œ ์ง€)
614
  this.updateWarningAudios();
615
 
616
- // ์—”์ง„ ์†Œ๋ฆฌ ๋ณผ๋ฅจ์„ ์Šค๋กœํ‹€์— ์—ฐ๋™
617
  if (this.warningAudios.normal && !this.warningAudios.normal.paused) {
618
- this.warningAudios.normal.volume = 0.3 + this.throttle * 0.4; // 0.3~0.7
619
  }
620
  }
621
 
622
  shoot(scene) {
623
- // ํƒ„์•ฝ์ด ์—†์œผ๋ฉด ๋ฐœ์‚ฌํ•˜์ง€ ์•Š์Œ
624
  if (this.ammo <= 0) return;
625
 
626
  this.ammo--;
627
 
628
- // ์ง์„  ๋ชจ์–‘์˜ ํƒ„ํ™˜ (100% ๋” ํฌ๊ฒŒ)
629
- const bulletGeometry = new THREE.CylinderGeometry(1.0, 1.0, 16, 8); // ๋ฐ˜์ง€๋ฆ„ 0.75โ†’1.0, ๊ธธ์ด 12โ†’16
630
  const bulletMaterial = new THREE.MeshBasicMaterial({
631
  color: 0xffff00,
632
  emissive: 0xffff00,
@@ -634,38 +600,35 @@ class Fighter {
634
  });
635
  const bullet = new THREE.Mesh(bulletGeometry, bulletMaterial);
636
 
637
- // ๊ธฐ์ˆ˜ ๋์—์„œ ๋ฐœ์‚ฌ (๋” ์•ž์ชฝ์—์„œ)
638
  const muzzleOffset = new THREE.Vector3(0, 0, 10);
639
- muzzleOffset.applyEuler(this.rotation);
640
  bullet.position.copy(this.position).add(muzzleOffset);
641
 
642
- // ํƒ„ํ™˜์„ ๋ฐœ์‚ฌ ๋ฐฉํ–ฅ์œผ๋กœ ํšŒ์ „
643
- bullet.rotation.copy(this.rotation);
644
- bullet.rotateX(Math.PI / 2); // ์‹ค๋ฆฐ๋”๊ฐ€ Z์ถ• ๋ฐฉํ–ฅ์„ ํ–ฅํ•˜๋„๋ก
 
645
 
646
- // ํƒ„ํ™˜ ์ดˆ๊ธฐ ์œ„์น˜ ์ €์žฅ
647
  bullet.startPosition = bullet.position.clone();
648
 
649
- const bulletSpeed = 1500; // 1000์—์„œ 1500์œผ๋กœ ์ฆ๊ฐ€ (50% ๋น ๋ฅด๊ฒŒ)
650
  const direction = new THREE.Vector3(0, 0, 1);
651
- direction.applyEuler(this.rotation);
652
  bullet.velocity = direction.multiplyScalar(bulletSpeed);
653
 
654
  scene.add(bullet);
655
  this.bullets.push(bullet);
656
 
657
- // m134.ogg ์†Œ๋ฆฌ ์žฌ์ƒ - ์ค‘์ฒฉ ์ œํ•œ ํ•ด์ œ, ๋žœ๋ค ํ”ผ์น˜
658
  try {
659
  const audio = new Audio('sounds/m134.ogg');
660
- audio.volume = 0.15; // 0.3์—์„œ 0.15๋กœ ๊ฐ์†Œ (50% ์ค„์ž„)
661
 
662
- // -2 ~ +2 ์‚ฌ์ด์˜ ๋žœ๋ค ํ”ผ์น˜ (playbackRate๋กœ ์‹œ๋ฎฌ๋ ˆ์ด์…˜)
663
- const randomPitch = 0.8 + Math.random() * 0.4; // 0.8 ~ 1.2 ๋ฒ”์œ„
664
  audio.playbackRate = randomPitch;
665
 
666
  audio.play().catch(e => console.log('Gunfire sound failed to play'));
667
 
668
- // ์žฌ์ƒ ์™„๋ฃŒ ์‹œ ๋ฉ”๋ชจ๋ฆฌ ์ •๋ฆฌ๋ฅผ ์œ„ํ•ด ์ฐธ์กฐ ์ œ๊ฑฐ
669
  audio.addEventListener('ended', () => {
670
  audio.remove();
671
  });
@@ -679,21 +642,13 @@ class Fighter {
679
  const bullet = this.bullets[i];
680
  bullet.position.add(bullet.velocity.clone().multiplyScalar(deltaTime));
681
 
682
- // ํƒ„ํ™˜๋„ ๊ฐ™์€ ๋ฐฉํ–ฅ์„ ์œ ์ง€ํ•˜๋„๋ก ํšŒ์ „ ์—…๋ฐ์ดํŠธ
683
- const direction = bullet.velocity.clone().normalize();
684
- const angle = Math.atan2(direction.x, direction.z);
685
- bullet.rotation.y = angle;
686
-
687
- // ์ง€๋ฉด ์ถฉ๋Œ ์ฒดํฌ ์ถ”๊ฐ€
688
  if (bullet.position.y <= 0) {
689
- // ํฌ๊ณ  ํ™”๋ คํ•œ ์ง€๋ฉด ์ถฉ๋Œ ํšจ๊ณผ
690
  gameInstance.createGroundImpactEffect(bullet.position);
691
  scene.remove(bullet);
692
  this.bullets.splice(i, 1);
693
  continue;
694
  }
695
 
696
- // 6000m ์ด์ƒ ๋‚ ์•„๊ฐ€๊ฑฐ๋‚˜ ๋†’์ด ์ œํ•œ ์ดˆ๊ณผ ์‹œ ์ œ๊ฑฐ
697
  if (bullet.position.distanceTo(bullet.startPosition) > 6000 ||
698
  bullet.position.y > GAME_CONSTANTS.MAX_ALTITUDE + 500) {
699
  scene.remove(bullet);
@@ -711,8 +666,8 @@ class Fighter {
711
  const backward = new THREE.Vector3(0, 0, -1);
712
  const up = new THREE.Vector3(0, 1, 0);
713
 
714
- backward.applyEuler(this.rotation);
715
- up.applyEuler(this.rotation);
716
 
717
  const cameraPosition = this.position.clone()
718
  .add(backward.multiplyScalar(this.cameraDistance))
 
33
  this.position = new THREE.Vector3(0, 2000, 0);
34
  this.velocity = new THREE.Vector3(0, 0, 350); // ์ดˆ๊ธฐ ์†๋„ 350kt
35
  this.acceleration = new THREE.Vector3(0, 0, 0);
36
+
37
+ // ์ฟผํ„ฐ๋‹ˆ์–ธ ๊ธฐ๋ฐ˜ ํšŒ์ „ (์ง๋ฒŒ ๋ฝ ๋ฐฉ์ง€)
38
+ this.quaternion = new THREE.Quaternion();
39
+
40
+ // ์˜ค์ผ๋Ÿฌ ๊ฐ๋„ (ํ‘œ์‹œ ๋ฐ ์ œํ•œ์šฉ)
41
+ this.rotation = new THREE.Euler(0, 0, 0);
42
+
43
+ // ๋ถ€๋“œ๋Ÿฌ์šด ํšŒ์ „์„ ์œ„ํ•œ ๋ชฉํ‘œ๊ฐ’
44
+ this.targetPitch = 0;
45
+ this.targetRoll = 0;
46
+ this.targetYaw = 0;
47
 
48
  // ๋น„ํ–‰ ์ œ์–ด
49
  this.throttle = 0.7; // ์ดˆ๊ธฐ ์Šค๋กœํ‹€ 70%
 
61
  this.mousePitch = 0;
62
  this.mouseRoll = 0;
63
 
 
 
 
 
 
64
  // ๋ฌด๊ธฐ
65
  this.missiles = GAME_CONSTANTS.MISSILE_COUNT;
66
  this.ammo = GAME_CONSTANTS.AMMO_COUNT;
 
188
  this.mesh = result.scene;
189
  this.mesh.position.copy(this.position);
190
  this.mesh.scale.set(2, 2, 2);
191
+
192
+ // ์ดˆ๊ธฐ ํšŒ์ „ ์„ค์ •
193
+ const initialYaw = Math.PI / 4;
194
+ this.quaternion.setFromEuler(new THREE.Euler(0, initialYaw, 0));
195
+ this.targetYaw = initialYaw;
196
 
197
  this.mesh.traverse((child) => {
198
  if (child.isMesh) {
 
251
  // Over-G ์ƒํƒœ์—์„œ ์Šคํ†จ์ด ํ•ด์ œ๋˜์ง€ ์•Š์•˜์œผ๋ฉด ํ”ผ์น˜ ์กฐ์ž‘ ๋ถˆ๊ฐ€
252
  if (this.overG && this.overGTimer > 1.0 && this.stallWarning) {
253
  // ์š”(Yaw)๋งŒ ์ œํ•œ์ ์œผ๋กœ ํ—ˆ์šฉ
254
+ const sensitivity = GAME_CONSTANTS.MOUSE_SENSITIVITY * 0.3;
255
  this.targetYaw += deltaX * sensitivity * 0.3;
256
 
257
  // ํ”ผ์น˜๋Š” ์กฐ์ž‘ ๋ถˆ๊ฐ€
258
  // ๋กค๋„ ์ œํ•œ์ ์œผ๋กœ๋งŒ ํ—ˆ์šฉ
259
  const yawRate = deltaX * sensitivity * 0.3;
260
+ this.targetRoll = -yawRate * 5;
261
 
262
+ return;
263
  }
264
 
265
  const sensitivity = GAME_CONSTANTS.MOUSE_SENSITIVITY * 1.0;
266
 
267
+ // ๋งˆ์šฐ์Šค ์ž…๋ ฅ์„ ๋ชฉํ‘œ ๊ฐ๋„๋กœ ๋ณ€ํ™˜
268
  this.targetPitch -= deltaY * sensitivity;
 
 
269
  this.targetYaw += deltaX * sensitivity * 0.8;
270
 
271
  // ์š” ํšŒ์ „์— ๋”ฐ๋ฅธ ์ž๋™ ๋กค (๋ฑ…ํฌ ํ„ด)
 
272
  const yawRate = deltaX * sensitivity * 0.8;
273
+ this.targetRoll = -yawRate * 15;
274
 
275
  // ๊ฐ๋„ ์ œํ•œ
276
  const maxPitchAngle = Math.PI / 3; // 60๋„
277
+ const maxRollAngle = Math.PI * 0.5; // 90๋„
278
 
279
  this.targetPitch = Math.max(-maxPitchAngle, Math.min(maxPitchAngle, this.targetPitch));
280
 
 
281
  if (Math.abs(this.targetRoll) < maxRollAngle) {
 
282
  this.targetRoll = Math.max(-maxRollAngle, Math.min(maxRollAngle, this.targetRoll));
283
  }
284
  }
285
 
286
  updateControls(keys, deltaTime) {
 
 
 
 
 
 
 
 
 
 
 
 
 
287
  // W/S: ์Šค๋กœํ‹€๋งŒ ์ œ์–ด (๊ฐ€์†/๊ฐ์†)
288
  if (keys.w) {
289
  this.throttle = Math.min(1.0, this.throttle + deltaTime * 0.5);
 
290
  }
291
  if (keys.s) {
292
  this.throttle = Math.max(0.1, this.throttle - deltaTime * 0.5);
 
293
  }
294
 
295
+ // A/D: ๋ณด์กฐ ์š” ์ œ์–ด (๋Ÿฌ๋”)
296
  if (keys.a) {
297
  this.targetYaw -= deltaTime * 1.2;
 
298
  }
299
  if (keys.d) {
300
  this.targetYaw += deltaTime * 1.2;
 
301
  }
302
  }
303
 
304
  updatePhysics(deltaTime) {
305
  if (!this.mesh) return;
306
 
307
+ // ์ฟผํ„ฐ๋‹ˆ์–ธ์„ ์‚ฌ์šฉํ•œ ๋ถ€๋“œ๋Ÿฌ์šด ํšŒ์ „
308
  const rotationSpeed = deltaTime * 2.0;
309
+ const yawRotationSpeed = deltaTime * 3.0;
310
 
311
+ // ํ˜„์žฌ ์ฟผํ„ฐ๋‹ˆ์–ธ์—์„œ ์˜ค์ผ๋Ÿฌ ๊ฐ๋„ ์ถ”์ถœ (ํ‘œ์‹œ์šฉ)
312
+ this.rotation.setFromQuaternion(this.quaternion);
 
313
 
314
+ // ๊ฐ ์ถ•์˜ ํšŒ์ „ ์ฐจ์ด ๊ณ„์‚ฐ
315
+ let pitchDiff = this.targetPitch - this.rotation.x;
316
  let yawDiff = this.targetYaw - this.rotation.y;
317
+ let rollDiff = this.targetRoll - this.rotation.z;
318
 
319
+ // ๊ฐ๋„ ์ฐจ์ด ์ •๊ทœํ™” (-ฯ€ ~ ฯ€)
320
  while (yawDiff > Math.PI) yawDiff -= Math.PI * 2;
321
  while (yawDiff < -Math.PI) yawDiff += Math.PI * 2;
322
 
323
+ // ์ฟผํ„ฐ๋‹ˆ์–ธ ํšŒ์ „ ์ ์šฉ
324
+ // 1. Yaw ํšŒ์ „ (์›”๋“œ Y์ถ• ๊ธฐ์ค€)
325
+ const yawQuat = new THREE.Quaternion().setFromAxisAngle(
326
+ new THREE.Vector3(0, 1, 0),
327
+ yawDiff * yawRotationSpeed
328
+ );
329
+
330
+ // 2. Pitch ํšŒ์ „ (๋กœ์ปฌ X์ถ• ๊ธฐ์ค€)
331
+ const localXAxis = new THREE.Vector3(1, 0, 0).applyQuaternion(this.quaternion);
332
+ const pitchQuat = new THREE.Quaternion().setFromAxisAngle(
333
+ localXAxis,
334
+ pitchDiff * rotationSpeed
335
+ );
336
+
337
+ // 3. Roll ํšŒ์ „ (๋กœ์ปฌ Z์ถ• ๊ธฐ์ค€)
338
+ const localZAxis = new THREE.Vector3(0, 0, 1).applyQuaternion(this.quaternion);
339
+ const rollQuat = new THREE.Quaternion().setFromAxisAngle(
340
+ localZAxis,
341
+ rollDiff * rotationSpeed * 1.5
342
+ );
343
+
344
+ // ํšŒ์ „ ์ ์šฉ ์ˆœ์„œ: Yaw -> Pitch -> Roll
345
+ this.quaternion.multiply(yawQuat);
346
+ this.quaternion.multiply(pitchQuat);
347
+ this.quaternion.multiply(rollQuat);
348
+ this.quaternion.normalize();
349
 
350
  // ๋กค ์ž๋™ ๋ณต๊ท€ ์‹œ์Šคํ…œ
351
+ if (Math.abs(yawDiff) < 0.05) {
 
352
  this.targetRoll *= 0.95;
353
  }
354
 
355
+ // ๋ฑ…ํฌ ํ„ด ํšจ๊ณผ
 
356
  let bankTurnRate = 0;
357
+ if (Math.abs(this.rotation.z) > 0.3) {
358
  const bankAngle = this.rotation.z;
359
+ bankTurnRate = Math.sin(bankAngle) * deltaTime * 0.1;
360
  this.targetYaw += bankTurnRate;
361
  }
362
 
363
+ // ์†๋„ ๊ณ„์‚ฐ
364
+ const minSpeed = 0;
365
+ const maxSpeed = 600;
366
  let targetSpeed = minSpeed + (maxSpeed - minSpeed) * this.throttle;
367
 
368
  // ํ”ผ์น˜ ๊ฐ๋„์— ๋”ฐ๋ฅธ ์†๋„ ๋ณ€ํ™”
369
  const pitchAngle = this.rotation.x;
370
  const pitchDegrees = Math.abs(pitchAngle) * (180 / Math.PI);
371
 
372
+ if (pitchAngle < -0.1 && !this.stallWarning) {
373
+ const climbFactor = Math.abs(pitchAngle) / (Math.PI / 2);
374
+ if (pitchDegrees > 30) {
375
+ targetSpeed *= Math.max(0, 1 - climbFactor * 1.5);
 
376
  } else {
377
+ targetSpeed *= (1 - climbFactor * 0.3);
378
  }
379
+ } else if (pitchAngle > 0.1) {
380
  const diveFactor = pitchAngle / (Math.PI / 3);
381
+ targetSpeed *= (1 + diveFactor * 0.4);
382
  }
383
 
384
+ // G-Force ๊ณ„์‚ฐ
385
  const turnRate = Math.abs(bankTurnRate) * 100;
386
+ const pitchRate = Math.abs(pitchDiff) * 10;
387
 
388
+ const altitudeInKm = this.position.y / 1000;
389
+ const altitudeMultiplier = 1 + (altitudeInKm * 0.2);
 
390
 
 
 
391
  let throttleGMultiplier = 0;
392
  if (this.throttle > 0.5) {
 
393
  throttleGMultiplier = (this.throttle - 0.5) * 2.0;
394
  }
395
 
396
+ // ๋น„์ •์ƒ์ ์ธ ์ž์„ธ์— ์˜ํ•œ G-Force
397
  let abnormalG = 0;
398
 
399
+ const rollDegrees = Math.abs(this.rotation.z) * (180 / Math.PI);
400
  const isInverted = Math.abs(this.rotation.z) > Math.PI / 2;
401
+
402
  if (isInverted) {
403
  const baseG = 3.0 + Math.abs(Math.abs(this.rotation.z) - Math.PI / 2) * 2;
404
+ abnormalG += baseG * altitudeMultiplier * (1 + throttleGMultiplier);
405
  }
406
 
 
 
407
  if (rollDegrees > 30) {
 
408
  const extremeRollG = (rollDegrees - 30) * 0.1;
409
  abnormalG += extremeRollG * altitudeMultiplier * (1 + throttleGMultiplier);
410
  }
411
 
 
412
  if (pitchDegrees >= 40) {
413
+ const extremePitchG = (pitchDegrees - 40) * 0.15;
 
414
  abnormalG += extremePitchG * altitudeMultiplier * (1 + throttleGMultiplier);
415
  }
416
 
 
417
  if (pitchAngle < -Math.PI / 6) {
418
  const baseG = 2.0 + Math.abs(pitchAngle + Math.PI / 6) * 3;
419
+ abnormalG += baseG * altitudeMultiplier * (1 + throttleGMultiplier);
420
  }
421
 
 
422
  if (pitchAngle > Math.PI / 3) {
423
  const baseG = 2.0 + Math.abs(pitchAngle - Math.PI / 3) * 3;
424
+ abnormalG += baseG * altitudeMultiplier * (1 + throttleGMultiplier);
425
  }
426
 
 
427
  const maneuverG = (turnRate + pitchRate + (Math.abs(this.rotation.z) * 3)) * (1 + throttleGMultiplier * 0.5);
428
 
 
429
  this.gForce = 1.0 + maneuverG + abnormalG;
430
 
431
+ // G-Force ํšŒ๋ณต
 
 
 
432
  const isPitchNeutral = Math.abs(pitchDegrees) <= 10;
433
 
434
  if (!this.overG && isPitchNeutral && !isInverted && !this.stallWarning) {
435
+ const recoveryRate = 2.0 - throttleGMultiplier * 1.5;
 
436
  this.gForce = THREE.MathUtils.lerp(this.gForce, 1.0 + maneuverG, deltaTime * recoveryRate);
437
  } else if (this.overG) {
 
438
  if (!isPitchNeutral || this.stallWarning) {
 
439
  this.gForce = Math.max(this.gForce, 1.0 + maneuverG + abnormalG);
440
  } else {
441
+ const overGRecoveryRate = 0.3 - throttleGMultiplier * 0.2;
 
442
  this.gForce = THREE.MathUtils.lerp(this.gForce, 1.0 + maneuverG, deltaTime * overGRecoveryRate);
443
  }
444
  }
445
 
 
446
  if (this.stallWarning && this.overG) {
 
447
  this.gForce = Math.max(this.gForce, 9.0);
448
  }
449
 
450
  this.overG = this.gForce > this.maxGForce;
451
 
452
+ // Over-G ํƒ€์ด๋จธ
453
  if (this.overG) {
454
  this.overGTimer += deltaTime;
 
 
455
  if (this.overGTimer > 1.5) {
 
456
  if (this.overGTimer > 2.5) {
457
  targetSpeed *= Math.max(0.3, 1 - (this.overGTimer - 2.5) * 0.3);
458
  }
 
459
  }
460
  } else {
461
+ this.overGTimer = 0;
462
  }
463
 
464
+ // ์Šคํ†จ ์ฒดํฌ
465
+ const speedKnots = this.speed * 1.94384;
 
466
 
 
467
  if (!this.stallWarning && speedKnots < GAME_CONSTANTS.STALL_SPEED) {
468
  this.stallWarning = true;
469
+ this.stallEscapeProgress = 0;
470
  }
471
 
 
472
  if (this.stallWarning) {
473
  if (this.escapeKeyPressed) {
 
474
  this.stallEscapeProgress += deltaTime;
 
 
475
  if (this.stallEscapeProgress >= 2.0 && speedKnots >= 350) {
476
  this.stallWarning = false;
477
  this.stallEscapeProgress = 0;
 
478
  this.targetPitch = Math.max(-0.2, Math.min(0.2, this.targetPitch));
479
  }
480
  } else {
 
481
  this.stallEscapeProgress = Math.max(0, this.stallEscapeProgress - deltaTime * 2);
482
  }
483
  }
484
 
485
+ // ์†๋„ ์—…๋ฐ์ดํŠธ
486
  if (this.stallWarning) {
487
+ if (pitchAngle > 0.1) {
488
+ const diveSpeedGain = Math.min(pitchAngle * 300, 200);
 
 
489
  this.speed = Math.min(maxSpeed, this.speed + diveSpeedGain * deltaTime);
490
  } else {
 
491
  this.speed = Math.max(0, this.speed - deltaTime * 100);
492
  }
493
  } else {
 
494
  this.speed = THREE.MathUtils.lerp(this.speed, targetSpeed, deltaTime * 0.5);
495
  }
496
 
497
+ // ์Šคํ†จ ๋ฌผ๋ฆฌ
498
  if (this.stallWarning) {
499
+ this.targetPitch = Math.min(Math.PI / 2.5, this.targetPitch + deltaTime * 2.5);
500
+
501
+ // ์ฟผํ„ฐ๋‹ˆ์–ธ์œผ๋กœ ํ”๋“ค๋ฆผ ํšจ๊ณผ
502
+ const shakeAxis = new THREE.Vector3(
503
+ (Math.random() - 0.5),
504
+ (Math.random() - 0.5),
505
+ (Math.random() - 0.5)
506
+ ).normalize();
507
+ const shakeQuat = new THREE.Quaternion().setFromAxisAngle(shakeAxis, deltaTime * 0.8);
508
+ this.quaternion.multiply(shakeQuat);
509
+
510
+ const gravityAcceleration = GAME_CONSTANTS.GRAVITY * deltaTime * 3.0;
511
  this.velocity.y -= gravityAcceleration;
512
  }
513
 
514
+ // ์†๋„ ๋ฒกํ„ฐ ๊ณ„์‚ฐ (์ฟผํ„ฐ๋‹ˆ์–ธ ์‚ฌ์šฉ)
515
  const noseDirection = new THREE.Vector3(0, 0, 1);
516
+ noseDirection.applyQuaternion(this.quaternion);
517
 
518
  if (!this.stallWarning) {
 
519
  this.velocity = noseDirection.multiplyScalar(this.speed);
520
  } else {
521
+ this.velocity.x = noseDirection.x * this.speed * 0.5;
 
522
  this.velocity.z = noseDirection.z * this.speed * 0.5;
 
523
  }
524
 
525
+ // ์ค‘๋ ฅ ํšจ๊ณผ
526
  if (!this.stallWarning) {
527
  const gravityEffect = GAME_CONSTANTS.GRAVITY * deltaTime * 0.15;
528
  this.velocity.y -= gravityEffect;
529
 
 
530
  const liftFactor = (this.speed / maxSpeed) * 0.8;
531
  const lift = gravityEffect * liftFactor;
532
  this.velocity.y += lift;
 
540
  this.position.y = GAME_CONSTANTS.MIN_ALTITUDE;
541
  this.health = 0;
542
 
 
543
  if (window.gameInstance) {
544
  window.gameInstance.createExplosionEffect(this.position);
545
  }
 
547
  return;
548
  }
549
 
550
+ // ๊ณ ๋„ ์ œํ•œ
551
  if (this.position.y > GAME_CONSTANTS.MAX_ALTITUDE) {
552
  this.position.y = GAME_CONSTANTS.MAX_ALTITUDE;
553
  this.altitudeWarning = true;
 
556
  this.altitudeWarning = false;
557
  }
558
 
559
+ // ๋งต ๊ฒฝ๊ณ„
560
  const mapLimit = GAME_CONSTANTS.MAP_SIZE / 2;
561
  if (this.position.x > mapLimit) this.position.x = -mapLimit;
562
  if (this.position.x < -mapLimit) this.position.x = mapLimit;
563
  if (this.position.z > mapLimit) this.position.z = -mapLimit;
564
  if (this.position.z < -mapLimit) this.position.z = mapLimit;
565
 
566
+ // ๋ฉ”์‹œ ์—…๋ฐ์ดํŠธ
567
  this.mesh.position.copy(this.position);
568
+ this.mesh.quaternion.copy(this.quaternion);
569
+
570
+ // ๋ชจ๋ธ ์˜คํ”„์…‹ ์ ์šฉ
571
+ const modelOffset = new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(0, 1, 0), 3 * Math.PI / 2);
572
+ this.mesh.quaternion.multiply(modelOffset);
573
 
574
+ // ๊ฒฝ๊ณ  ํƒ€์ด๋จธ
575
  this.warningBlinkTimer += deltaTime;
576
  if (this.warningBlinkTimer >= 1.0) {
577
  this.warningBlinkTimer = 0;
578
  this.warningBlinkState = !this.warningBlinkState;
579
  }
580
 
 
581
  this.altitude = this.position.y;
582
 
 
583
  this.updateWarningAudios();
584
 
 
585
  if (this.warningAudios.normal && !this.warningAudios.normal.paused) {
586
+ this.warningAudios.normal.volume = 0.3 + this.throttle * 0.4;
587
  }
588
  }
589
 
590
  shoot(scene) {
 
591
  if (this.ammo <= 0) return;
592
 
593
  this.ammo--;
594
 
595
+ const bulletGeometry = new THREE.CylinderGeometry(1.0, 1.0, 16, 8);
 
596
  const bulletMaterial = new THREE.MeshBasicMaterial({
597
  color: 0xffff00,
598
  emissive: 0xffff00,
 
600
  });
601
  const bullet = new THREE.Mesh(bulletGeometry, bulletMaterial);
602
 
603
+ // ์ฟผํ„ฐ๋‹ˆ์–ธ์„ ์‚ฌ์šฉํ•œ ๋ฐœ์‚ฌ ์œ„์น˜
604
  const muzzleOffset = new THREE.Vector3(0, 0, 10);
605
+ muzzleOffset.applyQuaternion(this.quaternion);
606
  bullet.position.copy(this.position).add(muzzleOffset);
607
 
608
+ // ํƒ„ํ™˜ ํšŒ์ „ ์„ค์ •
609
+ bullet.quaternion.copy(this.quaternion);
610
+ const bulletRotation = new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(1, 0, 0), Math.PI / 2);
611
+ bullet.quaternion.multiply(bulletRotation);
612
 
 
613
  bullet.startPosition = bullet.position.clone();
614
 
615
+ const bulletSpeed = 1500;
616
  const direction = new THREE.Vector3(0, 0, 1);
617
+ direction.applyQuaternion(this.quaternion);
618
  bullet.velocity = direction.multiplyScalar(bulletSpeed);
619
 
620
  scene.add(bullet);
621
  this.bullets.push(bullet);
622
 
 
623
  try {
624
  const audio = new Audio('sounds/m134.ogg');
625
+ audio.volume = 0.15;
626
 
627
+ const randomPitch = 0.8 + Math.random() * 0.4;
 
628
  audio.playbackRate = randomPitch;
629
 
630
  audio.play().catch(e => console.log('Gunfire sound failed to play'));
631
 
 
632
  audio.addEventListener('ended', () => {
633
  audio.remove();
634
  });
 
642
  const bullet = this.bullets[i];
643
  bullet.position.add(bullet.velocity.clone().multiplyScalar(deltaTime));
644
 
 
 
 
 
 
 
645
  if (bullet.position.y <= 0) {
 
646
  gameInstance.createGroundImpactEffect(bullet.position);
647
  scene.remove(bullet);
648
  this.bullets.splice(i, 1);
649
  continue;
650
  }
651
 
 
652
  if (bullet.position.distanceTo(bullet.startPosition) > 6000 ||
653
  bullet.position.y > GAME_CONSTANTS.MAX_ALTITUDE + 500) {
654
  scene.remove(bullet);
 
666
  const backward = new THREE.Vector3(0, 0, -1);
667
  const up = new THREE.Vector3(0, 1, 0);
668
 
669
+ backward.applyQuaternion(this.quaternion);
670
+ up.applyQuaternion(this.quaternion);
671
 
672
  const cameraPosition = this.position.clone()
673
  .add(backward.multiplyScalar(this.cameraDistance))