[트러블슈팅] 물체의 밑면과 경사면을 일치시키는 방법

Jongmin Kim·2025년 9월 9일

Cast Away

목록 보기
1/1

시작

Cast Away 프로젝트에서 중립 동물 NPC를 구현하는 과정에서 동물 NPC가 경사를 올라가는 도중 동물 NPC의 발과 그림자에서 위화감을 느꼈다.

Scene 뷰를 통하여 관찰한 결과 Box Collider 컴포넌트의 한 모서리만 붙어있는 채로 공중에 떠 있는 동물 NPC를 발견할 수 있었다.

이를 해결하기 위해 간단히 Rigidbody 컴포넌트의 X축 회전 고정을 풀면 경사면에 맞춰 밑면이 들리거나 가라 앉을 줄 알았으나 동물이 뒤집혀 버둥버둥 대는 꼴만 보게 되었다..



아이디어

위에서 얻은 실패 덕분에 많은 고민을 하게 되었다. 내가 생각한 방법은 지면의 법선 벡터에 동물의 업 벡터를 일치시키는 방법이었다.

지면의 법선 벡터에 동물의 업 벡터를 일치시키게 되면 동물은 자연스럽게 경사면 위에 있다는 느낌을 받게 되지 않을까 싶었다.

물론 동물의 업 벡터를 지면의 법선 벡터에 단순히 보간하는 작업이기 때문에 저렇게 딱 알맞게 붙는다기보다 툭 하고 떨어지는 느낌이지만 그 경사가 높지 않아서 자연스러워 보인다.

생각은 단순했지만 지면의 법선 벡터를 어떻게 하면 얻을 수 있을까?에 대해서 고민을 했다. 면의 법선 벡터는 레이캐스팅을 통해서 구할 수 있었다.

레이캐스팅의 결과 값으로 사용하는 RaycastHit에는 normal이라는 속성이 존재한다.
normal충돌한 지점에서 충돌체 면에 수직인 벡터다. 즉, 법선 벡터다.



해결

Quaternion.FromToRotation

동물의 업 벡터를 지면의 법선 벡터와 일치시키는 회전을 만들기 위해서 사용했다.

Quaternion.FromToRotation(from, to)from 벡터를 to 벡터로 만들기 위한 회전을 생성한다.
예를 들어, (0,1,0)(0, 1, 0)(1,0,0)(1, 0, 0)로 회전시킨다고 하면 다음과 같다.

Y축을 X축으로 만드는 것과 같기 때문에 Z축으로 90˚ 회전이 발생한다. 회전 쿼터니언을 생성한 것이다.


이처럼 동물 NPC의 업 벡터를 지면의 법선 벡터에 일치시켜 회전 쿼터니언을 생성할 수 있다.

Quaternion.FromToRotation(transform.up, hit.normal);

그리고 원하는 회전 값을 얻기 위해 현재 회전 값을 곱한다.

var target_rotation = Quaternion.FromToRotation(transform.up, hit.normal) * transform.rotation;

Quaternion.Slerp

두 벡터의 방향이 너무 다르면 자연스럽고 부드럽게 보간되지 못하기 때문에 Quaternion.Slerp를 이용하여 구면 보간을 한다.

구면 보간의 강도를 조절할 수 있도록 강도를 변수로 둔다.

transform.rotation = Quaternion.Slerp(transform.rotation, target_rotation, Time.deltaTime * m_smoothness);

전체 코드

// AnimalMovement.cs의 일부 발췌

public class AnimalMovement
{
	[Header("레이의 길이")]
    [SerializeField] private float m_ray_distance;
    
    [Header("레이가 감지할 레이어")]
    [SerializeField] private LayerMask m_ground_mask;
    
    [Header("보간 강도")]
    [SerializeField] private float m_smoothness;
    
    public InclineInterpolation()
    {
    	if(Physics.Raycast(transform.position + Vector3.up, 
        				   Vector3.down, 
                           out var hit, 
                           m_ray_distance,
                           m_ground_layer))
        {
        	var target_rotation = Quaternion.FromToRotation(transform.up, hit.normal) * tranform.rotation;
            transform.rotation = Quaternion.Slerp(transform.rotation, target_rotation, Time.deltaTime * m_smoothness);
        }
    }
}



결과

나름 만족스럽게 경사면에 하단이 붙은 채로 움직이는 것을 확인할 수 있다.

profile
Game Client Programmer

0개의 댓글