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: