TOOLS GAMES DEVLOG DOCS ABOUT CONTACT
HOME > DOCS > Unity / C# > 3D Rigidbody 물리 기반 이동과 SphereCast 지면·경사로 투영(ProjectOnPlane)
Unity / C# Intermediate Unity 2022.3+ / Unity 6

3D Rigidbody 물리 기반 이동과 SphereCast 지면·경사로 투영(ProjectOnPlane)

물리 상호작용 오브젝트를 밀거나 밀려나는 리얼타임 3D 게임을 위한 Rigidbody.linearVelocity 직접 제어와 SphereCast 기반 급경사면 미끄러짐 방지 기법.

#Unity #3D #Rigidbody #Physics #SphereCast #ProjectOnPlane
핵심 개념CharacterController는 가볍지만 물리 오브젝트와의 상호작용(상자 밀기, 폭발 반경 밀려남 등) 처리가 번거롭습니다. Rigidbody 기반 캐릭터는 현실적인 물리 충돌을 즉시 지원하지만, 경사로에서 마찰력이 부족해 미끄러지거나 통통 튀는 현상을 방지하기 위해 지면 법선 투영(Vector3.ProjectOnPlane) 기법이 필수적입니다.

1. 물리 기반 Rigidbody 3D 이동 스크립트

RigidbodyPlayerMovement.cs
using UnityEngine;

[RequireComponent(typeof(Rigidbody), typeof(CapsuleCollider))]
public class RigidbodyPlayerMovement : MonoBehaviour
{
    [Header("이동")]
    [SerializeField] private float moveSpeed = 8f;
    [SerializeField] private float maxAcceleration = 60f;

    [Header("지면 감지 (SphereCast)")]
    [SerializeField] private float groundCheckRadius = 0.35f;
    [SerializeField] private float groundCheckDistance = 0.5f;
    [SerializeField] private LayerMask groundLayer;
    [SerializeField] private float maxSlopeAngle = 45f;

    private Rigidbody _rb;
    private CapsuleCollider _col;
    private Vector3 _moveInput;
    private Vector3 _groundNormal = Vector3.up;
    private bool _isGrounded;

    private void Awake()
    {
        _rb = GetComponent<Rigidbody>();
        _col = GetComponent<CapsuleCollider>();

        // 물리 회전 잠금
        _rb.freezeRotation = true;
        // 충돌 보간으로 화면 흔들림 억제
        _rb.interpolation = RigidbodyInterpolation.Interpolate;
    }

    private void Update()
    {
        float h = Input.GetAxisRaw("Horizontal");
        float v = Input.GetAxisRaw("Vertical");
        _moveInput = new Vector3(h, 0f, v).normalized;
    }

    private void FixedUpdate()
    {
        CheckGrounded();
        MoveCharacter();
    }

    private void CheckGrounded()
    {
        // 캡슐 하단 구체 위치 계산
        Vector3 origin = transform.position + Vector3.up * (groundCheckRadius + 0.05f);

        if (Physics.SphereCast(origin, groundCheckRadius, Vector3.down, out RaycastHit hit, groundCheckDistance, groundLayer))
        {
            float slopeAngle = Vector3.Angle(Vector3.up, hit.normal);
            if (slopeAngle <= maxSlopeAngle)
            {
                _isGrounded = true;
                _groundNormal = hit.normal;
                return;
            }
        }

        _isGrounded = false;
        _groundNormal = Vector3.up;
    }

    private void MoveCharacter()
    {
        // 경사면에 맞게 입력 벡터 투영
        Vector3 targetDirection = Vector3.ProjectOnPlane(_moveInput, _groundNormal).normalized;
        Vector3 targetVelocity = targetDirection * moveSpeed;

        Vector3 currentVel = _rb.linearVelocity;
        Vector3 currentHorizontalVel = new Vector3(currentVel.x, 0f, currentVel.z);
        Vector3 targetHorizontalVel = new Vector3(targetVelocity.x, 0f, targetVelocity.z);

        // 목표 속도와 현재 속도 차이 계산 및 가속도 제한
        Vector3 velocityChange = targetHorizontalVel - currentHorizontalVel;
        velocityChange = Vector3.ClampMagnitude(velocityChange, maxAcceleration * Time.fixedDeltaTime);

        // Y축 속도는 중력 보존, XZ만 가속
        _rb.linearVelocity = new Vector3(currentVel.x + velocityChange.x, currentVel.y, currentVel.z + velocityChange.z);

        // 경사로 정지 시 아래로 미끄러짐 방지
        if (_isGrounded && _moveInput.sqrMagnitude < 0.001f)
        {
            _rb.useGravity = false;
            _rb.linearVelocity = new Vector3(0f, 0f, 0f);
        }
        else
        {
            _rb.useGravity = true;
        }
    }
}

2. Rigidbody 설정 필수 팁

  • Interpolate 활성화: FixedUpdate 주기(50Hz)와 모니터 주사율(144Hz+)이 다를 때 발생하는 미세한 카메라 덜컹거림(Jitter)을 방지하려면 Rigidbody.interpolation = Interpolate가 필수입니다.
  • PhysicMaterial 슬립 방지: 벽이나 경사로와 마찰할 때 끈적거리게 걸리는 현상을 방지하기 위해 마찰력(Dynamic/Static Friction)이 0인 무마찰 피직스 머티리얼을 캡슐에 부착하는 것이 좋습니다.

참고자료 & 관련 링크