cutechicken commited on
Commit
ab5cbc8
ยท
verified ยท
1 Parent(s): ae1840c

Update game.js

Browse files
Files changed (1) hide show
  1. game.js +126 -203
game.js CHANGED
@@ -441,11 +441,11 @@ class Fighter {
441
 
442
  // 1.5์ดˆ ์ด์ƒ Over-G ์ƒํƒœ์ผ ๊ฒฝ์šฐ ์‹œ์•ผ ํ๋ฆผ ์‹œ์ž‘
443
  if (this.overGTimer > 1.5) {
444
- // ์†๋„ ๊ธ‰๊ฒฉํžˆ ๊ฐ์†Œ (1.75์ดˆ๋ถ€ํ„ฐ)
445
- if (this.overGTimer > 1.75) {
446
- targetSpeed *= Math.max(0, 1 - (this.overGTimer - 1.75) * 0.5);
447
  }
448
- // ์‹œ์•ผ ํ๋ฆผ ํšจ๊ณผ๋Š” UI์—์„œ ์ฒ˜๋ฆฌ (1์ดˆ๋ถ€ํ„ฐ)
449
  }
450
  } else {
451
  this.overGTimer = 0; // Over-G ์ƒํƒœ๊ฐ€ ์•„๋‹ˆ๋ฉด ํƒ€์ด๋จธ ๋ฆฌ์…‹
@@ -697,31 +697,29 @@ class EnemyFighter {
697
 
698
  // ๊ฐœ์„ ๋œ AI ์ƒํƒœ
699
  this.aiState = 'patrol';
700
- this.targetPosition = position.clone();
701
- this.patrolCenter = position.clone();
702
- this.patrolRadius = 3000;
703
- this.lastStateChange = 0;
704
  this.playerFighter = null; // ํ”Œ๋ ˆ์ด์–ด ์ฐธ์กฐ ์ €์žฅ์šฉ
705
 
706
- // ํ˜„์‹ค์ ์ธ ๋น„ํ–‰ ๋ฌผ๋ฆฌ
707
- this.targetRotation = new THREE.Euler(0, 0, 0);
708
- this.currentRoll = 0;
709
- this.currentPitch = 0;
710
- this.currentYaw = 0;
711
- this.throttle = 0.75; // ๊ธฐ๋ณธ ์Šค๋กœํ‹€ 75%
712
-
713
- // ํšŒํ”ผ ๊ธฐ๋™
714
- this.evasionTimer = 0;
715
- this.evasionPattern = 0;
716
 
717
  // ๋‹ค๋ฅธ ์ ๊ธฐ๋“ค๊ณผ์˜ ์ถฉ๋Œ ํšŒํ”ผ
718
  this.nearbyEnemies = [];
719
  this.separationRadius = 300; // ์ตœ์†Œ ๊ฑฐ๋ฆฌ 300m
720
-
721
- // ๊ณต๊ฒฉ ํŒจํ„ด
722
- this.attackAngle = 0;
723
- this.attackDistance = 0;
724
- this.lastAttackTime = 0;
 
 
 
 
 
725
  }
726
 
727
  async initialize(loader) {
@@ -778,62 +776,84 @@ class EnemyFighter {
778
  const currentTime = Date.now();
779
  const distanceToPlayer = this.position.distanceTo(playerPosition);
780
 
781
- // AI ์ƒํƒœ ๊ฒฐ์ •
 
782
  if (distanceToPlayer < 3000) {
783
- if (this.aiState !== 'combat') {
784
- this.aiState = 'combat';
785
- this.lastStateChange = currentTime;
 
 
 
 
 
 
 
 
786
  }
787
- } else if (distanceToPlayer < 6000) {
788
- if (this.aiState !== 'approach') {
789
- this.aiState = 'approach';
790
- this.lastStateChange = currentTime;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
791
  }
792
  } else {
793
- if (this.aiState !== 'patrol') {
794
- this.aiState = 'patrol';
795
- this.lastStateChange = currentTime;
796
- }
797
  }
798
 
799
- // ์ƒํƒœ๋ณ„ ํ–‰๋™
800
- switch(this.aiState) {
801
- case 'combat':
802
- this.updateCombatBehavior(playerPosition, deltaTime, currentTime);
803
- break;
804
- case 'approach':
805
- this.updateApproachBehavior(playerPosition, deltaTime);
806
- break;
807
- case 'patrol':
808
- this.updatePatrolBehavior(deltaTime);
809
- break;
810
  }
811
 
812
  // ๋‹ค๋ฅธ ์ ๊ธฐ์™€์˜ ์ถฉ๋Œ ํšŒํ”ผ
813
  this.avoidOtherEnemies(deltaTime);
814
 
815
- // ๋ถ€๋“œ๋Ÿฌ์šด ํšŒ์ „ ๋ณด๊ฐ„
816
  this.rotation.x = THREE.MathUtils.lerp(this.rotation.x, this.targetRotation.x, deltaTime * 2.0);
817
- this.rotation.y = THREE.MathUtils.lerp(this.rotation.y, this.targetRotation.y, deltaTime * 3.0);
818
  this.rotation.z = THREE.MathUtils.lerp(this.rotation.z, this.targetRotation.z, deltaTime * 2.5);
819
 
820
- // ์†๋„ ๊ณ„์‚ฐ (์Šค๋กœํ‹€ ๊ธฐ๋ฐ˜)
821
- const targetSpeed = this.speed * this.throttle;
822
- const currentSpeed = this.velocity.length();
823
- const newSpeed = THREE.MathUtils.lerp(currentSpeed, targetSpeed, deltaTime * 0.5);
824
-
825
- // ๋ฐฉํ–ฅ ๋ฒกํ„ฐ ๊ณ„์‚ฐ
826
  const forward = new THREE.Vector3(0, 0, 1);
827
  forward.applyEuler(this.rotation);
828
-
829
- // ์ค‘๋ ฅ๊ณผ ์–‘๋ ฅ ํšจ๊ณผ
830
- const liftFactor = Math.min(1.0, newSpeed / this.speed);
831
- const gravityEffect = GAME_CONSTANTS.GRAVITY * deltaTime * 0.15;
832
- const lift = gravityEffect * liftFactor * 0.8;
833
-
834
- // ์†๋„ ๋ฒกํ„ฐ ์—…๋ฐ์ดํŠธ
835
- this.velocity = forward.multiplyScalar(newSpeed);
836
- this.velocity.y += (lift - gravityEffect);
837
 
838
  // ์œ„์น˜ ์—…๋ฐ์ดํŠธ
839
  this.position.add(this.velocity.clone().multiplyScalar(deltaTime));
@@ -841,20 +861,23 @@ class EnemyFighter {
841
  // ๊ณ ๋„ ์ œํ•œ
842
  if (this.position.y < 500) {
843
  this.position.y = 500;
844
- this.targetRotation.x = Math.max(this.targetRotation.x, -0.2); // ๊ธฐ์ˆ˜๋ฅผ ๋“ค์–ด ์ƒ์Šน
845
  }
846
  if (this.position.y > GAME_CONSTANTS.MAX_ALTITUDE - 500) {
847
  this.position.y = GAME_CONSTANTS.MAX_ALTITUDE - 500;
848
- this.targetRotation.x = Math.min(this.targetRotation.x, 0.2); // ๊ธฐ์ˆ˜๋ฅผ ๋‚ด๋ ค ํ•˜๊ฐ•
849
  }
850
 
851
  // ๋งต ๊ฒฝ๊ณ„ ์ฒ˜๋ฆฌ
852
  const mapLimit = GAME_CONSTANTS.MAP_SIZE / 2;
853
- if (Math.abs(this.position.x) > mapLimit * 0.8 || Math.abs(this.position.z) > mapLimit * 0.8) {
854
- // ๋งต ๊ฒฝ๊ณ„์— ๊ฐ€๊นŒ์›Œ์ง€๋ฉด ์ค‘์•™์œผ๋กœ ์„ ํšŒ
855
- const centerDirection = this.position.clone().negate().normalize();
856
- const angle = Math.atan2(centerDirection.x, centerDirection.z);
857
- this.targetRotation.y = angle;
 
 
 
858
  }
859
 
860
  // ๋ฉ”์‹œ ์—…๋ฐ์ดํŠธ
@@ -866,132 +889,37 @@ class EnemyFighter {
866
  this.updateBullets(deltaTime);
867
  }
868
 
869
- updateCombatBehavior(playerPosition, deltaTime, currentTime) {
870
- const toPlayer = playerPosition.clone().sub(this.position);
871
- const distance = toPlayer.length();
872
- toPlayer.normalize();
873
-
874
- // ๊ณต๊ฒฉ ๊ฐ๋„ ๊ณ„์‚ฐ
875
- const angle = Math.atan2(toPlayer.x, toPlayer.z);
876
- const pitchAngle = Math.atan2(-toPlayer.y, Math.sqrt(toPlayer.x * toPlayer.x + toPlayer.z * toPlayer.z));
877
-
878
- // BFM (Basic Fighter Maneuvers) ๊ตฌํ˜„
879
- if (distance < 1000) {
880
- // ๋„ˆ๋ฌด ๊ฐ€๊นŒ์šฐ๋ฉด ํšŒํ”ผ ๊ธฐ๋™
881
- this.evasionTimer += deltaTime;
882
- if (this.evasionTimer > 2.0) {
883
- this.evasionTimer = 0;
884
- this.evasionPattern = Math.floor(Math.random() * 3);
885
- }
886
-
887
- switch(this.evasionPattern) {
888
- case 0: // ๋ฐฐ๋Ÿด ๋กค
889
- this.targetRotation.z = Math.sin(this.evasionTimer * Math.PI) * 1.5;
890
- this.targetRotation.x = pitchAngle + Math.sin(this.evasionTimer * Math.PI * 2) * 0.3;
891
- break;
892
- case 1: // ์ด๋ฉœ๋งŒ ํ„ด
893
- this.targetRotation.x = -0.5 + this.evasionTimer * 0.5;
894
- this.targetRotation.z = this.evasionTimer * Math.PI;
895
- break;
896
- case 2: // ์Šคํ”Œ๋ฆฟ S
897
- this.targetRotation.x = 0.5 - this.evasionTimer * 0.5;
898
- this.targetRotation.z = Math.PI;
899
- break;
900
- }
901
- this.throttle = 1.0; // ์ตœ๋Œ€ ์†๋„๋กœ ํšŒํ”ผ
902
- } else if (distance < 2000) {
903
- // ๊ณต๊ฒฉ ์œ„์น˜ ์„ ์ •
904
- this.targetRotation.y = angle;
905
- this.targetRotation.x = pitchAngle;
906
- this.targetRotation.z = 0;
907
-
908
- // ๋ฆฌ๋“œ ์•ต๊ธ€ ๊ณ„์‚ฐ (์˜ˆ์ธก ์‚ฌ๊ฒฉ)
909
- const playerVelocity = this.playerFighter ? this.playerFighter.velocity : new THREE.Vector3();
910
- const bulletTime = distance / 1200; // ํƒ„ํ™˜ ์†๋„
911
- const leadPosition = playerPosition.clone().add(playerVelocity.clone().multiplyScalar(bulletTime));
912
- const leadDirection = leadPosition.sub(this.position).normalize();
913
- const leadAngle = Math.atan2(leadDirection.x, leadDirection.z);
914
-
915
- this.targetRotation.y = leadAngle;
916
-
917
- // ์‚ฌ๊ฒฉ
918
- if (currentTime - this.lastShootTime > 800) {
919
- this.shoot();
920
- }
921
-
922
- this.throttle = 0.9;
923
- } else {
924
- // ์ถ”์ 
925
- this.targetRotation.y = angle;
926
- this.targetRotation.x = pitchAngle * 0.5; // ๋ถ€๋“œ๋Ÿฌ์šด ํ”ผ์น˜ ์กฐ์ •
927
- this.targetRotation.z = 0;
928
- this.throttle = 0.85;
929
- }
930
- }
931
-
932
- updateApproachBehavior(playerPosition, deltaTime) {
933
- const toPlayer = playerPosition.clone().sub(this.position);
934
- const angle = Math.atan2(toPlayer.x, toPlayer.z);
935
-
936
- // ๋ถ€๋“œ๋Ÿฌ์šด ์ ‘๊ทผ
937
- this.targetRotation.y = angle;
938
- this.targetRotation.x = 0;
939
- this.targetRotation.z = Math.sin(Date.now() * 0.001) * 0.3; // ์•ฝ๊ฐ„์˜ ๋กค๋ง
940
- this.throttle = 0.8;
941
- }
942
-
943
- updatePatrolBehavior(deltaTime) {
944
- // ์ˆœ์ฐฐ ํŒจํ„ด
945
- if (this.position.distanceTo(this.targetPosition) < 500) {
946
- // ์ƒˆ๋กœ์šด ์ˆœ์ฐฐ ์ง€์  ์„ค์ •
947
- const angle = Math.random() * Math.PI * 2;
948
- const distance = this.patrolRadius * (0.5 + Math.random() * 0.5);
949
- this.targetPosition = this.patrolCenter.clone().add(
950
- new THREE.Vector3(
951
- Math.cos(angle) * distance,
952
- (Math.random() - 0.5) * 1000,
953
- Math.sin(angle) * distance
954
- )
955
- );
956
- }
957
-
958
- const toTarget = this.targetPosition.clone().sub(this.position);
959
- const angle = Math.atan2(toTarget.x, toTarget.z);
960
-
961
- this.targetRotation.y = angle;
962
- this.targetRotation.x = 0;
963
- this.targetRotation.z = Math.sin(Date.now() * 0.0005) * 0.2; // ์™„๋งŒํ•œ ๋กค๋ง
964
- this.throttle = 0.7;
965
- }
966
-
967
  avoidOtherEnemies(deltaTime) {
968
  if (!this.nearbyEnemies) return;
969
 
970
- let avoidanceVector = new THREE.Vector3();
971
- let tooClose = false;
 
972
 
973
  this.nearbyEnemies.forEach(enemy => {
974
  if (enemy === this || !enemy.position) return;
975
 
976
  const distance = this.position.distanceTo(enemy.position);
977
  if (distance < this.separationRadius && distance > 0) {
978
- // ๋ฐ˜๋ฐœ๋ ฅ ๊ณ„์‚ฐ
979
- const force = this.position.clone().sub(enemy.position).normalize();
980
- const strength = 1 - (distance / this.separationRadius);
981
- avoidanceVector.add(force.multiplyScalar(strength));
982
- tooClose = true;
 
983
  }
984
  });
985
 
986
- if (tooClose) {
987
- // ํšŒํ”ผ ๋ฐฉํ–ฅ์œผ๋กœ ์„ ํšŒ
988
- const avoidAngle = Math.atan2(avoidanceVector.x, avoidanceVector.z);
989
- this.targetRotation.y = THREE.MathUtils.lerp(this.targetRotation.y, avoidAngle, deltaTime * 5);
 
990
 
991
- // ๊ณ ๋„ ๋ณ€๊ฒฝ์œผ๋กœ ์ถ”๊ฐ€ ํšŒํ”ผ
992
- if (avoidanceVector.y > 0.5) {
993
  this.targetRotation.x = -0.3; // ์ƒ์Šน
994
- } else if (avoidanceVector.y < -0.5) {
995
  this.targetRotation.x = 0.3; // ํ•˜๊ฐ•
996
  }
997
  }
@@ -1734,7 +1662,7 @@ class Game {
1734
  }
1735
 
1736
  // Over-G ์‹œ์•ผ ํšจ๊ณผ - ์ˆ˜์ •๋œ ๋ถ€๋ถ„
1737
- if (this.fighter.overG && this.fighter.overGTimer > 0) {
1738
  let blurEffect = document.getElementById('overGBlurEffect');
1739
  if (!blurEffect) {
1740
  blurEffect = document.createElement('div');
@@ -1743,19 +1671,17 @@ class Game {
1743
  }
1744
 
1745
  // Over-G ์ง€์† ์‹œ๊ฐ„์— ๋”ฐ๋ผ ์ ์ง„์ ์œผ๋กœ ์–ด๋‘์›Œ์ง
1746
- // 0์ดˆ: ํšจ๊ณผ ์—†์Œ
1747
- // 1์ดˆ: ๊ฑฐ์˜ ์™„์ „ํžˆ ์–ด๋‘์›Œ์ง
1748
- const darknessFactor = Math.min(this.fighter.overGTimer, 1.0); // 0~1 ๋ฒ”์œ„๋กœ ์ œํ•œ
1749
 
1750
  // ์‹œ์•ผ ๊ฐ€์žฅ์ž๋ฆฌ๋ถ€ํ„ฐ ์–ด๋‘์›Œ์ง€๋Š” ํšจ๊ณผ
1751
  // ์ค‘์•™์€ ์ƒ๋Œ€์ ์œผ๋กœ ๋Šฆ๊ฒŒ ์–ด๋‘์›Œ์ง
1752
- const centerTransparency = Math.max(0, 1 - darknessFactor * 1.2); // ์ค‘์•™ ํˆฌ๋ช…๋„
1753
- const midTransparency = Math.max(0, 1 - darknessFactor * 0.8); // ์ค‘๊ฐ„ ํˆฌ๋ช…๋„
1754
- const edgeOpacity = Math.min(0.95, darknessFactor * 0.95); // ๊ฐ€์žฅ์ž๋ฆฌ ๋ถˆํˆฌ๋ช…๋„
1755
-
1756
- // ๋ถ‰์€ ์ƒ‰์กฐ ์ถ”๊ฐ€ (ํ˜ˆ์•ก ์ˆœํ™˜ ๋ฌธ์ œ๋ฅผ ์‹œ๊ฐํ™”) - ์•ฝํ™”๋œ ๋ฒ„์ „
1757
- const redTint = Math.min(darknessFactor * 0.1, 0.1); // 0.3์—์„œ 0.1๋กœ ๊ฐ์†Œ
1758
 
 
1759
  blurEffect.style.cssText = `
1760
  position: fixed;
1761
  top: 0;
@@ -1763,24 +1689,21 @@ class Game {
1763
  width: 100%;
1764
  height: 100%;
1765
  background: radial-gradient(ellipse at center,
1766
- rgba(${Math.floor(255 * redTint)}, 0, 0, ${1 - centerTransparency}) 0%,
1767
- rgba(${Math.floor(150 * redTint)}, 0, 0, ${1 - midTransparency}) 40%,
1768
- rgba(0, 0, 0, ${edgeOpacity}) 70%,
1769
- rgba(0, 0, 0, ${Math.min(0.98, edgeOpacity + 0.05)}) 100%);
1770
  pointer-events: none;
1771
  z-index: 1400;
1772
- transition: background 0.1s ease-out;
1773
  `;
1774
 
1775
- // ์‹ฌํ•œ Over-G ์ƒํƒœ์—์„œ ํ™”๋ฉด ํ”๋“ค๋ฆผ ํšจ๊ณผ
1776
- if (darknessFactor > 0.7) {
1777
- const shake = (1 - Math.random() * 2) * 2;
1778
  blurEffect.style.transform = `translate(${shake}px, ${shake}px)`;
1779
  }
1780
 
1781
- // ๋””๋ฒ„๊ทธ ์ •๋ณด (์„ ํƒ์‚ฌํ•ญ)
1782
- // console.log(`Over-G Timer: ${this.fighter.overGTimer.toFixed(2)}s, Darkness: ${(darknessFactor * 100).toFixed(0)}%`);
1783
-
1784
  } else {
1785
  // Over-G ์ƒํƒœ๊ฐ€ ์•„๋‹ˆ๋ฉด ํšจ๊ณผ ์ œ๊ฑฐ
1786
  const blurEffect = document.getElementById('overGBlurEffect');
 
441
 
442
  // 1.5์ดˆ ์ด์ƒ Over-G ์ƒํƒœ์ผ ๊ฒฝ์šฐ ์‹œ์•ผ ํ๋ฆผ ์‹œ์ž‘
443
  if (this.overGTimer > 1.5) {
444
+ // ์†๋„ ๊ธ‰๊ฒฉํžˆ ๊ฐ์†Œ (2.5์ดˆ๋ถ€ํ„ฐ)
445
+ if (this.overGTimer > 2.5) {
446
+ targetSpeed *= Math.max(0.3, 1 - (this.overGTimer - 2.5) * 0.3);
447
  }
448
+ // ์‹œ์•ผ ํ๋ฆผ ํšจ๊ณผ๋Š” UI์—์„œ ์ฒ˜๋ฆฌ (1.5์ดˆ๋ถ€ํ„ฐ)
449
  }
450
  } else {
451
  this.overGTimer = 0; // Over-G ์ƒํƒœ๊ฐ€ ์•„๋‹ˆ๋ฉด ํƒ€์ด๋จธ ๋ฆฌ์…‹
 
697
 
698
  // ๊ฐœ์„ ๋œ AI ์ƒํƒœ
699
  this.aiState = 'patrol';
700
+ this.targetPosition = this.generateRandomTarget();
701
+ this.lastDirectionChange = Date.now();
 
 
702
  this.playerFighter = null; // ํ”Œ๋ ˆ์ด์–ด ์ฐธ์กฐ ์ €์žฅ์šฉ
703
 
704
+ // ๋ถ€๋“œ๋Ÿฌ์šด ์„ ํšŒ๋ฅผ ์œ„ํ•œ ๋ณ€์ˆ˜
705
+ this.targetRotation = new THREE.Euler(0, Math.random() * Math.PI * 2, 0);
706
+ this.turnRadius = 1500; // ์„ ํšŒ ๋ฐ˜๊ฒฝ
707
+ this.isTurning = false;
708
+ this.turnDirection = 1; // 1: ์šฐํšŒ์ „, -1: ์ขŒํšŒ์ „
 
 
 
 
 
709
 
710
  // ๋‹ค๋ฅธ ์ ๊ธฐ๋“ค๊ณผ์˜ ์ถฉ๋Œ ํšŒํ”ผ
711
  this.nearbyEnemies = [];
712
  this.separationRadius = 300; // ์ตœ์†Œ ๊ฑฐ๋ฆฌ 300m
713
+ }
714
+
715
+ generateRandomTarget() {
716
+ // ๋งต ๋ฒ”์œ„ ๋‚ด ๋žœ๋ค ๋ชฉํ‘œ ์ง€์  ์ƒ์„ฑ
717
+ const mapLimit = GAME_CONSTANTS.MAP_SIZE / 2 * 0.8;
718
+ return new THREE.Vector3(
719
+ (Math.random() - 0.5) * 2 * mapLimit,
720
+ 1000 + Math.random() * 3000, // 1000m ~ 4000m ๊ณ ๋„
721
+ (Math.random() - 0.5) * 2 * mapLimit
722
+ );
723
  }
724
 
725
  async initialize(loader) {
 
776
  const currentTime = Date.now();
777
  const distanceToPlayer = this.position.distanceTo(playerPosition);
778
 
779
+ // ํ”Œ๋ ˆ์ด์–ด ๊ฐ์ง€ ๋ฐ ๊ณต๊ฒฉ
780
+ let shouldAttack = false;
781
  if (distanceToPlayer < 3000) {
782
+ // ํ”Œ๋ ˆ์ด์–ด๊ฐ€ ์‹œ์•ผ๊ฐ ๋‚ด์— ์žˆ๋Š”์ง€ ํ™•์ธ
783
+ const toPlayer = playerPosition.clone().sub(this.position).normalize();
784
+ const forward = new THREE.Vector3(0, 0, 1);
785
+ forward.applyEuler(this.rotation);
786
+ const angle = forward.dot(toPlayer);
787
+
788
+ if (angle > 0.5) { // 120๋„ ์‹œ์•ผ๊ฐ
789
+ shouldAttack = true;
790
+ if (distanceToPlayer < 2000 && currentTime - this.lastShootTime > 800) {
791
+ this.shoot();
792
+ }
793
  }
794
+ }
795
+
796
+ // ๋ชฉํ‘œ ์ง€์  ๋„๋‹ฌ ๋˜๋Š” ์ผ์ • ์‹œ๊ฐ„๋งˆ๋‹ค ์ƒˆ๋กœ์šด ๋ชฉํ‘œ ์„ค์ •
797
+ if (this.position.distanceTo(this.targetPosition) < 500 ||
798
+ currentTime - this.lastDirectionChange > 15000) { // 15์ดˆ๋งˆ๋‹ค ๋ฐฉํ–ฅ ๋ณ€๊ฒฝ
799
+ this.targetPosition = this.generateRandomTarget();
800
+ this.lastDirectionChange = currentTime;
801
+ this.isTurning = false;
802
+ }
803
+
804
+ // ๋ชฉํ‘œ ๋ฐฉํ–ฅ ๊ณ„์‚ฐ
805
+ const toTarget = this.targetPosition.clone().sub(this.position);
806
+ const targetAngle = Math.atan2(toTarget.x, toTarget.z);
807
+ let angleDiff = targetAngle - this.rotation.y;
808
+
809
+ // ๊ฐ๋„ ์ฐจ์ด๋ฅผ -PI ~ PI ๋ฒ”์œ„๋กœ ์ •๊ทœํ™”
810
+ while (angleDiff > Math.PI) angleDiff -= Math.PI * 2;
811
+ while (angleDiff < -Math.PI) angleDiff += Math.PI * 2;
812
+
813
+ // ํฐ ๊ฐ๋„ ์ฐจ์ด๊ฐ€ ์žˆ์œผ๋ฉด ์›ํ˜• ์„ ํšŒ
814
+ if (Math.abs(angleDiff) > Math.PI / 6) { // 30๋„ ์ด์ƒ
815
+ this.isTurning = true;
816
+ this.turnDirection = angleDiff > 0 ? 1 : -1;
817
+ }
818
+
819
+ // ์„ ํšŒ ์ค‘์ผ ๋•Œ
820
+ if (this.isTurning) {
821
+ // ์›ํ˜• ์„ ํšŒ ๊ตฌํ˜„
822
+ const turnRate = (this.speed / this.turnRadius) * this.turnDirection;
823
+ this.targetRotation.y += turnRate * deltaTime;
824
+ this.targetRotation.z = this.turnDirection * 0.5; // ์„ ํšŒ ์‹œ ๋กค
825
+
826
+ // ๋ชฉํ‘œ ๊ฐ๋„์— ๊ทผ์ ‘ํ•˜๋ฉด ์„ ํšŒ ์ข…๋ฃŒ
827
+ const newAngleDiff = targetAngle - this.targetRotation.y;
828
+ if (Math.abs(newAngleDiff) < Math.PI / 12) { // 15๋„ ์ด๋‚ด
829
+ this.isTurning = false;
830
  }
831
  } else {
832
+ // ์ง์ง„ ๋น„ํ–‰
833
+ this.targetRotation.y = THREE.MathUtils.lerp(this.targetRotation.y, targetAngle, deltaTime * 0.5);
834
+ this.targetRotation.z = THREE.MathUtils.lerp(this.targetRotation.z, 0, deltaTime * 2);
 
835
  }
836
 
837
+ // ๊ณ ๋„ ์กฐ์ ˆ
838
+ const altitudeDiff = this.targetPosition.y - this.position.y;
839
+ if (Math.abs(altitudeDiff) > 100) {
840
+ this.targetRotation.x = Math.max(-0.3, Math.min(0.3, altitudeDiff * 0.0001));
841
+ } else {
842
+ this.targetRotation.x = 0;
 
 
 
 
 
843
  }
844
 
845
  // ๋‹ค๋ฅธ ์ ๊ธฐ์™€์˜ ์ถฉ๋Œ ํšŒํ”ผ
846
  this.avoidOtherEnemies(deltaTime);
847
 
848
+ // ๋ถ€๋“œ๋Ÿฌ์šด ํšŒ์ „ ์ ์šฉ
849
  this.rotation.x = THREE.MathUtils.lerp(this.rotation.x, this.targetRotation.x, deltaTime * 2.0);
850
+ this.rotation.y = THREE.MathUtils.lerp(this.rotation.y, this.targetRotation.y, deltaTime * 2.0);
851
  this.rotation.z = THREE.MathUtils.lerp(this.rotation.z, this.targetRotation.z, deltaTime * 2.5);
852
 
853
+ // ์†๋„ ๋ฒกํ„ฐ ๊ณ„์‚ฐ
 
 
 
 
 
854
  const forward = new THREE.Vector3(0, 0, 1);
855
  forward.applyEuler(this.rotation);
856
+ this.velocity = forward.multiplyScalar(this.speed);
 
 
 
 
 
 
 
 
857
 
858
  // ์œ„์น˜ ์—…๋ฐ์ดํŠธ
859
  this.position.add(this.velocity.clone().multiplyScalar(deltaTime));
 
861
  // ๊ณ ๋„ ์ œํ•œ
862
  if (this.position.y < 500) {
863
  this.position.y = 500;
864
+ this.targetRotation.x = -0.2; // ์ƒ์Šน
865
  }
866
  if (this.position.y > GAME_CONSTANTS.MAX_ALTITUDE - 500) {
867
  this.position.y = GAME_CONSTANTS.MAX_ALTITUDE - 500;
868
+ this.targetRotation.x = 0.2; // ํ•˜๊ฐ•
869
  }
870
 
871
  // ๋งต ๊ฒฝ๊ณ„ ์ฒ˜๋ฆฌ
872
  const mapLimit = GAME_CONSTANTS.MAP_SIZE / 2;
873
+ if (Math.abs(this.position.x) > mapLimit * 0.9 || Math.abs(this.position.z) > mapLimit * 0.9) {
874
+ // ๋งต ๊ฒฝ๊ณ„์— ๊ฐ€๊นŒ์›Œ์ง€๋ฉด ์ค‘์•™์œผ๋กœ ํ–ฅํ•˜๋Š” ์ƒˆ ๋ชฉํ‘œ ์„ค์ •
875
+ this.targetPosition = new THREE.Vector3(
876
+ (Math.random() - 0.5) * mapLimit * 0.5,
877
+ 1000 + Math.random() * 3000,
878
+ (Math.random() - 0.5) * mapLimit * 0.5
879
+ );
880
+ this.lastDirectionChange = currentTime;
881
  }
882
 
883
  // ๋ฉ”์‹œ ์—…๋ฐ์ดํŠธ
 
889
  this.updateBullets(deltaTime);
890
  }
891
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
892
  avoidOtherEnemies(deltaTime) {
893
  if (!this.nearbyEnemies) return;
894
 
895
+ let closestDistance = Infinity;
896
+ let avoidanceNeeded = false;
897
+ let avoidDirection = new THREE.Vector3();
898
 
899
  this.nearbyEnemies.forEach(enemy => {
900
  if (enemy === this || !enemy.position) return;
901
 
902
  const distance = this.position.distanceTo(enemy.position);
903
  if (distance < this.separationRadius && distance > 0) {
904
+ avoidanceNeeded = true;
905
+ if (distance < closestDistance) {
906
+ closestDistance = distance;
907
+ // ๋ฐ˜๋Œ€ ๋ฐฉํ–ฅ๏ฟฝ๏ฟฝ๋กœ ํšŒํ”ผ
908
+ avoidDirection = this.position.clone().sub(enemy.position).normalize();
909
+ }
910
  }
911
  });
912
 
913
+ if (avoidanceNeeded) {
914
+ // ๊ธด๊ธ‰ ํšŒํ”ผ: ๊ธ‰์„ ํšŒ
915
+ const avoidAngle = Math.atan2(avoidDirection.x, avoidDirection.z);
916
+ this.targetRotation.y = avoidAngle;
917
+ this.isTurning = true;
918
 
919
+ // ๊ณ ๋„๋„ ๋ณ€๊ฒฝ
920
+ if (avoidDirection.y > 0) {
921
  this.targetRotation.x = -0.3; // ์ƒ์Šน
922
+ } else {
923
  this.targetRotation.x = 0.3; // ํ•˜๊ฐ•
924
  }
925
  }
 
1662
  }
1663
 
1664
  // Over-G ์‹œ์•ผ ํšจ๊ณผ - ์ˆ˜์ •๋œ ๋ถ€๋ถ„
1665
+ if (this.fighter.overG && this.fighter.overGTimer > 1.5) {
1666
  let blurEffect = document.getElementById('overGBlurEffect');
1667
  if (!blurEffect) {
1668
  blurEffect = document.createElement('div');
 
1671
  }
1672
 
1673
  // Over-G ์ง€์† ์‹œ๊ฐ„์— ๋”ฐ๋ผ ์ ์ง„์ ์œผ๋กœ ์–ด๋‘์›Œ์ง
1674
+ // 1.5์ดˆ๋ถ€ํ„ฐ ์‹œ์ž‘ํ•˜์—ฌ ์„œ์„œํžˆ ์ง„ํ–‰
1675
+ const adjustedTimer = Math.max(0, this.fighter.overGTimer - 1.5); // 1.5์ดˆ ์ดํ›„๋ถ€ํ„ฐ ์นด์šดํŠธ
1676
+ const darknessFactor = Math.min(adjustedTimer / 2.0, 0.7); // 2์ดˆ์— ๊ฑธ์ณ ์ตœ๋Œ€ 70%๊นŒ์ง€๋งŒ ์–ด๋‘์›Œ์ง
1677
 
1678
  // ์‹œ์•ผ ๊ฐ€์žฅ์ž๋ฆฌ๋ถ€ํ„ฐ ์–ด๋‘์›Œ์ง€๋Š” ํšจ๊ณผ
1679
  // ์ค‘์•™์€ ์ƒ๋Œ€์ ์œผ๋กœ ๋Šฆ๊ฒŒ ์–ด๋‘์›Œ์ง
1680
+ const centerTransparency = Math.max(0.3, 1 - darknessFactor * 0.8); // ์ค‘์•™์€ ์ตœ์†Œ 30% ํˆฌ๋ช…๋„ ์œ ์ง€
1681
+ const midTransparency = Math.max(0.2, 1 - darknessFactor * 0.6); // ์ค‘๊ฐ„ ํˆฌ๋ช…๋„
1682
+ const edgeOpacity = Math.min(0.8, darknessFactor * 0.8); // ๊ฐ€์žฅ์ž๋ฆฌ ์ตœ๋Œ€ 80% ๋ถˆํˆฌ๋ช…
 
 
 
1683
 
1684
+ // ๋ถ‰์€ ์ƒ‰์กฐ ์ œ๊ฑฐ, ๊ฒ€์€์ƒ‰๋งŒ ์‚ฌ์šฉ
1685
  blurEffect.style.cssText = `
1686
  position: fixed;
1687
  top: 0;
 
1689
  width: 100%;
1690
  height: 100%;
1691
  background: radial-gradient(ellipse at center,
1692
+ rgba(0, 0, 0, ${1 - centerTransparency}) 0%,
1693
+ rgba(0, 0, 0, ${1 - midTransparency}) 50%,
1694
+ rgba(0, 0, 0, ${edgeOpacity}) 80%,
1695
+ rgba(0, 0, 0, ${Math.min(0.85, edgeOpacity + 0.05)}) 100%);
1696
  pointer-events: none;
1697
  z-index: 1400;
1698
+ transition: background 0.3s ease-out;
1699
  `;
1700
 
1701
+ // ์‹ฌํ•œ Over-G ์ƒํƒœ์—์„œ ์•ฝ๊ฐ„์˜ ํ™”๋ฉด ํ”๋“ค๋ฆผ ํšจ๊ณผ
1702
+ if (darknessFactor > 0.5) {
1703
+ const shake = (1 - Math.random() * 2) * 1;
1704
  blurEffect.style.transform = `translate(${shake}px, ${shake}px)`;
1705
  }
1706
 
 
 
 
1707
  } else {
1708
  // Over-G ์ƒํƒœ๊ฐ€ ์•„๋‹ˆ๋ฉด ํšจ๊ณผ ์ œ๊ฑฐ
1709
  const blurEffect = document.getElementById('overGBlurEffect');