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

Update game.js

Browse files
Files changed (1) hide show
  1. game.js +205 -128
game.js CHANGED
@@ -33,18 +33,7 @@ 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
- this.targetQuaternion = new THREE.Quaternion();
40
-
41
- // ์˜ค์ผ๋Ÿฌ ๊ฐ๋„ (ํ‘œ์‹œ์šฉ)
42
- this.rotation = new THREE.Euler(0, 0, 0);
43
-
44
- // ํšŒ์ „ ์†๋„ (๋ผ๋””์•ˆ/์ดˆ)
45
- this.pitchRate = 0;
46
- this.yawRate = 0;
47
- this.rollRate = 0;
48
 
49
  // ๋น„ํ–‰ ์ œ์–ด
50
  this.throttle = 0.7; // ์ดˆ๊ธฐ ์Šค๋กœํ‹€ 70%
@@ -53,6 +42,20 @@ class Fighter {
53
  this.gForce = 1.0;
54
  this.health = GAME_CONSTANTS.MAX_HEALTH; // ์ฒด๋ ฅ 1000
55
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
56
  // ๋ฌด๊ธฐ
57
  this.missiles = GAME_CONSTANTS.MISSILE_COUNT;
58
  this.ammo = GAME_CONSTANTS.AMMO_COUNT;
@@ -180,11 +183,7 @@ class Fighter {
180
  this.mesh = result.scene;
181
  this.mesh.position.copy(this.position);
182
  this.mesh.scale.set(2, 2, 2);
183
-
184
- // ์ดˆ๊ธฐ ํšŒ์ „ ์„ค์ • (์ฟผํ„ฐ๋‹ˆ์–ธ ์‚ฌ์šฉ)
185
- const initialRotation = new THREE.Euler(0, Math.PI / 4, 0);
186
- this.quaternion.setFromEuler(initialRotation);
187
- this.mesh.quaternion.copy(this.quaternion);
188
 
189
  this.mesh.traverse((child) => {
190
  if (child.isMesh) {
@@ -243,88 +242,117 @@ class Fighter {
243
  // Over-G ์ƒํƒœ์—์„œ ์Šคํ†จ์ด ํ•ด์ œ๋˜์ง€ ์•Š์•˜์œผ๋ฉด ํ”ผ์น˜ ์กฐ์ž‘ ๋ถˆ๊ฐ€
244
  if (this.overG && this.overGTimer > 1.0 && this.stallWarning) {
245
  // ์š”(Yaw)๋งŒ ์ œํ•œ์ ์œผ๋กœ ํ—ˆ์šฉ
246
- const sensitivity = GAME_CONSTANTS.MOUSE_SENSITIVITY * 0.3;
247
- this.yawRate = deltaX * sensitivity * 0.3;
248
 
249
- // ํ”ผ์น˜์™€ ๋กค์€ ์ œํ•œ
250
- this.pitchRate = 0;
251
- this.rollRate = -this.yawRate * 5; // ๋งค์šฐ ์ œํ•œ๋œ ๋กค
 
252
 
253
- return;
254
  }
255
 
256
  const sensitivity = GAME_CONSTANTS.MOUSE_SENSITIVITY * 1.0;
257
 
258
- // ๋งˆ์šฐ์Šค ์ž…๋ ฅ์„ ํšŒ์ „ ์†๋„๋กœ ๋ณ€ํ™˜
259
- this.pitchRate = -deltaY * sensitivity * 2.0;
260
- this.yawRate = deltaX * sensitivity * 0.8 * 2.0;
 
 
261
 
262
- // ์š” ํšŒ์ „์— ๋”ฐ๋ฅธ ์ž๋™ ๋กค
263
- this.rollRate = -deltaX * sensitivity * 15;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
264
  }
265
 
266
  updateControls(keys, deltaTime) {
 
 
 
 
 
 
 
 
 
 
 
 
 
267
  // W/S: ์Šค๋กœํ‹€๋งŒ ์ œ์–ด (๊ฐ€์†/๊ฐ์†)
268
  if (keys.w) {
269
  this.throttle = Math.min(1.0, this.throttle + deltaTime * 0.5);
 
270
  }
271
  if (keys.s) {
272
  this.throttle = Math.max(0.1, this.throttle - deltaTime * 0.5);
 
273
  }
274
 
275
- // A/D: ๋ณด์กฐ ์š” ์ œ์–ด (๋Ÿฌ๋”)
276
  if (keys.a) {
277
- this.yawRate = -1.2;
278
- } else if (keys.d) {
279
- this.yawRate = 1.2;
280
- } else if (!this.isMouseMoving) {
281
- // ๋งˆ์šฐ์Šค ์ž…๋ ฅ์ด ์—†๊ณ  ํ‚ค ์ž…๋ ฅ๋„ ์—†์œผ๋ฉด ์š” ์†๋„๋ฅผ 0์œผ๋กœ
282
- this.yawRate *= 0.9;
283
  }
284
  }
285
 
286
  updatePhysics(deltaTime) {
287
  if (!this.mesh) return;
288
 
289
- // ์ฟผํ„ฐ๋‹ˆ์–ธ์„ ์‚ฌ์šฉํ•œ ํšŒ์ „ ์—…๋ฐ์ดํŠธ
290
- // ๊ฐ ์ถ•์— ๋Œ€ํ•œ ํšŒ์ „์„ ๋…๋ฆฝ์ ์œผ๋กœ ์ ์šฉ
291
-
292
- // ํ”ผ์น˜ ํšŒ์ „ (ํ•ญ๊ณต๊ธฐ์˜ ๋กœ์ปฌ X์ถ• ๊ธฐ์ค€)
293
- const pitchAxis = new THREE.Vector3(1, 0, 0);
294
- pitchAxis.applyQuaternion(this.quaternion);
295
- const pitchQuat = new THREE.Quaternion().setFromAxisAngle(pitchAxis, this.pitchRate * deltaTime);
296
 
297
- // ์š” ํšŒ์ „ (์›”๋“œ Y์ถ• ๊ธฐ์ค€)
298
- const yawAxis = new THREE.Vector3(0, 1, 0);
299
- const yawQuat = new THREE.Quaternion().setFromAxisAngle(yawAxis, this.yawRate * deltaTime);
300
 
301
- // ๋กค ํšŒ์ „ (ํ•ญ๊ณต๊ธฐ์˜ ๋กœ์ปฌ Z์ถ• ๊ธฐ์ค€)
302
- const rollAxis = new THREE.Vector3(0, 0, 1);
303
- rollAxis.applyQuaternion(this.quaternion);
304
- const rollQuat = new THREE.Quaternion().setFromAxisAngle(rollAxis, this.rollRate * deltaTime);
305
 
306
- // ํšŒ์ „ ์ ์šฉ ์ˆœ์„œ: yaw -> pitch -> roll
307
- this.quaternion.multiply(yawQuat);
308
- this.quaternion.multiply(pitchQuat);
309
- this.quaternion.multiply(rollQuat);
310
 
311
- // ์ฟผํ„ฐ๋‹ˆ์–ธ์„ ์˜ค์ผ๋Ÿฌ ๊ฐ๋„๋กœ ๋ณ€ํ™˜ (ํ‘œ์‹œ์šฉ)
312
- this.rotation.setFromQuaternion(this.quaternion);
313
 
314
- // ๋กค ์ž๋™ ๋ณต๊ท€
315
- if (Math.abs(this.yawRate) < 0.1) {
316
- this.rollRate *= 0.95;
 
317
  }
318
 
319
- // ํšŒ์ „ ์†๋„ ๊ฐ์‡ 
320
- this.pitchRate *= 0.9;
321
- if (!this.stallWarning) {
322
- this.rollRate *= 0.9;
 
 
 
323
  }
324
 
325
  // ํ˜„์‹ค์ ์ธ ์†๋„ ๊ณ„์‚ฐ
326
- const minSpeed = 0;
327
- const maxSpeed = 600;
328
  let targetSpeed = minSpeed + (maxSpeed - minSpeed) * this.throttle;
329
 
330
  // ํ”ผ์น˜ ๊ฐ๋„์— ๋”ฐ๋ฅธ ์†๋„ ๋ณ€ํ™”
@@ -332,81 +360,102 @@ class Fighter {
332
  const pitchDegrees = Math.abs(pitchAngle) * (180 / Math.PI);
333
 
334
  // ๊ธฐ์ˆ˜๊ฐ€ ์œ„๋ฅผ ํ–ฅํ•˜๊ณ  ์žˆ์„ ๊ฒฝ์šฐ ๋น ๋ฅธ ์†๋„ ๊ฐ์†Œ
335
- if (pitchAngle < -0.1 && !this.stallWarning) {
336
- const climbFactor = Math.abs(pitchAngle) / (Math.PI / 2);
337
- if (pitchDegrees > 30) {
338
- targetSpeed *= Math.max(0, 1 - climbFactor * 1.5);
339
  } else {
340
- targetSpeed *= (1 - climbFactor * 0.3);
341
  }
342
- } else if (pitchAngle > 0.1) {
343
  const diveFactor = pitchAngle / (Math.PI / 3);
344
- targetSpeed *= (1 + diveFactor * 0.4);
345
  }
346
 
347
- // G-Force ๊ณ„์‚ฐ
348
- const turnRate = Math.abs(this.yawRate) * 10;
349
- const pitchChangeRate = Math.abs(this.pitchRate) * 10;
350
 
351
- const altitudeInKm = this.position.y / 1000;
352
- const altitudeMultiplier = 1 + (altitudeInKm * 0.2);
 
353
 
 
 
354
  let throttleGMultiplier = 0;
355
  if (this.throttle > 0.5) {
 
356
  throttleGMultiplier = (this.throttle - 0.5) * 2.0;
357
  }
358
 
359
  // ๋น„์ •์ƒ์ ์ธ ์ž์„ธ์— ์˜ํ•œ G-Force ์ถ”๊ฐ€
360
  let abnormalG = 0;
361
 
362
- const rollDegrees = Math.abs(this.rotation.z) * (180 / Math.PI);
363
  const isInverted = Math.abs(this.rotation.z) > Math.PI / 2;
364
-
365
  if (isInverted) {
366
  const baseG = 3.0 + Math.abs(Math.abs(this.rotation.z) - Math.PI / 2) * 2;
367
- abnormalG += baseG * altitudeMultiplier * (1 + throttleGMultiplier);
368
  }
369
 
 
 
370
  if (rollDegrees > 30) {
 
371
  const extremeRollG = (rollDegrees - 30) * 0.1;
372
  abnormalG += extremeRollG * altitudeMultiplier * (1 + throttleGMultiplier);
373
  }
374
 
 
375
  if (pitchDegrees >= 40) {
376
- const extremePitchG = (pitchDegrees - 40) * 0.15;
 
377
  abnormalG += extremePitchG * altitudeMultiplier * (1 + throttleGMultiplier);
378
  }
379
 
 
380
  if (pitchAngle < -Math.PI / 6) {
381
  const baseG = 2.0 + Math.abs(pitchAngle + Math.PI / 6) * 3;
382
- abnormalG += baseG * altitudeMultiplier * (1 + throttleGMultiplier);
383
  }
384
 
 
385
  if (pitchAngle > Math.PI / 3) {
386
  const baseG = 2.0 + Math.abs(pitchAngle - Math.PI / 3) * 3;
387
- abnormalG += baseG * altitudeMultiplier * (1 + throttleGMultiplier);
388
  }
389
 
390
- const maneuverG = (turnRate + pitchChangeRate + (Math.abs(this.rotation.z) * 3)) * (1 + throttleGMultiplier * 0.5);
 
391
 
 
392
  this.gForce = 1.0 + maneuverG + abnormalG;
393
 
394
- // G-Force ํšŒ๋ณต
 
 
 
395
  const isPitchNeutral = Math.abs(pitchDegrees) <= 10;
396
 
397
  if (!this.overG && isPitchNeutral && !isInverted && !this.stallWarning) {
398
- const recoveryRate = 2.0 - throttleGMultiplier * 1.5;
 
399
  this.gForce = THREE.MathUtils.lerp(this.gForce, 1.0 + maneuverG, deltaTime * recoveryRate);
400
  } else if (this.overG) {
 
401
  if (!isPitchNeutral || this.stallWarning) {
 
402
  this.gForce = Math.max(this.gForce, 1.0 + maneuverG + abnormalG);
403
  } else {
404
- const overGRecoveryRate = 0.3 - throttleGMultiplier * 0.2;
 
405
  this.gForce = THREE.MathUtils.lerp(this.gForce, 1.0 + maneuverG, deltaTime * overGRecoveryRate);
406
  }
407
  }
408
 
 
409
  if (this.stallWarning && this.overG) {
 
410
  this.gForce = Math.max(this.gForce, 9.0);
411
  }
412
 
@@ -416,75 +465,89 @@ class Fighter {
416
  if (this.overG) {
417
  this.overGTimer += deltaTime;
418
 
 
419
  if (this.overGTimer > 1.5) {
 
420
  if (this.overGTimer > 2.5) {
421
  targetSpeed *= Math.max(0.3, 1 - (this.overGTimer - 2.5) * 0.3);
422
  }
 
423
  }
424
  } else {
425
- this.overGTimer = 0;
426
  }
427
 
428
- // ์Šคํ†จ ๊ฒฝ๊ณ 
429
- const speedKnots = this.speed * 1.94384;
 
430
 
 
431
  if (!this.stallWarning && speedKnots < GAME_CONSTANTS.STALL_SPEED) {
432
  this.stallWarning = true;
433
- this.stallEscapeProgress = 0;
434
  }
435
 
 
436
  if (this.stallWarning) {
437
  if (this.escapeKeyPressed) {
 
438
  this.stallEscapeProgress += deltaTime;
439
 
 
440
  if (this.stallEscapeProgress >= 2.0 && speedKnots >= 350) {
441
  this.stallWarning = false;
442
  this.stallEscapeProgress = 0;
443
- this.pitchRate = 0;
 
444
  }
445
  } else {
 
446
  this.stallEscapeProgress = Math.max(0, this.stallEscapeProgress - deltaTime * 2);
447
  }
448
  }
449
 
450
  // ์†๋„ ๋ณ€ํ™” ์ ์šฉ
451
  if (this.stallWarning) {
452
- if (pitchAngle > 0.1) {
453
- const diveSpeedGain = Math.min(pitchAngle * 300, 200);
 
 
454
  this.speed = Math.min(maxSpeed, this.speed + diveSpeedGain * deltaTime);
455
  } else {
 
456
  this.speed = Math.max(0, this.speed - deltaTime * 100);
457
  }
458
  } else {
 
459
  this.speed = THREE.MathUtils.lerp(this.speed, targetSpeed, deltaTime * 0.5);
460
  }
461
 
462
  // ์Šคํ†จ ์ƒํƒœ์—์„œ์˜ ๋ฌผ๋ฆฌ ํšจ๊ณผ
463
  if (this.stallWarning) {
464
- this.pitchRate = 2.5;
465
-
466
- // ์กฐ์ข… ๋ถˆ๋Šฅ ์ƒํƒœ - ํ”๋“ค๋ฆผ ํšจ๊ณผ๋ฅผ ์ฟผํ„ฐ๋‹ˆ์–ธ์œผ๋กœ
467
- const randomAxis = new THREE.Vector3(
468
- (Math.random() - 0.5),
469
- (Math.random() - 0.5),
470
- (Math.random() - 0.5)
471
- ).normalize();
472
- const randomQuat = new THREE.Quaternion().setFromAxisAngle(randomAxis, deltaTime * 0.8);
473
- this.quaternion.multiply(randomQuat);
474
-
475
- const gravityAcceleration = GAME_CONSTANTS.GRAVITY * deltaTime * 3.0;
476
  this.velocity.y -= gravityAcceleration;
477
  }
478
 
479
- // ์†๋„ ๋ฒกํ„ฐ ๊ณ„์‚ฐ (์ฟผํ„ฐ๋‹ˆ์–ธ ์‚ฌ์šฉ)
480
  const noseDirection = new THREE.Vector3(0, 0, 1);
481
- noseDirection.applyQuaternion(this.quaternion);
482
 
483
  if (!this.stallWarning) {
 
484
  this.velocity = noseDirection.multiplyScalar(this.speed);
485
  } else {
486
- this.velocity.x = noseDirection.x * this.speed * 0.5;
 
487
  this.velocity.z = noseDirection.z * this.speed * 0.5;
 
488
  }
489
 
490
  // ์ •์ƒ ๋น„ํ–‰ ์‹œ ์ค‘๋ ฅ ํšจ๊ณผ
@@ -492,6 +555,7 @@ class Fighter {
492
  const gravityEffect = GAME_CONSTANTS.GRAVITY * deltaTime * 0.15;
493
  this.velocity.y -= gravityEffect;
494
 
 
495
  const liftFactor = (this.speed / maxSpeed) * 0.8;
496
  const lift = gravityEffect * liftFactor;
497
  this.velocity.y += lift;
@@ -505,6 +569,7 @@ class Fighter {
505
  this.position.y = GAME_CONSTANTS.MIN_ALTITUDE;
506
  this.health = 0;
507
 
 
508
  if (window.gameInstance) {
509
  window.gameInstance.createExplosionEffect(this.position);
510
  }
@@ -530,11 +595,10 @@ class Fighter {
530
 
531
  // ๋ฉ”์‹œ ์œ„์น˜ ๋ฐ ํšŒ์ „ ์—…๋ฐ์ดํŠธ
532
  this.mesh.position.copy(this.position);
533
- this.mesh.quaternion.copy(this.quaternion);
534
-
535
- // ๋ชจ๋ธ์˜ ๋ฐฉํ–ฅ ๋ณด์ • (F-15 ๋ชจ๋ธ์ด Y์ถ•์œผ๋กœ ํšŒ์ „๋˜์–ด ์žˆ์Œ)
536
- const modelOffset = new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(0, 1, 0), 3 * Math.PI / 2);
537
- this.mesh.quaternion.multiply(modelOffset);
538
 
539
  // ๊ฒฝ๊ณ  ๊นœ๋นก์ž„ ํƒ€์ด๋จธ
540
  this.warningBlinkTimer += deltaTime;
@@ -546,21 +610,23 @@ class Fighter {
546
  // ๊ณ ๋„ ๊ณ„์‚ฐ
547
  this.altitude = this.position.y;
548
 
549
- // ๊ฒฝ๊ณ ์Œ ์—…๋ฐ์ดํŠธ
550
  this.updateWarningAudios();
551
 
552
  // ์—”์ง„ ์†Œ๋ฆฌ ๋ณผ๋ฅจ์„ ์Šค๋กœํ‹€์— ์—ฐ๋™
553
  if (this.warningAudios.normal && !this.warningAudios.normal.paused) {
554
- this.warningAudios.normal.volume = 0.3 + this.throttle * 0.4;
555
  }
556
  }
557
 
558
  shoot(scene) {
 
559
  if (this.ammo <= 0) return;
560
 
561
  this.ammo--;
562
 
563
- const bulletGeometry = new THREE.CylinderGeometry(1.0, 1.0, 16, 8);
 
564
  const bulletMaterial = new THREE.MeshBasicMaterial({
565
  color: 0xffff00,
566
  emissive: 0xffff00,
@@ -568,35 +634,38 @@ class Fighter {
568
  });
569
  const bullet = new THREE.Mesh(bulletGeometry, bulletMaterial);
570
 
571
- // ๊ธฐ์ˆ˜ ๋์—์„œ ๋ฐœ์‚ฌ (์ฟผํ„ฐ๋‹ˆ์–ธ ์‚ฌ์šฉ)
572
  const muzzleOffset = new THREE.Vector3(0, 0, 10);
573
- muzzleOffset.applyQuaternion(this.quaternion);
574
  bullet.position.copy(this.position).add(muzzleOffset);
575
 
576
- // ํƒ„ํ™˜ ๋ฐฉํ–ฅ ์„ค์ •
577
- bullet.quaternion.copy(this.quaternion);
578
- const bulletRotation = new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(1, 0, 0), Math.PI / 2);
579
- bullet.quaternion.multiply(bulletRotation);
580
 
 
581
  bullet.startPosition = bullet.position.clone();
582
 
583
- const bulletSpeed = 1500;
584
  const direction = new THREE.Vector3(0, 0, 1);
585
- direction.applyQuaternion(this.quaternion);
586
  bullet.velocity = direction.multiplyScalar(bulletSpeed);
587
 
588
  scene.add(bullet);
589
  this.bullets.push(bullet);
590
 
 
591
  try {
592
  const audio = new Audio('sounds/m134.ogg');
593
- audio.volume = 0.15;
594
 
595
- const randomPitch = 0.8 + Math.random() * 0.4;
 
596
  audio.playbackRate = randomPitch;
597
 
598
  audio.play().catch(e => console.log('Gunfire sound failed to play'));
599
 
 
600
  audio.addEventListener('ended', () => {
601
  audio.remove();
602
  });
@@ -610,13 +679,21 @@ class Fighter {
610
  const bullet = this.bullets[i];
611
  bullet.position.add(bullet.velocity.clone().multiplyScalar(deltaTime));
612
 
 
 
 
 
 
 
613
  if (bullet.position.y <= 0) {
 
614
  gameInstance.createGroundImpactEffect(bullet.position);
615
  scene.remove(bullet);
616
  this.bullets.splice(i, 1);
617
  continue;
618
  }
619
 
 
620
  if (bullet.position.distanceTo(bullet.startPosition) > 6000 ||
621
  bullet.position.y > GAME_CONSTANTS.MAX_ALTITUDE + 500) {
622
  scene.remove(bullet);
@@ -634,8 +711,8 @@ class Fighter {
634
  const backward = new THREE.Vector3(0, 0, -1);
635
  const up = new THREE.Vector3(0, 1, 0);
636
 
637
- backward.applyQuaternion(this.quaternion);
638
- up.applyQuaternion(this.quaternion);
639
 
640
  const cameraPosition = this.position.clone()
641
  .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
+ this.rotation = new THREE.Euler(0, 0, 0, 'YXZ'); // ์˜ค์ผ๋Ÿฌ ๊ฐ๋„ ์ˆœ์„œ๋ฅผ YXZ๋กœ ๋ณ€๊ฒฝํ•˜์—ฌ ์ง๋ฒŒ ๋ฝ ๋ฐฉ์ง€
 
 
 
 
 
 
 
 
 
 
 
37
 
38
  // ๋น„ํ–‰ ์ œ์–ด
39
  this.throttle = 0.7; // ์ดˆ๊ธฐ ์Šค๋กœํ‹€ 70%
 
42
  this.gForce = 1.0;
43
  this.health = GAME_CONSTANTS.MAX_HEALTH; // ์ฒด๋ ฅ 1000
44
 
45
+ // ์กฐ์ข… ์ž…๋ ฅ ์‹œ์Šคํ…œ
46
+ this.pitchInput = 0;
47
+ this.rollInput = 0;
48
+ this.yawInput = 0;
49
+
50
+ // ๋งˆ์šฐ์Šค ๋ˆ„์  ์ž…๋ ฅ
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
  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
  // 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
  // ํ”ผ์น˜ ๊ฐ๋„์— ๋”ฐ๋ฅธ ์†๋„ ๋ณ€ํ™”
 
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
 
 
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
  // ์ •์ƒ ๋น„ํ–‰ ์‹œ ์ค‘๋ ฅ ํšจ๊ณผ
 
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
  this.position.y = GAME_CONSTANTS.MIN_ALTITUDE;
570
  this.health = 0;
571
 
572
+ // ์ง€๋ฉด ์ถฉ๋Œ ์‹œ ํญ๋ฐœ ํšจ๊ณผ ์ถ”๊ฐ€
573
  if (window.gameInstance) {
574
  window.gameInstance.createExplosionEffect(this.position);
575
  }
 
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;
 
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
  });
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
  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
  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))