[2025_12_26] 공격 범위 계산 버그 - 좌표 기준점 설정의 중요점

2025. 12. 26. 20:51TIL

문제 상황

오토배틀 게임에서 마법사(AOE 공격 유닛)가 사거리 밖에 있는 넥서스를 공격하는 버그 발생

버그

- 마법사가 넥서스와 멀리 떨어져 있음 (사거리 5, 실제 거리 10+)
- "넥서스 피격중" 로그가 계속 출력됨
- 넥서스 HP가 지속적으로 감소

 원인 분석

잘못된 코드

csharp
private void OnAOEAttack()
{
    Vector3 targetPos = unit.GetTargetPosition(); // 넥서스 위치
    
    if (unit.CurrentTargetNexus != null)
    {
        // targetPos가 넥서스 위치이므로 distance = 0
        float distanceToNexus = Vector3.Distance(targetPos, unit.CurrentTargetNexus.transform.position);
        
        if (distanceToNexus <= aoeRadius) // 항상 true
        {
            unit.CurrentTargetNexus.TakeDamage(damage); // 버그 발생
        }
    }
}

문제점

  1. AOE 중심(targetPos) = 넥서스 위치
  2. 거리 체크도 넥서스 위치 기준
  3. 결과: Distance(넥서스, 넥서스) = 0 → 항상 AOE 범위 내로 판정
  4. 유닛의 실제 사거리는 전혀 체크되지 않음

 해결 방법

핵심: 두 가지 거리를 분리해서 체크

csharp
private void OnAOEAttack()
{
    if (!unit.IsTargetInAttackRange()) return; // 1. 사거리 체크 (Execute에서도 확인)
    
    Vector3 targetPos = unit.GetTargetPosition(); // 타겟 위치
    Vector3 attackCenter = targetPos; // AOE 중심
    
    float aoeRadius = unit.Data.AOERadius ?? 3f;
    float damage = unit.CurAtk;
    
    if (unit.CurrentTargetNexus != null)
    {
        // 2. 유닛과 넥서스 사이의 거리 (사거리 체크)
        float distanceFromUnit = Vector3.Distance(unitTransform.position, unit.CurrentTargetNexus.transform.position);
        float attackRange = unit.Data.unitAttackRange ?? 3f;
        
        // 3. 사거리 내에 있는지 먼저 확인
        if (distanceFromUnit <= attackRange)
        {
            // 4. AOE 범위 내에 있는지 확인
            float distanceFromAOECenter = Vector3.Distance(attackCenter, unit.CurrentTargetNexus.transform.position);
            if (distanceFromAOECenter <= aoeRadius)
            {
                unit.CurrentTargetNexus.TakeDamage(damage);
            }
        }
    }
}
```

체크 순서
1. Execute()에서 사거리 체크** (`IsTargetInAttackRange()`)
2. OnAOEAttack() 초반에 한 번 더 사거리 체크
3. 유닛 위치 ↔ 넥서스 거리 확인 (사거리)
4. AOE 중심 ↔ 넥서스 거리확인 (AOE 범위)

---

Before & After

Before (버그)
```
마법사 위치: (0, 0)
넥서스 위치: (15, 0)  // 사거리 5 밖!
AOE 중심: (15, 0)     // 넥서스 위치

Distance(AOE 중심, 넥서스) = 0
→ 0 <= 3 (AOE 반경) 데미지 발생! (버그)
```

After (정상)
```
마법사 위치: (0, 0)
넥서스 위치: (15, 0)
사거리: 5

Distance(마법사, 넥서스) = 15
→ 15 > 5 거리 밖이므로 공격 불가 (정상)

배운 점

1. 좌표 기준점을 명확히 하자

  • AOE 중심, 사거리 체크, 거리 계산 등 각각의 기준점이 무엇인지 명확히 정의
  • 특히 "거리"를 계산할 때는 "어디서 어디까지의 거리인가?" 항상 확인

2. 같은 대상을 다른 기준으로 체크해야 할 때 주의

csharp
// 잘못된 예
Vector3 center = target.position;
float distance = Vector3.Distance(center, target.position); // 의미 없음!

// 올바른 예
float distanceFromUnit = Vector3.Distance(unit.position, target.position);
float distanceFromAOECenter = Vector3.Distance(aoeCenter, target.position);

3. 다층 검증의 중요성

  • Execute()에서 1차 사거리 체크
  • Attack() 직전 2차 사거리 체크
  • OnAOEAttack() 내부에서 3차 상세 체크
  • 중복되더라도 안전장치를 여러 겹 두는 것이 좋다

4. 버그 발견 팁

  • 로그에 거리 값을 출력해서 예상과 다른지 확인
  • Debug.Log($"거리: {distance:F2}, 사거리: {attackRange}") 같은 식으로 수치 비교

추가 개선사항

공격 직전에도 사거리 재확인 추가:

csharp
public void Execute(UnitFSM unitFSM)
{
    // ...
    
    if (attackTimer >= 1f / attackSpeed)
    {
        attackTimer = 0f;
        
        // 공격 직전 사거리 재확인!
        if (unit.IsTargetInAttackRange())
        {
            Attack();
            unit.TryUseSkill();
        }
    }
}

결론

"거리 계산은 항상 기준점이 2개 필요하다*는 기본을 놓쳐서 발생한 버그였다. 특히 AOE나 범위 공격처럼 여러 좌표 기준이 섞이는 경우, 각 거리 계산의 의미를 명확히 하고 로그로 검증하는 습관이 중요하다.