[Unity, C#] Raycast를 사용해 벽 통과 방지하기

담장과 고양이 그림

유니티로 게임 개발을 하다 보면, 플레이어와 벽에 모두 collider(콜라이더)를 넣었음에도 불구하고 플레이어를 벽으로 계속 돌진시키면 플레이어가 벽을 통과하는 현상이 발생한다.

이는 Transform.Translate가 이동 전 위치와 이동 후 위치만을 고려하고, 그 사이에 collider가 있는지는 고려하지 않기 때문이다.

아래 그림처럼 A가 플레이어의 이동 전 위치, B가 플레이어의 이동 후 위치이고 D가 벽 두께라고 했을 때, dist > D라면 A와 B 사이에 collider가 존재해도 플레이어가 이동된다.

A: 플레이어 이동 전 / B: 플레이어 이동 후 / D: 벽두께 / dist: A와 B 사이의 거리

이를 해결하는 방법에는 여러 가지가 있는데, 이번 포스트에서는 Raycast를 사용해 벽 통과를 방지하는 방법에 대해 설명할 것이다. 이 방법은 2D, 3D 모두 적용 가능하다.

Raycast를 사용해 벽 통과 방지하기

  1. 플레이어가 움직이는 방향으로 ray를 쏜다.
  2. ray가 벽에 부딪혔다면 이동시키지 않는다.

이를 코드로 살펴보도록 하자.

내 프로젝트의 경우 Player.cs의 Update에 Move 함수가 있으며, 여기서 CheckHitWall이라는 함수를 사용해 ray 충돌을 확인한다.

public override void Move()
{
  float moveHorizontal = Input.GetAxis("Horizontal");
  float moveVertical = Input.GetAxis("Vertical");
  Vector3 movement = new Vector3(moveHorizontal, 0f, moveVertical);

  // ray와 벽이 충돌한 경우 이동하지 않는다.
  if (CheckHitWall(movement))
    movement = Vector3.zero;

  if (Input.GetKey(KeyCode.LeftShift))
  {
    transform.Translate(movement * m_Stat.SpeedRun * Time.deltaTime);
  }
  else
  {
    transform.Translate(movement * m_Stat.SpeedWalk * Time.deltaTime);
  }
}
bool CheckHitWall(Vector3 movement)
{
  // 움직임에 대한 로컬 벡터를 월드 벡터로 변환해준다.
  movement = transform.TransformDirection(movement);
  // scope로 ray 충돌을 확인할 범위를 지정할 수 있다.
  float scope = 1f;

  // 플레이어의 머리, 가슴, 발 총 3군데에서 ray를 쏜다.
  List<Vector3> rayPositions = new List<Vector3>();
  rayPositions.Add(transform.position + Vector3.up * 0.1f);
  rayPositions.Add(transform.position + Vector3.up * boxCollider.size.y * 0.5f);
  rayPositions.Add(transform.position + Vector3.up * boxCollider.size.y);

  // 디버깅을 위해 ray를 화면에 그린다.
  foreach (Vector3 pos in rayPositions)
  {
    Debug.DrawRay(pos, movement * scope, Color.red);
  }

  // ray와 벽의 충돌을 확인한다.
  foreach (Vector3 pos in rayPositions)
  {
    if (Physics.Raycast(pos, movement, out RaycastHit hit, scope))
    {
      if (hit.collider.CompareTag("Wall"))
        return true;
    }
  }
  return false;
}

플레이어의 머리, 가슴, 발 3군데에서 ray를 쏘는 이유는 벽과의 충돌을 더 정확하게 감지하기 위함이다. 만약 머리에서만 ray를 쏜다면 허리 높이까지만 올라오는 벽은 여전히 뚫리는 버그가 있을 수 있다.