cutechicken commited on
Commit
4b16f5f
ยท
verified ยท
1 Parent(s): 312c87c

Update game.js

Browse files
Files changed (1) hide show
  1. game.js +479 -417
game.js CHANGED
@@ -19,7 +19,7 @@ const GAME_CONSTANTS = {
19
  ENEMY_COUNT: 4,
20
  MISSILE_COUNT: 6,
21
  AMMO_COUNT: 940, // 940๋ฐœ๋กœ ๋ณ€๊ฒฝ
22
- BULLET_DAMAGE: 50, // ๋ฐœ๋‹น 25 ๋ฐ๋ฏธ์ง€
23
  MAX_HEALTH: 1000 // ์ฒด๋ ฅ 1000
24
  };
25
 
@@ -311,427 +311,489 @@ class Fighter {
311
  this.targetYaw += deltaTime * 1.2;
312
  }
313
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
314
 
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
-
537
- // ํšŒ์ „ ๋งคํŠธ๋ฆญ์Šค๋ฅผ ์ง์ ‘ ๊ตฌ์„ฑํ•˜์—ฌ ์ ์šฉ
538
- const rotationMatrix = new THREE.Matrix4();
539
- rotationMatrix.makeRotationFromEuler(new THREE.Euler(this.rotation.x, this.rotation.y, this.rotation.z, 'YXZ'));
540
- noseDirection.applyMatrix4(rotationMatrix);
541
-
542
- if (!this.stallWarning) {
543
- // ์ •์ƒ ๋น„ํ–‰ ์‹œ
544
- this.velocity = noseDirection.multiplyScalar(this.speed);
545
- } else {
546
- // ์Šคํ†จ ์‹œ์—๋Š” ์ค‘๋ ฅ์ด ์ฃผ๋„์ ์ด์ง€๋งŒ ๋‹ค์ด๋น™ ์†๋„๋„ ๋ฐ˜์˜
547
- this.velocity.x = noseDirection.x * this.speed * 0.5; // ์ „๋ฐฉ ์†๋„ ์ฆ๊ฐ€
548
- this.velocity.z = noseDirection.z * this.speed * 0.5;
549
- // y ์†๋„๋Š” ์œ„์—์„œ ์ค‘๋ ฅ์œผ๋กœ ์ฒ˜๋ฆฌ๋จ
550
- }
551
-
552
- // ์ •์ƒ ๋น„ํ–‰ ์‹œ ์ค‘๋ ฅ ํšจ๊ณผ
553
- if (!this.stallWarning) {
554
- const gravityEffect = GAME_CONSTANTS.GRAVITY * deltaTime * 0.15;
555
- this.velocity.y -= gravityEffect;
556
-
557
- // ์–‘๋ ฅ ํšจ๊ณผ (์†๋„์— ๋น„๋ก€)
558
- const liftFactor = (this.speed / maxSpeed) * 0.8;
559
- const lift = gravityEffect * liftFactor;
560
- this.velocity.y += lift;
561
- }
562
-
563
- // ์œ„์น˜ ์—…๋ฐ์ดํŠธ
564
- this.position.add(this.velocity.clone().multiplyScalar(deltaTime));
565
-
566
- // ์ง€๋ฉด ์ถฉ๋Œ
567
- if (this.position.y <= GAME_CONSTANTS.MIN_ALTITUDE) {
568
- this.position.y = GAME_CONSTANTS.MIN_ALTITUDE;
569
- this.health = 0;
570
-
571
- // ์ง€๋ฉด ์ถฉ๋Œ ์‹œ ํญ๋ฐœ ํšจ๊ณผ ์ถ”๊ฐ€
572
- if (window.gameInstance) {
573
- window.gameInstance.createExplosionEffect(this.position);
574
- }
575
-
576
- return;
577
- }
578
-
579
- // ์ตœ๋Œ€ ๊ณ ๋„ ์ œํ•œ
580
- if (this.position.y > GAME_CONSTANTS.MAX_ALTITUDE) {
581
- this.position.y = GAME_CONSTANTS.MAX_ALTITUDE;
582
- this.altitudeWarning = true;
583
- if (this.velocity.y > 0) this.velocity.y = 0;
584
- } else {
585
- this.altitudeWarning = false;
586
- }
587
-
588
- // ๋งต ๊ฒฝ๊ณ„ ์ฒ˜๋ฆฌ
589
- const mapLimit = GAME_CONSTANTS.MAP_SIZE / 2;
590
- if (this.position.x > mapLimit) this.position.x = -mapLimit;
591
- if (this.position.x < -mapLimit) this.position.x = mapLimit;
592
- if (this.position.z > mapLimit) this.position.z = -mapLimit;
593
- if (this.position.z < -mapLimit) this.position.z = mapLimit;
594
-
595
- // ๋ฉ”์‹œ ์œ„์น˜ ๋ฐ ํšŒ์ „ ์—…๋ฐ์ดํŠธ
596
- this.mesh.position.copy(this.position);
597
- this.mesh.rotation.x = this.rotation.x;
598
- this.mesh.rotation.y = this.rotation.y + 3 * Math.PI / 2;
599
- this.mesh.rotation.z = this.rotation.z;
600
-
601
- // ๊ฒฝ๊ณ  ๊นœ๋นก์ž„ ํƒ€์ด๋จธ
602
- this.warningBlinkTimer += deltaTime;
603
- if (this.warningBlinkTimer >= 1.0) {
604
- this.warningBlinkTimer = 0;
605
- this.warningBlinkState = !this.warningBlinkState;
606
- }
607
-
608
- // ๊ณ ๋„ ๊ณ„์‚ฐ
609
- this.altitude = this.position.y;
610
-
611
- // ๊ฒฝ๊ณ ์Œ ์—…๋ฐ์ดํŠธ (์—”์ง„ ์†Œ๋ฆฌ๋Š” ๊ณ„์† ์œ ์ง€)
612
- this.updateWarningAudios();
613
-
614
- // ์—”์ง„ ์†Œ๋ฆฌ ๋ณผ๋ฅจ์„ ์Šค๋กœํ‹€์— ์—ฐ๋™
615
- if (this.warningAudios.normal && !this.warningAudios.normal.paused) {
616
- this.warningAudios.normal.volume = 0.3 + this.throttle * 0.4; // 0.3~0.7
617
- }
618
- }
619
-
620
- shoot(scene) {
621
- // ํƒ„์•ฝ์ด ์—†์œผ๋ฉด ๋ฐœ์‚ฌํ•˜์ง€ ์•Š์Œ
622
- if (this.ammo <= 0) return;
623
-
624
- this.ammo--;
625
-
626
- // ์ง์„  ๋ชจ์–‘์˜ ํƒ„ํ™˜ (100% ๋” ํฌ๊ฒŒ)
627
- const bulletGeometry = new THREE.CylinderGeometry(1.0, 1.0, 16, 8); // ๋ฐ˜์ง€๋ฆ„ 0.75โ†’1.0, ๊ธธ์ด 12โ†’16
628
- const bulletMaterial = new THREE.MeshBasicMaterial({
629
- color: 0xffff00,
630
- emissive: 0xffff00,
631
- emissiveIntensity: 1.0
632
- });
633
- const bullet = new THREE.Mesh(bulletGeometry, bulletMaterial);
634
-
635
- // ๊ธฐ์ˆ˜ ๋์—์„œ ๋ฐœ์‚ฌ (๋” ์•ž์ชฝ์—์„œ)
636
- const muzzleOffset = new THREE.Vector3(0, 0, 10);
637
- const rotationMatrix = new THREE.Matrix4();
638
- rotationMatrix.makeRotationFromEuler(new THREE.Euler(this.rotation.x, this.rotation.y, this.rotation.z, 'YXZ'));
639
- muzzleOffset.applyMatrix4(rotationMatrix);
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.applyMatrix4(rotationMatrix);
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
- });
672
- } catch (e) {
673
- console.log('Audio error:', e);
674
- }
675
- }
676
 
677
- updateBullets(scene, deltaTime, gameInstance) {
678
- for (let i = this.bullets.length - 1; i >= 0; i--) {
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);
700
- this.bullets.splice(i, 1);
701
- }
702
- }
703
- }
704
 
705
- takeDamage(damage) {
706
- this.health -= damage;
707
- return this.health <= 0;
708
- }
709
 
710
- getCameraPosition() {
711
- const backward = new THREE.Vector3(0, 0, -1);
712
- const up = new THREE.Vector3(0, 1, 0);
713
-
714
- const rotationMatrix = new THREE.Matrix4();
715
- rotationMatrix.makeRotationFromEuler(new THREE.Euler(this.rotation.x, this.rotation.y, this.rotation.z, 'YXZ'));
716
-
717
- backward.applyMatrix4(rotationMatrix);
718
- up.applyMatrix4(rotationMatrix);
719
-
720
- const cameraPosition = this.position.clone()
721
- .add(backward.multiplyScalar(this.cameraDistance))
722
- .add(up.multiplyScalar(this.cameraHeight));
723
-
724
- return cameraPosition;
725
- }
 
 
 
 
 
 
 
 
 
 
 
726
 
727
- getCameraTarget() {
728
- return this.position.clone();
729
- }
730
-
731
- // HUD ํ‘œ์‹œ์šฉ ํ—ค๋”ฉ getter (0~360๋„)
732
- getHeadingDegrees() {
733
- return this.normalizeHeading(this.rotation.y);
734
- }
735
  }
736
 
737
  // ์  ์ „ํˆฌ๊ธฐ ํด๋ž˜์Šค - ์™„์ „ํžˆ ์žฌ์„ค๊ณ„
 
19
  ENEMY_COUNT: 4,
20
  MISSILE_COUNT: 6,
21
  AMMO_COUNT: 940, // 940๋ฐœ๋กœ ๋ณ€๊ฒฝ
22
+ BULLET_DAMAGE: 50, // ๋ฐœ๋‹น 50 ๋ฐ๋ฏธ์ง€
23
  MAX_HEALTH: 1000 // ์ฒด๋ ฅ 1000
24
  };
25
 
 
311
  this.targetYaw += deltaTime * 1.2;
312
  }
313
  }
314
+ updatePhysics(deltaTime) {
315
+ if (!this.mesh) return;
316
+
317
+ // ๋ถ€๋“œ๋Ÿฌ์šด ํšŒ์ „ ๋ณด๊ฐ„
318
+ const rotationSpeed = deltaTime * 2.0;
319
+ const yawRotationSpeed = deltaTime * 3.0;
320
+
321
+ // ํ”ผ์น˜์™€ ๋กค์€ ์ง์ ‘ ๋ณด๊ฐ„
322
+ this.rotation.x = THREE.MathUtils.lerp(this.rotation.x, this.targetPitch, rotationSpeed);
323
+ this.rotation.z = THREE.MathUtils.lerp(this.rotation.z, this.targetRoll, rotationSpeed * 1.5);
324
+
325
+ // Yaw ๋ณด๊ฐ„ (์ตœ๋‹จ ๊ฒฝ๋กœ)
326
+ let yawDiff = this.normalizeAngle(this.targetYaw - this.rotation.y);
327
+ this.rotation.y += yawDiff * yawRotationSpeed;
328
+
329
+ // rotation.y๋ฅผ -ฯ€ ~ ฯ€ ๋ฒ”์œ„๋กœ ์œ ์ง€
330
+ this.rotation.y = this.normalizeAngle(this.rotation.y);
331
+ this.targetYaw = this.normalizeAngle(this.targetYaw);
332
+
333
+ // ๋กค ์ž๋™ ๋ณต๊ท€ ์‹œ์Šคํ…œ
334
+ if (Math.abs(yawDiff) < 0.05) {
335
+ // ์š” ํšŒ์ „์ด ๊ฑฐ์˜ ์—†์„ ๋•Œ ๋กค์„ 0์œผ๋กœ ๋ณต๊ท€
336
+ this.targetRoll *= 0.95;
337
+ }
338
+
339
+ // ์›Œ์ฌ๋” ์Šคํƒ€์ผ: ์š” ํšŒ์ „์ด ์ฃผ๋„์ , ๋กค์€ ๋ณด์กฐ์ 
340
+ let bankTurnRate = 0;
341
+ if (Math.abs(this.rotation.z) > 0.3) { // ๋กค์ด ์ถฉ๋ถ„ํžˆ ํด ๋•Œ๋งŒ
342
+ const bankAngle = this.rotation.z;
343
+ bankTurnRate = Math.sin(bankAngle) * deltaTime * 0.1; // ๋งค์šฐ ์ž‘์€ ์„ ํšŒ์œจ
344
+ this.targetYaw += bankTurnRate;
345
+ }
346
+
347
+ // ํ˜„์‹ค์ ์ธ ์†๋„ ๊ณ„์‚ฐ
348
+ const minSpeed = 0; // ์ตœ์†Œ ์†๋„ 0kt
349
+ const maxSpeed = 600; // ์ตœ๋Œ€ ์†๋„ 600kt
350
+ let targetSpeed = minSpeed + (maxSpeed - minSpeed) * this.throttle;
351
+
352
+ // ํ”ผ์น˜ ๊ฐ๋„์— ๋”ฐ๋ฅธ ์†๋„ ๋ณ€ํ™”
353
+ const pitchAngle = this.rotation.x;
354
+ const pitchDegrees = Math.abs(pitchAngle) * (180 / Math.PI);
355
+
356
+ // ๊ธฐ์ˆ˜๊ฐ€ ์œ„๋ฅผ ํ–ฅํ•˜๊ณ  ์žˆ์„ ๊ฒฝ์šฐ ๋น ๋ฅธ ์†๋„ ๊ฐ์†Œ
357
+ if (pitchAngle < -0.1 && !this.stallWarning) { // ์Šคํ†จ์ด ์•„๋‹ ๋•Œ๋งŒ ์ƒ์Šน์œผ๋กœ ์ธํ•œ ๊ฐ์†
358
+ const climbFactor = Math.abs(pitchAngle) / (Math.PI / 2); // 90๋„ ๊ธฐ์ค€
359
+ if (pitchDegrees > 30) { // 30๋„ ์ด์ƒ์ผ ๋•Œ ๊ธ‰๊ฒฉํ•œ ๊ฐ์†
360
+ targetSpeed *= Math.max(0, 1 - climbFactor * 1.5); // ์ตœ๋Œ€ 150% ๊ฐ์† (0kt๊นŒ์ง€)
361
+ } else {
362
+ targetSpeed *= (1 - climbFactor * 0.3); // ์ •์ƒ์ ์ธ ๊ฐ์†
363
+ }
364
+ } else if (pitchAngle > 0.1) { // ๊ธฐ์ˆ˜๊ฐ€ ์•„๋ž˜๋กœ (ํ•˜๊ฐ•) - ์Šคํ†จ ์ƒํƒœ์—์„œ๋„ ์ ์šฉ
365
+ const diveFactor = pitchAngle / (Math.PI / 3);
366
+ targetSpeed *= (1 + diveFactor * 0.4); // ํ•˜๊ฐ• ์‹œ ๊ฐ€์† ์ฆ๊ฐ€ (0.2 -> 0.4)
367
+ }
368
+
369
+ // G-Force ๊ณ„์‚ฐ ๊ฐœ์„ 
370
+ const turnRate = Math.abs(bankTurnRate) * 100;
371
+ const pitchRate = Math.abs(this.rotation.x - this.targetPitch) * 10;
372
+
373
+ // ๊ณ ๋„์— ๋”ฐ๋ฅธ G-Force ์ฆ๊ฐ€ ๋ฐฐ์œจ ๊ณ„์‚ฐ
374
+ const altitudeInKm = this.position.y / 1000; // ๋ฏธํ„ฐ๋ฅผ ํ‚ฌ๋กœ๋ฏธํ„ฐ๋กœ ๋ณ€ํ™˜
375
+ const altitudeMultiplier = 1 + (altitudeInKm * 0.2); // 1km๋‹น 20% ์ฆ๊ฐ€
376
+
377
+ // ์Šค๋กœํ‹€์— ๋”ฐ๋ฅธ G-Force ์ฆ๊ฐ€ ๋ฐฐ์œจ ๊ณ„์‚ฐ
378
+ // THR 50% ์ดํ•˜: 0๋ฐฐ, THR 75%: 0.5๋ฐฐ, THR 100%: 1.0๋ฐฐ
379
+ let throttleGMultiplier = 0;
380
+ if (this.throttle > 0.5) {
381
+ // 0.5 ~ 1.0 ๋ฒ”์œ„๋ฅผ 0 ~ 1.0์œผ๋กœ ๋งคํ•‘
382
+ throttleGMultiplier = (this.throttle - 0.5) * 2.0;
383
+ }
384
+
385
+ // ๋น„์ •์ƒ์ ์ธ ์ž์„ธ์— ์˜ํ•œ G-Force ์ถ”๊ฐ€
386
+ let abnormalG = 0;
387
+
388
+ // ๋’ค์ง‘ํžŒ ์ƒํƒœ (๋กค์ด 90๋„ ์ด์ƒ)
389
+ const isInverted = Math.abs(this.rotation.z) > Math.PI / 2;
390
+ if (isInverted) {
391
+ const baseG = 3.0 + Math.abs(Math.abs(this.rotation.z) - Math.PI / 2) * 2;
392
+ abnormalG += baseG * altitudeMultiplier * (1 + throttleGMultiplier); // ์Šค๋กœํ‹€ ๋ฐฐ์œจ ์ถ”๊ฐ€
393
+ }
394
+
395
+ // ๋กค์ด ยฑ30๋„ ์ด์ƒ์ผ ๋•Œ ์ถ”๊ฐ€ G-Force
396
+ const rollDegrees = Math.abs(this.rotation.z) * (180 / Math.PI);
397
+ if (rollDegrees > 30) {
398
+ // 30๋„ ์ดˆ๊ณผ๋ถ„๋‹น 0.1G ์ถ”๊ฐ€
399
+ const extremeRollG = (rollDegrees - 30) * 0.1;
400
+ abnormalG += extremeRollG * altitudeMultiplier * (1 + throttleGMultiplier);
401
+ }
402
+
403
+ // ํ”ผ์น˜ ๊ฐ๋„๊ฐ€ ยฑ40๋„ ์ด์ƒ์ผ ๋•Œ ์ถ”๊ฐ€ G-Force
404
+ if (pitchDegrees >= 40) {
405
+ // 40๋„ ์ด์ƒ์ผ ๋•Œ ๊ธ‰๊ฒฉํ•œ G-Force ์ฆ๊ฐ€
406
+ const extremePitchG = (pitchDegrees - 40) * 0.15; // 40๋„ ์ดˆ๊ณผ๋ถ„๋‹น 0.15G ์ถ”๊ฐ€
407
+ abnormalG += extremePitchG * altitudeMultiplier * (1 + throttleGMultiplier);
408
+ }
409
+
410
+ // ๊ธฐ์ˆ˜๊ฐ€ ๊ณ„์† ์œ„๋ฅผ ํ–ฅํ•˜๊ณ  ์žˆ๋Š” ๊ฒฝ์šฐ (ํ”ผ์น˜๊ฐ€ -30๋„ ์ดํ•˜)
411
+ if (pitchAngle < -Math.PI / 6) {
412
+ const baseG = 2.0 + Math.abs(pitchAngle + Math.PI / 6) * 3;
413
+ abnormalG += baseG * altitudeMultiplier * (1 + throttleGMultiplier); // ์Šค๋กœํ‹€ ๋ฐฐ์œจ ์ถ”๊ฐ€
414
+ }
415
+
416
+ // ๊ธฐ์ˆ˜๊ฐ€ ๊ณผ๋„ํ•˜๊ฒŒ ์•„๋ž˜๋ฅผ ํ–ฅํ•˜๊ณ  ์žˆ๋Š” ๊ฒฝ์šฐ (ํ”ผ์น˜๊ฐ€ 60๋„ ์ด์ƒ)
417
+ if (pitchAngle > Math.PI / 3) {
418
+ const baseG = 2.0 + Math.abs(pitchAngle - Math.PI / 3) * 3;
419
+ abnormalG += baseG * altitudeMultiplier * (1 + throttleGMultiplier); // ์Šค๋กœํ‹€ ๋ฐฐ์œจ ์ถ”๊ฐ€
420
+ }
421
+
422
+ // ๊ธ‰๊ฒฉํ•œ ๊ธฐ๋™์— ์˜ํ•œ G-Force
423
+ const maneuverG = (turnRate + pitchRate + (Math.abs(this.rotation.z) * 3)) * (1 + throttleGMultiplier * 0.5);
424
+
425
+ // ์ด G-Force ๊ณ„์‚ฐ
426
+ this.gForce = 1.0 + maneuverG + abnormalG;
427
+
428
+ // G-Force ํšŒ๋ณต ์กฐ๊ฑด ์ˆ˜์ •
429
+ // 1. Over-G ์ƒํƒœ๊ฐ€ ์•„๋‹Œ ๊ฒฝ์šฐ์—๋งŒ ํšŒ๋ณต
430
+ // 2. ํ”ผ์น˜๊ฐ€ ยฑ10๋„ ์ด๋‚ด์ผ ๋•Œ๋งŒ ํšŒ๋ณต
431
+ // 3. ์Šคํ†จ ์ƒํƒœ๊ฐ€ ์•„๋‹ ๋•Œ๋งŒ ํšŒ๋ณต
432
+ const isPitchNeutral = Math.abs(pitchDegrees) <= 10;
433
+
434
+ if (!this.overG && isPitchNeutral && !isInverted && !this.stallWarning) {
435
+ // ์Šค๋กœํ‹€์ด ๋†’์„์ˆ˜๋ก G-Force๊ฐ€ ์ฒœ์ฒœํžˆ ๊ฐ์†Œ
436
+ const recoveryRate = 2.0 - throttleGMultiplier * 1.5; // THR 100%์ผ ๋•Œ 0.5, THR 50%์ผ ๋•Œ 2.0
437
+ this.gForce = THREE.MathUtils.lerp(this.gForce, 1.0 + maneuverG, deltaTime * recoveryRate);
438
+ } else if (this.overG) {
439
+ // Over-G ์ƒํƒœ์—์„œ๋Š” ํ”ผ์น˜๊ฐ€ 0๋„ ๊ทผ์ฒ˜(ยฑ10๋„)๊ฐ€ ๋˜๊ณ  ์Šคํ†จ์ด ํšŒ๋ณต๋  ๋•Œ๊นŒ์ง€ ํšŒ๋ณตํ•˜์ง€ ์•Š์Œ
440
+ if (!isPitchNeutral || this.stallWarning) {
441
+ // ํ”ผ์น˜๊ฐ€ ์ค‘๋ฆฝ์ด ์•„๋‹ˆ๊ฑฐ๋‚˜ ์Šคํ†จ ์ƒํƒœ๋ฉด G-Force ์œ ์ง€ ๋˜๋Š” ์ฆ๊ฐ€๋งŒ ๊ฐ€๋Šฅ
442
+ this.gForce = Math.max(this.gForce, 1.0 + maneuverG + abnormalG);
443
+ } else {
444
+ // ํ”ผ์น˜๊ฐ€ ์ค‘๋ฆฝ์ด๊ณ  ์Šคํ†จ์ด ์•„๋‹ ๋•Œ๋งŒ ๋งค์šฐ ์ฒœ์ฒœํžˆ ํšŒ๋ณต
445
+ const overGRecoveryRate = 0.3 - throttleGMultiplier * 0.2; // Over-G ์ƒํƒœ์—์„œ๋Š” ๋” ๋А๋ฆฐ ํšŒ๋ณต
446
+ this.gForce = THREE.MathUtils.lerp(this.gForce, 1.0 + maneuverG, deltaTime * overGRecoveryRate);
447
+ }
448
+ }
449
+
450
+ // ์Šคํ†จ ์ƒํƒœ์—์„œ๋Š” Over-G๊ฐ€ ๊ฐ์†Œํ•˜์ง€ ์•Š๋„๋ก ์ถ”๊ฐ€ ์ฒ˜๋ฆฌ
451
+ if (this.stallWarning && this.overG) {
452
+ // ์Šคํ†จ ์ค‘์—๋Š” G-Force๋ฅผ 9.0 ์ด์ƒ์œผ๋กœ ์œ ์ง€
453
+ this.gForce = Math.max(this.gForce, 9.0);
454
+ }
455
+
456
+ this.overG = this.gForce > this.maxGForce;
457
+
458
+ // Over-G ํƒ€์ด๋จธ ์—…๋ฐ์ดํŠธ
459
+ if (this.overG) {
460
+ this.overGTimer += deltaTime;
461
+
462
+ // 1.5์ดˆ ์ด์ƒ Over-G ์ƒํƒœ์ผ ๊ฒฝ์šฐ ์‹œ์•ผ ํ๋ฆผ ์‹œ์ž‘
463
+ if (this.overGTimer > 1.5) {
464
+ // ์†๋„ ๊ธ‰๊ฒฉํžˆ ๊ฐ์†Œ (2.5์ดˆ๋ถ€ํ„ฐ)
465
+ if (this.overGTimer > 2.5) {
466
+ targetSpeed *= Math.max(0.3, 1 - (this.overGTimer - 2.5) * 0.3);
467
+ }
468
+ // ์‹œ์•ผ ํ๋ฆผ ํšจ๊ณผ๋Š” UI์—์„œ ์ฒ˜๋ฆฌ (1.5์ดˆ๋ถ€ํ„ฐ)
469
+ }
470
+ } else {
471
+ this.overGTimer = 0; // Over-G ์ƒํƒœ๊ฐ€ ์•„๋‹ˆ๋ฉด ํƒ€์ด๋จธ ๋ฆฌ์…‹
472
+ }
473
+
474
+ // ์Šคํ†จ ๊ฒฝ๊ณ : 300kt ์ดํ•˜์—์„œ ์Šคํ†จ ์œ„ํ—˜
475
+ const speedKnots = this.speed * 1.94384; // m/s to knots
476
+ const wasStalling = this.stallWarning;
477
+
478
+ // ์Šคํ†จ ์ง„์ž… ์กฐ๊ฑด
479
+ if (!this.stallWarning && speedKnots < GAME_CONSTANTS.STALL_SPEED) {
480
+ this.stallWarning = true;
481
+ this.stallEscapeProgress = 0; // ์Šคํ†จ ์ง„์ž… ์‹œ ์ง„ํ–‰๋„ ์ดˆ๊ธฐํ™”
482
+ }
483
+
484
+ // ์Šคํ†จ ํƒˆ์ถœ ์กฐ๊ฑด
485
+ if (this.stallWarning) {
486
+ if (this.escapeKeyPressed) {
487
+ // Fํ‚ค๋ฅผ ๋ˆ„๋ฅด๊ณ  ์žˆ์œผ๋ฉด ์ง„ํ–‰๋„ ์ฆ๊ฐ€
488
+ this.stallEscapeProgress += deltaTime;
489
+
490
+ // 2์ดˆ ์ด์ƒ ๋ˆ„๋ฅด๋ฉด ์Šคํ†จ์—์„œ ํƒˆ์ถœ
491
+ if (this.stallEscapeProgress >= 2.0 && speedKnots >= 350) {
492
+ this.stallWarning = false;
493
+ this.stallEscapeProgress = 0;
494
+ // ์Šคํ†จ์—์„œ ๋ฒ—์–ด๋‚˜๋ฉด ์ž์„ธ๋ฅผ ์•ฝ๊ฐ„ ์•ˆ์ •ํ™”
495
+ this.targetPitch = Math.max(-0.2, Math.min(0.2, this.targetPitch));
496
+ }
497
+ } else {
498
+ // Fํ‚ค๋ฅผ ๋†“์œผ๋ฉด ์ง„ํ–‰๋„ ๊ฐ์†Œ
499
+ this.stallEscapeProgress = Math.max(0, this.stallEscapeProgress - deltaTime * 2);
500
+ }
501
+ }
502
+
503
+ // ์†๋„ ๋ณ€ํ™” ์ ์šฉ
504
+ if (this.stallWarning) {
505
+ // ์Šคํ†จ ์ƒํƒœ์—์„œ์˜ ์†๋„ ๋ณ€ํ™”
506
+ if (pitchAngle > 0.1) { // ๊ธฐ์ˆ˜๊ฐ€ ์•„๋ž˜๋ฅผ ํ–ฅํ•  ๋•Œ
507
+ // ๋‹ค์ด๋น™์œผ๋กœ ์ธํ•œ ์†๋„ ์ฆ๊ฐ€
508
+ const diveSpeedGain = Math.min(pitchAngle * 300, 200); // ์ตœ๋Œ€ 200m/s ์ฆ๊ฐ€
509
+ this.speed = Math.min(maxSpeed, this.speed + diveSpeedGain * deltaTime);
510
+ } else {
511
+ // ๊ธฐ์ˆ˜๊ฐ€ ์œ„๋ฅผ ํ–ฅํ•˜๊ฑฐ๋‚˜ ์ˆ˜ํ‰์ผ ๋•Œ๋Š” ์†๋„ ๊ฐ์†Œ
512
+ this.speed = Math.max(0, this.speed - deltaTime * 100);
513
+ }
514
+ } else {
515
+ // ์ •์ƒ ๋น„ํ–‰ ์‹œ ์†๋„ ๋ณ€ํ™”
516
+ this.speed = THREE.MathUtils.lerp(this.speed, targetSpeed, deltaTime * 0.5);
517
+ }
518
+
519
+ // ์Šคํ†จ ์ƒํƒœ์—์„œ์˜ ๋ฌผ๋ฆฌ ํšจ๊ณผ
520
+ if (this.stallWarning) {
521
+ // ๋ฐ”๋‹ฅ์œผ๋กœ ์ถ”๋ฝํ•˜๋ฉฐ ๊ฐ€์†๋„๊ฐ€ ๋น ๋ฅด๊ฒŒ ๋ถ™์Œ
522
+ this.targetPitch = Math.min(Math.PI / 2.5, this.targetPitch + deltaTime * 2.5); // ๊ธฐ์ˆ˜๊ฐ€ ๋” ๊ทน๋‹จ์ ์œผ๋กœ ์•„๋ž˜๋กœ (72๋„๊นŒ์ง€)
523
+
524
+ // ์กฐ์ข… ๋ถˆ๋Šฅ ์ƒํƒœ - ๋” ์‹ฌํ•œ ํ”๋“ค๋ฆผ
525
+ this.rotation.x += (Math.random() - 0.5) * deltaTime * 0.8;
526
+ this.rotation.z += (Math.random() - 0.5) * deltaTime * 0.8;
527
+
528
+ // ์ค‘๋ ฅ์— ์˜ํ•œ ๊ฐ€์†
529
+ const gravityAcceleration = GAME_CONSTANTS.GRAVITY * deltaTime * 3.0; // 3๋ฐฐ ์ค‘๋ ฅ
530
+ this.velocity.y -= gravityAcceleration;
531
+ }
532
+ // ์†๋„ ๋ฒกํ„ฐ ๊ณ„์‚ฐ - ์ฟผํ„ฐ๋‹ˆ์–ธ์„ ์‚ฌ์šฉํ•˜์—ฌ ์ง๋ฒŒ ๋ฝ ์™„์ „ ํšŒํ”ผ
533
+ const noseDirection = new THREE.Vector3(0, 0, 1);
534
+
535
+ // ์ฟผํ„ฐ๋‹ˆ์–ธ ๊ธฐ๋ฐ˜ ํšŒ์ „ ์‹œ์Šคํ…œ
536
+ const quaternion = new THREE.Quaternion();
537
+
538
+ // ๊ฐ ์ถ•์˜ ํšŒ์ „์„ ๊ฐœ๋ณ„ ์ฟผํ„ฐ๋‹ˆ์–ธ์œผ๋กœ ์ƒ์„ฑ
539
+ const pitchQuat = new THREE.Quaternion();
540
+ const yawQuat = new THREE.Quaternion();
541
+ const rollQuat = new THREE.Quaternion();
542
+
543
+ // ์ถ• ๋ฒกํ„ฐ ์ •์˜
544
+ const xAxis = new THREE.Vector3(1, 0, 0);
545
+ const yAxis = new THREE.Vector3(0, 1, 0);
546
+ const zAxis = new THREE.Vector3(0, 0, 1);
547
+
548
+ // ๊ฐ ์ถ•์— ๋Œ€ํ•œ ํšŒ์ „ ์„ค์ •
549
+ pitchQuat.setFromAxisAngle(xAxis, this.rotation.x);
550
+ yawQuat.setFromAxisAngle(yAxis, this.rotation.y);
551
+ rollQuat.setFromAxisAngle(zAxis, this.rotation.z);
552
+
553
+ // ์ฟผํ„ฐ๋‹ˆ์–ธ ํ•ฉ์„ฑ (ํ•ญ๊ณต๊ธฐ ํ‘œ์ค€ ์ˆœ์„œ: Yaw -> Pitch -> Roll)
554
+ quaternion.multiply(yawQuat);
555
+ quaternion.multiply(pitchQuat);
556
+ quaternion.multiply(rollQuat);
557
+
558
+ // ๋ฐฉํ–ฅ ๋ฒกํ„ฐ์— ํšŒ์ „ ์ ์šฉ
559
+ noseDirection.applyQuaternion(quaternion);
560
+
561
+ if (!this.stallWarning) {
562
+ // ์ •์ƒ ๋น„ํ–‰ ์‹œ
563
+ this.velocity = noseDirection.multiplyScalar(this.speed);
564
+ } else {
565
+ // ์Šคํ†จ ์‹œ์—๋Š” ์ค‘๋ ฅ์ด ์ฃผ๋„์ ์ด์ง€๋งŒ ๋‹ค์ด๋น™ ์†๋„๋„ ๋ฐ˜์˜
566
+ this.velocity.x = noseDirection.x * this.speed * 0.5; // ์ „๋ฐฉ ์†๋„ ์ฆ๊ฐ€
567
+ this.velocity.z = noseDirection.z * this.speed * 0.5;
568
+ // y ์†๋„๋Š” ์œ„์—์„œ ์ค‘๋ ฅ์œผ๋กœ ์ฒ˜๋ฆฌ๋จ
569
+ }
570
+
571
+ // ์ •์ƒ ๋น„ํ–‰ ์‹œ ์ค‘๋ ฅ ํšจ๊ณผ
572
+ if (!this.stallWarning) {
573
+ const gravityEffect = GAME_CONSTANTS.GRAVITY * deltaTime * 0.15;
574
+ this.velocity.y -= gravityEffect;
575
+
576
+ // ์–‘๋ ฅ ํšจ๊ณผ (์†๋„์— ๋น„๋ก€)
577
+ const liftFactor = (this.speed / maxSpeed) * 0.8;
578
+ const lift = gravityEffect * liftFactor;
579
+ this.velocity.y += lift;
580
+ }
581
+
582
+ // ์œ„์น˜ ์—…๋ฐ์ดํŠธ
583
+ this.position.add(this.velocity.clone().multiplyScalar(deltaTime));
584
+
585
+ // ์ง€๋ฉด ์ถฉ๋Œ
586
+ if (this.position.y <= GAME_CONSTANTS.MIN_ALTITUDE) {
587
+ this.position.y = GAME_CONSTANTS.MIN_ALTITUDE;
588
+ this.health = 0;
589
+
590
+ // ์ง€๋ฉด ์ถฉ๋Œ ์‹œ ํญ๋ฐœ ํšจ๊ณผ ์ถ”๊ฐ€
591
+ if (window.gameInstance) {
592
+ window.gameInstance.createExplosionEffect(this.position);
593
+ }
594
+
595
+ return;
596
+ }
597
+
598
+ // ์ตœ๋Œ€ ๊ณ ๋„ ์ œํ•œ
599
+ if (this.position.y > GAME_CONSTANTS.MAX_ALTITUDE) {
600
+ this.position.y = GAME_CONSTANTS.MAX_ALTITUDE;
601
+ this.altitudeWarning = true;
602
+ if (this.velocity.y > 0) this.velocity.y = 0;
603
+ } else {
604
+ this.altitudeWarning = false;
605
+ }
606
+
607
+ // ๋งต ๊ฒฝ๊ณ„ ์ฒ˜๋ฆฌ
608
+ const mapLimit = GAME_CONSTANTS.MAP_SIZE / 2;
609
+ if (this.position.x > mapLimit) this.position.x = -mapLimit;
610
+ if (this.position.x < -mapLimit) this.position.x = mapLimit;
611
+ if (this.position.z > mapLimit) this.position.z = -mapLimit;
612
+ if (this.position.z < -mapLimit) this.position.z = mapLimit;
613
+
614
+ // ๋ฉ”์‹œ ์œ„์น˜ ๋ฐ ํšŒ์ „ ์—…๋ฐ์ดํŠธ - ์ฟผํ„ฐ๋‹ˆ์–ธ ์‚ฌ์šฉ
615
+ this.mesh.position.copy(this.position);
616
+
617
+ // ๋ฉ”์‹œ ํšŒ์ „์„ ์ฟผํ„ฐ๋‹ˆ์–ธ์œผ๋กœ ์„ค์ •
618
+ const meshQuaternion = new THREE.Quaternion();
619
+
620
+ // Y์ถ• ํšŒ์ „์— 3ฯ€/2 ์ถ”๊ฐ€ (๋ชจ๋ธ ์˜คํ”„์…‹)
621
+ const meshPitchQuat = new THREE.Quaternion();
622
+ const meshYawQuat = new THREE.Quaternion();
623
+ const meshRollQuat = new THREE.Quaternion();
624
+
625
+ meshPitchQuat.setFromAxisAngle(new THREE.Vector3(1, 0, 0), this.rotation.x);
626
+ meshYawQuat.setFromAxisAngle(new THREE.Vector3(0, 1, 0), this.rotation.y + 3 * Math.PI / 2);
627
+ meshRollQuat.setFromAxisAngle(new THREE.Vector3(0, 0, 1), this.rotation.z);
628
+
629
+ // ์ฟผํ„ฐ๋‹ˆ์–ธ ํ•ฉ์„ฑ
630
+ meshQuaternion.multiply(meshYawQuat);
631
+ meshQuaternion.multiply(meshPitchQuat);
632
+ meshQuaternion.multiply(meshRollQuat);
633
+
634
+ this.mesh.quaternion.copy(meshQuaternion);
635
+
636
+ // ๊ฒฝ๊ณ  ๊นœ๋นก์ž„ ํƒ€์ด๋จธ
637
+ this.warningBlinkTimer += deltaTime;
638
+ if (this.warningBlinkTimer >= 1.0) {
639
+ this.warningBlinkTimer = 0;
640
+ this.warningBlinkState = !this.warningBlinkState;
641
+ }
642
+
643
+ // ๊ณ ๋„ ๊ณ„์‚ฐ
644
+ this.altitude = this.position.y;
645
+
646
+ // ๊ฒฝ๊ณ ์Œ ์—…๋ฐ์ดํŠธ (์—”์ง„ ์†Œ๋ฆฌ๋Š” ๊ณ„์† ์œ ์ง€)
647
+ this.updateWarningAudios();
648
+
649
+ // ์—”์ง„ ์†Œ๋ฆฌ ๋ณผ๋ฅจ์„ ์Šค๋กœํ‹€์— ์—ฐ๋™
650
+ if (this.warningAudios.normal && !this.warningAudios.normal.paused) {
651
+ this.warningAudios.normal.volume = 0.3 + this.throttle * 0.4; // 0.3~0.7
652
+ }
653
+ }
654
 
655
+ shoot(scene) {
656
+ // ํƒ„์•ฝ์ด ์—†์œผ๋ฉด ๋ฐœ์‚ฌํ•˜์ง€ ์•Š์Œ
657
+ if (this.ammo <= 0) return;
658
+
659
+ this.ammo--;
660
+
661
+ // ์ง์„  ๋ชจ์–‘์˜ ํƒ„ํ™˜ (100% ๋” ํฌ๊ฒŒ)
662
+ const bulletGeometry = new THREE.CylinderGeometry(1.0, 1.0, 16, 8); // ๋ฐ˜์ง€๋ฆ„ 0.75โ†’1.0, ๊ธธ์ด 12โ†’16
663
+ const bulletMaterial = new THREE.MeshBasicMaterial({
664
+ color: 0xffff00,
665
+ emissive: 0xffff00,
666
+ emissiveIntensity: 1.0
667
+ });
668
+ const bullet = new THREE.Mesh(bulletGeometry, bulletMaterial);
669
+
670
+ // ๊ธฐ์ˆ˜ ๋์—์„œ ๋ฐœ์‚ฌ (์ฟผํ„ฐ๋‹ˆ์–ธ ์‚ฌ์šฉ)
671
+ const muzzleOffset = new THREE.Vector3(0, 0, 10);
672
+
673
+ // ์ „ํˆฌ๊ธฐ์™€ ๋™์ผํ•œ ์ฟผํ„ฐ๋‹ˆ์–ธ ์ƒ์„ฑ
674
+ const quaternion = new THREE.Quaternion();
675
+ const pitchQuat = new THREE.Quaternion();
676
+ const yawQuat = new THREE.Quaternion();
677
+ const rollQuat = new THREE.Quaternion();
678
+
679
+ pitchQuat.setFromAxisAngle(new THREE.Vector3(1, 0, 0), this.rotation.x);
680
+ yawQuat.setFromAxisAngle(new THREE.Vector3(0, 1, 0), this.rotation.y);
681
+ rollQuat.setFromAxisAngle(new THREE.Vector3(0, 0, 1), this.rotation.z);
682
+
683
+ quaternion.multiply(yawQuat);
684
+ quaternion.multiply(pitchQuat);
685
+ quaternion.multiply(rollQuat);
686
+
687
+ muzzleOffset.applyQuaternion(quaternion);
688
+ bullet.position.copy(this.position).add(muzzleOffset);
689
+
690
+ // ํƒ„ํ™˜์„ ๋ฐœ์‚ฌ ๋ฐฉํ–ฅ์œผ๋กœ ํšŒ์ „
691
+ bullet.quaternion.copy(quaternion);
692
+ // ์‹ค๋ฆฐ๋”๊ฐ€ Z์ถ• ๋ฐฉํ–ฅ์„ ํ–ฅํ•˜๋„๋ก ์ถ”๊ฐ€ ํšŒ์ „
693
+ const cylinderRotation = new THREE.Quaternion();
694
+ cylinderRotation.setFromAxisAngle(new THREE.Vector3(1, 0, 0), Math.PI / 2);
695
+ bullet.quaternion.multiply(cylinderRotation);
696
+
697
+ // ํƒ„ํ™˜ ์ดˆ๊ธฐ ์œ„์น˜ ์ €์žฅ
698
+ bullet.startPosition = bullet.position.clone();
699
+
700
+ const bulletSpeed = 1500; // 1000์—์„œ 1500์œผ๋กœ ์ฆ๊ฐ€ (50% ๋น ๋ฅด๊ฒŒ)
701
+ const direction = new THREE.Vector3(0, 0, 1);
702
+ direction.applyQuaternion(quaternion);
703
+ bullet.velocity = direction.multiplyScalar(bulletSpeed);
704
+
705
+ scene.add(bullet);
706
+ this.bullets.push(bullet);
707
+
708
+ // m134.ogg ์†Œ๋ฆฌ ์žฌ์ƒ - ์ค‘์ฒฉ ์ œํ•œ ํ•ด์ œ, ๋žœ๋ค ํ”ผ์น˜
709
+ try {
710
+ const audio = new Audio('sounds/m134.ogg');
711
+ audio.volume = 0.15; // 0.3์—์„œ 0.15๋กœ ๊ฐ์†Œ (50% ์ค„์ž„)
712
+
713
+ // -2 ~ +2 ์‚ฌ์ด์˜ ๋žœ๋ค ํ”ผ์น˜ (playbackRate๋กœ ์‹œ๋ฎฌ๋ ˆ์ด์…˜)
714
+ const randomPitch = 0.8 + Math.random() * 0.4; // 0.8 ~ 1.2 ๋ฒ”์œ„
715
+ audio.playbackRate = randomPitch;
716
+
717
+ audio.play().catch(e => console.log('Gunfire sound failed to play'));
718
+
719
+ // ์žฌ์ƒ ์™„๋ฃŒ ์‹œ ๋ฉ”๋ชจ๋ฆฌ ์ •๋ฆฌ๋ฅผ ์œ„ํ•ด ์ฐธ์กฐ ์ œ๊ฑฐ
720
+ audio.addEventListener('ended', () => {
721
+ audio.remove();
722
+ });
723
+ } catch (e) {
724
+ console.log('Audio error:', e);
725
+ }
726
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
727
 
728
+ updateBullets(scene, deltaTime, gameInstance) {
729
+ for (let i = this.bullets.length - 1; i >= 0; i--) {
730
+ const bullet = this.bullets[i];
731
+ bullet.position.add(bullet.velocity.clone().multiplyScalar(deltaTime));
732
+
733
+ // ํƒ„ํ™˜๋„ ๊ฐ™์€ ๋ฐฉํ–ฅ์„ ์œ ์ง€ํ•˜๋„๋ก ํšŒ์ „ ์—…๋ฐ์ดํŠธ
734
+ const direction = bullet.velocity.clone().normalize();
735
+ const angle = Math.atan2(direction.x, direction.z);
736
+ bullet.rotation.y = angle;
737
+
738
+ // ์ง€๋ฉด ์ถฉ๋Œ ์ฒดํฌ ์ถ”๊ฐ€
739
+ if (bullet.position.y <= 0) {
740
+ // ํฌ๊ณ  ํ™”๋ คํ•œ ์ง€๋ฉด ์ถฉ๋Œ ํšจ๊ณผ
741
+ gameInstance.createGroundImpactEffect(bullet.position);
742
+ scene.remove(bullet);
743
+ this.bullets.splice(i, 1);
744
+ continue;
745
+ }
746
+
747
+ // 6000m ์ด์ƒ ๋‚ ์•„๊ฐ€๊ฑฐ๋‚˜ ๋†’์ด ์ œํ•œ ์ดˆ๊ณผ ์‹œ ์ œ๊ฑฐ
748
+ if (bullet.position.distanceTo(bullet.startPosition) > 6000 ||
749
+ bullet.position.y > GAME_CONSTANTS.MAX_ALTITUDE + 500) {
750
+ scene.remove(bullet);
751
+ this.bullets.splice(i, 1);
752
+ }
753
+ }
754
+ }
755
 
756
+ takeDamage(damage) {
757
+ this.health -= damage;
758
+ return this.health <= 0;
759
+ }
760
 
761
+ getCameraPosition() {
762
+ const backward = new THREE.Vector3(0, 0, -1);
763
+ const up = new THREE.Vector3(0, 1, 0);
764
+
765
+ // ์นด๋ฉ”๋ผ ์œ„์น˜ ๊ณ„์‚ฐ์—๋„ ์ฟผํ„ฐ๋‹ˆ์–ธ ์‚ฌ์šฉ
766
+ const quaternion = new THREE.Quaternion();
767
+ const pitchQuat = new THREE.Quaternion();
768
+ const yawQuat = new THREE.Quaternion();
769
+ const rollQuat = new THREE.Quaternion();
770
+
771
+ pitchQuat.setFromAxisAngle(new THREE.Vector3(1, 0, 0), this.rotation.x);
772
+ yawQuat.setFromAxisAngle(new THREE.Vector3(0, 1, 0), this.rotation.y);
773
+ rollQuat.setFromAxisAngle(new THREE.Vector3(0, 0, 1), this.rotation.z);
774
+
775
+ quaternion.multiply(yawQuat);
776
+ quaternion.multiply(pitchQuat);
777
+ quaternion.multiply(rollQuat);
778
+
779
+ backward.applyQuaternion(quaternion);
780
+ up.applyQuaternion(quaternion);
781
+
782
+ const cameraPosition = this.position.clone()
783
+ .add(backward.multiplyScalar(this.cameraDistance))
784
+ .add(up.multiplyScalar(this.cameraHeight));
785
+
786
+ return cameraPosition;
787
+ }
788
 
789
+ getCameraTarget() {
790
+ return this.position.clone();
791
+ }
792
+
793
+ // HUD ํ‘œ์‹œ์šฉ ํ—ค๋”ฉ getter (0~360๋„)
794
+ getHeadingDegrees() {
795
+ return this.normalizeHeading(this.rotation.y);
796
+ }
797
  }
798
 
799
  // ์  ์ „ํˆฌ๊ธฐ ํด๋ž˜์Šค - ์™„์ „ํžˆ ์žฌ์„ค๊ณ„