cutechicken commited on
Commit
fff7ab3
ยท
verified ยท
1 Parent(s): 7e2879b

Update game.js

Browse files
Files changed (1) hide show
  1. game.js +128 -235
game.js CHANGED
@@ -731,7 +731,7 @@ class Enemy {
731
  }
732
 
733
  // ๋ฉ”์ธ ์—…๋ฐ์ดํŠธ ํ•จ์ˆ˜
734
- update(playerPosition) {
735
  if (!this.mesh || !this.isLoaded) return;
736
 
737
  // AI ์ƒํƒœ ์—…๋ฐ์ดํŠธ
@@ -743,24 +743,8 @@ class Enemy {
743
  const hasLineOfSight = this.checkLineOfSight(playerPosition);
744
  const distanceToPlayer = this.mesh.position.distanceTo(playerPosition);
745
 
746
- // ๊ฒฝ๋กœ ์—…๋ฐ์ดํŠธ ์ฃผ๊ธฐ ์ฒดํฌ
747
- if (currentTime - this.lastPathUpdateTime > this.pathUpdateInterval) {
748
- if (!hasLineOfSight) {
749
- this.alternativePath = this.findAlternativePath(playerPosition);
750
- }
751
- this.lastPathUpdateTime = currentTime;
752
- }
753
-
754
  // ์žฅ์• ๋ฌผ ํšŒํ”ผ ๋กœ์ง
755
- if (obstacles.length > 0 && !this.pathfinding.isAvoidingObstacle) {
756
- this.pathfinding.isAvoidingObstacle = true;
757
- this.pathfinding.avoidanceDirection = this.calculateAvoidanceDirection(obstacles);
758
- this.pathfinding.avoidanceTime = 0;
759
- }
760
-
761
- // ์ด๋™ ๋กœ์ง ์‹คํ–‰
762
  if (this.pathfinding.isAvoidingObstacle) {
763
- // ํšŒํ”ผ ๋™์ž‘
764
  this.pathfinding.avoidanceTime += 16;
765
  if (this.pathfinding.avoidanceTime >= this.pathfinding.maxAvoidanceTime) {
766
  this.pathfinding.isAvoidingObstacle = false;
@@ -769,15 +753,35 @@ class Enemy {
769
  this.mesh.position.add(avoidMove);
770
  }
771
  } else if (!hasLineOfSight) {
772
- // ์‹œ์•ผ๊ฐ€ ์—†์„ ๋•Œ์˜ ์ด๋™
 
 
 
 
773
  if (this.alternativePath) {
774
  const pathDirection = new THREE.Vector3()
775
  .subVectors(this.alternativePath, this.mesh.position)
776
  .normalize();
777
- this.mesh.position.add(pathDirection.multiplyScalar(this.moveSpeed));
 
 
 
 
 
 
 
 
 
 
 
778
 
 
779
  const targetRotation = Math.atan2(pathDirection.x, pathDirection.z);
780
- this.mesh.rotation.y = this.smoothRotation(this.mesh.rotation.y, targetRotation, 0.1);
 
 
 
 
781
  }
782
  } else {
783
  // ์‹œ์•ผ๊ฐ€ ์žˆ์„ ๋•Œ์˜ ์ด๋™
@@ -817,8 +821,7 @@ class Enemy {
817
  const targetRotation = Math.atan2(directionToPlayer.x, directionToPlayer.z);
818
  this.mesh.rotation.y = this.smoothRotation(this.mesh.rotation.y, targetRotation, 0.1);
819
  }
820
-
821
- // ์ „ํˆฌ ๊ฑฐ๋ฆฌ ์กฐ์ •
822
  const combatMove = this.maintainCombatDistance(playerPosition);
823
  if (combatMove.length() > 0) {
824
  this.mesh.position.add(combatMove.multiplyScalar(this.moveSpeed));
@@ -836,6 +839,7 @@ class Enemy {
836
  this.adjustTankTilt();
837
  }
838
 
 
839
  checkLineOfSight(targetPosition) {
840
  if (!this.mesh) return false;
841
 
@@ -852,12 +856,43 @@ checkLineOfSight(targetPosition) {
852
  return intersects.length === 0;
853
  }
854
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
855
  findAlternativePath(playerPosition) {
856
  const currentPos = this.mesh.position.clone();
857
  const directionToPlayer = new THREE.Vector3()
858
  .subVectors(playerPosition, currentPos)
859
  .normalize();
860
 
 
 
 
 
 
 
 
 
 
 
 
861
  // ์ขŒ์šฐ 90๋„ ๋ฐฉํ–ฅ ๊ณ„์‚ฐ
862
  const leftDirection = new THREE.Vector3()
863
  .copy(directionToPlayer)
@@ -866,7 +901,7 @@ findAlternativePath(playerPosition) {
866
  .copy(directionToPlayer)
867
  .applyAxisAngle(new THREE.Vector3(0, 1, 0), -Math.PI / 2);
868
 
869
- // ์ขŒ์šฐ 30๋ฏธํ„ฐ ์ง€์  ํ™•์ธ
870
  const checkDistance = 30;
871
  const leftPoint = currentPos.clone().add(leftDirection.multiplyScalar(checkDistance));
872
  const rightPoint = currentPos.clone().add(rightDirection.multiplyScalar(checkDistance));
@@ -875,18 +910,36 @@ findAlternativePath(playerPosition) {
875
  const leftClear = this.checkPathClear(currentPos, leftPoint);
876
  const rightClear = this.checkPathClear(currentPos, rightPoint);
877
 
 
 
878
  if (leftClear && rightClear) {
879
- // ๋‘˜ ๋‹ค ๊ฐ€๋Šฅํ•˜๋ฉด ๋žœ๋ค ์„ ํƒ
880
- return Math.random() < 0.5 ? leftPoint : rightPoint;
 
 
 
 
 
 
 
 
 
881
  } else if (leftClear) {
882
- return leftPoint;
 
883
  } else if (rightClear) {
884
- return rightPoint;
 
 
 
 
 
 
885
  }
886
 
887
- return null;
888
  }
889
-
890
  checkPathClear(start, end) {
891
  const direction = new THREE.Vector3().subVectors(end, start).normalize();
892
  const distance = start.distanceTo(end);
@@ -895,214 +948,54 @@ checkPathClear(start, end) {
895
  return intersects.length === 0;
896
  }
897
 
898
- async initialize(loader) {
899
- try {
900
- const modelPath = this.type === 'tank' ? '/models/t90.glb' : '/models/t90.glb';
901
- const result = await loader.loadAsync(modelPath);
902
- this.mesh = result.scene;
903
- this.mesh.position.copy(this.position);
904
- this.mesh.scale.set(ENEMY_SCALE, ENEMY_SCALE, ENEMY_SCALE);
905
-
906
- this.mesh.traverse((child) => {
907
- if (child.isMesh) {
908
- child.castShadow = true;
909
- child.receiveShadow = true;
910
- }
911
- });
912
-
913
- this.scene.add(this.mesh);
914
- this.isLoaded = true;
915
- } catch (error) {
916
- console.error('Error loading enemy model:', error);
917
- this.isLoaded = false;
918
- }
919
- }
920
-
921
- // ์‹œ์•ผ ํ™•์ธ ๋ฉ”์„œ๋“œ (๊ธฐ์กด ์ฝ”๋“œ ์ˆ˜์ •)
922
- checkLineOfSight(playerPosition) {
923
- if (!this.mesh) return false;
924
-
925
- const startPos = this.mesh.position.clone();
926
- startPos.y += 2; // ํฌํƒ‘ ๋†’์ด
927
- const direction = new THREE.Vector3()
928
- .subVectors(playerPosition, startPos)
929
- .normalize();
930
- const distance = startPos.distanceTo(playerPosition);
931
-
932
- const raycaster = new THREE.Raycaster(startPos, direction, 0, distance);
933
- const intersects = raycaster.intersectObjects(window.gameInstance.obstacles, true);
934
-
935
- // ์žฅ์• ๋ฌผ๊ณผ์˜ ์ถฉ๋Œ์ด ์žˆ๋Š”์ง€ ํ™•์ธ
936
- return intersects.length === 0;
937
- }
938
- // ๋Œ€์ฒด ๊ฒฝ๋กœ ์ฐพ๊ธฐ ๋ฉ”์„œ๋“œ
939
- findAlternativePath(playerPosition) {
940
- const currentPos = this.mesh.position.clone();
941
- const directionToPlayer = new THREE.Vector3()
942
- .subVectors(playerPosition, currentPos)
943
- .normalize();
944
-
945
- // ์ขŒ์šฐ 90๋„ ๋ฐฉํ–ฅ ๊ณ„์‚ฐ
946
- const leftDirection = new THREE.Vector3()
947
- .copy(directionToPlayer)
948
- .applyAxisAngle(new THREE.Vector3(0, 1, 0), Math.PI / 2);
949
- const rightDirection = new THREE.Vector3()
950
- .copy(directionToPlayer)
951
- .applyAxisAngle(new THREE.Vector3(0, 1, 0), -Math.PI / 2);
952
-
953
- // ์ขŒ์šฐ 30๋ฏธํ„ฐ ์ง€์  ํ™•์ธ
954
- const checkDistance = 30;
955
- const leftPoint = currentPos.clone().add(leftDirection.multiplyScalar(checkDistance));
956
- const rightPoint = currentPos.clone().add(rightDirection.multiplyScalar(checkDistance));
957
-
958
- // ๊ฐ ๋ฐฉํ–ฅ์˜ ์žฅ์• ๋ฌผ ์ฒดํฌ
959
- const leftClear = this.checkPathClear(currentPos, leftPoint);
960
- const rightClear = this.checkPathClear(currentPos, rightPoint);
961
-
962
- if (leftClear && rightClear) {
963
- // ๋‘˜ ๋‹ค ๊ฐ€๋Šฅํ•˜๋ฉด ๋žœ๋ค ์„ ํƒ
964
- return Math.random() < 0.5 ? leftPoint : rightPoint;
965
- } else if (leftClear) {
966
- return leftPoint;
967
- } else if (rightClear) {
968
- return rightPoint;
969
- }
970
-
971
- return null;
972
- }
973
- // ๊ฒฝ๋กœ ์œ ํšจ์„ฑ ํ™•์ธ
974
- checkPathClear(start, end) {
975
- const direction = new THREE.Vector3().subVectors(end, start).normalize();
976
- const distance = start.distanceTo(end);
977
- const raycaster = new THREE.Raycaster(start, direction, 0, distance);
978
- const intersects = raycaster.intersectObjects(window.gameInstance.obstacles, true);
979
- return intersects.length === 0;
980
- }
981
-
982
- // ๋ถ€๋“œ๋Ÿฌ์šด ํšŒ์ „ ์ฒ˜๋ฆฌ
983
- smoothRotation(current, target, factor) {
984
- let delta = target - current;
985
-
986
- // ๊ฐ๋„ ์ฐจ์ด๋ฅผ -PI์—์„œ PI ์‚ฌ์ด๋กœ ์ •๊ทœํ™”
987
- while (delta > Math.PI) delta -= Math.PI * 2;
988
- while (delta < -Math.PI) delta += Math.PI * 2;
989
-
990
- return current + delta * factor;
991
- }
992
-
993
-
994
- updateAIState(playerPosition) {
995
- const currentTime = Date.now();
996
- const distanceToPlayer = this.mesh.position.distanceTo(playerPosition);
997
-
998
- if (currentTime - this.aiState.lastVisibilityCheck > this.aiState.visibilityCheckInterval) {
999
- this.aiState.canSeePlayer = this.checkLineOfSight(playerPosition);
1000
- this.aiState.lastVisibilityCheck = currentTime;
1001
-
1002
- if (this.aiState.canSeePlayer) {
1003
- this.aiState.lastKnownPlayerPosition = playerPosition.clone();
1004
- this.aiState.searchStartTime = null;
1005
- }
1006
- }
1007
- // ์ƒํƒœ ๋ณ€๊ฒฝ ์ฟจ๋‹ค์šด์„ 2์ดˆ๋กœ ์„ค์ •
1008
- const stateChangeCooldown = 2000;
1009
-
1010
- if (currentTime - this.aiState.lastStateChange > this.aiState.stateChangeCooldown) {
1011
- if (this.health < 30) {
1012
- this.aiState.mode = 'retreat';
1013
- } else if (distanceToPlayer < 30 && this.aiState.canSeePlayer) {
1014
- this.aiState.mode = 'flank';
1015
- } else {
1016
- this.aiState.mode = 'pursue';
1017
- }
1018
- this.aiState.lastStateChange = currentTime;
1019
- }
1020
- }
1021
-
1022
- findPathToTarget(targetPosition) {
1023
- const currentTime = Date.now();
1024
- if (currentTime - this.pathfinding.lastPathUpdate < this.pathfinding.pathUpdateInterval) {
1025
- return;
1026
- }
1027
-
1028
- this.pathfinding.currentPath = this.generatePathPoints(this.mesh.position.clone(), targetPosition);
1029
- this.pathfinding.lastPathUpdate = currentTime;
1030
- }
1031
-
1032
- generatePathPoints(start, end) {
1033
- const points = [];
1034
- const direction = new THREE.Vector3().subVectors(end, start).normalize();
1035
- const distance = start.distanceTo(end);
1036
- const steps = Math.ceil(distance / 10);
1037
-
1038
- for (let i = 0; i <= steps; i++) {
1039
- const point = start.clone().add(direction.multiplyScalar(i * 10));
1040
- points.push(point);
1041
- }
1042
-
1043
- return points;
1044
- }
1045
-
1046
- moveAlongPath() {
1047
- if (this.pathfinding.currentPath.length === 0) return;
1048
-
1049
- const targetPoint = this.pathfinding.currentPath[0];
1050
- const direction = new THREE.Vector3()
1051
- .subVectors(targetPoint, this.mesh.position)
1052
- .normalize();
1053
-
1054
- const moveVector = direction.multiplyScalar(this.moveSpeed);
1055
- this.mesh.position.add(moveVector);
1056
-
1057
- if (this.mesh.position.distanceTo(targetPoint) < 2) {
1058
- this.pathfinding.currentPath.shift();
1059
- }
1060
- }
1061
-
1062
- calculateFlankPosition(playerPosition) {
1063
- const angle = Math.random() * Math.PI * 2;
1064
- const radius = 40;
1065
- return new THREE.Vector3(
1066
- playerPosition.x + Math.cos(angle) * radius,
1067
- playerPosition.y,
1068
- playerPosition.z + Math.sin(angle) * radius
1069
- );
1070
- }
1071
 
1072
- calculateRetreatPosition(playerPosition) {
1073
- const direction = new THREE.Vector3()
1074
- .subVectors(this.mesh.position, playerPosition)
1075
- .normalize();
1076
- return this.mesh.position.clone().add(direction.multiplyScalar(50));
1077
- }
 
 
 
 
1078
 
1079
- adjustTankTilt() {
1080
- const forwardVector = new THREE.Vector3(0, 0, 1).applyQuaternion(this.mesh.quaternion);
1081
- const rightVector = new THREE.Vector3(1, 0, 0).applyQuaternion(this.mesh.quaternion);
1082
-
1083
- const frontHeight = window.gameInstance.getHeightAtPosition(
1084
- this.mesh.position.x + forwardVector.x,
1085
- this.mesh.position.z + forwardVector.z
1086
- );
1087
- const backHeight = window.gameInstance.getHeightAtPosition(
1088
- this.mesh.position.x - forwardVector.x,
1089
- this.mesh.position.z - forwardVector.z
1090
- );
1091
- const rightHeight = window.gameInstance.getHeightAtPosition(
1092
- this.mesh.position.x + rightVector.x,
1093
- this.mesh.position.z + rightVector.z
1094
- );
1095
- const leftHeight = window.gameInstance.getHeightAtPosition(
1096
- this.mesh.position.x - rightVector.x,
1097
- this.mesh.position.z - rightVector.z
1098
- );
1099
-
1100
- const pitch = Math.atan2(frontHeight - backHeight, 2);
1101
- const roll = Math.atan2(rightHeight - leftHeight, 2);
1102
-
1103
- const currentRotation = this.mesh.rotation.y;
1104
- this.mesh.rotation.set(pitch, currentRotation, roll);
1105
- }
 
1106
 
1107
  updateBullets() {
1108
  for (let i = this.bullets.length - 1; i >= 0; i--) {
 
731
  }
732
 
733
  // ๋ฉ”์ธ ์—…๋ฐ์ดํŠธ ํ•จ์ˆ˜
734
+ update(playerPosition) {
735
  if (!this.mesh || !this.isLoaded) return;
736
 
737
  // AI ์ƒํƒœ ์—…๋ฐ์ดํŠธ
 
743
  const hasLineOfSight = this.checkLineOfSight(playerPosition);
744
  const distanceToPlayer = this.mesh.position.distanceTo(playerPosition);
745
 
 
 
 
 
 
 
 
 
746
  // ์žฅ์• ๋ฌผ ํšŒํ”ผ ๋กœ์ง
 
 
 
 
 
 
 
747
  if (this.pathfinding.isAvoidingObstacle) {
 
748
  this.pathfinding.avoidanceTime += 16;
749
  if (this.pathfinding.avoidanceTime >= this.pathfinding.maxAvoidanceTime) {
750
  this.pathfinding.isAvoidingObstacle = false;
 
753
  this.mesh.position.add(avoidMove);
754
  }
755
  } else if (!hasLineOfSight) {
756
+ // ์‹œ์•ผ๊ฐ€ ์—†์„ ๋•Œ์˜ ์ด๋™ ๋กœ์ง ์ถ”๊ฐ€
757
+ if (!this.alternativePath || this.mesh.position.distanceTo(this.alternativePath) < 2) {
758
+ this.alternativePath = this.findAlternativePath(playerPosition);
759
+ }
760
+
761
  if (this.alternativePath) {
762
  const pathDirection = new THREE.Vector3()
763
  .subVectors(this.alternativePath, this.mesh.position)
764
  .normalize();
765
+
766
+ // ์ด๋™ ์ „ ์žฅ์• ๋ฌผ ์ฒดํฌ
767
+ const nextPosition = this.mesh.position.clone()
768
+ .add(pathDirection.multiplyScalar(this.moveSpeed));
769
+
770
+ if (!this.checkCollisionAtPosition(nextPosition)) {
771
+ this.mesh.position.copy(nextPosition);
772
+ } else {
773
+ // ์žฅ์• ๋ฌผ์ด ์žˆ์„ ๊ฒฝ์šฐ ์ƒˆ๋กœ์šด ๊ฒฝ๋กœ ํƒ์ƒ‰
774
+ this.alternativePath = null;
775
+ this.lastAvoidanceDirection = null;
776
+ }
777
 
778
+ // ์ด๋™ ๋ฐฉํ–ฅ์œผ๋กœ ๋ถ€๋“œ๋Ÿฝ๊ฒŒ ํšŒ์ „
779
  const targetRotation = Math.atan2(pathDirection.x, pathDirection.z);
780
+ this.mesh.rotation.y = this.smoothRotation(
781
+ this.mesh.rotation.y,
782
+ targetRotation,
783
+ 0.1
784
+ );
785
  }
786
  } else {
787
  // ์‹œ์•ผ๊ฐ€ ์žˆ์„ ๋•Œ์˜ ์ด๋™
 
821
  const targetRotation = Math.atan2(directionToPlayer.x, directionToPlayer.z);
822
  this.mesh.rotation.y = this.smoothRotation(this.mesh.rotation.y, targetRotation, 0.1);
823
  }
824
+ // ์ „ํˆฌ ๊ฑฐ๋ฆฌ ์กฐ์ •
 
825
  const combatMove = this.maintainCombatDistance(playerPosition);
826
  if (combatMove.length() > 0) {
827
  this.mesh.position.add(combatMove.multiplyScalar(this.moveSpeed));
 
839
  this.adjustTankTilt();
840
  }
841
 
842
+ // ์‹œ์•ผ ํ™•์ธ ๋ฉ”์„œ๋“œ
843
  checkLineOfSight(targetPosition) {
844
  if (!this.mesh) return false;
845
 
 
856
  return intersects.length === 0;
857
  }
858
 
859
+ // ์žฅ์• ๋ฌผ ์ถฉ๋Œ ์ฒดํฌ
860
+ checkCollisionAtPosition(position) {
861
+ const testBox = new THREE.Box3();
862
+ const tankSize = new THREE.Vector3(3, 3, 3); // ํƒฑํฌ ํฌ๊ธฐ์— ๋งž๊ฒŒ ์กฐ์ •
863
+
864
+ testBox.setFromCenterAndSize(
865
+ position,
866
+ tankSize
867
+ );
868
+
869
+ return window.gameInstance.obstacles.some(obstacle => {
870
+ if (obstacle.userData.isCollidable) {
871
+ const obstacleBox = new THREE.Box3().setFromObject(obstacle);
872
+ return testBox.intersectsBox(obstacleBox);
873
+ }
874
+ return false;
875
+ });
876
+ }
877
+
878
+ // ๋Œ€์ฒด ๊ฒฝ๋กœ ์ฐพ๊ธฐ
879
  findAlternativePath(playerPosition) {
880
  const currentPos = this.mesh.position.clone();
881
  const directionToPlayer = new THREE.Vector3()
882
  .subVectors(playerPosition, currentPos)
883
  .normalize();
884
 
885
+ // ์ด์ „์— ์„ ํƒํ•œ ์šฐํšŒ ๋ฐฉํ–ฅ์ด ์žˆ๋‹ค๋ฉด ๊ทธ ๋ฐฉํ–ฅ์„ ์œ ์ง€
886
+ if (this.lastAvoidanceDirection) {
887
+ const checkPoint = currentPos.clone().add(
888
+ this.lastAvoidanceDirection.multiplyScalar(30)
889
+ );
890
+
891
+ if (this.checkPathClear(currentPos, checkPoint)) {
892
+ return checkPoint;
893
+ }
894
+ }
895
+
896
  // ์ขŒ์šฐ 90๋„ ๋ฐฉํ–ฅ ๊ณ„์‚ฐ
897
  const leftDirection = new THREE.Vector3()
898
  .copy(directionToPlayer)
 
901
  .copy(directionToPlayer)
902
  .applyAxisAngle(new THREE.Vector3(0, 1, 0), -Math.PI / 2);
903
 
904
+ // ์ฒดํฌ ๊ฑฐ๋ฆฌ๋ฅผ 30๋ฏธํ„ฐ๋กœ ์„ค์ •
905
  const checkDistance = 30;
906
  const leftPoint = currentPos.clone().add(leftDirection.multiplyScalar(checkDistance));
907
  const rightPoint = currentPos.clone().add(rightDirection.multiplyScalar(checkDistance));
 
910
  const leftClear = this.checkPathClear(currentPos, leftPoint);
911
  const rightClear = this.checkPathClear(currentPos, rightPoint);
912
 
913
+ // ์ƒˆ๋กœ์šด ๋ฐฉํ–ฅ ๊ฒฐ์ • ๋กœ์ง
914
+ let targetPoint = null;
915
  if (leftClear && rightClear) {
916
+ // ํ”Œ๋ ˆ์ด์–ด์™€์˜ ๊ฐ๋„๋ฅผ ๊ณ ๋ คํ•˜์—ฌ ๋” ํšจ์œจ์ ์ธ ๋ฐฉํ–ฅ ์„ ํƒ
917
+ const leftAngle = Math.abs(this.calculateAngleToPlayer(leftPoint, playerPosition));
918
+ const rightAngle = Math.abs(this.calculateAngleToPlayer(rightPoint, playerPosition));
919
+
920
+ if (leftAngle < rightAngle) {
921
+ targetPoint = leftPoint;
922
+ this.lastAvoidanceDirection = leftDirection;
923
+ } else {
924
+ targetPoint = rightPoint;
925
+ this.lastAvoidanceDirection = rightDirection;
926
+ }
927
  } else if (leftClear) {
928
+ targetPoint = leftPoint;
929
+ this.lastAvoidanceDirection = leftDirection;
930
  } else if (rightClear) {
931
+ targetPoint = rightPoint;
932
+ this.lastAvoidanceDirection = rightDirection;
933
+ } else {
934
+ // ์–‘์ชฝ ๋‹ค ๋ง‰ํ˜€์žˆ์„ ๊ฒฝ์šฐ ํ›„์ง„
935
+ const backDirection = directionToPlayer.multiplyScalar(-1);
936
+ targetPoint = currentPos.clone().add(backDirection.multiplyScalar(checkDistance));
937
+ this.lastAvoidanceDirection = backDirection;
938
  }
939
 
940
+ return targetPoint;
941
  }
942
+ // ๊ฒฝ๋กœ ์œ ํšจ์„ฑ ํ™•์ธ
943
  checkPathClear(start, end) {
944
  const direction = new THREE.Vector3().subVectors(end, start).normalize();
945
  const distance = start.distanceTo(end);
 
948
  return intersects.length === 0;
949
  }
950
 
951
+ // ํ”Œ๋ ˆ์ด์–ด์™€์˜ ๊ฐ๋„ ๊ณ„์‚ฐ
952
+ calculateAngleToPlayer(position, playerPosition) {
953
+ const dirToPlayer = new THREE.Vector3()
954
+ .subVectors(playerPosition, position)
955
+ .normalize();
956
+ const forward = new THREE.Vector3(0, 0, 1);
957
+ return Math.atan2(dirToPlayer.x, dirToPlayer.z);
958
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
959
 
960
+ // ๋ถ€๋“œ๋Ÿฌ์šด ํšŒ์ „ ์ฒ˜๋ฆฌ
961
+ smoothRotation(current, target, factor) {
962
+ let delta = target - current;
963
+
964
+ // ๊ฐ๋„ ์ฐจ์ด๋ฅผ -PI์—์„œ PI ์‚ฌ์ด๋กœ ์ •๊ทœํ™”
965
+ while (delta > Math.PI) delta -= Math.PI * 2;
966
+ while (delta < -Math.PI) delta += Math.PI * 2;
967
+
968
+ return current + delta * factor;
969
+ }
970
 
971
+ // ํƒฑํฌ ๊ธฐ์šธ๊ธฐ ์กฐ์ •
972
+ adjustTankTilt() {
973
+ const forwardVector = new THREE.Vector3(0, 0, 1).applyQuaternion(this.mesh.quaternion);
974
+ const rightVector = new THREE.Vector3(1, 0, 0).applyQuaternion(this.mesh.quaternion);
975
+
976
+ const frontHeight = window.gameInstance.getHeightAtPosition(
977
+ this.mesh.position.x + forwardVector.x,
978
+ this.mesh.position.z + forwardVector.z
979
+ );
980
+ const backHeight = window.gameInstance.getHeightAtPosition(
981
+ this.mesh.position.x - forwardVector.x,
982
+ this.mesh.position.z - forwardVector.z
983
+ );
984
+ const rightHeight = window.gameInstance.getHeightAtPosition(
985
+ this.mesh.position.x + rightVector.x,
986
+ this.mesh.position.z + rightVector.z
987
+ );
988
+ const leftHeight = window.gameInstance.getHeightAtPosition(
989
+ this.mesh.position.x - rightVector.x,
990
+ this.mesh.position.z - rightVector.z
991
+ );
992
+
993
+ const pitch = Math.atan2(frontHeight - backHeight, 2);
994
+ const roll = Math.atan2(rightHeight - leftHeight, 2);
995
+
996
+ const currentRotation = this.mesh.rotation.y;
997
+ this.mesh.rotation.set(pitch, currentRotation, roll);
998
+ }
999
 
1000
  updateBullets() {
1001
  for (let i = this.bullets.length - 1; i >= 0; i--) {