cutechicken commited on
Commit
8792afc
Β·
verified Β·
1 Parent(s): 9179d15

Update game.js

Browse files
Files changed (1) hide show
  1. game.js +16 -580
game.js CHANGED
@@ -461,230 +461,23 @@ class Fighter {
461
  this.stallEscapeProgress = 0; // μŠ€ν†¨ μ§„μž… μ‹œ 진행도 μ΄ˆκΈ°ν™”
462
  }
463
 
464
- // Uncaught SyntaxError 해결을 μœ„ν•΄ ν•¨μˆ˜ 끝 λΆ€λΆ„ μˆ˜μ •
465
- } else if (this.stallWarning && !this.escapeKeyPressed) {
466
- // Fν‚€λ₯Ό λ†“μœΌλ©΄ 진행도 κ°μ†Œ
467
- this.stallEscapeProgress = Math.max(0, this.stallEscapeProgress - deltaTime * 2);
468
- }
469
-
470
- // 속도 λ³€ν™” 적용
471
  if (this.stallWarning) {
472
- // μŠ€ν†¨ μƒνƒœμ—μ„œμ˜ 속도 λ³€ν™”
473
- if (pitchAngle > 0.1) { // κΈ°μˆ˜κ°€ μ•„λž˜λ₯Ό ν–₯ν•  λ•Œ
474
- // λ‹€μ΄λΉ™μœΌλ‘œ μΈν•œ 속도 증가
475
- const diveSpeedGain = Math.min(pitchAngle * 300, 200); // μ΅œλŒ€ 200m/s 증가
476
- this.speed = Math.min(maxSpeed, this.speed + diveSpeedGain * deltaTime);
 
 
 
 
 
 
477
  } else {
478
- // κΈ°μˆ˜κ°€ μœ„λ₯Ό ν–₯ν•˜κ±°λ‚˜ μˆ˜ν‰μΌ λ•ŒλŠ” 속도 κ°μ†Œ
479
- this.speed = Math.max(0, this.speed - deltaTime * 100);
480
  }
481
- } else {
482
- // 정상 λΉ„ν–‰ μ‹œ 속도 λ³€ν™”
483
- this.speed = THREE.MathUtils.lerp(this.speed, targetSpeed, deltaTime * 0.5);
484
- }
485
-
486
- // μŠ€ν†¨ μƒνƒœμ—μ„œμ˜ 물리 효과
487
- if (this.stallWarning) {
488
- // λ°”λ‹₯으둜 μΆ”λ½ν•˜λ©° 가속도가 λΉ λ₯΄κ²Œ λΆ™μŒ
489
- this.targetPitch = Math.min(Math.PI / 2.5, this.targetPitch + deltaTime * 2.5); // κΈ°μˆ˜κ°€ 더 κ·Ήλ‹¨μ μœΌλ‘œ μ•„λž˜λ‘œ (72λ„κΉŒμ§€)
490
-
491
- // μ‘°μ’… 뢈λŠ₯ μƒνƒœ - 더 μ‹¬ν•œ 흔듀림
492
- this.rotation.x += (Math.random() - 0.5) * deltaTime * 0.8;
493
- this.rotation.z += (Math.random() - 0.5) * deltaTime * 0.8;
494
-
495
- // 쀑λ ₯에 μ˜ν•œ 가속
496
- const gravityAcceleration = GAME_CONSTANTS.GRAVITY * deltaTime * 3.0; // 3λ°° 쀑λ ₯
497
- this.velocity.y -= gravityAcceleration;
498
- }
499
-
500
- // 속도 벑터 계산
501
- const noseDirection = new THREE.Vector3(0, 0, 1);
502
- noseDirection.applyEuler(this.rotation);
503
-
504
- if (!this.stallWarning) {
505
- // 정상 λΉ„ν–‰ μ‹œ
506
- this.velocity = noseDirection.multiplyScalar(this.speed);
507
- } else {
508
- // μŠ€ν†¨ μ‹œμ—λŠ” 쀑λ ₯이 μ£Όλ„μ μ΄μ§€λ§Œ 닀이빙 속도도 반영
509
- this.velocity.x = noseDirection.x * this.speed * 0.5; // μ „λ°© 속도 증가
510
- this.velocity.z = noseDirection.z * this.speed * 0.5;
511
- // y μ†λ„λŠ” μœ„μ—μ„œ 쀑λ ₯으둜 처리됨
512
- }
513
-
514
- // 정상 λΉ„ν–‰ μ‹œ 쀑λ ₯ 효과
515
- if (!this.stallWarning) {
516
- const gravityEffect = GAME_CONSTANTS.GRAVITY * deltaTime * 0.15;
517
- this.velocity.y -= gravityEffect;
518
-
519
- // μ–‘λ ₯ 효과 (속도에 λΉ„λ‘€)
520
- const liftFactor = (this.speed / maxSpeed) * 0.8;
521
- const lift = gravityEffect * liftFactor;
522
- this.velocity.y += lift;
523
- }
524
-
525
- // μœ„μΉ˜ μ—…λ°μ΄νŠΈ
526
- this.position.add(this.velocity.clone().multiplyScalar(deltaTime));
527
-
528
- // μ§€ν˜• 좩돌 감지 (Game ν΄λž˜μŠ€μ—μ„œ μ²˜λ¦¬ν•˜λ„λ‘ μˆ˜μ •)
529
-
530
- // μ§€λ©΄ 좩돌
531
- if (this.position.y <= GAME_CONSTANTS.MIN_ALTITUDE) {
532
- this.position.y = GAME_CONSTANTS.MIN_ALTITUDE;
533
- this.health = 0;
534
- return;
535
- }
536
-
537
- // μ΅œλŒ€ 고도 μ œν•œ
538
- if (this.position.y > GAME_CONSTANTS.MAX_ALTITUDE) {
539
- this.position.y = GAME_CONSTANTS.MAX_ALTITUDE;
540
- this.altitudeWarning = true;
541
- if (this.velocity.y > 0) this.velocity.y = 0;
542
- } else {
543
- this.altitudeWarning = false;
544
- }
545
-
546
- // λ§΅ 경계 처리
547
- const mapLimit = GAME_CONSTANTS.MAP_SIZE / 2;
548
- if (this.position.x > mapLimit) this.position.x = -mapLimit;
549
- if (this.position.x < -mapLimit) this.position.x = mapLimit;
550
- if (this.position.z > mapLimit) this.position.z = -mapLimit;
551
- if (this.position.z < -mapLimit) this.position.z = mapLimit;
552
-
553
- // λ©”μ‹œ μœ„μΉ˜ 및 νšŒμ „ μ—…λ°μ΄νŠΈ
554
- this.mesh.position.copy(this.position);
555
- this.mesh.rotation.x = this.rotation.x;
556
- this.mesh.rotation.y = this.rotation.y + 3 * Math.PI / 2;
557
- this.mesh.rotation.z = this.rotation.z;
558
-
559
- // κ²½κ³  κΉœλΉ‘μž„ 타이머
560
- this.warningBlinkTimer += deltaTime;
561
- if (this.warningBlinkTimer >= 1.0) {
562
- this.warningBlinkTimer = 0;
563
- this.warningBlinkState = !this.warningBlinkState;
564
- }
565
-
566
- // 고도 계산
567
- this.altitude = this.position.y;
568
-
569
- // 경고음 μ—…λ°μ΄νŠΈ (μ—”μ§„ μ†Œλ¦¬λŠ” 계속 μœ μ§€)
570
- this.updateWarningAudios();
571
-
572
- // μ—”μ§„ μ†Œλ¦¬ λ³Όλ₯¨μ„ μŠ€λ‘œν‹€μ— 연동
573
- if (this.warningAudios.normal && !this.warningAudios.normal.paused) {
574
- this.warningAudios.normal.volume = 0.3 + this.throttle * 0.4; // 0.3~0.7
575
- }
576
- } Fν‚€λ₯Ό λ†“μœΌλ©΄ 진행도 κ°μ†Œ
577
- this.stallEscapeProgress = Math.max(0, this.stallEscapeProgress - deltaTime * 2);
578
- }
579
-
580
- // 속도 λ³€ν™” 적용
581
- if (this.stallWarning) {
582
- // μŠ€ν†¨ μƒνƒœμ—μ„œμ˜ 속도 λ³€ν™”
583
- if (pitchAngle > 0.1) { // κΈ°μˆ˜κ°€ μ•„λž˜λ₯Ό ν–₯ν•  λ•Œ
584
- // λ‹€μ΄λΉ™μœΌλ‘œ μΈν•œ 속도 증가
585
- const diveSpeedGain = Math.min(pitchAngle * 300, 200); // μ΅œλŒ€ 200m/s 증가
586
- this.speed = Math.min(maxSpeed, this.speed + diveSpeedGain * deltaTime);
587
- } else {
588
- // κΈ°μˆ˜κ°€ μœ„λ₯Ό ν–₯ν•˜κ±°λ‚˜ μˆ˜ν‰μΌ λ•ŒλŠ” 속도 κ°μ†Œ
589
- this.speed = Math.max(0, this.speed - deltaTime * 100);
590
- }
591
- } else {
592
- // 정상 λΉ„ν–‰ μ‹œ 속도 λ³€ν™”
593
- this.speed = THREE.MathUtils.lerp(this.speed, targetSpeed, deltaTime * 0.5);
594
- }
595
-
596
- // μŠ€ν†¨ μƒνƒœμ—μ„œμ˜ 물리 효과
597
- if (this.stallWarning) {
598
- // λ°”λ‹₯으둜 μΆ”λ½ν•˜λ©° 가속도가 λΉ λ₯΄κ²Œ λΆ™μŒ
599
- this.targetPitch = Math.min(Math.PI / 2.5, this.targetPitch + deltaTime * 2.5); // κΈ°μˆ˜κ°€ 더 κ·Ήλ‹¨μ μœΌλ‘œ μ•„λž˜λ‘œ (72λ„κΉŒμ§€)
600
-
601
- // μ‘°μ’… 뢈λŠ₯ μƒνƒœ - 더 μ‹¬ν•œ 흔듀림
602
- this.rotation.x += (Math.random() - 0.5) * deltaTime * 0.8;
603
- this.rotation.z += (Math.random() - 0.5) * deltaTime * 0.8;
604
-
605
- // 쀑λ ₯에 μ˜ν•œ 가속
606
- const gravityAcceleration = GAME_CONSTANTS.GRAVITY * deltaTime * 3.0; // 3λ°° 쀑λ ₯
607
- this.velocity.y -= gravityAcceleration;
608
- }
609
-
610
- // 속도 벑터 계산
611
- const noseDirection = new THREE.Vector3(0, 0, 1);
612
- noseDirection.applyEuler(this.rotation);
613
-
614
- if (!this.stallWarning) {
615
- // 정상 λΉ„ν–‰ μ‹œ
616
- this.velocity = noseDirection.multiplyScalar(this.speed);
617
- } else {
618
- // μŠ€ν†¨ μ‹œμ—λŠ” 쀑λ ₯이 μ£Όλ„μ μ΄μ§€λ§Œ 닀이빙 속도도 반영
619
- this.velocity.x = noseDirection.x * this.speed * 0.5; // μ „λ°© 속도 증가
620
- this.velocity.z = noseDirection.z * this.speed * 0.5;
621
- // y μ†λ„λŠ” μœ„μ—μ„œ 쀑λ ₯으둜 처리됨
622
- }
623
-
624
- // 정상 λΉ„ν–‰ μ‹œ 쀑λ ₯ 효과
625
- if (!this.stallWarning) {
626
- const gravityEffect = GAME_CONSTANTS.GRAVITY * deltaTime * 0.15;
627
- this.velocity.y -= gravityEffect;
628
-
629
- // μ–‘λ ₯ 효과 (속도에 λΉ„λ‘€)
630
- const liftFactor = (this.speed / maxSpeed) * 0.8;
631
- const lift = gravityEffect * liftFactor;
632
- this.velocity.y += lift;
633
- }
634
-
635
- // μœ„μΉ˜ μ—…λ°μ΄νŠΈ
636
- this.position.add(this.velocity.clone().multiplyScalar(deltaTime));
637
-
638
- // μ§€ν˜• 좩돌 감지 (Game ν΄λž˜μŠ€μ—μ„œ μ²˜λ¦¬ν•˜λ„λ‘ μˆ˜μ •)
639
-
640
- // μ§€λ©΄ 좩돌
641
- if (this.position.y <= GAME_CONSTANTS.MIN_ALTITUDE) {
642
- this.position.y = GAME_CONSTANTS.MIN_ALTITUDE;
643
- this.health = 0;
644
- return;
645
- }
646
-
647
- // μ΅œλŒ€ 고도 μ œν•œ
648
- if (this.position.y > GAME_CONSTANTS.MAX_ALTITUDE) {
649
- this.position.y = GAME_CONSTANTS.MAX_ALTITUDE;
650
- this.altitudeWarning = true;
651
- if (this.velocity.y > 0) this.velocity.y = 0;
652
- } else {
653
- this.altitudeWarning = false;
654
- }
655
-
656
- // λ§΅ 경계 처리
657
- const mapLimit = GAME_CONSTANTS.MAP_SIZE / 2;
658
- if (this.position.x > mapLimit) this.position.x = -mapLimit;
659
- if (this.position.x < -mapLimit) this.position.x = mapLimit;
660
- if (this.position.z > mapLimit) this.position.z = -mapLimit;
661
- if (this.position.z < -mapLimit) this.position.z = mapLimit;
662
-
663
- // λ©”μ‹œ μœ„μΉ˜ 및 νšŒμ „ μ—…λ°μ΄νŠΈ
664
- this.mesh.position.copy(this.position);
665
- this.mesh.rotation.x = this.rotation.x;
666
- this.mesh.rotation.y = this.rotation.y + 3 * Math.PI / 2;
667
- this.mesh.rotation.z = this.rotation.z;
668
-
669
- // κ²½κ³  κΉœλΉ‘μž„ 타이머
670
- this.warningBlinkTimer += deltaTime;
671
- if (this.warningBlinkTimer >= 1.0) {
672
- this.warningBlinkTimer = 0;
673
- this.warningBlinkState = !this.warningBlinkState;
674
- }
675
-
676
- // 고도 계산
677
- this.altitude = this.position.y;
678
-
679
- // 경고음 μ—…λ°μ΄νŠΈ (μ—”μ§„ μ†Œλ¦¬λŠ” 계속 μœ μ§€)
680
- this.updateWarningAudios();
681
-
682
- // μ—”μ§„ μ†Œλ¦¬ λ³Όλ₯¨μ„ μŠ€λ‘œν‹€μ— 연동
683
- if (this.warningAudios.normal && !this.warningAudios.normal.paused) {
684
- this.warningAudios.normal.volume = 0.3 + this.throttle * 0.4; // 0.3~0.7
685
- }
686
- } Fν‚€λ₯Ό λ†“μœΌλ©΄ 진행도 κ°μ†Œ
687
- this.stallEscapeProgress = Math.max(0, this.stallEscapeProgress - deltaTime * 2);
688
  }
689
 
690
  // 속도 λ³€ν™” 적용
@@ -1440,362 +1233,6 @@ class Game {
1440
  // κ°œμ„ λœ ꡬ름 μΆ”κ°€
1441
  this.addClouds();
1442
  }
1443
-
1444
- createProceduralTerrain() {
1445
- // μ§€ν˜• 생성을 μœ„ν•œ μ„Έκ·Έλ¨ΌνŠΈ
1446
- const segments = 100;
1447
- const terrainGeometry = new THREE.PlaneGeometry(
1448
- GAME_CONSTANTS.MAP_SIZE,
1449
- GAME_CONSTANTS.MAP_SIZE,
1450
- segments,
1451
- segments
1452
- );
1453
-
1454
- // λ²„ν…μŠ€μ— λ…Έμ΄μ¦ˆ μΆ”κ°€ν•˜μ—¬ 언덕 효과
1455
- const vertices = terrainGeometry.attributes.position.array;
1456
- const heightMap = [];
1457
-
1458
- for (let i = 0; i < vertices.length; i += 3) {
1459
- const x = vertices[i];
1460
- const y = vertices[i + 1];
1461
-
1462
- // 닀쀑 μŠ€μΌ€μΌ λ…Έμ΄μ¦ˆλ‘œ μžμ—°μŠ€λŸ¬μš΄ μ§€ν˜• 생성
1463
- let height = 0;
1464
-
1465
- // 큰 μŠ€μΌ€μΌ 언덕 (μ£Όμš” μ§€ν˜•)
1466
- height += Math.sin(x * 0.0001) * Math.cos(y * 0.0001) * 300;
1467
-
1468
- // 쀑간 μŠ€μΌ€μΌ 언덕
1469
- height += Math.sin(x * 0.0003) * Math.cos(y * 0.0003) * 150;
1470
-
1471
- // μž‘μ€ μŠ€μΌ€μΌ λ””ν…ŒμΌ
1472
- height += Math.sin(x * 0.001) * Math.cos(y * 0.001) * 50;
1473
-
1474
- // 랜덀 λ…Έμ΄μ¦ˆ
1475
- height += (Math.random() - 0.5) * 20;
1476
-
1477
- // λ§΅ κ°€μž₯자리둜 갈수둝 높이 κ°μ†Œ (경계 λΆ€λ“œλŸ½κ²Œ)
1478
- const distanceFromCenter = Math.sqrt(x * x + y * y);
1479
- const maxDistance = GAME_CONSTANTS.MAP_SIZE * 0.4;
1480
- if (distanceFromCenter > maxDistance) {
1481
- const fade = 1 - (distanceFromCenter - maxDistance) / (GAME_CONSTANTS.MAP_SIZE * 0.1);
1482
- height *= Math.max(0, fade);
1483
- }
1484
-
1485
- vertices[i + 2] = height;
1486
- heightMap.push(height);
1487
- }
1488
-
1489
- terrainGeometry.computeVertexNormals();
1490
-
1491
- // λ‹¨μˆœν•œ μ΄ˆλ‘μƒ‰ μ§€ν˜• 머티리얼
1492
- const terrainMaterial = new THREE.MeshLambertMaterial({
1493
- color: 0x8FBC8F,
1494
- flatShading: false
1495
- });
1496
-
1497
- const terrain = new THREE.Mesh(terrainGeometry, terrainMaterial);
1498
- terrain.rotation.x = -Math.PI / 2;
1499
- terrain.receiveShadow = true;
1500
-
1501
- // μ§€ν˜• 정보 μ €μž₯ (초λͺ© 배치 및 좩돌 νŒμ •μš©)
1502
- this.terrain = terrain;
1503
- this.terrainGeometry = terrainGeometry;
1504
- this.heightMap = heightMap;
1505
- this.terrainSegments = segments;
1506
-
1507
- this.scene.add(terrain);
1508
- }
1509
-
1510
- addVegetation() {
1511
- if (!this.terrain || !this.heightMap) return;
1512
-
1513
- // κ°„λ‹¨ν•œ λ‚˜λ¬΄ 배치
1514
- const treeCount = 300;
1515
-
1516
- for (let i = 0; i < treeCount; i++) {
1517
- // 랜덀 μœ„μΉ˜ 선택
1518
- const x = (Math.random() - 0.5) * GAME_CONSTANTS.MAP_SIZE * 0.8;
1519
- const z = (Math.random() - 0.5) * GAME_CONSTANTS.MAP_SIZE * 0.8;
1520
-
1521
- // ν•΄λ‹Ή μœ„μΉ˜μ˜ μ§€ν˜• 높이 계산
1522
- const terrainHeight = this.getTerrainHeightAt(x, z);
1523
-
1524
- // λ„ˆλ¬΄ 높은 κ³³μ—λŠ” λ‚˜λ¬΄λ₯Ό λ°°μΉ˜ν•˜μ§€ μ•ŠμŒ
1525
- if (terrainHeight > 200) continue;
1526
-
1527
- // κ°„λ‹¨ν•œ λ‚˜λ¬΄ 생성
1528
- const tree = new THREE.Group();
1529
-
1530
- // λͺΈν†΅
1531
- const trunkGeometry = new THREE.CylinderGeometry(1, 1.5, 15, 6);
1532
- const trunkMaterial = new THREE.MeshLambertMaterial({
1533
- color: 0x4a3c28
1534
- });
1535
- const trunk = new THREE.Mesh(trunkGeometry, trunkMaterial);
1536
- trunk.position.y = 7.5;
1537
- trunk.castShadow = true;
1538
- trunk.receiveShadow = true;
1539
- tree.add(trunk);
1540
-
1541
- // 잎
1542
- const leavesGeometry = new THREE.SphereGeometry(8, 6, 5);
1543
- const leavesMaterial = new THREE.MeshLambertMaterial({
1544
- color: 0x2d5016
1545
- });
1546
- const leaves = new THREE.Mesh(leavesGeometry, leavesMaterial);
1547
- leaves.position.y = 20;
1548
- leaves.castShadow = true;
1549
- leaves.receiveShadow = true;
1550
- tree.add(leaves);
1551
-
1552
- // λ‚˜λ¬΄ μœ„μΉ˜ 및 크기 μ„€μ •
1553
- tree.position.set(x, terrainHeight, z);
1554
-
1555
- // 크기 λ³€ν™”
1556
- const scale = 0.8 + Math.random() * 0.4;
1557
- tree.scale.set(scale, scale, scale);
1558
-
1559
- // 랜덀 νšŒμ „
1560
- tree.rotation.y = Math.random() * Math.PI * 2;
1561
-
1562
- this.scene.add(tree);
1563
- }
1564
-
1565
- // κ°„λ‹¨ν•œ κ΄€λͺ© μΆ”κ°€
1566
- const bushCount = 200;
1567
-
1568
- for (let i = 0; i < bushCount; i++) {
1569
- const x = (Math.random() - 0.5) * GAME_CONSTANTS.MAP_SIZE * 0.8;
1570
- const z = (Math.random() - 0.5) * GAME_CONSTANTS.MAP_SIZE * 0.8;
1571
- const terrainHeight = this.getTerrainHeightAt(x, z);
1572
-
1573
- if (terrainHeight > 150) continue;
1574
-
1575
- const bushGeometry = new THREE.SphereGeometry(3, 5, 4);
1576
- const bushMaterial = new THREE.MeshLambertMaterial({
1577
- color: 0x3a5f3a
1578
- });
1579
- const bush = new THREE.Mesh(bushGeometry, bushMaterial);
1580
-
1581
- bush.position.set(x, terrainHeight + 1.5, z);
1582
- bush.scale.set(
1583
- 1 + Math.random() * 0.5,
1584
- 0.6 + Math.random() * 0.3,
1585
- 1 + Math.random() * 0.5
1586
- );
1587
-
1588
- bush.castShadow = true;
1589
- bush.receiveShadow = true;
1590
-
1591
- this.scene.add(bush);
1592
- }
1593
- }
1594
-
1595
- getTerrainHeightAt(x, z) {
1596
- if (!this.terrainGeometry || !this.heightMap) return 0;
1597
-
1598
- // μ§€ν˜• μ’Œν‘œλ₯Ό μ •κ·œν™”
1599
- const normalizedX = (x + GAME_CONSTANTS.MAP_SIZE / 2) / GAME_CONSTANTS.MAP_SIZE;
1600
- const normalizedZ = (z + GAME_CONSTANTS.MAP_SIZE / 2) / GAME_CONSTANTS.MAP_SIZE;
1601
-
1602
- // κ°€μž₯ κ°€κΉŒμš΄ λ²„ν…μŠ€ 인덱슀 μ°ΎκΈ°
1603
- const segments = this.terrainSegments || 100; // terrainSegmentsκ°€ μ—†μœΌλ©΄ κΈ°λ³Έκ°’ μ‚¬μš©
1604
- const gridX = Math.floor(normalizedX * (segments - 1));
1605
- const gridZ = Math.floor(normalizedZ * (segments - 1));
1606
-
1607
- if (gridX < 0 || gridX >= segments - 1 || gridZ < 0 || gridZ >= segments - 1) {
1608
- return 0;
1609
- }
1610
-
1611
- const index = gridZ * segments + gridX;
1612
- return this.heightMap[index] || 0;
1613
- }
1614
-
1615
- addAtmosphericEffects() {
1616
- // κ°œμ„ λœ μ‚° μ§€ν˜• μ‹œμŠ€ν…œ
1617
- this.createMountainRanges();
1618
- }
1619
-
1620
- createMountainRanges() {
1621
- // μ‚°λ§₯ 생성을 μœ„ν•œ μ„€μ •
1622
- const mountainRanges = [
1623
- {
1624
- distance: GAME_CONSTANTS.MAP_SIZE * 0.6,
1625
- count: 25,
1626
- heightRange: { min: 2000, max: 5000 },
1627
- widthRange: { min: 1500, max: 3000 },
1628
- color: 0x5a6b7a
1629
- },
1630
- {
1631
- distance: GAME_CONSTANTS.MAP_SIZE * 0.8,
1632
- count: 20,
1633
- heightRange: { min: 3000, max: 7000 },
1634
- widthRange: { min: 2000, max: 4000 },
1635
- color: 0x4a5b6a
1636
- },
1637
- {
1638
- distance: GAME_CONSTANTS.MAP_SIZE * 1.0,
1639
- count: 15,
1640
- heightRange: { min: 4000, max: 9000 },
1641
- widthRange: { min: 3000, max: 5000 },
1642
- color: 0x3a4b5a
1643
- }
1644
- ];
1645
-
1646
- mountainRanges.forEach(range => {
1647
- for (let i = 0; i < range.count; i++) {
1648
- const angle = (i / range.count) * Math.PI * 2 + (Math.random() - 0.5) * 0.3;
1649
- const distance = range.distance + (Math.random() - 0.5) * 1000;
1650
-
1651
- // μ‚° κ·Έλ£Ή 생성 (μ—¬λŸ¬ λ΄‰μš°λ¦¬λ‘œ ꡬ성)
1652
- const mountainGroup = new THREE.Group();
1653
-
1654
- // 메인 λ΄‰μš°λ¦¬
1655
- const mainPeakHeight = range.heightRange.min + Math.random() * (range.heightRange.max - range.heightRange.min);
1656
- const mainPeakWidth = range.widthRange.min + Math.random() * (range.widthRange.max - range.widthRange.min);
1657
-
1658
- // κ°œμ„ λœ μ‚° ν˜•νƒœ (νŒ”κ°λΏ” λŒ€μ‹  더 μžμ—°μŠ€λŸ¬μš΄ ν˜•νƒœ)
1659
- const mainPeakGeometry = new THREE.ConeGeometry(mainPeakWidth, mainPeakHeight, 12);
1660
- const vertices = mainPeakGeometry.attributes.position.array;
1661
-
1662
- // 정점 λ³€ν˜•μœΌλ‘œ 더 μžμ—°μŠ€λŸ¬μš΄ μ‚° ν˜•νƒœ λ§Œλ“€κΈ°
1663
- for (let j = 0; j < vertices.length; j += 3) {
1664
- const x = vertices[j];
1665
- const y = vertices[j + 1];
1666
- const z = vertices[j + 2];
1667
-
1668
- // 높이에 λ”°λ₯Έ 랜덀 λ³€ν˜•
1669
- const heightFactor = (y + mainPeakHeight * 0.5) / mainPeakHeight;
1670
- const noise = (Math.random() - 0.5) * mainPeakWidth * 0.3 * (1 - heightFactor);
1671
-
1672
- vertices[j] += noise;
1673
- vertices[j + 2] += noise;
1674
- }
1675
-
1676
- mainPeakGeometry.computeVertexNormals();
1677
-
1678
- const mainPeakMaterial = new THREE.MeshLambertMaterial({
1679
- color: range.color,
1680
- flatShading: true,
1681
- fog: true
1682
- });
1683
-
1684
- const mainPeak = new THREE.Mesh(mainPeakGeometry, mainPeakMaterial);
1685
- mountainGroup.add(mainPeak);
1686
-
1687
- // μ£Όλ³€ μž‘μ€ λ΄‰μš°λ¦¬λ“€ μΆ”κ°€
1688
- const subPeakCount = 2 + Math.floor(Math.random() * 3);
1689
- for (let j = 0; j < subPeakCount; j++) {
1690
- const subPeakHeight = mainPeakHeight * (0.5 + Math.random() * 0.3);
1691
- const subPeakWidth = mainPeakWidth * (0.6 + Math.random() * 0.3);
1692
-
1693
- const subPeakGeometry = new THREE.ConeGeometry(subPeakWidth, subPeakHeight, 8);
1694
- const subPeak = new THREE.Mesh(subPeakGeometry, mainPeakMaterial);
1695
-
1696
- // 메인 λ΄‰μš°λ¦¬ 주변에 배치
1697
- const offsetAngle = (j / subPeakCount) * Math.PI * 2;
1698
- const offsetDistance = mainPeakWidth * (0.6 + Math.random() * 0.4);
1699
-
1700
- subPeak.position.set(
1701
- Math.cos(offsetAngle) * offsetDistance,
1702
- -mainPeakHeight * 0.3,
1703
- Math.sin(offsetAngle) * offsetDistance
1704
- );
1705
-
1706
- mountainGroup.add(subPeak);
1707
- }
1708
-
1709
- // μ‚° κΈ°λ°˜λΆ€ (더 μžμ—°μŠ€λŸ¬μš΄ 경사)
1710
- const baseGeometry = new THREE.CylinderGeometry(
1711
- mainPeakWidth * 1.5,
1712
- mainPeakWidth * 2.5,
1713
- mainPeakHeight * 0.3,
1714
- 12
1715
- );
1716
- const baseMaterial = new THREE.MeshLambertMaterial({
1717
- color: new THREE.Color(range.color).multiplyScalar(0.8),
1718
- flatShading: true,
1719
- fog: true
1720
- });
1721
- const base = new THREE.Mesh(baseGeometry, baseMaterial);
1722
- base.position.y = -mainPeakHeight * 0.35;
1723
- mountainGroup.add(base);
1724
-
1725
- // μ‚° κ·Έλ£Ή μœ„μΉ˜ μ„€μ •
1726
- mountainGroup.position.set(
1727
- Math.cos(angle) * distance,
1728
- mainPeakHeight * 0.5,
1729
- Math.sin(angle) * distance
1730
- );
1731
-
1732
- // μ•½κ°„μ˜ 랜덀 νšŒμ „
1733
- mountainGroup.rotation.y = Math.random() * Math.PI;
1734
-
1735
- // 거리별 색상: 멀리 μžˆλŠ” μ‚°μΌμˆ˜λ‘ 더 ν‘Έλ₯Έμƒ‰μœΌλ‘œ 보이도둝 μ„€μ •
1736
- mountainGroup.layers.set(0);
1737
-
1738
- this.scene.add(mountainGroup);
1739
- }
1740
- });
1741
-
1742
- // κ°€μž₯ λ¨Ό 거리에 κ±°λŒ€ν•œ μ‚°λ§₯ 싀루엣 μΆ”κ°€
1743
- this.createDistantMountainSilhouette();
1744
- }
1745
-
1746
- createDistantMountainSilhouette() {
1747
- // 원거리 μ‚°λ§₯ 싀루엣 (2D 평면)
1748
- const silhouetteDistance = GAME_CONSTANTS.MAP_SIZE * 1.2;
1749
- const segments = 50;
1750
-
1751
- for (let side = 0; side < 4; side++) {
1752
- const curve = new THREE.CatmullRomCurve3([]);
1753
-
1754
- // μ‚°λ§₯ 싀루엣 곑선 생성
1755
- for (let i = 0; i <= segments; i++) {
1756
- const t = i / segments;
1757
- const x = (t - 0.5) * GAME_CONSTANTS.MAP_SIZE;
1758
- const baseHeight = 3000;
1759
- const variation = Math.sin(t * Math.PI * 3) * 2000 +
1760
- Math.sin(t * Math.PI * 7) * 1000 +
1761
- Math.sin(t * Math.PI * 13) * 500;
1762
- const y = baseHeight + variation + Math.random() * 500;
1763
-
1764
- curve.points.push(new THREE.Vector3(x, y, 0));
1765
- }
1766
-
1767
- // 싀루엣 λ©”μ‹œ 생성
1768
- const points = curve.getPoints(100);
1769
- const shape = new THREE.Shape();
1770
-
1771
- shape.moveTo(points[0].x, 0);
1772
- points.forEach(p => shape.lineTo(p.x, p.y));
1773
- shape.lineTo(points[points.length - 1].x, 0);
1774
- shape.lineTo(points[0].x, 0);
1775
-
1776
- const silhouetteGeometry = new THREE.ShapeGeometry(shape);
1777
- const silhouetteMaterial = new THREE.MeshBasicMaterial({
1778
- color: 0x2a3b4a,
1779
- side: THREE.DoubleSide,
1780
- fog: true,
1781
- transparent: true,
1782
- opacity: 0.7
1783
- });
1784
-
1785
- const silhouette = new THREE.Mesh(silhouetteGeometry, silhouetteMaterial);
1786
-
1787
- // 각 면에 배치
1788
- const angle = (side / 4) * Math.PI * 2;
1789
- silhouette.position.set(
1790
- Math.cos(angle) * silhouetteDistance,
1791
- 0,
1792
- Math.sin(angle) * silhouetteDistance
1793
- );
1794
- silhouette.lookAt(0, 0, 0);
1795
-
1796
- this.scene.add(silhouette);
1797
- }
1798
- }
1799
 
1800
  addClouds() {
1801
  // λ‹€μΈ΅ ꡬ름 μ‹œμŠ€ν…œ
@@ -2051,11 +1488,11 @@ class Game {
2051
  const rollDegrees = this.fighter.rotation.z * (180 / Math.PI);
2052
  hudElement.style.transform = `translate(-50%, -50%) rotate(${-rollDegrees}deg)`;
2053
 
2054
- // ν”ΌμΉ˜ λž˜λ” μ—…λ°μ΄νŠΈ - μˆ˜μ •λœ λΆ€λΆ„
2055
  const pitchLadder = document.getElementById('pitchLadder');
2056
  if (pitchLadder) {
2057
  const pitchDegrees = this.fighter.rotation.x * (180 / Math.PI);
2058
- // μˆ˜μ •: 음수λ₯Ό κ³±ν•΄μ„œ λ°˜λŒ€ λ°©ν–₯으둜, 10도당 20ν”½μ…€
2059
  const pitchOffset = -pitchDegrees * 2;
2060
  pitchLadder.style.transform = `translateY(${pitchOffset}px)`;
2061
  }
@@ -2164,7 +1601,6 @@ class Game {
2164
  return { x, y };
2165
  }
2166
 
2167
- // Game 클래슀의 updateWarnings λ©”μ„œλ“œ μˆ˜μ •
2168
  updateWarnings() {
2169
  // κΈ°μ‘΄ κ²½κ³  λ©”μ‹œμ§€ 제거
2170
  const existingWarnings = document.querySelectorAll('.warning-message');
 
461
  this.stallEscapeProgress = 0; // μŠ€ν†¨ μ§„μž… μ‹œ 진행도 μ΄ˆκΈ°ν™”
462
  }
463
 
464
+ // μŠ€ν†¨ νƒˆμΆœ 쑰건
 
 
 
 
 
 
465
  if (this.stallWarning) {
466
+ if (this.escapeKeyPressed) {
467
+ // Fν‚€λ₯Ό λˆ„λ₯΄κ³  있으면 진행도 증가
468
+ this.stallEscapeProgress += deltaTime;
469
+
470
+ // 2초 이상 λˆ„λ₯΄λ©΄ μŠ€ν†¨μ—μ„œ νƒˆμΆœ
471
+ if (this.stallEscapeProgress >= 2.0 && speedKnots >= 350) {
472
+ this.stallWarning = false;
473
+ this.stallEscapeProgress = 0;
474
+ // μŠ€ν†¨μ—μ„œ λ²—μ–΄λ‚˜λ©΄ μžμ„Έλ₯Ό μ•½κ°„ μ•ˆμ •ν™”
475
+ this.targetPitch = Math.max(-0.2, Math.min(0.2, this.targetPitch));
476
+ }
477
  } else {
478
+ // Fν‚€λ₯Ό λ†“μœΌλ©΄ 진행도 κ°μ†Œ
479
+ this.stallEscapeProgress = Math.max(0, this.stallEscapeProgress - deltaTime * 2);
480
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
481
  }
482
 
483
  // 속도 λ³€ν™” 적용
 
1233
  // κ°œμ„ λœ ꡬ름 μΆ”κ°€
1234
  this.addClouds();
1235
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1236
 
1237
  addClouds() {
1238
  // λ‹€μΈ΅ ꡬ름 μ‹œμŠ€ν…œ
 
1488
  const rollDegrees = this.fighter.rotation.z * (180 / Math.PI);
1489
  hudElement.style.transform = `translate(-50%, -50%) rotate(${-rollDegrees}deg)`;
1490
 
1491
+ // ν”ΌμΉ˜ λž˜λ” μ—…λ°μ΄νŠΈ
1492
  const pitchLadder = document.getElementById('pitchLadder');
1493
  if (pitchLadder) {
1494
  const pitchDegrees = this.fighter.rotation.x * (180 / Math.PI);
1495
+ // 음수λ₯Ό κ³±ν•΄μ„œ λ°˜λŒ€ λ°©ν–₯으둜, 10도당 20ν”½μ…€
1496
  const pitchOffset = -pitchDegrees * 2;
1497
  pitchLadder.style.transform = `translateY(${pitchOffset}px)`;
1498
  }
 
1601
  return { x, y };
1602
  }
1603
 
 
1604
  updateWarnings() {
1605
  // κΈ°μ‘΄ κ²½κ³  λ©”μ‹œμ§€ 제거
1606
  const existingWarnings = document.querySelectorAll('.warning-message');