Spaces:
Running
Running
Update game.js
Browse files
game.js
CHANGED
@@ -809,7 +809,7 @@ class EnemyFighter {
|
|
809 |
this.maxSpeed = 386; // 750kt in m/s
|
810 |
|
811 |
// AI 상태
|
812 |
-
this.aiState = 'patrol'; // patrol, combat, evade
|
813 |
this.targetPosition = null;
|
814 |
this.playerFighter = null;
|
815 |
|
@@ -817,6 +817,10 @@ class EnemyFighter {
|
|
817 |
this.temporaryEvadeMode = false;
|
818 |
this.evadeTimer = 0;
|
819 |
|
|
|
|
|
|
|
|
|
820 |
// 충돌 예측
|
821 |
this.predictedPosition = new THREE.Vector3();
|
822 |
|
@@ -897,8 +901,21 @@ class EnemyFighter {
|
|
897 |
|
898 |
const distanceToPlayer = this.position.distanceTo(playerPosition);
|
899 |
|
900 |
-
//
|
901 |
-
if (
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
902 |
this.aiState = 'evade';
|
903 |
} else if (distanceToPlayer <= 3000) {
|
904 |
this.aiState = 'combat';
|
@@ -923,6 +940,9 @@ class EnemyFighter {
|
|
923 |
case 'evade':
|
924 |
this.executeEmergencyEvade(deltaTime);
|
925 |
break;
|
|
|
|
|
|
|
926 |
}
|
927 |
|
928 |
// 물리 업데이트
|
@@ -932,6 +952,24 @@ class EnemyFighter {
|
|
932 |
this.updateBullets(deltaTime);
|
933 |
}
|
934 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
935 |
executePatrol(deltaTime) {
|
936 |
// 목표 지점까지의 거리 확인
|
937 |
if (!this.targetPosition || this.position.distanceTo(this.targetPosition) < 500) {
|
@@ -949,6 +987,14 @@ class EnemyFighter {
|
|
949 |
}
|
950 |
|
951 |
executeCombat(playerPosition, deltaTime) {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
952 |
// 플레이어를 향해 회전
|
953 |
this.smoothTurnToTarget(playerPosition, deltaTime);
|
954 |
|
@@ -961,7 +1007,7 @@ class EnemyFighter {
|
|
961 |
const isStraightFlying = isPitchLevel && isRollLevel;
|
962 |
|
963 |
// 직진 비행 중이고 정확한 조준 시에만 발사 가능
|
964 |
-
this.canShoot = isStraightFlying && aimAccuracy < 0.15;
|
965 |
|
966 |
// 발사 조건 충족 시 발사
|
967 |
if (this.canShoot) {
|
@@ -972,7 +1018,6 @@ class EnemyFighter {
|
|
972 |
}
|
973 |
|
974 |
// 거리에 따라 자유롭게 기동 결정
|
975 |
-
const distance = this.position.distanceTo(playerPosition);
|
976 |
if (distance < 500) {
|
977 |
// 너무 가까우면 회피 기동
|
978 |
this.selectEvadeTarget();
|
@@ -1059,15 +1104,15 @@ class EnemyFighter {
|
|
1059 |
this.speed = this.maxSpeed;
|
1060 |
}
|
1061 |
|
1062 |
-
smoothTurnToTarget(targetPos, deltaTime) {
|
1063 |
// 타겟 방향 계산
|
1064 |
const direction = targetPos.clone().sub(this.position);
|
1065 |
direction.y *= 0.5; // 수직 이동을 덜 제한적으로
|
1066 |
direction.normalize();
|
1067 |
|
1068 |
// 충돌 회피 벡터 적용 (회피가 우선)
|
1069 |
-
if (this.avoidanceVector.length() > 0) {
|
1070 |
-
//
|
1071 |
const avoidanceStrength = this.temporaryEvadeMode ? 1.0 : 0.5;
|
1072 |
direction.add(this.avoidanceVector.multiplyScalar(avoidanceStrength));
|
1073 |
direction.normalize();
|
@@ -1078,7 +1123,7 @@ class EnemyFighter {
|
|
1078 |
const targetPitch = Math.asin(-direction.y);
|
1079 |
|
1080 |
// 부드러운 회전 (최대 회전 속도 제한)
|
1081 |
-
const turnSpeed = this.temporaryEvadeMode ? this.turnSpeed * 1.5 : this.turnSpeed;
|
1082 |
const maxTurnRate = turnSpeed * deltaTime;
|
1083 |
|
1084 |
// Yaw 회전
|
@@ -1102,7 +1147,7 @@ class EnemyFighter {
|
|
1102 |
this.rotation.x = THREE.MathUtils.clamp(this.rotation.x, -maxPitchAngle, maxPitchAngle);
|
1103 |
|
1104 |
// 롤 자동 계산 (선회 시)
|
1105 |
-
if (!this.temporaryEvadeMode) {
|
1106 |
this.rotation.z = -yawDiff * 0.5; // 선회 방향으로 기울기
|
1107 |
this.rotation.z = THREE.MathUtils.clamp(this.rotation.z, -Math.PI / 4, Math.PI / 4);
|
1108 |
}
|
|
|
809 |
this.maxSpeed = 386; // 750kt in m/s
|
810 |
|
811 |
// AI 상태
|
812 |
+
this.aiState = 'patrol'; // patrol, combat, evade, retreat
|
813 |
this.targetPosition = null;
|
814 |
this.playerFighter = null;
|
815 |
|
|
|
817 |
this.temporaryEvadeMode = false;
|
818 |
this.evadeTimer = 0;
|
819 |
|
820 |
+
// 후퇴 시스템
|
821 |
+
this.isRetreating = false;
|
822 |
+
this.retreatTargetDistance = 250; // 목표 후퇴 거리
|
823 |
+
|
824 |
// 충돌 예측
|
825 |
this.predictedPosition = new THREE.Vector3();
|
826 |
|
|
|
901 |
|
902 |
const distanceToPlayer = this.position.distanceTo(playerPosition);
|
903 |
|
904 |
+
// 100m 이내면 즉시 후퇴 모드 활성화
|
905 |
+
if (distanceToPlayer < 100) {
|
906 |
+
this.isRetreating = true;
|
907 |
+
this.aiState = 'retreat';
|
908 |
+
}
|
909 |
+
|
910 |
+
// 250m 이상 떨어지면 후퇴 모드 해제
|
911 |
+
if (this.isRetreating && distanceToPlayer >= this.retreatTargetDistance) {
|
912 |
+
this.isRetreating = false;
|
913 |
+
}
|
914 |
+
|
915 |
+
// 상태 결정 - 후퇴가 최우선
|
916 |
+
if (this.isRetreating) {
|
917 |
+
this.aiState = 'retreat';
|
918 |
+
} else if (this.temporaryEvadeMode) {
|
919 |
this.aiState = 'evade';
|
920 |
} else if (distanceToPlayer <= 3000) {
|
921 |
this.aiState = 'combat';
|
|
|
940 |
case 'evade':
|
941 |
this.executeEmergencyEvade(deltaTime);
|
942 |
break;
|
943 |
+
case 'retreat':
|
944 |
+
this.executeRetreat(playerPosition, deltaTime);
|
945 |
+
break;
|
946 |
}
|
947 |
|
948 |
// 물리 업데이트
|
|
|
952 |
this.updateBullets(deltaTime);
|
953 |
}
|
954 |
|
955 |
+
// 새로운 메서드: 후퇴 실행
|
956 |
+
executeRetreat(playerPosition, deltaTime) {
|
957 |
+
// 플레이어로부터 멀어지는 방향 계산
|
958 |
+
const retreatDirection = this.position.clone().sub(playerPosition).normalize();
|
959 |
+
|
960 |
+
// 목표 위치 설정 (현재 위치에서 플레이어 반대 방향으로)
|
961 |
+
const targetPosition = this.position.clone().add(retreatDirection.multiplyScalar(200));
|
962 |
+
|
963 |
+
// 후퇴 중에는 최대 속도
|
964 |
+
this.speed = this.maxSpeed;
|
965 |
+
|
966 |
+
// 빠른 회전으로 후퇴
|
967 |
+
this.smoothTurnToTarget(targetPosition, deltaTime, true); // true는 긴급 회전
|
968 |
+
|
969 |
+
// 후퇴 중에는 발사 금지
|
970 |
+
this.canShoot = false;
|
971 |
+
}
|
972 |
+
|
973 |
executePatrol(deltaTime) {
|
974 |
// 목표 지점까지의 거리 확인
|
975 |
if (!this.targetPosition || this.position.distanceTo(this.targetPosition) < 500) {
|
|
|
987 |
}
|
988 |
|
989 |
executeCombat(playerPosition, deltaTime) {
|
990 |
+
const distance = this.position.distanceTo(playerPosition);
|
991 |
+
|
992 |
+
// 100m 미만이면 즉시 후퇴 (이중 체크)
|
993 |
+
if (distance < 100) {
|
994 |
+
this.isRetreating = true;
|
995 |
+
return;
|
996 |
+
}
|
997 |
+
|
998 |
// 플레이어를 향해 회전
|
999 |
this.smoothTurnToTarget(playerPosition, deltaTime);
|
1000 |
|
|
|
1007 |
const isStraightFlying = isPitchLevel && isRollLevel;
|
1008 |
|
1009 |
// 직진 비행 중이고 정확한 조준 시에만 발사 가능
|
1010 |
+
this.canShoot = isStraightFlying && aimAccuracy < 0.15 && distance > 150; // 150m 이상에서만 발사
|
1011 |
|
1012 |
// 발사 조건 충족 시 발사
|
1013 |
if (this.canShoot) {
|
|
|
1018 |
}
|
1019 |
|
1020 |
// 거리에 따라 자유롭게 기동 결정
|
|
|
1021 |
if (distance < 500) {
|
1022 |
// 너무 가까우면 회피 기동
|
1023 |
this.selectEvadeTarget();
|
|
|
1104 |
this.speed = this.maxSpeed;
|
1105 |
}
|
1106 |
|
1107 |
+
smoothTurnToTarget(targetPos, deltaTime, isEmergency = false) {
|
1108 |
// 타겟 방향 계산
|
1109 |
const direction = targetPos.clone().sub(this.position);
|
1110 |
direction.y *= 0.5; // 수직 이동을 덜 제한적으로
|
1111 |
direction.normalize();
|
1112 |
|
1113 |
// 충돌 회피 벡터 적용 (회피가 우선)
|
1114 |
+
if (this.avoidanceVector.length() > 0 && !this.isRetreating) {
|
1115 |
+
// 후퇴 중이 아닐 때만 회피 벡터 적용
|
1116 |
const avoidanceStrength = this.temporaryEvadeMode ? 1.0 : 0.5;
|
1117 |
direction.add(this.avoidanceVector.multiplyScalar(avoidanceStrength));
|
1118 |
direction.normalize();
|
|
|
1123 |
const targetPitch = Math.asin(-direction.y);
|
1124 |
|
1125 |
// 부드러운 회전 (최대 회전 속도 제한)
|
1126 |
+
const turnSpeed = isEmergency ? this.turnSpeed * 2.0 : (this.temporaryEvadeMode ? this.turnSpeed * 1.5 : this.turnSpeed);
|
1127 |
const maxTurnRate = turnSpeed * deltaTime;
|
1128 |
|
1129 |
// Yaw 회전
|
|
|
1147 |
this.rotation.x = THREE.MathUtils.clamp(this.rotation.x, -maxPitchAngle, maxPitchAngle);
|
1148 |
|
1149 |
// 롤 자동 계산 (선회 시)
|
1150 |
+
if (!this.temporaryEvadeMode && !this.isRetreating) {
|
1151 |
this.rotation.z = -yawDiff * 0.5; // 선회 방향으로 기울기
|
1152 |
this.rotation.z = THREE.MathUtils.clamp(this.rotation.z, -Math.PI / 4, Math.PI / 4);
|
1153 |
}
|