cutechicken commited on
Commit
98a6af2
ยท
verified ยท
1 Parent(s): cd4590b

Update game.js

Browse files
Files changed (1) hide show
  1. game.js +422 -76
game.js CHANGED
@@ -734,6 +734,27 @@ class EnemyFighter {
734
  this.burstCount = 0; // ํ˜„์žฌ ์—ฐ๋ฐœ ์ˆ˜
735
  this.lastBurstTime = 0; // ๋งˆ์ง€๋ง‰ ์—ฐ๋ฐœ ์‹œ์ž‘ ์‹œ๊ฐ„
736
  this.predictedTargetPos = new THREE.Vector3(); // ์˜ˆ์ธก ์‚ฌ๊ฒฉ ์œ„์น˜
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
737
  }
738
 
739
  generateRandomTarget() {
@@ -745,8 +766,86 @@ class EnemyFighter {
745
  (Math.random() - 0.5) * 2 * mapLimit
746
  );
747
  }
748
-
749
- async initialize(loader) {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
750
  try {
751
  const result = await loader.loadAsync('models/mig-29.glb');
752
  this.mesh = result.scene;
@@ -808,11 +907,18 @@ class EnemyFighter {
808
  } else if (distanceToPlayer > 5000) {
809
  this.aiState = 'patrol';
810
  this.isEngaged = false;
 
811
  }
812
 
813
  // ๋‹ค๋ฅธ ์ ๊ธฐ์™€์˜ ์ถฉ๋Œ ํšŒํ”ผ
814
  this.avoidOtherEnemies(deltaTime);
815
 
 
 
 
 
 
 
816
  // AI ํ–‰๋™ ์‹คํ–‰
817
  switch (this.aiState) {
818
  case 'patrol':
@@ -859,130 +965,371 @@ class EnemyFighter {
859
 
860
  // ๊ณ ๋„๋„ ๋ณ€๊ฒฝ (๋” ๋ถ€๋“œ๋Ÿฝ๊ฒŒ)
861
  if (avoidDirection.y > 0) {
862
- this.targetRotation.x = -0.15; // ๊ธฐ์กด -0.3 โ†’ -0.15 (50% ๊ฐ์†Œ)
863
  } else {
864
- this.targetRotation.x = 0.15; // ๊ธฐ์กด 0.3 โ†’ 0.15 (50% ๊ฐ์†Œ)
865
  }
866
  }
867
  }
868
 
869
  patrol(deltaTime) {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
870
  // ๋ชฉํ‘œ ์ง€์ ์— ๋„๋‹ฌํ–ˆ๋Š”์ง€ ํ™•์ธ
871
  const distanceToTarget = this.position.distanceTo(this.targetPosition);
872
  if (distanceToTarget < 500) {
873
  // ์ƒˆ๋กœ์šด ๋ชฉํ‘œ ์ƒ์„ฑ
874
  this.targetPosition = this.generateRandomTarget();
 
875
  }
876
 
877
- // ๋ชฉํ‘œ๋ฅผ ํ–ฅํ•ด ์„ ํšŒ
878
- const direction = this.targetPosition.clone().sub(this.position).normalize();
879
- const targetYaw = Math.atan2(direction.x, direction.z);
 
 
 
 
 
 
880
 
881
- // ๋ถ€๋“œ๋Ÿฌ์šด ์„ ํšŒ
882
- const yawDiff = targetYaw - this.rotation.y;
883
- const normalizedYawDiff = ((yawDiff + Math.PI) % (2 * Math.PI)) - Math.PI;
 
 
 
884
 
885
- this.targetRotation.y += normalizedYawDiff * deltaTime * 0.5;
 
886
 
887
- // ๊ณ ๋„ ์กฐ์ •
888
- const altitudeDiff = this.targetPosition.y - this.position.y;
889
- if (Math.abs(altitudeDiff) > 50) {
890
- this.targetRotation.x = Math.max(-0.3, Math.min(0.3, altitudeDiff * 0.0001));
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
891
  } else {
892
- this.targetRotation.x = 0;
893
  }
894
  }
895
 
896
- combat(playerPosition, deltaTime) {
897
- // ํ”Œ๋ ˆ์ด์–ด ์œ„์น˜ ์˜ˆ์ธก
898
- if (this.playerFighter && this.playerFighter.velocity) {
899
- const bulletTravelTime = this.position.distanceTo(playerPosition) / 1200; // ํƒ„ํ™˜ ์†๋„
900
- this.predictedTargetPos = playerPosition.clone().add(
901
- this.playerFighter.velocity.clone().multiplyScalar(bulletTravelTime)
902
- );
 
 
 
 
903
  } else {
904
- this.predictedTargetPos = playerPosition.clone();
 
 
 
 
 
905
  }
906
 
907
- // ์˜ˆ์ธก ์œ„์น˜๋ฅผ ํ–ฅํ•ด ์กฐ์ค€
908
- const aimDirection = this.predictedTargetPos.clone().sub(this.position).normalize();
909
- const targetYaw = Math.atan2(aimDirection.x, aimDirection.z);
910
- const targetPitch = Math.asin(-aimDirection.y);
 
 
 
 
911
 
912
- // ๋ถ€๋“œ๋Ÿฌ์šด ์กฐ์ค€
913
- const yawDiff = targetYaw - this.rotation.y;
914
- const normalizedYawDiff = ((yawDiff + Math.PI) % (2 * Math.PI)) - Math.PI;
915
 
916
- this.targetRotation.y += normalizedYawDiff * deltaTime * 1.0;
917
- this.targetRotation.x = THREE.MathUtils.lerp(this.targetRotation.x, targetPitch, deltaTime * 2.0);
 
 
 
 
 
 
 
 
918
 
919
- // ์‚ฌ๊ฒฉ ํŒ๋‹จ
920
- const distanceToPlayer = this.position.distanceTo(playerPosition);
921
- const aimAccuracy = Math.abs(normalizedYawDiff) + Math.abs(targetPitch - this.rotation.x);
922
-
923
- if (distanceToPlayer < 1500 && aimAccuracy < 0.1) {
924
- // ์—ฐ๋ฐœ ์‚ฌ๊ฒฉ ์‹œ์Šคํ…œ
925
- const now = Date.now();
926
- if (now - this.lastBurstTime > 2000) { // 2์ดˆ๋งˆ๋‹ค ์—ฐ๋ฐœ
927
- this.burstCount = 0;
928
- this.lastBurstTime = now;
929
- }
930
-
931
- if (this.burstCount < 5 && now - this.lastShootTime > 200) { // 0.2์ดˆ ๊ฐ„๊ฒฉ์œผ๋กœ 5๋ฐœ
932
- this.shoot();
933
- this.burstCount++;
934
- }
935
  }
 
 
 
 
 
 
 
 
 
 
936
 
937
- // ํšŒํ”ผ ๊ธฐ๋™
938
- if (distanceToPlayer < 500) {
939
- // ๋„ˆ๋ฌด ๊ฐ€๊นŒ์šฐ๋ฉด ๊ธ‰์„ ํšŒ
940
- this.targetRotation.z = Math.sin(Date.now() * 0.001) * 0.5;
941
- this.targetRotation.y += deltaTime * 2.0 * (Math.random() > 0.5 ? 1 : -1);
942
  }
 
 
 
 
 
 
 
 
 
 
943
  }
944
 
945
  updatePhysics(deltaTime) {
946
  if (!this.mesh) return;
947
 
948
- // ๋ถ€๋“œ๋Ÿฌ์šด ํšŒ์ „ ์ ์šฉ
949
- this.rotation.x = THREE.MathUtils.lerp(this.rotation.x, this.targetRotation.x, deltaTime * 2.0);
950
- this.rotation.y = THREE.MathUtils.lerp(this.rotation.y, this.targetRotation.y, deltaTime * 1.5);
951
- this.rotation.z = THREE.MathUtils.lerp(this.rotation.z, this.targetRotation.z, deltaTime * 3.0);
 
 
 
 
 
952
 
953
- // ์†๋„ ๋ฒกํ„ฐ ๊ณ„์‚ฐ
 
 
 
 
 
954
  const forward = new THREE.Vector3(0, 0, 1);
955
  forward.applyEuler(this.rotation);
956
- this.velocity = forward.multiplyScalar(this.speed);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
957
 
958
  // ์œ„์น˜ ์—…๋ฐ์ดํŠธ
959
  this.position.add(this.velocity.clone().multiplyScalar(deltaTime));
960
 
961
  // ๊ณ ๋„ ์ œํ•œ
962
- if (this.position.y < 500) {
963
- this.position.y = 500;
964
- this.targetRotation.x = -0.2; // ์ƒ์Šน
 
965
  } else if (this.position.y > 8000) {
966
  this.position.y = 8000;
 
967
  this.targetRotation.x = 0.2; // ํ•˜๊ฐ•
968
  }
969
 
970
- // ๋งต ๊ฒฝ๊ณ„ ์ฒ˜๋ฆฌ
971
  const mapLimit = GAME_CONSTANTS.MAP_SIZE / 2;
972
- if (Math.abs(this.position.x) > mapLimit * 0.9 || Math.abs(this.position.z) > mapLimit * 0.9) {
973
- // ๋งต ์ค‘์•™์„ ํ–ฅํ•ด ํšŒ์ „
974
- const centerDirection = new THREE.Vector3(0, this.position.y, 0).sub(this.position).normalize();
975
- this.targetRotation.y = Math.atan2(centerDirection.x, centerDirection.z);
 
 
 
 
 
 
 
 
 
 
976
  }
977
 
 
 
 
 
 
 
978
  // ๋ฉ”์‹œ ์—…๋ฐ์ดํŠธ
979
  this.mesh.position.copy(this.position);
980
  this.mesh.rotation.x = this.rotation.x;
981
  this.mesh.rotation.y = this.rotation.y + Math.PI;
982
  this.mesh.rotation.z = this.rotation.z;
983
 
984
- // ์ž๋™ ๋กค ๋ณต๊ท€
985
- if (Math.abs(this.targetRotation.z) > 0.01) {
986
  this.targetRotation.z *= 0.95;
987
  }
988
  }
@@ -999,8 +1346,8 @@ class EnemyFighter {
999
  });
1000
  const bullet = new THREE.Mesh(bulletGeometry, bulletMaterial);
1001
 
1002
- // ๊ธฐ์ˆ˜ ๋์—์„œ ๋ฐœ์‚ฌ (8 โ†’ 12๋กœ ์ฆ๊ฐ€)
1003
- const muzzleOffset = new THREE.Vector3(0, 0, 12); // ๊ธฐ์กด 8์—์„œ 12๋กœ ๋ณ€๊ฒฝ
1004
  muzzleOffset.applyEuler(this.rotation);
1005
  bullet.position.copy(this.position).add(muzzleOffset);
1006
 
@@ -1023,13 +1370,12 @@ class EnemyFighter {
1023
  const audio = new Audio('sounds/MGLAUNCH.ogg');
1024
  audio.volume = 0.5;
1025
 
1026
- // ๊ฑฐ๋ฆฌ์— ๋”ฐ๋ฅธ ์Œ๋Ÿ‰ ์กฐ์ ˆ (๊ฑฐ๋ฆฌ๊ฐ€ ๋ฉ€์ˆ˜๋ก ์ž‘๊ฒŒ)
1027
  const volumeMultiplier = 1 - (distanceToPlayer / 3000);
1028
  audio.volume = 0.5 * volumeMultiplier;
1029
 
1030
  audio.play().catch(e => console.log('Enemy gunfire sound failed to play'));
1031
 
1032
- // ์žฌ์ƒ ์™„๋ฃŒ ์‹œ ๋ฉ”๋ชจ๋ฆฌ ์ •๋ฆฌ
1033
  audio.addEventListener('ended', () => {
1034
  audio.remove();
1035
  });
@@ -1045,9 +1391,9 @@ class EnemyFighter {
1045
  const bullet = this.bullets[i];
1046
  bullet.position.add(bullet.velocity.clone().multiplyScalar(deltaTime));
1047
 
1048
- // ์ง€๋ฉด ์ถฉ๋Œ ์ฒดํฌ ์ถ”๊ฐ€
1049
  if (bullet.position.y <= 0) {
1050
- // ํฌ๊ณ  ํ™”๋ คํ•œ ์ง€๋ฉด ์ถฉ๋Œ ํšจ๊ณผ - Game ์ธ์Šคํ„ด์Šค๋ฅผ ํ†ตํ•ด ํ˜ธ์ถœ
1051
  if (window.gameInstance) {
1052
  window.gameInstance.createGroundImpactEffect(bullet.position);
1053
  }
 
734
  this.burstCount = 0; // ํ˜„์žฌ ์—ฐ๋ฐœ ์ˆ˜
735
  this.lastBurstTime = 0; // ๋งˆ์ง€๋ง‰ ์—ฐ๋ฐœ ์‹œ์ž‘ ์‹œ๊ฐ„
736
  this.predictedTargetPos = new THREE.Vector3(); // ์˜ˆ์ธก ์‚ฌ๊ฒฉ ์œ„์น˜
737
+
738
+ // ์ƒˆ๋กœ์šด ํ•ญ๊ณต์—ญํ•™์  ๊ธฐ๋™ ๋ณ€์ˆ˜
739
+ this.maneuverState = 'straight'; // straight, turning, climbing, diving, combat_turn
740
+ this.maneuverTimer = 0;
741
+ this.nextManeuverTime = 3 + Math.random() * 4; // 3-7์ดˆ๋งˆ๋‹ค ๊ธฐ๋™ ๋ณ€๊ฒฝ
742
+
743
+ // ์ „ํˆฌ ๊ธฐ๋™ ํŒจํ„ด
744
+ this.combatPattern = 'approach'; // approach, attack, evade, reposition
745
+ this.combatTargetOffset = new THREE.Vector3();
746
+ this.lastAttackPosition = new THREE.Vector3();
747
+ this.evasionDirection = new THREE.Vector3();
748
+
749
+ // ๋ฌผ๋ฆฌ์  ์ œํ•œ ์ถ”๊ฐ€
750
+ this.maxBankAngle = Math.PI / 3; // ์ตœ๋Œ€ 60๋„ ๋ฑ…ํฌ
751
+ this.maxPitchRate = 1.0; // ์ดˆ๋‹น ์ตœ๋Œ€ ํ”ผ์น˜ ๋ณ€ํ™”์œจ
752
+ this.maxRollRate = 2.0; // ์ดˆ๋‹น ์ตœ๋Œ€ ๋กค ๋ณ€ํ™”์œจ
753
+ this.currentG = 1.0; // ํ˜„์žฌ G-Force
754
+
755
+ // ์—๋„ˆ์ง€ ๊ด€๋ฆฌ
756
+ this.throttle = 0.7; // ์Šค๋กœํ‹€ ์ œ์–ด
757
+ this.optimalSpeed = 386; // ์ตœ์  ์ „ํˆฌ ์†๋„
758
  }
759
 
760
  generateRandomTarget() {
 
766
  (Math.random() - 0.5) * 2 * mapLimit
767
  );
768
  }
769
+
770
+ // ์ƒˆ๋กœ์šด ๋ฉ”์„œ๋“œ: ์ž์—ฐ์Šค๋Ÿฌ์šด ๊ธฐ๋™ ์„ ํƒ
771
+ selectNewManeuver() {
772
+ const maneuvers = ['straight', 'turning_left', 'turning_right', 'climbing', 'diving', 'barrel_roll'];
773
+ const weights = [0.3, 0.2, 0.2, 0.15, 0.1, 0.05]; // ๊ฐ ๊ธฐ๋™์˜ ํ™•๋ฅ 
774
+
775
+ let random = Math.random();
776
+ let accumulator = 0;
777
+
778
+ for (let i = 0; i < maneuvers.length; i++) {
779
+ accumulator += weights[i];
780
+ if (random <= accumulator) {
781
+ this.maneuverState = maneuvers[i];
782
+ break;
783
+ }
784
+ }
785
+
786
+ // ๊ธฐ๋™ ์ง€์† ์‹œ๊ฐ„ ์„ค์ •
787
+ switch (this.maneuverState) {
788
+ case 'straight':
789
+ this.maneuverTimer = 2 + Math.random() * 3;
790
+ break;
791
+ case 'turning_left':
792
+ case 'turning_right':
793
+ this.maneuverTimer = 3 + Math.random() * 4;
794
+ this.turnDirection = this.maneuverState === 'turning_left' ? -1 : 1;
795
+ break;
796
+ case 'climbing':
797
+ case 'diving':
798
+ this.maneuverTimer = 2 + Math.random() * 2;
799
+ break;
800
+ case 'barrel_roll':
801
+ this.maneuverTimer = 4;
802
+ break;
803
+ }
804
+ }
805
+
806
+ // ์ƒˆ๋กœ์šด ๋ฉ”์„œ๋“œ: ์ „ํˆฌ ๊ธฐ๋™ ํŒจํ„ด ์„ ํƒ
807
+ selectCombatPattern(playerPosition, distance) {
808
+ const angleToPlayer = this.getAngleToTarget(playerPosition);
809
+
810
+ if (this.combatPattern === 'approach' && distance < 2000) {
811
+ // ์ ‘๊ทผ ์™„๋ฃŒ, ๊ณต๊ฒฉ ํŒจํ„ด์œผ๋กœ ์ „ํ™˜
812
+ this.combatPattern = 'attack';
813
+ this.lastAttackPosition = this.position.clone();
814
+ } else if (this.combatPattern === 'attack' && distance < 500) {
815
+ // ๋„ˆ๋ฌด ๊ฐ€๊นŒ์›€, ํšŒํ”ผ ๊ธฐ๋™
816
+ this.combatPattern = 'evade';
817
+ this.evasionDirection = new THREE.Vector3(
818
+ Math.random() - 0.5,
819
+ Math.random() * 0.5,
820
+ Math.random() - 0.5
821
+ ).normalize();
822
+ } else if (this.combatPattern === 'evade' && distance > 1500) {
823
+ // ์•ˆ์ „๊ฑฐ๋ฆฌ ํ™•๋ณด, ์žฌ์œ„์น˜ ์„ ์ •
824
+ this.combatPattern = 'reposition';
825
+ } else if (this.combatPattern === 'reposition' && distance > 1000 && distance < 3000) {
826
+ // ์žฌ์œ„์น˜ ์™„๋ฃŒ, ๋‹ค์‹œ ์ ‘๊ทผ
827
+ this.combatPattern = 'approach';
828
+ }
829
+
830
+ // ๊ณต๊ฒฉ ์œ„์น˜ ์˜คํ”„์…‹ ๊ณ„์‚ฐ (ํ”Œ๋ ˆ์ด์–ด ์ฃผ๋ณ€ 300m ๋ฐ˜๊ฒฝ)
831
+ if (this.combatPattern === 'reposition' || this.combatPattern === 'approach') {
832
+ const offsetAngle = Math.random() * Math.PI * 2;
833
+ const offsetDistance = 200 + Math.random() * 100; // 200-300m
834
+ this.combatTargetOffset = new THREE.Vector3(
835
+ Math.cos(offsetAngle) * offsetDistance,
836
+ (Math.random() - 0.5) * 100,
837
+ Math.sin(offsetAngle) * offsetDistance
838
+ );
839
+ }
840
+ }
841
+
842
+ // ์ƒˆ๋กœ์šด ๋ฉ”์„œ๋“œ: ํƒ€๊ฒŸ๊นŒ์ง€์˜ ๊ฐ๋„ ๊ณ„์‚ฐ
843
+ getAngleToTarget(targetPosition) {
844
+ const toTarget = targetPosition.clone().sub(this.position).normalize();
845
+ const forward = new THREE.Vector3(0, 0, 1).applyEuler(this.rotation);
846
+ return Math.acos(Math.max(-1, Math.min(1, forward.dot(toTarget))));
847
+ }
848
+ async initialize(loader) {
849
  try {
850
  const result = await loader.loadAsync('models/mig-29.glb');
851
  this.mesh = result.scene;
 
907
  } else if (distanceToPlayer > 5000) {
908
  this.aiState = 'patrol';
909
  this.isEngaged = false;
910
+ this.combatPattern = 'approach'; // ์ „ํˆฌ ํŒจํ„ด ๋ฆฌ์…‹
911
  }
912
 
913
  // ๋‹ค๋ฅธ ์ ๊ธฐ์™€์˜ ์ถฉ๋Œ ํšŒํ”ผ
914
  this.avoidOtherEnemies(deltaTime);
915
 
916
+ // ๊ธฐ๋™ ํƒ€์ด๋จธ ์—…๋ฐ์ดํŠธ
917
+ this.maneuverTimer -= deltaTime;
918
+ if (this.maneuverTimer <= 0 && this.aiState === 'patrol') {
919
+ this.selectNewManeuver();
920
+ }
921
+
922
  // AI ํ–‰๋™ ์‹คํ–‰
923
  switch (this.aiState) {
924
  case 'patrol':
 
965
 
966
  // ๊ณ ๋„๋„ ๋ณ€๊ฒฝ (๋” ๋ถ€๋“œ๋Ÿฝ๊ฒŒ)
967
  if (avoidDirection.y > 0) {
968
+ this.targetRotation.x = -0.15;
969
  } else {
970
+ this.targetRotation.x = 0.15;
971
  }
972
  }
973
  }
974
 
975
  patrol(deltaTime) {
976
+ // ์ž์—ฐ์Šค๋Ÿฌ์šด ๊ธฐ๋™ ์‹คํ–‰
977
+ switch (this.maneuverState) {
978
+ case 'straight':
979
+ // ์ง์ง„ ๋น„ํ–‰
980
+ this.targetRotation.x = 0;
981
+ this.targetRotation.z = 0;
982
+
983
+ // ๋ชฉํ‘œ ์ง€์ ์„ ํ–ฅํ•ด ์ฒœ์ฒœํžˆ ๋ฐฉํ–ฅ ์กฐ์ •
984
+ const dirToTarget = this.targetPosition.clone().sub(this.position).normalize();
985
+ const targetYaw = Math.atan2(dirToTarget.x, dirToTarget.z);
986
+ const yawDiff = this.normalizeAngle(targetYaw - this.rotation.y);
987
+ this.targetRotation.y += yawDiff * deltaTime * 0.3;
988
+ break;
989
+
990
+ case 'turning_left':
991
+ case 'turning_right':
992
+ // ๋ฑ…ํฌ ํ„ด ์‹คํ–‰
993
+ const bankAngle = this.turnDirection * Math.PI / 4; // 45๋„ ๋ฑ…ํฌ
994
+ this.targetRotation.z = bankAngle;
995
+ this.targetRotation.y += this.turnDirection * deltaTime * 0.8;
996
+
997
+ // ํ„ด ์ค‘ ์•ฝ๊ฐ„์˜ ๊ธฐ์ˆ˜ ํ•˜๊ฐ•
998
+ this.targetRotation.x = 0.1;
999
+ break;
1000
+
1001
+ case 'climbing':
1002
+ // ์ƒ์Šน
1003
+ this.targetRotation.x = -Math.PI / 6; // 30๋„ ์ƒ์Šน
1004
+ this.targetRotation.z = 0;
1005
+ this.throttle = Math.min(1.0, this.throttle + deltaTime * 0.3);
1006
+ break;
1007
+
1008
+ case 'diving':
1009
+ // ํ•˜๊ฐ•
1010
+ this.targetRotation.x = Math.PI / 8; // 22.5๋„ ํ•˜๊ฐ•
1011
+ this.targetRotation.z = 0;
1012
+ this.throttle = Math.max(0.5, this.throttle - deltaTime * 0.2);
1013
+ break;
1014
+
1015
+ case 'barrel_roll':
1016
+ // ๋ฐฐ๋Ÿด ๋กค
1017
+ const rollProgress = (4 - this.maneuverTimer) / 4;
1018
+ this.targetRotation.z = Math.sin(rollProgress * Math.PI * 4) * Math.PI / 2;
1019
+ this.targetRotation.x = Math.sin(rollProgress * Math.PI * 2) * 0.2;
1020
+ break;
1021
+ }
1022
+
1023
  // ๋ชฉํ‘œ ์ง€์ ์— ๋„๋‹ฌํ–ˆ๋Š”์ง€ ํ™•์ธ
1024
  const distanceToTarget = this.position.distanceTo(this.targetPosition);
1025
  if (distanceToTarget < 500) {
1026
  // ์ƒˆ๋กœ์šด ๋ชฉํ‘œ ์ƒ์„ฑ
1027
  this.targetPosition = this.generateRandomTarget();
1028
+ this.selectNewManeuver();
1029
  }
1030
 
1031
+ // ๊ณ ๋„ ์กฐ์ •
1032
+ const altitudeDiff = this.targetPosition.y - this.position.y;
1033
+ if (Math.abs(altitudeDiff) > 200 && this.maneuverState === 'straight') {
1034
+ if (altitudeDiff > 0) {
1035
+ this.targetRotation.x = Math.max(-0.3, Math.min(0, altitudeDiff * 0.0001));
1036
+ } else {
1037
+ this.targetRotation.x = Math.min(0.3, Math.max(0, altitudeDiff * 0.0001));
1038
+ }
1039
+ }
1040
 
1041
+ // ์†๋„ ๊ด€๋ฆฌ
1042
+ this.updateSpeedControl(deltaTime);
1043
+ }
1044
+
1045
+ combat(playerPosition, deltaTime) {
1046
+ const distance = this.position.distanceTo(playerPosition);
1047
 
1048
+ // ์ „ํˆฌ ํŒจํ„ด ์—…๋ฐ์ดํŠธ
1049
+ this.selectCombatPattern(playerPosition, distance);
1050
 
1051
+ // ์ „ํˆฌ ํŒจํ„ด๋ณ„ ๊ธฐ๋™
1052
+ switch (this.combatPattern) {
1053
+ case 'approach':
1054
+ // ํ”Œ๋ ˆ์ด์–ด ๊ทผ์ฒ˜ ๋ชฉํ‘œ์ ์œผ๋กœ ์ ‘๊ทผ
1055
+ const approachTarget = playerPosition.clone().add(this.combatTargetOffset);
1056
+ this.executeInterceptCourse(approachTarget, deltaTime);
1057
+
1058
+ // ์—๋„ˆ์ง€ ๊ด€๋ฆฌ - ์†๋„ ์œ ์ง€
1059
+ this.throttle = 0.8;
1060
+ break;
1061
+
1062
+ case 'attack':
1063
+ // ์˜ˆ์ธก ์‚ฌ๊ฒฉ์„ ์œ„ํ•œ ๋ฆฌ๋“œ ๊ณ„์‚ฐ
1064
+ if (this.playerFighter && this.playerFighter.velocity) {
1065
+ const bulletTravelTime = distance / 1200;
1066
+ this.predictedTargetPos = playerPosition.clone().add(
1067
+ this.playerFighter.velocity.clone().multiplyScalar(bulletTravelTime * 0.7)
1068
+ );
1069
+ } else {
1070
+ this.predictedTargetPos = playerPosition.clone();
1071
+ }
1072
+
1073
+ // ๊ณต๊ฒฉ ์ž์„ธ ์œ ์ง€
1074
+ this.executeAttackRun(this.predictedTargetPos, deltaTime);
1075
+
1076
+ // ์‚ฌ๊ฒฉ ํŒ๋‹จ
1077
+ const aimAccuracy = this.calculateAimAccuracy(this.predictedTargetPos);
1078
+ if (distance < 1500 && aimAccuracy < 0.15) {
1079
+ this.fireWeapon();
1080
+ }
1081
+ break;
1082
+
1083
+ case 'evade':
1084
+ // ํšŒํ”ผ ๊ธฐ๋™ - ๋ธŒ๋ ˆ์ดํฌ ํ„ด ๋˜๋Š” ๋ฐฐ๋Ÿด ๋กค
1085
+ this.executeEvasiveManeuver(deltaTime);
1086
+ break;
1087
+
1088
+ case 'reposition':
1089
+ // ์žฌ์œ„์น˜ - ๊ณ ๋„์™€ ์†๋„ ์ด์  ํ™•๋ณด
1090
+ const repositionTarget = playerPosition.clone();
1091
+ repositionTarget.y += 500 + Math.random() * 1000; // ์œ„์—์„œ ๊ณต๊ฒฉ
1092
+ repositionTarget.add(this.combatTargetOffset);
1093
+
1094
+ this.executeInterceptCourse(repositionTarget, deltaTime);
1095
+
1096
+ // ์†๋„ ํšŒ๋ณต
1097
+ this.throttle = 0.9;
1098
+ break;
1099
+ }
1100
+
1101
+ // ์†๋„ ๊ด€๋ฆฌ
1102
+ this.updateSpeedControl(deltaTime);
1103
+ }
1104
+
1105
+ // ์š”๊ฒฉ ์ฝ”์Šค ์‹คํ–‰
1106
+ executeInterceptCourse(target, deltaTime) {
1107
+ const toTarget = target.clone().sub(this.position);
1108
+ const distance = toTarget.length();
1109
+ toTarget.normalize();
1110
+
1111
+ // ๋ฆฌ๋“œ ํ„ด - ๋ชฉํ‘œ์˜ ๋ฏธ๋ž˜ ์œ„์น˜๋ฅผ ์˜ˆ์ธกํ•˜์—ฌ ์„ ํšŒ
1112
+ const targetYaw = Math.atan2(toTarget.x, toTarget.z);
1113
+ const targetPitch = Math.asin(-toTarget.y);
1114
+
1115
+ // ๋ถ€๋“œ๋Ÿฌ์šด ์„ ํšŒ๋ฅผ ์œ„ํ•œ ๊ฐ๋„ ์ฐจ์ด ๊ณ„์‚ฐ
1116
+ const yawDiff = this.normalizeAngle(targetYaw - this.rotation.y);
1117
+ const pitchDiff = targetPitch - this.rotation.x;
1118
+
1119
+ // ๋ฑ…ํฌ ๊ฐ๋„๋ฅผ ์ด์šฉํ•œ ์ž์—ฐ์Šค๋Ÿฌ์šด ์„ ํšŒ
1120
+ if (Math.abs(yawDiff) > 0.1) {
1121
+ // ์š” ํšŒ์ „์— ๋”ฐ๋ฅธ ๋กค ์ ์šฉ
1122
+ this.targetRotation.z = Math.max(-this.maxBankAngle,
1123
+ Math.min(this.maxBankAngle, -yawDiff * 2));
1124
+ } else {
1125
+ this.targetRotation.z *= 0.9; // ๋กค ๋ณต๊ท€
1126
+ }
1127
+
1128
+ // ๋ชฉํ‘œ ๋ฐฉํ–ฅ์œผ๋กœ ํšŒ์ „
1129
+ this.targetRotation.y += yawDiff * deltaTime * 1.2;
1130
+ this.targetRotation.x += pitchDiff * deltaTime * 0.8;
1131
+
1132
+ // ํ”ผ์น˜ ์ œํ•œ
1133
+ this.targetRotation.x = Math.max(-Math.PI / 4, Math.min(Math.PI / 4, this.targetRotation.x));
1134
+ }
1135
+
1136
+ // ๊ณต๊ฒฉ ์ง„์ž…
1137
+ executeAttackRun(target, deltaTime) {
1138
+ const toTarget = target.clone().sub(this.position).normalize();
1139
+ const targetYaw = Math.atan2(toTarget.x, toTarget.z);
1140
+ const targetPitch = Math.asin(-toTarget.y);
1141
+
1142
+ // ์ •๋ฐ€ ์กฐ์ค€์„ ์œ„ํ•œ ์ž‘์€ ์ˆ˜์ •
1143
+ const yawDiff = this.normalizeAngle(targetYaw - this.rotation.y);
1144
+ const pitchDiff = targetPitch - this.rotation.x;
1145
+
1146
+ // ์•ˆ์ •์ ์ธ ์‚ฌ๊ฒฉ ์ž์„ธ ์œ ์ง€
1147
+ this.targetRotation.y += yawDiff * deltaTime * 2.0;
1148
+ this.targetRotation.x += pitchDiff * deltaTime * 1.5;
1149
+ this.targetRotation.z = -yawDiff * 0.5; // ์•ฝ๊ฐ„์˜ ๋ฑ…ํฌ๋งŒ ์ ์šฉ
1150
+
1151
+ // ์†๋„ ์กฐ์ ˆ - ๋ชฉํ‘œ์™€์˜ ์ƒ๋Œ€ ์†๋„ ๊ด€๋ฆฌ
1152
+ const distance = this.position.distanceTo(target);
1153
+ if (distance < 800) {
1154
+ this.throttle = 0.6; // ๊ฐ์†
1155
  } else {
1156
+ this.throttle = 0.85;
1157
  }
1158
  }
1159
 
1160
+ // ํšŒํ”ผ ๊ธฐ๋™
1161
+ executeEvasiveManeuver(deltaTime) {
1162
+ // ๋ธŒ๋ ˆ์ดํฌ ํ„ด ๋˜๋Š” ์Šคํ”Œ๋ฆฟ-S
1163
+ const evasiveType = Math.random() > 0.5 ? 'break' : 'split_s';
1164
+
1165
+ if (evasiveType === 'break') {
1166
+ // ๊ธ‰๊ฒฉํ•œ ๋ธŒ๋ ˆ์ดํฌ ํ„ด
1167
+ this.targetRotation.z = this.evasionDirection.x * this.maxBankAngle * 1.2;
1168
+ this.targetRotation.y += this.evasionDirection.x * deltaTime * 3.0;
1169
+ this.targetRotation.x = 0.2; // ์•ฝ๊ฐ„์˜ ๊ธฐ์ˆ˜ ํ•˜๊ฐ•
1170
+ this.throttle = 0.4; // ๊ธ‰๊ฐ์†
1171
  } else {
1172
+ // ์Šคํ”Œ๋ฆฟ-S (๋ฐ˜์ „ ํ•˜๊ฐ•)
1173
+ this.targetRotation.z += deltaTime * 4.0; // ๋น ๋ฅธ ๋กค
1174
+ if (Math.abs(this.rotation.z) > Math.PI * 0.8) {
1175
+ this.targetRotation.x = Math.PI / 3; // ๊ธ‰ํ•˜๊ฐ•
1176
+ }
1177
+ this.throttle = 0.3;
1178
  }
1179
 
1180
+ // G-Force ์‹œ๋ฎฌ๋ ˆ์ด์…˜
1181
+ this.currentG = 3.0 + Math.abs(this.rotation.z) * 2;
1182
+ }
1183
+
1184
+ // ์กฐ์ค€ ์ •ํ™•๋„ ๊ณ„์‚ฐ
1185
+ calculateAimAccuracy(target) {
1186
+ const toTarget = target.clone().sub(this.position).normalize();
1187
+ const forward = new THREE.Vector3(0, 0, 1).applyEuler(this.rotation);
1188
 
1189
+ const dotProduct = forward.dot(toTarget);
1190
+ const angle = Math.acos(Math.max(-1, Math.min(1, dotProduct)));
 
1191
 
1192
+ return angle;
1193
+ }
1194
+
1195
+ // ๋ฌด๊ธฐ ๋ฐœ์‚ฌ
1196
+ fireWeapon() {
1197
+ const now = Date.now();
1198
+ if (now - this.lastBurstTime > 2000) {
1199
+ this.burstCount = 0;
1200
+ this.lastBurstTime = now;
1201
+ }
1202
 
1203
+ if (this.burstCount < 5 && now - this.lastShootTime > 200) {
1204
+ this.shoot();
1205
+ this.burstCount++;
 
 
 
 
 
 
 
 
 
 
 
 
 
1206
  }
1207
+ }
1208
+
1209
+ // ์†๋„ ์ œ์–ด
1210
+ updateSpeedControl(deltaTime) {
1211
+ // ๋ชฉํ‘œ ์†๋„ ๊ณ„์‚ฐ
1212
+ let targetSpeed = this.optimalSpeed * this.throttle;
1213
+
1214
+ // ๊ณ ๋„์— ๋”ฐ๋ฅธ ์†๋„ ๋ณด์ •
1215
+ const altitudeFactor = 1 + (this.position.y / 10000) * 0.2;
1216
+ targetSpeed *= altitudeFactor;
1217
 
1218
+ // ๊ธฐ๋™์— ๋”ฐ๋ฅธ ์†๋„ ์†์‹ค
1219
+ if (this.currentG > 2.0) {
1220
+ targetSpeed *= (1 - (this.currentG - 2.0) * 0.05);
 
 
1221
  }
1222
+
1223
+ // ๋ถ€๋“œ๋Ÿฌ์šด ์†๋„ ๋ณ€ํ™”
1224
+ this.speed = THREE.MathUtils.lerp(this.speed, targetSpeed, deltaTime * 0.5);
1225
+ }
1226
+
1227
+ // ๊ฐ๋„ ์ •๊ทœํ™” ํ—ฌํผ ๋ฉ”์„œ๋“œ
1228
+ normalizeAngle(angle) {
1229
+ while (angle > Math.PI) angle -= Math.PI * 2;
1230
+ while (angle < -Math.PI) angle += Math.PI * 2;
1231
+ return angle;
1232
  }
1233
 
1234
  updatePhysics(deltaTime) {
1235
  if (!this.mesh) return;
1236
 
1237
+ // ๋ถ€๋“œ๋Ÿฌ์šด ํšŒ์ „ ์ ์šฉ - ํ•ญ๊ณต์—ญํ•™์  ์ œํ•œ ์ ์šฉ
1238
+ const rotationSpeed = deltaTime * 2.0;
1239
+ const rollSpeed = deltaTime * this.maxRollRate;
1240
+ const pitchSpeed = deltaTime * this.maxPitchRate;
1241
+
1242
+ // ๊ฐ ์ถ•๋ณ„๋กœ ๋‹ค๋ฅธ ์†๋„๋กœ ํšŒ์ „
1243
+ this.rotation.x = THREE.MathUtils.lerp(this.rotation.x, this.targetRotation.x, pitchSpeed);
1244
+ this.rotation.y = THREE.MathUtils.lerp(this.rotation.y, this.targetRotation.y, rotationSpeed);
1245
+ this.rotation.z = THREE.MathUtils.lerp(this.rotation.z, this.targetRotation.z, rollSpeed);
1246
 
1247
+ // G-Force ๊ณ„์‚ฐ
1248
+ const turnRate = Math.abs(this.targetRotation.y - this.rotation.y) * 10;
1249
+ const pitchRate = Math.abs(this.targetRotation.x - this.rotation.x) * 10;
1250
+ this.currentG = 1.0 + turnRate + pitchRate + Math.abs(this.rotation.z) * 2;
1251
+
1252
+ // ์†๋„ ๋ฒกํ„ฐ ๊ณ„์‚ฐ - ์‹ค์ œ ํ•ญ๊ณต๊ธฐ์ฒ˜๋Ÿผ ๊ธฐ์ˆ˜ ๋ฐฉํ–ฅ์œผ๋กœ
1253
  const forward = new THREE.Vector3(0, 0, 1);
1254
  forward.applyEuler(this.rotation);
1255
+
1256
+ // ๋ฑ…ํฌ ๊ฐ๋„์— ๋”ฐ๋ฅธ ์„ ํšŒ๋ ฅ
1257
+ if (Math.abs(this.rotation.z) > 0.1) {
1258
+ const lift = new THREE.Vector3(0, 1, 0);
1259
+ lift.applyEuler(this.rotation);
1260
+ const turnForce = Math.sin(this.rotation.z) * this.currentG * 50;
1261
+ this.velocity.add(lift.multiplyScalar(turnForce * deltaTime));
1262
+ }
1263
+
1264
+ // ์ค‘๋ ฅ ํšจ๊ณผ
1265
+ const gravity = GAME_CONSTANTS.GRAVITY * deltaTime;
1266
+ this.velocity.y -= gravity;
1267
+
1268
+ // ์–‘๋ ฅ ๊ณ„์‚ฐ (์†๋„์— ๋น„๋ก€)
1269
+ const liftFactor = (this.speed / this.optimalSpeed) * 0.8;
1270
+ const lift = gravity * liftFactor * Math.cos(this.rotation.x);
1271
+ this.velocity.y += lift;
1272
+
1273
+ // ํ•ญ๋ ฅ (๋“œ๋ž˜๊ทธ)
1274
+ const dragFactor = 0.02 + Math.abs(this.rotation.x) * 0.01; // ํ”ผ์น˜ ๊ฐ๋„์— ๋”ฐ๋ผ ์ฆ๊ฐ€
1275
+ this.velocity.multiplyScalar(1 - dragFactor);
1276
+
1277
+ // ์ „์ง„ ์ถ”๋ ฅ
1278
+ const thrust = forward.multiplyScalar(this.speed);
1279
+ this.velocity = this.velocity.multiplyScalar(0.95).add(thrust.multiplyScalar(0.05));
1280
+
1281
+ // ์†๋„ ์ œํ•œ
1282
+ const currentSpeed = this.velocity.length();
1283
+ if (currentSpeed > this.speed * 1.2) {
1284
+ this.velocity.normalize().multiplyScalar(this.speed * 1.2);
1285
+ }
1286
 
1287
  // ์œ„์น˜ ์—…๋ฐ์ดํŠธ
1288
  this.position.add(this.velocity.clone().multiplyScalar(deltaTime));
1289
 
1290
  // ๊ณ ๋„ ์ œํ•œ
1291
+ if (this.position.y < 300) {
1292
+ this.position.y = 300;
1293
+ this.velocity.y = Math.max(0, this.velocity.y);
1294
+ this.targetRotation.x = -0.3; // ๊ฐ•์ œ ์ƒ์Šน
1295
  } else if (this.position.y > 8000) {
1296
  this.position.y = 8000;
1297
+ this.velocity.y = Math.min(0, this.velocity.y);
1298
  this.targetRotation.x = 0.2; // ํ•˜๊ฐ•
1299
  }
1300
 
1301
+ // ๋งต ๊ฒฝ๊ณ„ ์ฒ˜๋ฆฌ - ๋ถ€๋“œ๋Ÿฌ์šด ํšŒ๊ท€
1302
  const mapLimit = GAME_CONSTANTS.MAP_SIZE / 2;
1303
+ const boundaryBuffer = mapLimit * 0.9;
1304
+
1305
+ if (Math.abs(this.position.x) > boundaryBuffer || Math.abs(this.position.z) > boundaryBuffer) {
1306
+ // ๋งต ์ค‘์•™์„ ํ–ฅํ•ด ์ž์—ฐ์Šค๋Ÿฝ๊ฒŒ ํšŒ์ „
1307
+ const centerDirection = new THREE.Vector3(-this.position.x, 0, -this.position.z).normalize();
1308
+ const targetYaw = Math.atan2(centerDirection.x, centerDirection.z);
1309
+ this.targetRotation.y = targetYaw;
1310
+
1311
+ // ๊ฒฝ๊ณ„์—์„œ ๋ฑ…ํฌ ํ„ด
1312
+ if (this.position.x > boundaryBuffer) {
1313
+ this.targetRotation.z = -this.maxBankAngle;
1314
+ } else if (this.position.x < -boundaryBuffer) {
1315
+ this.targetRotation.z = this.maxBankAngle;
1316
+ }
1317
  }
1318
 
1319
+ // ํ•˜๋“œ ๋ฆฌ๋ฏธํŠธ
1320
+ if (this.position.x > mapLimit) this.position.x = mapLimit;
1321
+ if (this.position.x < -mapLimit) this.position.x = -mapLimit;
1322
+ if (this.position.z > mapLimit) this.position.z = mapLimit;
1323
+ if (this.position.z < -mapLimit) this.position.z = -mapLimit;
1324
+
1325
  // ๋ฉ”์‹œ ์—…๋ฐ์ดํŠธ
1326
  this.mesh.position.copy(this.position);
1327
  this.mesh.rotation.x = this.rotation.x;
1328
  this.mesh.rotation.y = this.rotation.y + Math.PI;
1329
  this.mesh.rotation.z = this.rotation.z;
1330
 
1331
+ // ์ž๋™ ๋กค ๋ณต๊ท€ (์ „ํˆฌ ์ค‘์ด ์•„๋‹ ๋•Œ)
1332
+ if (!this.isEngaged && Math.abs(this.targetRotation.z) > 0.01) {
1333
  this.targetRotation.z *= 0.95;
1334
  }
1335
  }
 
1346
  });
1347
  const bullet = new THREE.Mesh(bulletGeometry, bulletMaterial);
1348
 
1349
+ // ๊ธฐ์ˆ˜ ๋์—์„œ ๋ฐœ์‚ฌ
1350
+ const muzzleOffset = new THREE.Vector3(0, 0, 12);
1351
  muzzleOffset.applyEuler(this.rotation);
1352
  bullet.position.copy(this.position).add(muzzleOffset);
1353
 
 
1370
  const audio = new Audio('sounds/MGLAUNCH.ogg');
1371
  audio.volume = 0.5;
1372
 
1373
+ // ๊ฑฐ๋ฆฌ์— ๋”ฐ๋ฅธ ์Œ๋Ÿ‰ ์กฐ์ ˆ
1374
  const volumeMultiplier = 1 - (distanceToPlayer / 3000);
1375
  audio.volume = 0.5 * volumeMultiplier;
1376
 
1377
  audio.play().catch(e => console.log('Enemy gunfire sound failed to play'));
1378
 
 
1379
  audio.addEventListener('ended', () => {
1380
  audio.remove();
1381
  });
 
1391
  const bullet = this.bullets[i];
1392
  bullet.position.add(bullet.velocity.clone().multiplyScalar(deltaTime));
1393
 
1394
+ // ์ง€๋ฉด ์ถฉ๋Œ ์ฒดํฌ
1395
  if (bullet.position.y <= 0) {
1396
+ // ํฌ๊ณ  ํ™”๋ คํ•œ ์ง€๋ฉด ์ถฉ๋Œ ํšจ๊ณผ
1397
  if (window.gameInstance) {
1398
  window.gameInstance.createGroundImpactEffect(bullet.position);
1399
  }