Spaces:
Running
Running
Update game.js
Browse files
game.js
CHANGED
@@ -737,6 +737,13 @@ class EnemyFighter {
|
|
737 |
this.targetPosition = null;
|
738 |
this.playerFighter = null;
|
739 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
740 |
// ์ ํฌ ์์คํ
|
741 |
this.bullets = [];
|
742 |
this.burstCounter = 0; // ํ์ฌ ์ฐ๋ฐ ์นด์ดํฐ
|
@@ -804,18 +811,31 @@ class EnemyFighter {
|
|
804 |
update(playerPosition, deltaTime) {
|
805 |
if (!this.mesh || !this.isLoaded) return;
|
806 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
807 |
const distanceToPlayer = this.position.distanceTo(playerPosition);
|
808 |
|
809 |
-
// ์ํ ๊ฒฐ์ -
|
810 |
-
if (
|
|
|
|
|
811 |
this.aiState = 'combat';
|
812 |
} else {
|
813 |
this.aiState = 'patrol';
|
814 |
}
|
815 |
|
816 |
-
// ์ถฉ๋ ํํผ ๊ณ์ฐ
|
817 |
this.calculateAvoidance();
|
818 |
|
|
|
|
|
|
|
819 |
// AI ํ๋ ์คํ
|
820 |
switch (this.aiState) {
|
821 |
case 'patrol':
|
@@ -824,6 +844,9 @@ class EnemyFighter {
|
|
824 |
case 'combat':
|
825 |
this.executeCombat(playerPosition, deltaTime);
|
826 |
break;
|
|
|
|
|
|
|
827 |
}
|
828 |
|
829 |
// ๋ฌผ๋ฆฌ ์
๋ฐ์ดํธ
|
@@ -883,7 +906,82 @@ class EnemyFighter {
|
|
883 |
}
|
884 |
}
|
885 |
|
886 |
-
//
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
887 |
|
888 |
smoothTurnToTarget(targetPos, deltaTime) {
|
889 |
// ํ๊ฒ ๋ฐฉํฅ ๊ณ์ฐ
|
@@ -891,9 +989,11 @@ class EnemyFighter {
|
|
891 |
direction.y *= 0.5; // ์์ง ์ด๋์ ๋ ์ ํ์ ์ผ๋ก
|
892 |
direction.normalize();
|
893 |
|
894 |
-
// ์ถฉ๋ ํํผ ๋ฒกํฐ ์ ์ฉ
|
895 |
if (this.avoidanceVector.length() > 0) {
|
896 |
-
|
|
|
|
|
897 |
direction.normalize();
|
898 |
}
|
899 |
|
@@ -902,12 +1002,11 @@ class EnemyFighter {
|
|
902 |
const targetPitch = Math.asin(-direction.y);
|
903 |
|
904 |
// ๋ถ๋๋ฌ์ด ํ์ (์ต๋ ํ์ ์๋ ์ ํ)
|
905 |
-
const
|
906 |
-
const
|
907 |
-
|
908 |
-
const maxTurnRate = this.turnSpeed * deltaTime;
|
909 |
|
910 |
// Yaw ํ์
|
|
|
911 |
if (Math.abs(yawDiff) > maxTurnRate) {
|
912 |
this.rotation.y += Math.sign(yawDiff) * maxTurnRate;
|
913 |
} else {
|
@@ -916,8 +1015,8 @@ class EnemyFighter {
|
|
916 |
|
917 |
// Pitch ํ์ (์ ํ์ )
|
918 |
const maxPitchRate = maxTurnRate * 0.7; // ํผ์น๋ ์กฐ๊ธ ๋ ๋น ๋ฅด๊ฒ
|
919 |
-
if (Math.abs(
|
920 |
-
this.rotation.x += Math.sign(
|
921 |
} else {
|
922 |
this.rotation.x = targetPitch;
|
923 |
}
|
@@ -925,6 +1024,12 @@ class EnemyFighter {
|
|
925 |
// Pitch ์ ํ (ยฑ40๋)
|
926 |
const maxPitchAngle = Math.PI * 40 / 180; // 40๋๋ฅผ ๋ผ๋์์ผ๋ก
|
927 |
this.rotation.x = THREE.MathUtils.clamp(this.rotation.x, -maxPitchAngle, maxPitchAngle);
|
|
|
|
|
|
|
|
|
|
|
|
|
928 |
}
|
929 |
|
930 |
calculateAvoidance() {
|
@@ -933,15 +1038,36 @@ class EnemyFighter {
|
|
933 |
if (!this.nearbyEnemies) return;
|
934 |
|
935 |
let avoidCount = 0;
|
|
|
936 |
|
937 |
this.nearbyEnemies.forEach(enemy => {
|
938 |
if (enemy === this || !enemy.position) return;
|
939 |
|
940 |
const distance = this.position.distanceTo(enemy.position);
|
941 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
942 |
// ๋ฐ๋ ๋ฐฉํฅ์ผ๋ก ํํผ
|
943 |
const avoidDir = this.position.clone().sub(enemy.position).normalize();
|
944 |
-
const strength = (300 - distance) /
|
945 |
this.avoidanceVector.add(avoidDir.multiplyScalar(strength));
|
946 |
avoidCount++;
|
947 |
}
|
@@ -950,6 +1076,14 @@ class EnemyFighter {
|
|
950 |
if (avoidCount > 0) {
|
951 |
this.avoidanceVector.divideScalar(avoidCount);
|
952 |
this.avoidanceVector.normalize();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
953 |
}
|
954 |
}
|
955 |
|
|
|
737 |
this.targetPosition = null;
|
738 |
this.playerFighter = null;
|
739 |
|
740 |
+
// ํํผ ์์คํ
|
741 |
+
this.temporaryEvadeMode = false;
|
742 |
+
this.evadeTimer = 0;
|
743 |
+
|
744 |
+
// ์ถฉ๋ ์์ธก
|
745 |
+
this.predictedPosition = new THREE.Vector3();
|
746 |
+
|
747 |
// ์ ํฌ ์์คํ
|
748 |
this.bullets = [];
|
749 |
this.burstCounter = 0; // ํ์ฌ ์ฐ๋ฐ ์นด์ดํฐ
|
|
|
811 |
update(playerPosition, deltaTime) {
|
812 |
if (!this.mesh || !this.isLoaded) return;
|
813 |
|
814 |
+
// ํํผ ํ์ด๋จธ ์
๋ฐ์ดํธ
|
815 |
+
if (this.temporaryEvadeMode && this.evadeTimer > 0) {
|
816 |
+
this.evadeTimer -= deltaTime;
|
817 |
+
if (this.evadeTimer <= 0) {
|
818 |
+
this.temporaryEvadeMode = false;
|
819 |
+
}
|
820 |
+
}
|
821 |
+
|
822 |
const distanceToPlayer = this.position.distanceTo(playerPosition);
|
823 |
|
824 |
+
// ์ํ ๊ฒฐ์ - ์ผ์์ ํํผ ๋ชจ๋๊ฐ ์ฐ์
|
825 |
+
if (this.temporaryEvadeMode) {
|
826 |
+
this.aiState = 'evade';
|
827 |
+
} else if (distanceToPlayer <= 3000) {
|
828 |
this.aiState = 'combat';
|
829 |
} else {
|
830 |
this.aiState = 'patrol';
|
831 |
}
|
832 |
|
833 |
+
// ์ถฉ๋ ํํผ ๊ณ์ฐ (ํญ์ ์คํ)
|
834 |
this.calculateAvoidance();
|
835 |
|
836 |
+
// ์ถฉ๋ ์์ธก ๊ฒ์ฌ
|
837 |
+
this.checkCollisionPrediction(deltaTime);
|
838 |
+
|
839 |
// AI ํ๋ ์คํ
|
840 |
switch (this.aiState) {
|
841 |
case 'patrol':
|
|
|
844 |
case 'combat':
|
845 |
this.executeCombat(playerPosition, deltaTime);
|
846 |
break;
|
847 |
+
case 'evade':
|
848 |
+
this.executeEmergencyEvade(deltaTime);
|
849 |
+
break;
|
850 |
}
|
851 |
|
852 |
// ๋ฌผ๋ฆฌ ์
๋ฐ์ดํธ
|
|
|
906 |
}
|
907 |
}
|
908 |
|
909 |
+
// ์๋ก์ด ๋ฉ์๋: ์ถฉ๋ ์์ธก
|
910 |
+
checkCollisionPrediction(deltaTime) {
|
911 |
+
if (!this.nearbyEnemies) return;
|
912 |
+
|
913 |
+
// 2์ด ํ ์์ ์์น ๊ณ์ฐ
|
914 |
+
const predictTime = 2.0;
|
915 |
+
const forward = new THREE.Vector3(0, 0, 1).applyEuler(this.rotation);
|
916 |
+
this.predictedPosition.copy(this.position).add(forward.multiplyScalar(this.speed * predictTime));
|
917 |
+
|
918 |
+
this.nearbyEnemies.forEach(enemy => {
|
919 |
+
if (enemy === this || !enemy.position) return;
|
920 |
+
|
921 |
+
// ๋ค๋ฅธ ์ ๊ธฐ์ ์์ ์์น
|
922 |
+
const enemyForward = new THREE.Vector3(0, 0, 1).applyEuler(enemy.rotation);
|
923 |
+
const enemyPredicted = enemy.position.clone().add(enemyForward.multiplyScalar(enemy.speed * predictTime));
|
924 |
+
|
925 |
+
// ์์ ๊ฑฐ๋ฆฌ
|
926 |
+
const predictedDistance = this.predictedPosition.distanceTo(enemyPredicted);
|
927 |
+
|
928 |
+
// 150m ์ด๋ด๋ก ์ ๊ทผ ์์ ์ ์ฌ์ ํํผ
|
929 |
+
if (predictedDistance < 150) {
|
930 |
+
// ์๋ฐฉ์ ํํผ ๋ฐฉํฅ ์ค์
|
931 |
+
const avoidDir = this.predictedPosition.clone().sub(enemyPredicted).normalize();
|
932 |
+
|
933 |
+
// ์์ง ๋ถ๋ฆฌ ์ถ๊ฐ
|
934 |
+
if (this.position.y > enemy.position.y) {
|
935 |
+
avoidDir.y += 0.3;
|
936 |
+
} else {
|
937 |
+
avoidDir.y -= 0.3;
|
938 |
+
}
|
939 |
+
|
940 |
+
this.avoidanceVector.add(avoidDir.multiplyScalar(1.5));
|
941 |
+
}
|
942 |
+
});
|
943 |
+
}
|
944 |
+
|
945 |
+
// ์๋ก์ด ๋ฉ์๋: ๊ธด๊ธ ํํผ ์คํ
|
946 |
+
executeEmergencyEvade(deltaTime) {
|
947 |
+
// ํํผ ๋ฒกํฐ๊ฐ ์์ผ๋ฉด ๊ทธ ๋ฐฉํฅ์ผ๋ก, ์์ผ๋ฉด ๊ธ์์น
|
948 |
+
if (this.avoidanceVector.length() > 0) {
|
949 |
+
const evadeDirection = this.avoidanceVector.clone().normalize();
|
950 |
+
|
951 |
+
// ๊ธ์ ํ
|
952 |
+
const targetYaw = Math.atan2(evadeDirection.x, evadeDirection.z);
|
953 |
+
const targetPitch = Math.asin(-evadeDirection.y) * 0.5; // ํผ์น๋ ์ ๋ฐ๋ง
|
954 |
+
|
955 |
+
// ๋น ๋ฅธ ํ์ ์๋
|
956 |
+
const emergencyTurnSpeed = this.turnSpeed * 2.0;
|
957 |
+
const maxTurnRate = emergencyTurnSpeed * deltaTime;
|
958 |
+
|
959 |
+
// Yaw ํ์
|
960 |
+
const yawDiff = this.normalizeAngle(targetYaw - this.rotation.y);
|
961 |
+
if (Math.abs(yawDiff) > maxTurnRate) {
|
962 |
+
this.rotation.y += Math.sign(yawDiff) * maxTurnRate;
|
963 |
+
} else {
|
964 |
+
this.rotation.y = targetYaw;
|
965 |
+
}
|
966 |
+
|
967 |
+
// Pitch ํ์
|
968 |
+
const pitchDiff = targetPitch - this.rotation.x;
|
969 |
+
if (Math.abs(pitchDiff) > maxTurnRate * 0.7) {
|
970 |
+
this.rotation.x += Math.sign(pitchDiff) * maxTurnRate * 0.7;
|
971 |
+
} else {
|
972 |
+
this.rotation.x = targetPitch;
|
973 |
+
}
|
974 |
+
|
975 |
+
// ๊ธ์ ํ ์ ์ฝ๊ฐ์ ๋กค ์ถ๊ฐ
|
976 |
+
this.rotation.z = Math.sign(yawDiff) * Math.min(Math.abs(yawDiff), Math.PI / 6);
|
977 |
+
} else {
|
978 |
+
// ๊ธฐ๋ณธ ํํผ: ๊ธ์์น
|
979 |
+
this.rotation.x = -Math.PI / 6; // 30๋ ์์น
|
980 |
+
}
|
981 |
+
|
982 |
+
// ํํผ ์ค์๋ ์ต๋ ์๋
|
983 |
+
this.speed = this.maxSpeed;
|
984 |
+
}
|
985 |
|
986 |
smoothTurnToTarget(targetPos, deltaTime) {
|
987 |
// ํ๊ฒ ๋ฐฉํฅ ๊ณ์ฐ
|
|
|
989 |
direction.y *= 0.5; // ์์ง ์ด๋์ ๋ ์ ํ์ ์ผ๋ก
|
990 |
direction.normalize();
|
991 |
|
992 |
+
// ์ถฉ๋ ํํผ ๋ฒกํฐ ์ ์ฉ (ํํผ๊ฐ ์ฐ์ )
|
993 |
if (this.avoidanceVector.length() > 0) {
|
994 |
+
// ํํผ ๋ชจ๋์์๋ ํํผ ๋ฒกํฐ์ ์ํฅ์ ํฌ๊ฒ
|
995 |
+
const avoidanceStrength = this.temporaryEvadeMode ? 1.0 : 0.5;
|
996 |
+
direction.add(this.avoidanceVector.multiplyScalar(avoidanceStrength));
|
997 |
direction.normalize();
|
998 |
}
|
999 |
|
|
|
1002 |
const targetPitch = Math.asin(-direction.y);
|
1003 |
|
1004 |
// ๋ถ๋๋ฌ์ด ํ์ (์ต๋ ํ์ ์๋ ์ ํ)
|
1005 |
+
const turnSpeed = this.temporaryEvadeMode ? this.turnSpeed * 1.5 : this.turnSpeed;
|
1006 |
+
const maxTurnRate = turnSpeed * deltaTime;
|
|
|
|
|
1007 |
|
1008 |
// Yaw ํ์
|
1009 |
+
const yawDiff = this.normalizeAngle(targetYaw - this.rotation.y);
|
1010 |
if (Math.abs(yawDiff) > maxTurnRate) {
|
1011 |
this.rotation.y += Math.sign(yawDiff) * maxTurnRate;
|
1012 |
} else {
|
|
|
1015 |
|
1016 |
// Pitch ํ์ (์ ํ์ )
|
1017 |
const maxPitchRate = maxTurnRate * 0.7; // ํผ์น๋ ์กฐ๊ธ ๋ ๋น ๋ฅด๊ฒ
|
1018 |
+
if (Math.abs(targetPitch - this.rotation.x) > maxPitchRate) {
|
1019 |
+
this.rotation.x += Math.sign(targetPitch - this.rotation.x) * maxPitchRate;
|
1020 |
} else {
|
1021 |
this.rotation.x = targetPitch;
|
1022 |
}
|
|
|
1024 |
// Pitch ์ ํ (ยฑ40๋)
|
1025 |
const maxPitchAngle = Math.PI * 40 / 180; // 40๋๋ฅผ ๋ผ๋์์ผ๋ก
|
1026 |
this.rotation.x = THREE.MathUtils.clamp(this.rotation.x, -maxPitchAngle, maxPitchAngle);
|
1027 |
+
|
1028 |
+
// ๋กค ์๋ ๊ณ์ฐ (์ ํ ์)
|
1029 |
+
if (!this.temporaryEvadeMode) {
|
1030 |
+
this.rotation.z = -yawDiff * 0.5; // ์ ํ ๋ฐฉํฅ์ผ๋ก ๊ธฐ์ธ๊ธฐ
|
1031 |
+
this.rotation.z = THREE.MathUtils.clamp(this.rotation.z, -Math.PI / 4, Math.PI / 4);
|
1032 |
+
}
|
1033 |
}
|
1034 |
|
1035 |
calculateAvoidance() {
|
|
|
1038 |
if (!this.nearbyEnemies) return;
|
1039 |
|
1040 |
let avoidCount = 0;
|
1041 |
+
let criticalAvoidance = false;
|
1042 |
|
1043 |
this.nearbyEnemies.forEach(enemy => {
|
1044 |
if (enemy === this || !enemy.position) return;
|
1045 |
|
1046 |
const distance = this.position.distanceTo(enemy.position);
|
1047 |
+
|
1048 |
+
// 100m ๋ฏธ๋ง: ๊ธด๊ธ ํํผ
|
1049 |
+
if (distance < 100 && distance > 0) {
|
1050 |
+
criticalAvoidance = true;
|
1051 |
+
|
1052 |
+
// ๊ฐํ ๋ฐ๋ฐ๋ ฅ
|
1053 |
+
const avoidDir = this.position.clone().sub(enemy.position).normalize();
|
1054 |
+
const strength = 2.0; // ๋งค์ฐ ๊ฐํ ํํผ
|
1055 |
+
this.avoidanceVector.add(avoidDir.multiplyScalar(strength));
|
1056 |
+
|
1057 |
+
// ๊ณ ๋ ์ฐจ์ด ์ถ๊ฐ (์/์๋๋ก ๋ถ์ฐ)
|
1058 |
+
if (this.position.y > enemy.position.y) {
|
1059 |
+
this.avoidanceVector.y += 0.5; // ์๋ก
|
1060 |
+
} else {
|
1061 |
+
this.avoidanceVector.y -= 0.5; // ์๋๋ก
|
1062 |
+
}
|
1063 |
+
|
1064 |
+
avoidCount++;
|
1065 |
+
}
|
1066 |
+
// 100-300m: ์๋ฐฉ์ ํํผ
|
1067 |
+
else if (distance < 300) {
|
1068 |
// ๋ฐ๋ ๋ฐฉํฅ์ผ๋ก ํํผ
|
1069 |
const avoidDir = this.position.clone().sub(enemy.position).normalize();
|
1070 |
+
const strength = (300 - distance) / 200; // 100-300m ๋ฒ์์์ ๊ฐ๋ ๊ณ์ฐ
|
1071 |
this.avoidanceVector.add(avoidDir.multiplyScalar(strength));
|
1072 |
avoidCount++;
|
1073 |
}
|
|
|
1076 |
if (avoidCount > 0) {
|
1077 |
this.avoidanceVector.divideScalar(avoidCount);
|
1078 |
this.avoidanceVector.normalize();
|
1079 |
+
|
1080 |
+
// ๊ธด๊ธ ํํผ ์ ๋ ๊ฐํ ํํผ๋ ฅ ์ ์ฉ
|
1081 |
+
if (criticalAvoidance) {
|
1082 |
+
this.avoidanceVector.multiplyScalar(2.0);
|
1083 |
+
// ์ผ์์ ์ผ๋ก ์ ํฌ ์ํ ํด์
|
1084 |
+
this.temporaryEvadeMode = true;
|
1085 |
+
this.evadeTimer = 2.0; // 2์ด ๋๏ฟฝ๏ฟฝ๏ฟฝ ํํผ ์ฐ์
|
1086 |
+
}
|
1087 |
}
|
1088 |
}
|
1089 |
|