fix: 플레이어 접촉 이동과 타깃 표면 추적 보정
- TargetSurfaceUtility를 추가해 플레이어와 적의 실제 충돌 표면 기준으로 거리, 방향, 목적지를 계산 - 플레이어 이동과 적 루트모션, 추적 로직에서 접촉 시 수평 이동을 제한해 겹침과 밀어내기 문제를 완화 - 드로그 AI 거리 판정 노드들이 표면 거리 기준을 사용하도록 맞춰 사거리 분기 오차를 줄임
This commit is contained in:
@@ -249,29 +249,10 @@ namespace Colosseum.Player
|
||||
private void UpdateBlockedDirection()
|
||||
{
|
||||
blockedDirection = Vector3.zero;
|
||||
if (characterController == null)
|
||||
return;
|
||||
|
||||
Vector3 center = transform.position + characterController.center;
|
||||
float radius = characterController.radius + 0.15f;
|
||||
float halfHeight = Mathf.Max(0f, characterController.height * 0.5f - characterController.radius);
|
||||
|
||||
int count = Physics.OverlapCapsuleNonAlloc(
|
||||
center + Vector3.up * halfHeight,
|
||||
center - Vector3.up * halfHeight,
|
||||
radius, overlapBuffer);
|
||||
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
if (overlapBuffer[i].gameObject == gameObject) continue;
|
||||
if (!overlapBuffer[i].TryGetComponent<UnityEngine.AI.NavMeshAgent>(out _)) continue;
|
||||
|
||||
Vector3 toEnemy = overlapBuffer[i].transform.position - transform.position;
|
||||
toEnemy.y = 0f;
|
||||
if (toEnemy.sqrMagnitude > 0.0001f)
|
||||
{
|
||||
blockedDirection = toEnemy.normalized;
|
||||
break;
|
||||
}
|
||||
}
|
||||
TryGetEnemyContactBlockDirection(out blockedDirection);
|
||||
}
|
||||
|
||||
private void ApplyGravity()
|
||||
@@ -303,9 +284,10 @@ namespace Colosseum.Player
|
||||
if (actionState != null && !actionState.CanMove)
|
||||
moveDirection = Vector3.zero;
|
||||
|
||||
if (blockedDirection != Vector3.zero && Vector3.Dot(moveDirection, blockedDirection) > 0f)
|
||||
if (IsBlockedByEnemyContact(moveDirection))
|
||||
moveDirection = Vector3.zero;
|
||||
|
||||
forcedDelta = LimitHorizontalDeltaAgainstEnemyContact(forcedDelta);
|
||||
float actualMoveSpeed = moveSpeed * GetMoveSpeedMultiplier();
|
||||
characterController.Move((moveDirection * actualMoveSpeed + velocity) * Time.deltaTime + forcedDelta);
|
||||
|
||||
@@ -400,16 +382,8 @@ namespace Colosseum.Player
|
||||
|
||||
Vector3 deltaPosition = animator.deltaPosition;
|
||||
Vector3 forcedDelta = ConsumeForcedMovementDelta(Time.deltaTime);
|
||||
|
||||
if (blockedDirection != Vector3.zero)
|
||||
{
|
||||
Vector3 deltaXZ = new Vector3(deltaPosition.x, 0f, deltaPosition.z);
|
||||
if (Vector3.Dot(deltaXZ, blockedDirection) > 0f)
|
||||
{
|
||||
deltaPosition.x = 0f;
|
||||
deltaPosition.z = 0f;
|
||||
}
|
||||
}
|
||||
deltaPosition = LimitHorizontalDeltaAgainstEnemyContact(deltaPosition);
|
||||
forcedDelta = LimitHorizontalDeltaAgainstEnemyContact(forcedDelta);
|
||||
|
||||
if (skillController.IgnoreRootMotionY)
|
||||
{
|
||||
@@ -446,5 +420,100 @@ namespace Colosseum.Player
|
||||
|
||||
return delta;
|
||||
}
|
||||
|
||||
private Vector3 LimitHorizontalDeltaAgainstEnemyContact(Vector3 delta)
|
||||
{
|
||||
Vector3 horizontalDelta = new Vector3(delta.x, 0f, delta.z);
|
||||
if (!IsBlockedByEnemyContact(horizontalDelta))
|
||||
return delta;
|
||||
|
||||
delta.x = 0f;
|
||||
delta.z = 0f;
|
||||
return delta;
|
||||
}
|
||||
|
||||
private bool IsBlockedByEnemyContact(Vector3 horizontalDirection)
|
||||
{
|
||||
Vector3 horizontal = new Vector3(horizontalDirection.x, 0f, horizontalDirection.z);
|
||||
if (horizontal.sqrMagnitude <= 0.000001f || blockedDirection.sqrMagnitude <= 0.0001f)
|
||||
return false;
|
||||
|
||||
return Vector3.Dot(horizontal, blockedDirection) >= 0f;
|
||||
}
|
||||
|
||||
private bool TryGetEnemyContactBlockDirection(out Vector3 blockDirection)
|
||||
{
|
||||
blockDirection = Vector3.zero;
|
||||
if (characterController == null)
|
||||
return false;
|
||||
|
||||
Vector3 center = transform.position + characterController.center;
|
||||
float radius = characterController.radius + 0.15f;
|
||||
float halfHeight = Mathf.Max(0f, characterController.height * 0.5f - characterController.radius);
|
||||
|
||||
int count = Physics.OverlapCapsuleNonAlloc(
|
||||
center + Vector3.up * halfHeight,
|
||||
center - Vector3.up * halfHeight,
|
||||
radius,
|
||||
overlapBuffer);
|
||||
|
||||
int overlapCount = 0;
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
if (!TryGetEnemyCollider(overlapBuffer[i], out Collider enemyCollider))
|
||||
continue;
|
||||
|
||||
if (!Physics.ComputePenetration(
|
||||
characterController, transform.position, transform.rotation,
|
||||
enemyCollider, enemyCollider.transform.position, enemyCollider.transform.rotation,
|
||||
out Vector3 separationDirection, out float separationDistance) ||
|
||||
separationDistance <= 0.0001f)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
Vector3 towardEnemy = enemyCollider.bounds.center - center;
|
||||
towardEnemy.y = 0f;
|
||||
if (towardEnemy.sqrMagnitude <= 0.0001f)
|
||||
{
|
||||
towardEnemy = -separationDirection;
|
||||
towardEnemy.y = 0f;
|
||||
if (towardEnemy.sqrMagnitude <= 0.0001f)
|
||||
continue;
|
||||
}
|
||||
|
||||
blockDirection += towardEnemy.normalized;
|
||||
overlapCount++;
|
||||
}
|
||||
|
||||
if (overlapCount <= 0 || blockDirection.sqrMagnitude <= 0.0001f)
|
||||
{
|
||||
blockDirection = Vector3.zero;
|
||||
return false;
|
||||
}
|
||||
|
||||
blockDirection.Normalize();
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool TryGetEnemyCollider(Collider overlapCollider, out Collider enemyCollider)
|
||||
{
|
||||
enemyCollider = null;
|
||||
if (overlapCollider == null)
|
||||
return false;
|
||||
|
||||
if (overlapCollider.gameObject == gameObject || overlapCollider.transform.IsChildOf(transform))
|
||||
return false;
|
||||
|
||||
Colosseum.Enemy.EnemyBase enemy = overlapCollider.GetComponent<Colosseum.Enemy.EnemyBase>();
|
||||
if (enemy == null)
|
||||
enemy = overlapCollider.GetComponentInParent<Colosseum.Enemy.EnemyBase>();
|
||||
|
||||
if (enemy == null)
|
||||
return false;
|
||||
|
||||
enemyCollider = overlapCollider;
|
||||
return enemyCollider != null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user