diff --git a/Assets/Scenes/GameMain.unity b/Assets/Scenes/GameMain.unity
index 967f5c6..aecb6fe 100644
--- a/Assets/Scenes/GameMain.unity
+++ b/Assets/Scenes/GameMain.unity
@@ -2821,6 +2821,10 @@ PrefabInstance:
propertyPath: GlobalObjectIdHash
value: 1153289458
objectReference: {fileID: 0}
+ - target: {fileID: 6473738761679328420, guid: ace4a11b046dac9498e6897c2b5eb245, type: 3}
+ propertyPath: m_Convex
+ value: 0
+ objectReference: {fileID: 0}
- target: {fileID: 6727958582834582256, guid: ace4a11b046dac9498e6897c2b5eb245, type: 3}
propertyPath: m_LocalPosition.x
value: 10
@@ -3027,6 +3031,10 @@ PrefabInstance:
propertyPath: GlobalObjectIdHash
value: 1738409839
objectReference: {fileID: 0}
+ - target: {fileID: 9130994837512732106, guid: 368961a0f2d71ce4aad5d8ffe52e0b7f, type: 3}
+ propertyPath: m_Convex
+ value: 0
+ objectReference: {fileID: 0}
m_RemovedComponents: []
m_RemovedGameObjects: []
m_AddedGameObjects: []
@@ -3415,6 +3423,14 @@ PrefabInstance:
propertyPath: m_Name
value: Core
objectReference: {fileID: 0}
+ - target: {fileID: 8929210192865157768, guid: e56926eda34629f4fbf3e4c53f0f8bd4, type: 3}
+ propertyPath: m_Convex
+ value: 0
+ objectReference: {fileID: 0}
+ - target: {fileID: 8929210192865157768, guid: e56926eda34629f4fbf3e4c53f0f8bd4, type: 3}
+ propertyPath: m_Enabled
+ value: 1
+ objectReference: {fileID: 0}
m_RemovedComponents: []
m_RemovedGameObjects: []
m_AddedGameObjects: []
diff --git a/Assets/Scenes/GameMain/NavMesh-Primitive_Floor.asset b/Assets/Scenes/GameMain/NavMesh-Primitive_Floor.asset
index f771691..4136e99 100644
Binary files a/Assets/Scenes/GameMain/NavMesh-Primitive_Floor.asset and b/Assets/Scenes/GameMain/NavMesh-Primitive_Floor.asset differ
diff --git a/Assets/Scripts/Core.cs b/Assets/Scripts/Core.cs
index f0e0f94..b21d0c4 100644
--- a/Assets/Scripts/Core.cs
+++ b/Assets/Scripts/Core.cs
@@ -1,5 +1,6 @@
using Unity.Netcode;
using UnityEngine;
+using UnityEngine.AI;
namespace Northbound
{
@@ -332,5 +333,44 @@ namespace Northbound
}
#endregion
+
+ #region Navigation
+
+ ///
+ /// AI가 코어에 접근할 수 있는 NavMesh 위치 반환 (콜라이더 표면 근처)
+ ///
+ public Vector3 GetNavMeshPosition()
+ {
+ Collider coreCollider = GetComponent();
+ if (coreCollider == null)
+ {
+ return transform.position;
+ }
+
+ // 1. 코어 콜라이더 표면에서 남쪽(앞쪽) 방향으로 검색 시작
+ // 적들이 보통 남쪽에서 오기 때문에 전면부 검색
+ Vector3 searchDirection = Vector3.back; // -Z 방향 (남쪽)
+ Vector3 searchCenter = coreCollider.ClosestPoint(transform.position + searchDirection * 5f);
+
+ // 2. 표면 방향으로 attackRange(약 2m)보다 조금 더 큰 거리에서 NavMesh 샘플링
+ NavMeshHit hit;
+ float maxDistance = 3f; // 콜라이더 표면에서 탐색할 거리
+
+ if (NavMesh.SamplePosition(searchCenter, out hit, maxDistance, NavMesh.AllAreas))
+ {
+ return hit.position;
+ }
+
+ // 3. 실패 시 코어 중심에서 더 넓게 탐색 (fallback)
+ if (NavMesh.SamplePosition(transform.position, out hit, 10f, NavMesh.AllAreas))
+ {
+ return hit.position;
+ }
+
+ Debug.LogWarning("[Core] 코어 주변에서 NavMesh를 찾을 수 없습니다.");
+ return transform.position;
+ }
+
+ #endregion
}
}
\ No newline at end of file
diff --git a/Assets/Scripts/EnemyAIController.cs b/Assets/Scripts/EnemyAIController.cs
index 5b0c62f..bfcca17 100644
--- a/Assets/Scripts/EnemyAIController.cs
+++ b/Assets/Scripts/EnemyAIController.cs
@@ -63,6 +63,7 @@ namespace Northbound
private NavMeshAgent _agent;
private EnemyUnit _enemyUnit;
+ private Core _core;
private Transform _coreTransform;
private Collider _coreCollider;
private Vector3 _originPosition;
@@ -99,7 +100,7 @@ namespace Northbound
_agent.speed = moveSpeed;
_agent.acceleration = 8f;
_agent.angularSpeed = 120f;
- _agent.stoppingDistance = attackRange * 0.7f;
+ _agent.stoppingDistance = attackRange * 0.9f; // 공격 범위까지 더 가까이 이동
_agent.autoBraking = true;
_agent.updateRotation = true;
_agent.updateUpAxis = false;
@@ -164,7 +165,22 @@ namespace Northbound
if (_isRecalculatingPath) return; // 코루틴 대기 중이면 중단
if (_coreTransform == null) { FindCore(); return; }
- // 0. Player 감지 (코어로 가는 도중에도 Player를 타겟팅)
+ // 0. 경로 계산 중이거나 이동 중이 아니면 아무것도 하지 않음
+ if (_agent.pathPending) return; // 경로 계산 중
+
+ // 0.5. 실제로 이동 중인지 확인 (velocity로 판단)
+ if (_agent.hasPath && _agent.velocity.sqrMagnitude > 0.01f)
+ {
+ // 이동 중이면 코어 콜라이더 표면까지의 거리로 도달 확인
+ if (GetDistanceToCoreSurface() <= attackRange)
+ {
+ SetTargetPlayer(_coreTransform.gameObject);
+ TransitionToState(EnemyAIState.Attack);
+ return;
+ }
+ }
+
+ // 1. Player 감지 (코어로 가는 도중에도 Player를 타겟팅)
GameObject detectedPlayer = DetectTarget();
if (detectedPlayer != null)
{
@@ -173,26 +189,19 @@ namespace Northbound
return;
}
- // 1. 코어로 가는 경로가 '완전(Complete)'한지 먼저 확인
+ // 2. 코어로 가는 경로가 '완전(Complete)'한지 확인
// NavMesh가 갱신되었다면 에이전트는 즉시 Complete 상태가 됩니다.
if (_agent.hasPath && _agent.pathStatus == NavMeshPathStatus.PathComplete)
{
if (!_hasSetCoreDestination)
{
- _agent.SetDestination(_coreTransform.position);
+ _agent.SetDestination(_core.GetNavMeshPosition());
_hasSetCoreDestination = true;
}
// [중요] 길이 열렸으므로 아래의 장애물 탐지 로직을 아예 실행하지 않고 리턴!
return;
}
- // 2. 코어 도달 확인
- if (GetDistanceToCoreSurface() <= attackRange)
- {
- TransitionToState(EnemyAIState.Attack);
- return;
- }
-
// 3. 길이 막혔을 때(Partial)만 아주 좁은 범위에서 장애물을 찾음
GameObject obstacle = DetectObstacle();
if (obstacle != null)
@@ -205,7 +214,7 @@ namespace Northbound
// 4. 경로가 유효하지 않을 때만 재설정
if (!_agent.hasPath || _agent.pathStatus == NavMeshPathStatus.PathInvalid)
{
- _agent.SetDestination(_coreTransform.position);
+ _agent.SetDestination(_core.GetNavMeshPosition());
_hasSetCoreDestination = true;
}
}
@@ -267,31 +276,35 @@ namespace Northbound
return;
}
- // 핵심: ClosestPoint를 사용해 '벽 표면'까지의 실제 거리 계산
- float distance = GetDistanceToTarget(target);
+ // 코어 공격 시에는 도달 시점부터 항상 공격 가능하다고 가정 (거리 체크 생략)
+ bool isCoreTarget = (target == _coreTransform?.gameObject);
- // 코어 혹은 일반 타겟 공격 범위 체크 (공격 시 약간의 거리 여유 1.2f 부여)
- if (distance <= attackRange * 1.2f)
+ if (!isCoreTarget)
{
- // 타겟 바라보기
- Vector3 direction = (target.transform.position - transform.position).normalized;
- direction.y = 0;
- if (direction != Vector3.zero)
- {
- transform.rotation = Quaternion.Slerp(transform.rotation, Quaternion.LookRotation(direction), Time.deltaTime * 5f);
- }
+ // 일반 타겟(플레이어, 장애물)은 거리 체크 필요
+ float distance = GetDistanceToTarget(target);
- // 공격 실행
- if (Time.time - _lastAttackTime >= attackInterval)
+ // 공격 범위 체크 (공격 시 약간의 거리 여유 1.2f 부여)
+ if (distance > attackRange * 1.2f)
{
- AttackTarget(target);
+ // 거리가 멀어지면 추적 상태로 전환
+ TransitionToState(EnemyAIState.ChasePlayer);
+ return;
}
}
- else
+
+ // 타겟 바라보기
+ Vector3 direction = (target.transform.position - transform.position).normalized;
+ direction.y = 0;
+ if (direction != Vector3.zero)
{
- // 거리가 멀어지면 타겟 종류에 따라 상태 전환
- if (target == _coreTransform?.gameObject) TransitionToState(EnemyAIState.MoveToCore);
- else TransitionToState(EnemyAIState.ChasePlayer);
+ transform.rotation = Quaternion.Slerp(transform.rotation, Quaternion.LookRotation(direction), Time.deltaTime * 5f);
+ }
+
+ // 공격 실행
+ if (Time.time - _lastAttackTime >= attackInterval)
+ {
+ AttackTarget(target);
}
}
@@ -503,9 +516,9 @@ namespace Northbound
case EnemyAIState.MoveToCore:
_agent.isStopped = false;
_agent.speed = moveSpeed;
- if (_coreTransform != null)
+ if (_core != null)
{
- _agent.SetDestination(_coreTransform.position);
+ _agent.SetDestination(_core.GetNavMeshPosition());
_hasSetCoreDestination = true;
}
break;
@@ -579,11 +592,25 @@ namespace Northbound
private void FindCore()
{
- Core core = FindFirstObjectByType();
- if (core != null)
+ _core = FindFirstObjectByType();
+ if (_core != null)
{
- _coreTransform = core.transform;
- _coreCollider = core.GetComponent() ?? core.GetComponentInChildren();
+ _coreTransform = _core.transform;
+ // 물리적 충돌을 막는 MeshCollider를 우선 찾기 (Trigger 콜라이더 무시)
+ Collider[] allColliders = _core.GetComponentsInChildren();
+ foreach (Collider col in allColliders)
+ {
+ if (!col.isTrigger)
+ {
+ _coreCollider = col;
+ break;
+ }
+ }
+ // Fallback: 모든 콜라이더가 Trigger이면 첫 번째 콜라이더 사용
+ if (_coreCollider == null && allColliders.Length > 0)
+ {
+ _coreCollider = allColliders[0];
+ }
}
}
diff --git a/ProjectSettings/NavMeshAreas.asset b/ProjectSettings/NavMeshAreas.asset
index 3b0b7c3..b90647c 100644
--- a/ProjectSettings/NavMeshAreas.asset
+++ b/ProjectSettings/NavMeshAreas.asset
@@ -71,9 +71,9 @@ NavMeshProjectSettings:
cost: 1
m_LastAgentTypeID: -887442657
m_Settings:
- - serializedVersion: 2
+ - serializedVersion: 3
agentTypeID: 0
- agentRadius: 0.5
+ agentRadius: 0.05
agentHeight: 2
agentSlope: 45
agentClimb: 0.75
@@ -84,7 +84,9 @@ NavMeshProjectSettings:
cellSize: 0.16666667
manualTileSize: 0
tileSize: 256
- accuratePlacement: 0
+ buildHeightMesh: 0
+ maxJobWorkers: 0
+ preserveTilesOutsideBounds: 0
debug:
m_Flags: 0
m_SettingNames: