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

Update game.js

Browse files
Files changed (1) hide show
  1. game.js +179 -135
game.js CHANGED
@@ -33,18 +33,8 @@ 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
-
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%
50
  this.speed = 350; // ์ดˆ๊ธฐ ์†๋„ 350kt
@@ -61,6 +51,11 @@ class Fighter {
61
  this.mousePitch = 0;
62
  this.mouseRoll = 0;
63
 
 
 
 
 
 
64
  // ๋ฌด๊ธฐ
65
  this.missiles = GAME_CONSTANTS.MISSILE_COUNT;
66
  this.ammo = GAME_CONSTANTS.AMMO_COUNT;
@@ -100,6 +95,21 @@ class Fighter {
100
  this.initializeWarningAudios();
101
  }
102
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
103
  initializeWarningAudios() {
104
  try {
105
  this.warningAudios.altitude = new Audio('sounds/altitude.ogg');
@@ -188,11 +198,7 @@ class Fighter {
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,34 +257,39 @@ class Fighter {
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
  }
@@ -292,7 +303,7 @@ class Fighter {
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
  }
@@ -304,229 +315,242 @@ class Fighter {
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,6 +564,7 @@ class Fighter {
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,7 +572,7 @@ class Fighter {
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,43 +581,46 @@ class Fighter {
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,35 +628,38 @@ class Fighter {
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,13 +673,21 @@ class Fighter {
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,8 +705,8 @@ class Fighter {
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))
@@ -679,6 +718,11 @@ class Fighter {
679
  getCameraTarget() {
680
  return this.position.clone();
681
  }
 
 
 
 
 
682
  }
683
 
684
  // ์  ์ „ํˆฌ๊ธฐ ํด๋ž˜์Šค - ์™„์ „ํžˆ ์žฌ์„ค๊ณ„
@@ -1720,7 +1764,7 @@ class Game {
1720
  const altitudeMeters = Math.round(this.fighter.altitude);
1721
  const pitchDegrees = Math.round(this.fighter.rotation.x * (180 / Math.PI));
1722
  const rollDegreesRounded = Math.round(rollDegrees);
1723
- const headingDegrees = Math.round(((this.fighter.rotation.y * (180 / Math.PI)) + 360) % 360);
1724
 
1725
  // ์„ ํšŒ์œจ ๊ณ„์‚ฐ (๋„/์ดˆ)
1726
  if (!this.lastHeading) this.lastHeading = headingDegrees;
 
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);
37
 
 
 
 
 
 
38
  // ๋น„ํ–‰ ์ œ์–ด
39
  this.throttle = 0.7; // ์ดˆ๊ธฐ ์Šค๋กœํ‹€ 70%
40
  this.speed = 350; // ์ดˆ๊ธฐ ์†๋„ 350kt
 
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;
 
95
  this.initializeWarningAudios();
96
  }
97
 
98
+ // ํ—ค๋”ฉ์„ 0~360๋„๋กœ ์ •๊ทœํ™”ํ•˜๋Š” ํ—ฌํผ ํ•จ์ˆ˜
99
+ normalizeHeading(radians) {
100
+ let degrees = radians * (180 / Math.PI);
101
+ while (degrees < 0) degrees += 360;
102
+ while (degrees >= 360) degrees -= 360;
103
+ return degrees;
104
+ }
105
+
106
+ // ๋ผ๋””์•ˆ์„ -ฯ€ ~ ฯ€ ๋ฒ”์œ„๋กœ ์ •๊ทœํ™”
107
+ normalizeAngle(angle) {
108
+ while (angle > Math.PI) angle -= Math.PI * 2;
109
+ while (angle < -Math.PI) angle += Math.PI * 2;
110
+ return angle;
111
+ }
112
+
113
  initializeWarningAudios() {
114
  try {
115
  this.warningAudios.altitude = new Audio('sounds/altitude.ogg');
 
198
  this.mesh = result.scene;
199
  this.mesh.position.copy(this.position);
200
  this.mesh.scale.set(2, 2, 2);
201
+ this.mesh.rotation.y = Math.PI / 4;
 
 
 
 
202
 
203
  this.mesh.traverse((child) => {
204
  if (child.isMesh) {
 
257
  // Over-G ์ƒํƒœ์—์„œ ์Šคํ†จ์ด ํ•ด์ œ๋˜์ง€ ์•Š์•˜์œผ๋ฉด ํ”ผ์น˜ ์กฐ์ž‘ ๋ถˆ๊ฐ€
258
  if (this.overG && this.overGTimer > 1.0 && this.stallWarning) {
259
  // ์š”(Yaw)๋งŒ ์ œํ•œ์ ์œผ๋กœ ํ—ˆ์šฉ
260
+ const sensitivity = GAME_CONSTANTS.MOUSE_SENSITIVITY * 0.3; // ๊ฐ๋„ ๋Œ€ํญ ๊ฐ์†Œ
261
  this.targetYaw += deltaX * sensitivity * 0.3;
262
 
263
  // ํ”ผ์น˜๋Š” ์กฐ์ž‘ ๋ถˆ๊ฐ€
264
  // ๋กค๋„ ์ œํ•œ์ ์œผ๋กœ๋งŒ ํ—ˆ์šฉ
265
  const yawRate = deltaX * sensitivity * 0.3;
266
+ this.targetRoll = -yawRate * 5; // ๋งค์šฐ ์ œํ•œ๋œ ๋กค
267
 
268
+ return; // ์ถ”๊ฐ€ ์ฒ˜๋ฆฌ ์ค‘๋‹จ
269
  }
270
 
271
  const sensitivity = GAME_CONSTANTS.MOUSE_SENSITIVITY * 1.0;
272
 
273
+ // ๋งˆ์šฐ์Šค Y์ถ•: ํ”ผ์น˜(๊ธฐ์ˆ˜ ์ƒํ•˜) - ํ•ญ์ƒ ๋™์ผํ•˜๊ฒŒ ์ž‘๋™
274
  this.targetPitch -= deltaY * sensitivity;
275
+
276
+ // ๋งˆ์šฐ์Šค X์ถ•: ์š”(์ขŒ์šฐ ํšŒ์ „) - ํ•ญ์ƒ ๋™์ผํ•˜๊ฒŒ ์ž‘๋™
277
  this.targetYaw += deltaX * sensitivity * 0.8;
278
 
279
  // ์š” ํšŒ์ „์— ๋”ฐ๋ฅธ ์ž๋™ ๋กค (๋ฑ…ํฌ ํ„ด)
280
+ // ์ขŒ์šฐ๋กœ ํšŒ์ „ํ•  ๋•Œ ์ž์—ฐ์Šค๋Ÿฝ๊ฒŒ ๋‚ ๊ฐœ๊ฐ€ ๊ธฐ์šธ์–ด์ง
281
  const yawRate = deltaX * sensitivity * 0.8;
282
+ this.targetRoll = -yawRate * 15; // ์š” ํšŒ์ „๋Ÿ‰์— ๋น„๋ก€ํ•˜์—ฌ ๋กค ๋ฐœ์ƒ
283
 
284
  // ๊ฐ๋„ ์ œํ•œ
285
  const maxPitchAngle = Math.PI / 3; // 60๋„
286
+ const maxRollAngle = Math.PI * 0.5; // 90๋„๋กœ ์ œํ•œ (์ž๋™ ๋กค)
287
 
288
  this.targetPitch = Math.max(-maxPitchAngle, Math.min(maxPitchAngle, this.targetPitch));
289
 
290
+ // ์ž๋™ ๋กค์€ ์ œํ•œ๋œ ๋ฒ”์œ„ ๋‚ด์—์„œ๋งŒ ์ž‘๋™
291
  if (Math.abs(this.targetRoll) < maxRollAngle) {
292
+ // ๋กค ๊ฐ๋„ ์ œํ•œ ์ ์šฉ
293
  this.targetRoll = Math.max(-maxRollAngle, Math.min(maxRollAngle, this.targetRoll));
294
  }
295
  }
 
303
  this.throttle = Math.max(0.1, this.throttle - deltaTime * 0.5);
304
  }
305
 
306
+ // A/D: ๋ณด์กฐ ์š” ์ œ์–ด (๋Ÿฌ๋”) - ๋ฐ˜์‘์„ฑ ๊ฐœ์„ 
307
  if (keys.a) {
308
  this.targetYaw -= deltaTime * 1.2;
309
  }
 
315
  updatePhysics(deltaTime) {
316
  if (!this.mesh) return;
317
 
318
+ // ๋ถ€๋“œ๋Ÿฌ์šด ํšŒ์ „ ๋ณด๊ฐ„
319
  const rotationSpeed = deltaTime * 2.0;
320
  const yawRotationSpeed = deltaTime * 3.0;
321
 
322
+ // ํ”ผ์น˜์™€ ๋กค์€ ์ง์ ‘ ๋ณด๊ฐ„
323
+ this.rotation.x = THREE.MathUtils.lerp(this.rotation.x, this.targetPitch, rotationSpeed);
324
+ this.rotation.z = THREE.MathUtils.lerp(this.rotation.z, this.targetRoll, rotationSpeed * 1.5);
 
 
 
 
325
 
326
+ // Yaw ๋ณด๊ฐ„ (์ตœ๋‹จ ๊ฒฝ๋กœ)
327
+ let yawDiff = this.normalizeAngle(this.targetYaw - this.rotation.y);
328
+ this.rotation.y += yawDiff * yawRotationSpeed;
329
 
330
+ // rotation.y๋ฅผ -ฯ€ ~ ฯ€ ๋ฒ”์œ„๋กœ ์œ ์ง€
331
+ this.rotation.y = this.normalizeAngle(this.rotation.y);
332
+ this.targetYaw = this.normalizeAngle(this.targetYaw);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
333
 
334
  // ๋กค ์ž๋™ ๋ณต๊ท€ ์‹œ์Šคํ…œ
335
  if (Math.abs(yawDiff) < 0.05) {
336
+ // ์š” ํšŒ์ „์ด ๊ฑฐ์˜ ์—†์„ ๋•Œ ๋กค์„ 0์œผ๋กœ ๋ณต๊ท€
337
  this.targetRoll *= 0.95;
338
  }
339
 
340
+ // ์›Œ์ฌ๋” ์Šคํƒ€์ผ: ์š” ํšŒ์ „์ด ์ฃผ๋„์ , ๋กค์€ ๋ณด์กฐ์ 
341
  let bankTurnRate = 0;
342
+ if (Math.abs(this.rotation.z) > 0.3) { // ๋กค์ด ์ถฉ๋ถ„ํžˆ ํด ๋•Œ๋งŒ
343
  const bankAngle = this.rotation.z;
344
+ bankTurnRate = Math.sin(bankAngle) * deltaTime * 0.1; // ๋งค์šฐ ์ž‘์€ ์„ ํšŒ์œจ
345
  this.targetYaw += bankTurnRate;
346
  }
347
 
348
+ // ํ˜„์‹ค์ ์ธ ์†๋„ ๊ณ„์‚ฐ
349
+ const minSpeed = 0; // ์ตœ์†Œ ์†๋„ 0kt
350
+ const maxSpeed = 600; // ์ตœ๋Œ€ ์†๋„ 600kt
351
  let targetSpeed = minSpeed + (maxSpeed - minSpeed) * this.throttle;
352
 
353
  // ํ”ผ์น˜ ๊ฐ๋„์— ๋”ฐ๋ฅธ ์†๋„ ๋ณ€ํ™”
354
  const pitchAngle = this.rotation.x;
355
  const pitchDegrees = Math.abs(pitchAngle) * (180 / Math.PI);
356
 
357
+ // ๊ธฐ์ˆ˜๊ฐ€ ์œ„๋ฅผ ํ–ฅํ•˜๊ณ  ์žˆ์„ ๊ฒฝ์šฐ ๋น ๋ฅธ ์†๋„ ๊ฐ์†Œ
358
+ if (pitchAngle < -0.1 && !this.stallWarning) { // ์Šคํ†จ์ด ์•„๋‹ ๋•Œ๋งŒ ์ƒ์Šน์œผ๋กœ ์ธํ•œ ๊ฐ์†
359
+ const climbFactor = Math.abs(pitchAngle) / (Math.PI / 2); // 90๋„ ๊ธฐ์ค€
360
+ if (pitchDegrees > 30) { // 30๋„ ์ด์ƒ์ผ ๋•Œ ๊ธ‰๊ฒฉํ•œ ๊ฐ์†
361
+ targetSpeed *= Math.max(0, 1 - climbFactor * 1.5); // ์ตœ๋Œ€ 150% ๊ฐ์† (0kt๊นŒ์ง€)
362
  } else {
363
+ targetSpeed *= (1 - climbFactor * 0.3); // ์ •์ƒ์ ์ธ ๊ฐ์†
364
  }
365
+ } else if (pitchAngle > 0.1) { // ๊ธฐ์ˆ˜๊ฐ€ ์•„๋ž˜๋กœ (ํ•˜๊ฐ•) - ์Šคํ†จ ์ƒํƒœ์—์„œ๋„ ์ ์šฉ
366
  const diveFactor = pitchAngle / (Math.PI / 3);
367
+ targetSpeed *= (1 + diveFactor * 0.4); // ํ•˜๊ฐ• ์‹œ ๊ฐ€์† ์ฆ๊ฐ€ (0.2 -> 0.4)
368
  }
369
 
370
+ // G-Force ๊ณ„์‚ฐ ๊ฐœ์„ 
371
  const turnRate = Math.abs(bankTurnRate) * 100;
372
+ const pitchRate = Math.abs(this.rotation.x - this.targetPitch) * 10;
373
 
374
+ // ๊ณ ๋„์— ๋”ฐ๋ฅธ G-Force ์ฆ๊ฐ€ ๋ฐฐ์œจ ๊ณ„์‚ฐ
375
+ const altitudeInKm = this.position.y / 1000; // ๋ฏธํ„ฐ๋ฅผ ํ‚ฌ๋กœ๋ฏธํ„ฐ๋กœ ๋ณ€ํ™˜
376
+ const altitudeMultiplier = 1 + (altitudeInKm * 0.2); // 1km๋‹น 20% ์ฆ๊ฐ€
377
 
378
+ // ์Šค๋กœํ‹€์— ๋”ฐ๋ฅธ G-Force ์ฆ๊ฐ€ ๋ฐฐ์œจ ๊ณ„์‚ฐ
379
+ // THR 50% ์ดํ•˜: 0๋ฐฐ, THR 75%: 0.5๋ฐฐ, THR 100%: 1.0๋ฐฐ
380
  let throttleGMultiplier = 0;
381
  if (this.throttle > 0.5) {
382
+ // 0.5 ~ 1.0 ๋ฒ”์œ„๋ฅผ 0 ~ 1.0์œผ๋กœ ๋งคํ•‘
383
  throttleGMultiplier = (this.throttle - 0.5) * 2.0;
384
  }
385
 
386
+ // ๋น„์ •์ƒ์ ์ธ ์ž์„ธ์— ์˜ํ•œ G-Force ์ถ”๊ฐ€
387
  let abnormalG = 0;
388
 
389
+ // ๋’ค์ง‘ํžŒ ์ƒํƒœ (๋กค์ด 90๋„ ์ด์ƒ)
390
  const isInverted = Math.abs(this.rotation.z) > Math.PI / 2;
 
391
  if (isInverted) {
392
  const baseG = 3.0 + Math.abs(Math.abs(this.rotation.z) - Math.PI / 2) * 2;
393
+ abnormalG += baseG * altitudeMultiplier * (1 + throttleGMultiplier); // ์Šค๋กœํ‹€ ๋ฐฐ์œจ ์ถ”๊ฐ€
394
  }
395
 
396
+ // ๋กค์ด ยฑ30๋„ ์ด์ƒ์ผ ๋•Œ ์ถ”๊ฐ€ G-Force
397
+ const rollDegrees = Math.abs(this.rotation.z) * (180 / Math.PI);
398
  if (rollDegrees > 30) {
399
+ // 30๋„ ์ดˆ๊ณผ๋ถ„๋‹น 0.1G ์ถ”๊ฐ€
400
  const extremeRollG = (rollDegrees - 30) * 0.1;
401
  abnormalG += extremeRollG * altitudeMultiplier * (1 + throttleGMultiplier);
402
  }
403
 
404
+ // ํ”ผ์น˜ ๊ฐ๋„๊ฐ€ ยฑ40๋„ ์ด์ƒ์ผ ๋•Œ ์ถ”๊ฐ€ G-Force
405
  if (pitchDegrees >= 40) {
406
+ // 40๋„ ์ด์ƒ์ผ ๋•Œ ๊ธ‰๊ฒฉํ•œ G-Force ์ฆ๊ฐ€
407
+ const extremePitchG = (pitchDegrees - 40) * 0.15; // 40๋„ ์ดˆ๊ณผ๋ถ„๋‹น 0.15G ์ถ”๊ฐ€
408
  abnormalG += extremePitchG * altitudeMultiplier * (1 + throttleGMultiplier);
409
  }
410
 
411
+ // ๊ธฐ์ˆ˜๊ฐ€ ๊ณ„์† ์œ„๋ฅผ ํ–ฅํ•˜๊ณ  ์žˆ๋Š” ๊ฒฝ์šฐ (ํ”ผ์น˜๊ฐ€ -30๋„ ์ดํ•˜)
412
  if (pitchAngle < -Math.PI / 6) {
413
  const baseG = 2.0 + Math.abs(pitchAngle + Math.PI / 6) * 3;
414
+ abnormalG += baseG * altitudeMultiplier * (1 + throttleGMultiplier); // ์Šค๋กœํ‹€ ๋ฐฐ์œจ ์ถ”๊ฐ€
415
  }
416
 
417
+ // ๊ธฐ์ˆ˜๊ฐ€ ๊ณผ๋„ํ•˜๊ฒŒ ์•„๋ž˜๋ฅผ ํ–ฅํ•˜๊ณ  ์žˆ๋Š” ๊ฒฝ์šฐ (ํ”ผ์น˜๊ฐ€ 60๋„ ์ด์ƒ)
418
  if (pitchAngle > Math.PI / 3) {
419
  const baseG = 2.0 + Math.abs(pitchAngle - Math.PI / 3) * 3;
420
+ abnormalG += baseG * altitudeMultiplier * (1 + throttleGMultiplier); // ์Šค๋กœํ‹€ ๋ฐฐ์œจ ์ถ”๊ฐ€
421
  }
422
 
423
+ // ๊ธ‰๊ฒฉํ•œ ๊ธฐ๋™์— ์˜ํ•œ G-Force
424
  const maneuverG = (turnRate + pitchRate + (Math.abs(this.rotation.z) * 3)) * (1 + throttleGMultiplier * 0.5);
425
 
426
+ // ์ด G-Force ๊ณ„์‚ฐ
427
  this.gForce = 1.0 + maneuverG + abnormalG;
428
 
429
+ // G-Force ํšŒ๋ณต ์กฐ๊ฑด ์ˆ˜์ •
430
+ // 1. Over-G ์ƒํƒœ๊ฐ€ ์•„๋‹Œ ๊ฒฝ์šฐ์—๋งŒ ํšŒ๋ณต
431
+ // 2. ํ”ผ์น˜๊ฐ€ ยฑ10๋„ ์ด๋‚ด์ผ ๋•Œ๋งŒ ํšŒ๋ณต
432
+ // 3. ์Šคํ†จ ์ƒํƒœ๊ฐ€ ์•„๋‹ ๋•Œ๋งŒ ํšŒ๋ณต
433
  const isPitchNeutral = Math.abs(pitchDegrees) <= 10;
434
 
435
  if (!this.overG && isPitchNeutral && !isInverted && !this.stallWarning) {
436
+ // ์Šค๋กœํ‹€์ด ๋†’์„์ˆ˜๋ก G-Force๊ฐ€ ์ฒœ์ฒœํžˆ ๊ฐ์†Œ
437
+ const recoveryRate = 2.0 - throttleGMultiplier * 1.5; // THR 100%์ผ ๋•Œ 0.5, THR 50%์ผ ๋•Œ 2.0
438
  this.gForce = THREE.MathUtils.lerp(this.gForce, 1.0 + maneuverG, deltaTime * recoveryRate);
439
  } else if (this.overG) {
440
+ // Over-G ์ƒํƒœ์—์„œ๋Š” ํ”ผ์น˜๊ฐ€ 0๋„ ๊ทผ์ฒ˜(ยฑ10๋„)๊ฐ€ ๋˜๊ณ  ์Šคํ†จ์ด ํšŒ๋ณต๋  ๋•Œ๊นŒ์ง€ ํšŒ๋ณตํ•˜์ง€ ์•Š์Œ
441
  if (!isPitchNeutral || this.stallWarning) {
442
+ // ํ”ผ์น˜๊ฐ€ ์ค‘๋ฆฝ์ด ์•„๋‹ˆ๊ฑฐ๋‚˜ ์Šคํ†จ ์ƒํƒœ๋ฉด G-Force ์œ ์ง€ ๋˜๋Š” ์ฆ๊ฐ€๋งŒ ๊ฐ€๋Šฅ
443
  this.gForce = Math.max(this.gForce, 1.0 + maneuverG + abnormalG);
444
  } else {
445
+ // ํ”ผ์น˜๊ฐ€ ์ค‘๋ฆฝ์ด๊ณ  ์Šคํ†จ์ด ์•„๋‹ ๋•Œ๋งŒ ๋งค์šฐ ์ฒœ์ฒœํžˆ ํšŒ๋ณต
446
+ const overGRecoveryRate = 0.3 - throttleGMultiplier * 0.2; // Over-G ์ƒํƒœ์—์„œ๋Š” ๋” ๋А๋ฆฐ ํšŒ๋ณต
447
  this.gForce = THREE.MathUtils.lerp(this.gForce, 1.0 + maneuverG, deltaTime * overGRecoveryRate);
448
  }
449
  }
450
 
451
+ // ์Šคํ†จ ์ƒํƒœ์—์„œ๋Š” Over-G๊ฐ€ ๊ฐ์†Œํ•˜์ง€ ์•Š๋„๋ก ์ถ”๊ฐ€ ์ฒ˜๋ฆฌ
452
  if (this.stallWarning && this.overG) {
453
+ // ์Šคํ†จ ์ค‘์—๋Š” G-Force๋ฅผ 9.0 ์ด์ƒ์œผ๋กœ ์œ ์ง€
454
  this.gForce = Math.max(this.gForce, 9.0);
455
  }
456
 
457
  this.overG = this.gForce > this.maxGForce;
458
 
459
+ // Over-G ํƒ€์ด๋จธ ์—…๋ฐ์ดํŠธ
460
  if (this.overG) {
461
  this.overGTimer += deltaTime;
462
+
463
+ // 1.5์ดˆ ์ด์ƒ Over-G ์ƒํƒœ์ผ ๊ฒฝ์šฐ ์‹œ์•ผ ํ๋ฆผ ์‹œ์ž‘
464
  if (this.overGTimer > 1.5) {
465
+ // ์†๋„ ๊ธ‰๊ฒฉํžˆ ๊ฐ์†Œ (2.5์ดˆ๋ถ€ํ„ฐ)
466
  if (this.overGTimer > 2.5) {
467
  targetSpeed *= Math.max(0.3, 1 - (this.overGTimer - 2.5) * 0.3);
468
  }
469
+ // ์‹œ์•ผ ํ๋ฆผ ํšจ๊ณผ๋Š” UI์—์„œ ์ฒ˜๋ฆฌ (1.5์ดˆ๋ถ€ํ„ฐ)
470
  }
471
  } else {
472
+ this.overGTimer = 0; // Over-G ์ƒํƒœ๊ฐ€ ์•„๋‹ˆ๋ฉด ํƒ€์ด๋จธ ๋ฆฌ์…‹
473
  }
474
 
475
+ // ์Šคํ†จ ๊ฒฝ๊ณ : 300kt ์ดํ•˜์—์„œ ์Šคํ†จ ์œ„ํ—˜
476
+ const speedKnots = this.speed * 1.94384; // m/s to knots
477
+ const wasStalling = this.stallWarning;
478
 
479
+ // ์Šคํ†จ ์ง„์ž… ์กฐ๊ฑด
480
  if (!this.stallWarning && speedKnots < GAME_CONSTANTS.STALL_SPEED) {
481
  this.stallWarning = true;
482
+ this.stallEscapeProgress = 0; // ์Šคํ†จ ์ง„์ž… ์‹œ ์ง„ํ–‰๋„ ์ดˆ๊ธฐํ™”
483
  }
484
 
485
+ // ์Šคํ†จ ํƒˆ์ถœ ์กฐ๊ฑด
486
  if (this.stallWarning) {
487
  if (this.escapeKeyPressed) {
488
+ // Fํ‚ค๋ฅผ ๋ˆ„๋ฅด๊ณ  ์žˆ์œผ๋ฉด ์ง„ํ–‰๋„ ์ฆ๊ฐ€
489
  this.stallEscapeProgress += deltaTime;
490
+
491
+ // 2์ดˆ ์ด์ƒ ๋ˆ„๋ฅด๋ฉด ์Šคํ†จ์—์„œ ํƒˆ์ถœ
492
  if (this.stallEscapeProgress >= 2.0 && speedKnots >= 350) {
493
  this.stallWarning = false;
494
  this.stallEscapeProgress = 0;
495
+ // ์Šคํ†จ์—์„œ ๋ฒ—์–ด๋‚˜๋ฉด ์ž์„ธ๋ฅผ ์•ฝ๊ฐ„ ์•ˆ์ •ํ™”
496
  this.targetPitch = Math.max(-0.2, Math.min(0.2, this.targetPitch));
497
  }
498
  } else {
499
+ // Fํ‚ค๋ฅผ ๋†“์œผ๋ฉด ์ง„ํ–‰๋„ ๊ฐ์†Œ
500
  this.stallEscapeProgress = Math.max(0, this.stallEscapeProgress - deltaTime * 2);
501
  }
502
  }
503
 
504
+ // ์†๋„ ๋ณ€ํ™” ์ ์šฉ
505
  if (this.stallWarning) {
506
+ // ์Šคํ†จ ์ƒํƒœ์—์„œ์˜ ์†๋„ ๋ณ€ํ™”
507
+ if (pitchAngle > 0.1) { // ๊ธฐ์ˆ˜๊ฐ€ ์•„๋ž˜๋ฅผ ํ–ฅํ•  ๋•Œ
508
+ // ๋‹ค์ด๋น™์œผ๋กœ ์ธํ•œ ์†๋„ ์ฆ๊ฐ€
509
+ const diveSpeedGain = Math.min(pitchAngle * 300, 200); // ์ตœ๋Œ€ 200m/s ์ฆ๊ฐ€
510
  this.speed = Math.min(maxSpeed, this.speed + diveSpeedGain * deltaTime);
511
  } else {
512
+ // ๊ธฐ์ˆ˜๊ฐ€ ์œ„๋ฅผ ํ–ฅํ•˜๊ฑฐ๋‚˜ ์ˆ˜ํ‰์ผ ๋•Œ๋Š” ์†๋„ ๊ฐ์†Œ
513
  this.speed = Math.max(0, this.speed - deltaTime * 100);
514
  }
515
  } else {
516
+ // ์ •์ƒ ๋น„ํ–‰ ์‹œ ์†๋„ ๋ณ€ํ™”
517
  this.speed = THREE.MathUtils.lerp(this.speed, targetSpeed, deltaTime * 0.5);
518
  }
519
 
520
+ // ์Šคํ†จ ์ƒํƒœ์—์„œ์˜ ๋ฌผ๋ฆฌ ํšจ๊ณผ
521
  if (this.stallWarning) {
522
+ // ๋ฐ”๋‹ฅ์œผ๋กœ ์ถ”๋ฝํ•˜๋ฉฐ ๊ฐ€์†๋„๊ฐ€ ๋น ๋ฅด๊ฒŒ ๋ถ™์Œ
523
+ this.targetPitch = Math.min(Math.PI / 2.5, this.targetPitch + deltaTime * 2.5); // ๊ธฐ์ˆ˜๊ฐ€ ๋” ๊ทน๋‹จ์ ์œผ๋กœ ์•„๋ž˜๋กœ (72๋„๊นŒ์ง€)
524
+
525
+ // ์กฐ์ข… ๋ถˆ๋Šฅ ์ƒํƒœ - ๋” ์‹ฌํ•œ ํ”๋“ค๋ฆผ
526
+ this.rotation.x += (Math.random() - 0.5) * deltaTime * 0.8;
527
+ this.rotation.z += (Math.random() - 0.5) * deltaTime * 0.8;
528
+
529
+ // ์ค‘๋ ฅ์— ์˜ํ•œ ๊ฐ€์†
530
+ const gravityAcceleration = GAME_CONSTANTS.GRAVITY * deltaTime * 3.0; // 3๋ฐฐ ์ค‘๋ ฅ
 
 
 
531
  this.velocity.y -= gravityAcceleration;
532
  }
533
 
534
+ // ์†๋„ ๋ฒกํ„ฐ ๊ณ„์‚ฐ
535
  const noseDirection = new THREE.Vector3(0, 0, 1);
536
+ noseDirection.applyEuler(this.rotation);
537
 
538
  if (!this.stallWarning) {
539
+ // ์ •์ƒ ๋น„ํ–‰ ์‹œ
540
  this.velocity = noseDirection.multiplyScalar(this.speed);
541
  } else {
542
+ // ์Šคํ†จ ์‹œ์—๋Š” ์ค‘๋ ฅ์ด ์ฃผ๋„์ ์ด์ง€๋งŒ ๋‹ค์ด๋น™ ๏ฟฝ๏ฟฝ๋„๋„ ๋ฐ˜์˜
543
+ this.velocity.x = noseDirection.x * this.speed * 0.5; // ์ „๋ฐฉ ์†๋„ ์ฆ๊ฐ€
544
  this.velocity.z = noseDirection.z * this.speed * 0.5;
545
+ // y ์†๋„๋Š” ์œ„์—์„œ ์ค‘๋ ฅ์œผ๋กœ ์ฒ˜๋ฆฌ๋จ
546
  }
547
 
548
+ // ์ •์ƒ ๋น„ํ–‰ ์‹œ ์ค‘๋ ฅ ํšจ๊ณผ
549
  if (!this.stallWarning) {
550
  const gravityEffect = GAME_CONSTANTS.GRAVITY * deltaTime * 0.15;
551
  this.velocity.y -= gravityEffect;
552
 
553
+ // ์–‘๋ ฅ ํšจ๊ณผ (์†๋„์— ๋น„๋ก€)
554
  const liftFactor = (this.speed / maxSpeed) * 0.8;
555
  const lift = gravityEffect * liftFactor;
556
  this.velocity.y += lift;
 
564
  this.position.y = GAME_CONSTANTS.MIN_ALTITUDE;
565
  this.health = 0;
566
 
567
+ // ์ง€๋ฉด ์ถฉ๋Œ ์‹œ ํญ๋ฐœ ํšจ๊ณผ ์ถ”๊ฐ€
568
  if (window.gameInstance) {
569
  window.gameInstance.createExplosionEffect(this.position);
570
  }
 
572
  return;
573
  }
574
 
575
+ // ์ตœ๋Œ€ ๊ณ ๋„ ์ œํ•œ
576
  if (this.position.y > GAME_CONSTANTS.MAX_ALTITUDE) {
577
  this.position.y = GAME_CONSTANTS.MAX_ALTITUDE;
578
  this.altitudeWarning = true;
 
581
  this.altitudeWarning = false;
582
  }
583
 
584
+ // ๋งต ๊ฒฝ๊ณ„ ์ฒ˜๋ฆฌ
585
  const mapLimit = GAME_CONSTANTS.MAP_SIZE / 2;
586
  if (this.position.x > mapLimit) this.position.x = -mapLimit;
587
  if (this.position.x < -mapLimit) this.position.x = mapLimit;
588
  if (this.position.z > mapLimit) this.position.z = -mapLimit;
589
  if (this.position.z < -mapLimit) this.position.z = mapLimit;
590
 
591
+ // ๋ฉ”์‹œ ์œ„์น˜ ๋ฐ ํšŒ์ „ ์—…๋ฐ์ดํŠธ
592
  this.mesh.position.copy(this.position);
593
+ this.mesh.rotation.x = this.rotation.x;
594
+ this.mesh.rotation.y = this.rotation.y + 3 * Math.PI / 2;
595
+ this.mesh.rotation.z = this.rotation.z;
 
 
596
 
597
+ // ๊ฒฝ๊ณ  ๊นœ๋นก์ž„ ํƒ€์ด๋จธ
598
  this.warningBlinkTimer += deltaTime;
599
  if (this.warningBlinkTimer >= 1.0) {
600
  this.warningBlinkTimer = 0;
601
  this.warningBlinkState = !this.warningBlinkState;
602
  }
603
 
604
+ // ๊ณ ๋„ ๊ณ„์‚ฐ
605
  this.altitude = this.position.y;
606
 
607
+ // ๊ฒฝ๊ณ ์Œ ์—…๋ฐ์ดํŠธ (์—”์ง„ ์†Œ๋ฆฌ๋Š” ๊ณ„์† ์œ ์ง€)
608
  this.updateWarningAudios();
609
 
610
+ // ์—”์ง„ ์†Œ๋ฆฌ ๋ณผ๋ฅจ์„ ์Šค๋กœํ‹€์— ์—ฐ๋™
611
  if (this.warningAudios.normal && !this.warningAudios.normal.paused) {
612
+ this.warningAudios.normal.volume = 0.3 + this.throttle * 0.4; // 0.3~0.7
613
  }
614
  }
615
 
616
  shoot(scene) {
617
+ // ํƒ„์•ฝ์ด ์—†์œผ๋ฉด ๋ฐœ์‚ฌํ•˜์ง€ ์•Š์Œ
618
  if (this.ammo <= 0) return;
619
 
620
  this.ammo--;
621
 
622
+ // ์ง์„  ๋ชจ์–‘์˜ ํƒ„ํ™˜ (100% ๋” ํฌ๊ฒŒ)
623
+ const bulletGeometry = new THREE.CylinderGeometry(1.0, 1.0, 16, 8); // ๋ฐ˜์ง€๋ฆ„ 0.75โ†’1.0, ๊ธธ์ด 12โ†’16
624
  const bulletMaterial = new THREE.MeshBasicMaterial({
625
  color: 0xffff00,
626
  emissive: 0xffff00,
 
628
  });
629
  const bullet = new THREE.Mesh(bulletGeometry, bulletMaterial);
630
 
631
+ // ๊ธฐ์ˆ˜ ๋์—์„œ ๋ฐœ์‚ฌ (๋” ์•ž์ชฝ์—์„œ)
632
  const muzzleOffset = new THREE.Vector3(0, 0, 10);
633
+ muzzleOffset.applyEuler(this.rotation);
634
  bullet.position.copy(this.position).add(muzzleOffset);
635
 
636
+ // ํƒ„ํ™˜์„ ๋ฐœ์‚ฌ ๋ฐฉํ–ฅ์œผ๋กœ ํšŒ์ „
637
+ bullet.rotation.copy(this.rotation);
638
+ bullet.rotateX(Math.PI / 2); // ์‹ค๋ฆฐ๋”๊ฐ€ Z์ถ• ๋ฐฉํ–ฅ์„ ํ–ฅํ•˜๋„๋ก
 
639
 
640
+ // ํƒ„ํ™˜ ์ดˆ๊ธฐ ์œ„์น˜ ์ €์žฅ
641
  bullet.startPosition = bullet.position.clone();
642
 
643
+ const bulletSpeed = 1500; // 1000์—์„œ 1500์œผ๋กœ ์ฆ๊ฐ€ (50% ๋น ๋ฅด๊ฒŒ)
644
  const direction = new THREE.Vector3(0, 0, 1);
645
+ direction.applyEuler(this.rotation);
646
  bullet.velocity = direction.multiplyScalar(bulletSpeed);
647
 
648
  scene.add(bullet);
649
  this.bullets.push(bullet);
650
 
651
+ // m134.ogg ์†Œ๋ฆฌ ์žฌ์ƒ - ์ค‘์ฒฉ ์ œํ•œ ํ•ด์ œ, ๋žœ๋ค ํ”ผ์น˜
652
  try {
653
  const audio = new Audio('sounds/m134.ogg');
654
+ audio.volume = 0.15; // 0.3์—์„œ 0.15๋กœ ๊ฐ์†Œ (50% ์ค„์ž„)
655
 
656
+ // -2 ~ +2 ์‚ฌ์ด์˜ ๋žœ๋ค ํ”ผ์น˜ (playbackRate๋กœ ์‹œ๋ฎฌ๋ ˆ์ด์…˜)
657
+ const randomPitch = 0.8 + Math.random() * 0.4; // 0.8 ~ 1.2 ๋ฒ”์œ„
658
  audio.playbackRate = randomPitch;
659
 
660
  audio.play().catch(e => console.log('Gunfire sound failed to play'));
661
 
662
+ // ์žฌ์ƒ ์™„๋ฃŒ ์‹œ ๋ฉ”๋ชจ๋ฆฌ ์ •๋ฆฌ๋ฅผ ์œ„ํ•ด ์ฐธ์กฐ ์ œ๊ฑฐ
663
  audio.addEventListener('ended', () => {
664
  audio.remove();
665
  });
 
673
  const bullet = this.bullets[i];
674
  bullet.position.add(bullet.velocity.clone().multiplyScalar(deltaTime));
675
 
676
+ // ํƒ„ํ™˜๋„ ๊ฐ™์€ ๋ฐฉํ–ฅ์„ ์œ ์ง€ํ•˜๋„๋ก ํšŒ์ „ ์—…๋ฐ์ดํŠธ
677
+ const direction = bullet.velocity.clone().normalize();
678
+ const angle = Math.atan2(direction.x, direction.z);
679
+ bullet.rotation.y = angle;
680
+
681
+ // ์ง€๋ฉด ์ถฉ๋Œ ์ฒดํฌ ์ถ”๊ฐ€
682
  if (bullet.position.y <= 0) {
683
+ // ํฌ๊ณ  ํ™”๋ คํ•œ ์ง€๋ฉด ์ถฉ๋Œ ํšจ๊ณผ
684
  gameInstance.createGroundImpactEffect(bullet.position);
685
  scene.remove(bullet);
686
  this.bullets.splice(i, 1);
687
  continue;
688
  }
689
 
690
+ // 6000m ์ด์ƒ ๋‚ ์•„๊ฐ€๊ฑฐ๋‚˜ ๋†’์ด ์ œํ•œ ์ดˆ๊ณผ ์‹œ ์ œ๊ฑฐ
691
  if (bullet.position.distanceTo(bullet.startPosition) > 6000 ||
692
  bullet.position.y > GAME_CONSTANTS.MAX_ALTITUDE + 500) {
693
  scene.remove(bullet);
 
705
  const backward = new THREE.Vector3(0, 0, -1);
706
  const up = new THREE.Vector3(0, 1, 0);
707
 
708
+ backward.applyEuler(this.rotation);
709
+ up.applyEuler(this.rotation);
710
 
711
  const cameraPosition = this.position.clone()
712
  .add(backward.multiplyScalar(this.cameraDistance))
 
718
  getCameraTarget() {
719
  return this.position.clone();
720
  }
721
+
722
+ // HUD ํ‘œ์‹œ์šฉ ํ—ค๋”ฉ getter (0~360๋„)
723
+ getHeadingDegrees() {
724
+ return this.normalizeHeading(this.rotation.y);
725
+ }
726
  }
727
 
728
  // ์  ์ „ํˆฌ๊ธฐ ํด๋ž˜์Šค - ์™„์ „ํžˆ ์žฌ์„ค๊ณ„
 
1764
  const altitudeMeters = Math.round(this.fighter.altitude);
1765
  const pitchDegrees = Math.round(this.fighter.rotation.x * (180 / Math.PI));
1766
  const rollDegreesRounded = Math.round(rollDegrees);
1767
+ const headingDegrees = Math.round(fighter.getHeadingDegrees());
1768
 
1769
  // ์„ ํšŒ์œจ ๊ณ„์‚ฐ (๋„/์ดˆ)
1770
  if (!this.lastHeading) this.lastHeading = headingDegrees;