[Unity] 캐릭터가 벽을 통과하는 문제

Arthur·2023년 12월 4일
0
post-thumbnail

캐릭터가 벽을 통과하는 문제 발생


위 사진에 나오는 것처럼 Box Collider 컴포넌트가 추가되어 있는 벽(큐브)를 통과하는 버그입니다.

플레이어 캐릭터에는 Rigidbody와 Capsule Collider 컴포넌트가 추가되어 있는데도 충돌 처리가 제대로 되지 않고 있습니다.
캐릭터의 움직임에 저항이 생겨서 조금씩 밀어내기는 하지만, 계속 방향키를 누르고 있으면 통과가 됩니다.

이 문제를 해결하기 위해 Raycast를 활용해서 벽을 감지해서 해결하는 방식을 사용했습니다.
문제를 해결하기 위해 시도했던 과정에서도 많은 것들을 배웠기 때문에 하나씩 시도했던 것들을 글로 적어봤습니다.


움직이는 캐릭터와 벽에 Collider를 추가했음에도 벽을 통과하는 문제가 발생했습니다.

문제 해결을 위해 시도한 것들

  1. transform.position 대신 rigidbody.velocity를 사용해서 이동 처리
  2. Collision Detection, Interpolate 설정 변경
  3. Raycast를 사용해서 벽 감지


이 글의 핵심 키워드 및 요약



  • 물리

    • Collider
      • 캡슐 콜라이더(Capsule Collider)
      • 박스 콜라이더(Box Collider)
    • Rigidbody
      • Rigidbody.velocity
      • Rigidbody.angularVelocity
      • Rigidbody.AddForce
      • Rigidbody.AddTorque
      • Interpolate(보간)
      • Collision Detection
    • Physics
      • Raycast
  • 쿼터니언(Quaternion)

    • Quaternion.Lerp
    • Quaternion.LookRotation
  • FixedUpdate

    • Fixed Timestep
      • 프레임
  • transform

    • transform.position
    • Transform.rotation

ChatGPT 요약

Unity 게임 엔진에서 발생한 캐릭터가 벽을 통과하는 버그에 대한 해결 과정을 대한 글입니다. Rigidbody.velocity 및 물리 설정 변경 후에도 문제가 발생하여 Raycast를 사용하여 벽 감지 후 이동을 처리하는 방법을 채택했습니다.
초기에는 transform.position을 사용한 이동 처리로 인해 충돌 처리가 부적절하게 이루어져 문제가 발생했으며, 이를 해결하기 위해 Rigidbody.velocity를 사용한 코드로 변경하였습니다.
그 후 Collision Detection과 Interpolate 설정을 조정하여 문제를 일부 해결하였지만, 여전히 발생하는 문제로 Raycast를 도입하여 벽을 감지하고 이동을 제어하는 방식을 선택했습니다.



1. transform.position 대신 rigidbody.velocity를 사용해서 이동 처리


transform.position 이동 시 Collider를 고려하지 않는 문제

유니티에서는 Collider가 물체의 경계를 나타내며, 이는 물체 간의 충돌을 감지하는 데 사용됩니다.

Transform.position은 이동 전 위치와 이동 후 위치만을 고려하고, 그 사이에 collider가 있는지는 고려하지 않기 때문에 벽을 관통하는 문제 발생한 것입니다.


이런 문제를 해결하기 위해 Unity 물리 엔진이 충돌 처리를 관리할 수 있도록 Rigidbody를 사용해 봤습니다.

아래는 transform.position을 사용해 이동 처리한 코드와 rigidbody.velocity를 사용한 코드입니다.


1. transform.position을 사용한 코드

private void Update()
{
	UpdateMoving(_movementDirection);
}

private void UpdateMoving(Vector3 direction)
{
	direction = MultiplyMyPlayerMoveSpeed(direction);
    
    transform.position += direction * Time.deltaTime;
}

private Vector3 MultiplyMyPlayerMoveSpeed(Vector3 direction)
{
    _isWalk = Input.GetButton("Walk");
    _anim.SetBool("isWalk", _isWalk);
    if (_isWalk)
    {
        direction = direction * _walkSpeed;
    }
    else
    {
        _anim.SetBool("isRun", true);
        direction = direction * _runSpeed;
    }
    return direction;
}

2. Rigidbody.velocity를 사용한 코드

private void FixedUpdate()
{
	UpdateMoving(_movementDirection);
}

private void UpdateMoving(Vector3 direction)
{
	direction = MultiplyMyPlayerMoveSpeed(direction);

    GetComponent<Rigidbody>().velocity = direction;
}

private Vector3 MultiplyMyPlayerMoveSpeed(Vector3 direction)
{
    _isWalk = Input.GetButton("Walk");
    _anim.SetBool("isWalk", _isWalk);
    if (_isWalk)
    {
        direction = direction * _walkSpeed;
    }
    else
    {
        _anim.SetBool("isRun", true);
        direction = direction * _runSpeed;
    }
    return direction;
}  

Rigidbody란?

Rigidbody는 GameObject가 물리 제어로 동작하게 합니다. 리지드바디는 힘과 토크를 받아 오브젝트가 사실적으로 움직이도록 해줍니다. 리지드바디가 포함된 모든 게임 오브젝트는 중력의 영향을 받아야 하며 스크립팅을 통해 가해진 힘으로 움직이거나 NVIDIA PhysX 물리 엔진을 통해 다른 오브젝트와 상호 작용해야 합니다.
<Unity Docs - 리지드바디>

Rigidbody는 유니티에서 물리 시뮬리에션을 적용하기 위한 컴포넌트 중 하나입니다.

Rigidbody의 주요 기능에는 다양한 것들이 있습니다.

  • 중력 적용
    • Rigidbody가 적용된 GameObject에 중력에 따라 움직이도록 처리합니다.
  • 충돌 처리
    • 다른 물체와의 충돌을 자동으로 감지하고, 이에 대한 물리적인 반응을 보입니다.
      Collider가 있는 문제와 상호 작용 하거나 IsTrigger를 사용해 Trigger를 발동합니다.
  • 운동 및 힘 적용
    • velocity 및 AddForce 와 같은 메서드를 사용하여 물체에 운동을 부여하거나 외부에서 힘을 가할 수 있습니다.
  • 회전 제어
    • angularVelocity 및 AddTorque 와 같은 메서드를 사용하여 물체의 회전을 제어할 수 있습니다.
  • 충돌 이벤트
    • OnCollisionEnter, OnCollisionStay, OnCollisionExit 같은 이벤트를 사용하여 충돌에 대한 사용자 정의 로직을 구현할 수 있습니다.

velocity란?

속도는 속력에 위치 값을 갖는 것인데, 오브젝트의 속도를 바꿔 오브젝트의 움직임을 제어하는 것입니다.

Velocity를 사용할 때는 속력을 직접 선언해주어야 합니다.

float hAxis = Input.GetAxis("Horizontal");
float vAxis = Input.GetAxis("Virtical");

Vector3 direction = new Vector3(hAxis, 0, vAxis);

Rigidbody.velocity = direction * movespeed;

Rigidbody 사용 시 주의할 점

1. 트랜스폼과 리지드바디를 동시에 사용하면 안된다.
트랜스폼을 바꾸면서 리지드바디의 물리를 사용하면 충돌 및 기타 연산에 문제가 발생할 수 있습니다.

Transform은 오브젝트의 Vector를 변화해 움직임을 제어하고,
리지드바디는 Force 혹은 Velocity를 변경하여 움직임을 제어합니다.

둘에는 엄연한 차이가 있기 때문에 반드시 하나만 사요하도록 해야 합니다.


2. 리지드바디로 이동 처리 시 Update대신 FixedUpdate를 사용

Update는 프레임 속도에 영향을 받기 때문에 불규칙하게 호출되어서 물리 검사가 제대로 되지 않을 수가 있습니다.
또한 과도한 Update 호출을 막기 위하여 FixedUpdate를 사용하는 것입니다.

그리고 FixedUpdate는 프레임의 속도와 관계없이 Fixed Timestep에 호출이 되기 때문에 Time.deltaTime 만큼 값을 곱해주는 연산을 할 필요가 없습니다.


rigidbody.velocity로 해결이 되기는 했지만, 또 다른 문제가 발생을 했습니다.
그것은 바로 캐릭터 이동 속도가 상승하면 벽을 통과하는 것입니다.

그래서 다른 방법들을 더 찾아봤습니다.



2. Collision Detection과 Interpolate 설정 변경


캐릭터 이동 속도 상승에 따른 문제

매 프레임마다 캐릭터를 일정한 속도로 이동시키는 경우, 벽과 충돌하더라도 이미 이동이 발생한 후에 충돌을 감지하게 되기 때문에 벽을 통과하게 됩니다. 따라서 충돌 검사는 이동하기 전에 수행되어야 합니다.

2cm의 Collider가 있을 때, 1프레임 당 1cm 움직이는 물체에는 잘 적용되지만, 1프레임 당 3cm 움직이는 물체는
오브젝트의 현재 위치 + 3cm = Collider의 뒷쪽
이 되면서 충돌처리가 잘 되지 않는 것입니다.
<출처 => https://geukggom.tistory.com/5>

위와 같은 이유에서 리지드바디 velocity를 사용해도 벽을 통과하는 문제가 간혹다가 발생을 했습니다.

그래서 구글링을 해본 결과 Collision Detection과 Interpolate 설정을 변경하는 방법을 찾게 됩니다.


방법은 상당히 간단합니다.
캐릭터에 있는 Rigidbody에서 Interpolate는 Extrapolate Collision Detection는 Continuous로 변경합니다.


이렇게 설정하고 나니까 저는 벽을 통과하는 버그는 해결되었습니다.

해결하고 그냥 끝내면 다음에 같은 문제가 발생했을 때 알 수 없을 것 같다고 생각했습니다.
그래서 각 설정에 대해서 한 번 찾아봤습니다.


Interpolate란?

리지드바디의 움직임이 어색해 보일 경우 다음 옵션 중에서 하나를 시도해보아야 합니다.
<Unity Docs - 리지드바디>

유니티 문서(Docs)에도 잘 작성이 되어 있듯이 캐릭터의 움직임이 어색할 때 사용하는 옵션입니다.

각각의 옵션을 사용하는 시기를 정리하면 아래와 같습니다.

  • None
    • 보간이 적용되지 않습니다.
    • 물리 개체가 매우 빠르게 움직이지 않거나 약간의 흔들림이 문제가 되지 않는 게임에 적합
    • ex) 턴제 기반 전략 게임
  • Interpolate
    • 이전 프레임의 트랜스폼에 맞게 움직임을 부드럽게 처리합니다.
    • 게임의 프레임 속도가 물리 업데이트 속도보다 훨씬 높을 때 물리 개체의 움직임을 부드럽게 만드는데 도움을 줍니다.
    • ex) 레이싱 게임처럼 높은 프레임 속도로 실행되는 경우에 레이싱 차량의 움직임을 더욱 부드럽게 처리
  • Extrapolate
    • 다음 프레임의 트랜스폼을 추정해 움직임을 부드럽게 처리합니다.
    • 보간에 의해 도입될 수 있는 인지 지연을 줄이려는 경우, 특히 개체가 플레이어 입력에 의해 직접 제어되는 경우에 유용합니다.
    • ex) 플레이어가 캐릭터의 움직임을 직접 제어하면서 빠르게 진행되는 액션 게임

보간(Interpolation)이란?

  • 알고 있는 데이터들을 이용하여, 모르는 값을 추정하는 것
  • 두 개의 데이터가 있고 이에 대한 함수를 안다면 이를 이용해 중간 단계의 데이터를 얻어낼 수 있다는 것입니다.
  • 보간에는 대표적인 선형 보간법과 라그랑제 다항식 보간법 2가지가 있습니다.

캐릭터가 있는 곳과 캐릭터가 이동할 곳을 바탕으로 보간을 사용해 중간 위치를 구할 수 있습니다.
중간 위치 값을 알기 때문에 더욱 부드럽게 이동 처리가 되도록 보이게 할 수 있습니다.

유니티에서는 Interpolate 혹은 Extrapolate으로 설정만 하면 보간 연산 처리를 해주는 겁니다.


Collision Detection이란?

빠르게 움직이는 오브젝트가 충돌의 감지 없이 오브젝트를 지나쳐가는 것을 방지합니다.

  • Discrete
    • 물리 충돌 연산을 물리 시간 단계가 끌날 때만 하기 때문에 옵션 중에 CPU를 가장 적게 사용합니다. 하지만 빠르게 움직이는 물체의 충돌을 감지하지 못해 통과하는 문제가 발생할 수 있습니다.
  • Continous
    • 물리 충돌 연산을 마지막 물리 타임스텝에서 다음 타임스텝까지 미리 예측하고 해당 시간 동안 객체가 무엇과 충돌하는지 확인합니다.
    • Discrete 보다 CPU를 더 많이 사용합니다.
    • 정적 콜라이더에 대해서만 적용됩니다.
  • Continuous Dynamic
    • 동적 콜라이더와의 충돌에서도 적용됩니다.
    • 움직이는 오브젝트에도 충돌 감지를 하는 가장 정확한 충돌 감지를 제공합니다.
      하지만 CPU를 가장 많이 사용하는 단점이 있습니다.
  • Continous Speculative
    • Continous Dynamic과 유사한 수준의 정확도를 제공할 수 있지만 CPU 비용은 더 낮은 충돌 감지합니다.
    • 모든 객체가 현재 방향으로 계속 이동한다고 가정하고 이를 기반으로 예측적 접근 방식으로 충돌을 계산합니다.

Collision Detection에 대해 유니티 공식 문서를 보니까 너무 모르는 단어가 많아서 다른 블로그를 참고해서 정리해봤습니다.

Continuous Dynamic이나 ContinousSpeculative에 대해 자세히 알고 싶으신 분은
유니티 공식 문서 => 연속 충돌 검사(CCD)
를 참고하시기 바랍니다.



3. Raycast를 사용해서 벽 감지


Raycast를 사용해 플레이어가 움직이는 방향 일정 간격에 벽이 있는지 감지합니다.
Ray가 벽에 부딪히면 캐릭터가 움직이지 해당 방향으로 이동하지 않도록 처리합니다.

Raycast란?

Raycast는 Raycast 스크립팅을 가진 게임 오브젝트의 원점에서 내가 설정한 방향으로 Ray를 날려 내가 설정한 거리 이내에 물체가 있는지 없는지 충돌감지를 해주는 것입니다.
<출처 - Chameleon Studio(링크)

유니티에서 기본적으로 제공을 해주는 Physics라는 클래스에 Raycast에 대한 함수가 존재합니다.
Raycast는 충돌체를 감지했는지 bool을 반환해 결과를 알려줍니다.


소스 코드

private void Update()
{
	UpdateMoving(_movementDirection);
}

private void UpdateMoving(Vector3 direction)
{
    direction = MultiplyMyPlayerMoveSpeed(direction);

    transform.rotation = Quaternion.Lerp(transform.rotation, Quaternion.LookRotation(direction), Time.deltaTime * _rotationSpeed);
        
    if (IsWallCheck() == true)
        return;

    transform.position += direction * Time.deltaTime;
}

private Vector3 MultiplyMyPlayerMoveSpeed(Vector3 direction)
{
    _isWalk = Input.GetButton("Walk");
    _anim.SetBool("isWalk", _isWalk);
    if (_isWalk)
    {
        direction = direction * _walkSpeed;
    }
    else
    {
        _anim.SetBool("isRun", true);
        direction = direction * _runSpeed;
    }
    return direction;
}

Raycast를 사용하니까 쉽게 충돌을 감지해서 처리할 수 있게 되었습니다.



작성하면서 느낀 점


지금까지 너무 구현에만 집중하고 디테일한 부분을 신경쓰지 않았던 것 같습니다.
문제를 해결하면서 나오는 다양한 키워드와 내용들이 성장에 큰 도움이 된다는 것을 망각하고 있었습니다.

정리하는데 시간이 꽤 소모되기는 하지만, 동일한 문제 혹은 동일한 키워드에 대한 내용이 나왔을 때 좀 더 쉽게 해결할 수 있습니다.

그냥 때려 맞추기 식으로 해결하는게 아닌 배경 지식을 가지고 빠르게 문제점을 알고 찾아 해결하는 것입니다.



참고 자료


  • RigidBody : Collider를 만들었는데 벽을 통과할 때 => 링크
  • Unity Docs - 리지드바디 => 링크
  • Rigidbody Interpolate란? => 링크
  • 23.03.15.(수) - 보간법 => 링크
  • Unity3D Collision Detection => 링크
  • Rigidbody의 'Collision Detection' 이란? => 링크
  • FixedUpdate (유도탄 만들기) => 링크
  • Raycast를 사용해 벽 통과 방지하기 => 링크
  • 유니티 레이캐스트 Raycast 충돌 / Ray의 모든 것 => 링크
profile
기술에 대한 고민과 배운 것을 회고하는 게임 서버 개발자의 블로그입니다.

0개의 댓글