원-원 충돌 감지 알고리즘

Unity에서 원-원 충돌 감지를 구현하는 방법을 학습합니다. 피타고라스 정리를 사용한 거리 계산과 충돌 판정 알고리즘을 자세히 설명합니다.

개요

원-원 충돌 감지는 두 원이 겹쳤는지 판단하는 알고리즘입니다. 게임에서 플레이어와 적, 총알과 적 등의 충돌을 감지할 때 자주 사용됩니다.

ArrowController.cs의 충돌 감지 코드

// 화살의 현재 위치를 2D 벡터로 저장
Vector2 p1 = transform.position;  // Arrow

// 플레이어의 현재 위치를 2D 벡터로 저장
Vector2 p2 = player.transform.position; // Player

// 화살에서 플레이어로의 방향 벡터를 계산
Vector2 direction = p1 - p2;

// 두 오브젝트 사이의 거리를 계산 (벡터의 크기)
float d = direction.magnitude;

// 화살의 충돌 반경
float r1 = 0.5f; // Arrow radius

// 플레이어의 충돌 반경
float r2 = 1.0f; // Player radius

// 원-원 충돌 감지
if (d < r1 + r2)
{
    // 충돌 처리
}

변수(variable) 설명

p1 (화살의 위치)

Vector2 p1 = transform.position;  // Arrow
  • 의미: 화살의 현재 위치 좌표
  • 타입(type): Vector2 (x, y 좌표)
  • 예시: 화살이 (3, 5) 위치에 있으면 p1 = (3, 5)

p2 (플레이어의 위치)

Vector2 p2 = player.transform.position; // Player
  • 의미: 플레이어의 현재 위치 좌표
  • 타입(type): Vector2 (x, y 좌표)
  • 예시: 플레이어가 (1, 2) 위치에 있으면 p2 = (1, 2)

direction (방향 벡터)

Vector2 direction = p1 - p2;
  • 의미: 화살에서 플레이어로의 방향 벡터
  • 계산: p1 - p2 = 화살 위치 - 플레이어 위치
  • 예시:
    • p1 = (3, 5), p2 = (1, 2)
    • direction = (3-1, 5-2) = (2, 3)

d (거리)

float d = direction.magnitude;
  • 의미: 두 점(p1, p2) 사이의 거리
  • 계산: 방향 벡터의 크기(magnitude)
  • 피타고라스 정리 사용: d = √(dx² + dy²)

r1 (화살의 충돌 반경)

float r1 = 0.5f; // Arrow radius
  • 의미: 화살의 충돌 영역 반지름
  • : 0.5 유닛
  • 용도: 화살의 크기를 고려한 충돌 범위

r2 (플레이어의 충돌 반경)

float r2 = 1.0f; // Player radius
  • 의미: 플레이어의 충돌 영역 반지름
  • : 1.0 유닛
  • 용도: 플레이어의 크기를 고려한 충돌 범위

피타고라스 정리와 거리 계산

피타고라스 정리란?

직각삼각형에서 빗변의 제곱은 다른 두 변의 제곱의 합과 같습니다.

a² + b² = c²

여기서:

  • a, b: 직각을 이루는 두 변
  • c: 빗변

2D 공간에서의 거리 계산

두 점 사이의 거리를 계산할 때 피타고라스 정리를 사용합니다.

거리 = √((x2 - x1)² + (y2 - y1)²)

시각적 설명

     p1 (x1, y1)
      |
      |  dy = y1 - y2
      |
      +-------- p2 (x2, y2)
         dx = x1 - x2
  • dx: x축 방향 거리 = x1 - x2
  • dy: y축 방향 거리 = y1 - y2
  • 거리 d: 빗변 = √(dx² + dy²)

Unity에서의 구현

Vector2 direction = p1 - p2;
// direction = (dx, dy) = (x1 - x2, y1 - y2)

float d = direction.magnitude;
// d = √(dx² + dy²)

단계별 계산

// 1. 방향 벡터 계산
Vector2 direction = p1 - p2;
// 예: p1 = (3, 5), p2 = (1, 2)
// direction = (3-1, 5-2) = (2, 3)

// 2. 거리 계산 (magnitude는 내부적으로 피타고라스 정리 사용)
float d = direction.magnitude;
// d = √(2² + 3²) = √(4 + 9) = √13 ≈ 3.606

수동 계산 예제

// p1 = (3, 5), p2 = (1, 2)
float dx = 3 - 1;  // dx = 2
float dy = 5 - 2;  // dy = 3

// 피타고라스 정리로 거리 계산
float d = Mathf.Sqrt(dx * dx + dy * dy);
// d = √(2² + 3²) = √(4 + 9) = √13 ≈ 3.606

원-원 충돌 판정

충돌 조건

두 원이 충돌하려면 두 원의 중심 사이의 거리가 두 반지름의 합보다 작거나 같아야 합니다.

충돌 조건: d ≤ r1 + r2

시각적 설명

충돌하지 않는 경우

    r1          r2
    ○           ○
    |           |
    |     d     |
    |-----------|
    
d > r1 + r2  → 충돌 없음

충돌하는 경우

    r1    r2
    ○  ○
    |  |
    |d|
    
d < r1 + r2  → 충돌!

접촉하는 경우

    r1  r2
    ○○
    ||
    |d|
    
d = r1 + r2  → 접촉 (충돌로 처리)

코드 구현

// 거리 계산
float d = direction.magnitude;

// 충돌 판정
if (d < r1 + r2)
{
    // 충돌 발생!
    // HP 감소, 화살 삭제 등 처리
}

실제 예제

예제 1: 충돌하지 않는 경우

// 화살 위치: (0, 5)
Vector2 p1 = new Vector2(0, 5);

// 플레이어 위치: (5, 2)
Vector2 p2 = new Vector2(5, 2);

// 방향 벡터
Vector2 direction = p1 - p2;  // (-5, 3)

// 거리
float d = direction.magnitude;  // √(25 + 9) = √34 ≈ 5.83

// 반경
float r1 = 0.5f;  // 화살
float r2 = 1.0f;  // 플레이어

// 충돌 판정
if (d < r1 + r2)  // 5.83 < 1.5? → false
{
    // 충돌 없음
}

예제 2: 충돌하는 경우

// 화살 위치: (2, 3)
Vector2 p1 = new Vector2(2, 3);

// 플레이어 위치: (1, 2)
Vector2 p2 = new Vector2(1, 2);

// 방향 벡터
Vector2 direction = p1 - p2;  // (1, 1)

// 거리
float d = direction.magnitude;  // √(1 + 1) = √2 ≈ 1.414

// 반경
float r1 = 0.5f;  // 화살
float r2 = 1.0f;  // 플레이어

// 충돌 판정
if (d < r1 + r2)  // 1.414 < 1.5? → true
{
    // 충돌 발생!
    Debug.Log("화살에 맞았습니다!");
}

성능 최적화

거리 제곱 사용

실제 거리를 계산하지 않고 거리의 제곱을 사용하면 Mathf.Sqrt() 연산을 생략할 수 있어 성능이 향상됩니다.

// ❌ 느린 방법: 실제 거리 계산
float d = direction.magnitude;  // √(dx² + dy²) 계산
if (d < r1 + r2) { }

// ✅ 빠른 방법: 거리 제곱 사용
float dSquared = direction.sqrMagnitude;  // dx² + dy² (제곱근 없음)
float radiusSum = r1 + r2;
if (dSquared < radiusSum * radiusSum) { }

최적화된 코드

// 방향 벡터 계산
Vector2 direction = p1 - p2;

// 거리 제곱 계산 (제곱근 없이)
float dSquared = direction.sqrMagnitude;

// 반경 합의 제곱
float radiusSum = r1 + r2;
float radiusSumSquared = radiusSum * radiusSum;

// 충돌 판정 (제곱으로 비교)
if (dSquared < radiusSumSquared)
{
    // 충돌 처리
}

반경 값 조정

반경 값의 의미

반경 값은 오브젝트의 실제 크기보다 약간 크게 설정하는 것이 좋습니다.

// 화살의 실제 크기가 0.3이라면
float r1 = 0.5f;  // 약간 여유를 둔 값

// 플레이어의 실제 크기가 0.8이라면
float r2 = 1.0f;  // 약간 여유를 둔 값

반경 값 테스트

// Inspector에서 조정 가능하도록
public float arrowRadius = 0.5f;
public float playerRadius = 1.0f;

void Update()
{
    float d = direction.magnitude;
    if (d < arrowRadius + playerRadius)
    {
        // 충돌 처리
    }
}

정리

변수(variable) 요약

변수(variable) 의미 계산 방법
p1 화살의 위치 transform.position
p2 플레이어의 위치 player.transform.position
direction 방향 벡터 p1 - p2 = (dx, dy)
d 두 점 사이의 거리 √(dx² + dy²) (피타고라스 정리)
r1 화살의 충돌 반경 0.5f
r2 플레이어의 충돌 반경 1.0f

충돌 판정 공식

충돌 조건: d < r1 + r2

여기서:
d = √((x1 - x2)² + (y1 - y2)²)  (피타고라스 정리)

핵심 개념

  1. 피타고라스 정리: 두 점 사이의 거리 계산에 사용
  2. magnitude: 벡터의 크기 (거리)
  3. 원-원 충돌: 거리 < 반지름 합
  4. 성능 최적화: 거리 제곱 사용 (sqrMagnitude)

연습 문제

  1. 화살이 (3, 5) 위치에 있고, 플레이어가 (1, 2) 위치에 있을 때 거리를 계산하세요.

  2. r1 = 0.5, r2 = 1.0일 때, 거리가 1.2인 경우 충돌이 발생하는지 판정하세요.

  3. 성능 최적화를 위해 거리 제곱을 사용하는 코드로 변경하세요.

  4. 반경 값을 Inspector에서 조정할 수 있도록 public 변수(variable)로 변경하세요.

  5. 충돌이 발생했을 때 거리와 반경 값을 Debug.Log로 출력하는 코드를 추가하세요.