Unity / C#
Beginner
Unity 2022.3+ / Unity 6
3D CharacterController 이동, 카메라 기준 방향 변환 및 경사면(Slope) 제어
카메라 시점 기준 3D 이동 벡터 계산, SmoothDamp 회전 보간, 경사각(Slope Limit) 등반 제한 및 내리막 덜컹거림(Bouncing) 방지 CharacterController 스크립트.
#Unity #3D #CharacterController #Movement #Slope #Camera-Relative
핵심 개념3D 액션 게임에서 캐릭터는 월드 좌표가 아니라 '현재 카메라가 바라보는 방향'을 기준으로 전후좌우 이동해야 직관적입니다. 또한 내리막길을 달릴 때 공중에 살짝 떴다가 떨어지는 덜컹거림(Bouncing)을 방지하기 위해 경사면 법선(Normal)을 검출해 지면으로 수직 보정력을 가해주어야 합니다.
1. 카메라 기준 3D CharacterController 구현
using UnityEngine;
[RequireComponent(typeof(CharacterController))]
public class PlayerController3D : MonoBehaviour
{
[Header("이동 및 회전")]
[SerializeField] private float walkSpeed = 6f;
[SerializeField] private float runSpeed = 10f;
[SerializeField] private float rotationSmoothTime = 0.08f;
[Header("중력 및 점프")]
[SerializeField] private float gravity = -20f;
[SerializeField] private float jumpHeight = 1.8f;
[SerializeField] private float groundedGravity = -2f;
[Header("경사면 접지")]
[SerializeField] private float slopeRayLength = 1.2f;
private CharacterController _controller;
private Transform _camTransform;
private float _verticalVelocity;
private float _currentRotationVelocity;
private void Awake()
{
_controller = GetComponent<CharacterController>();
if (Camera.main != null) _camTransform = Camera.main.transform;
}
private void Update()
{
// 1. 카메라 기준 입력 변환
float h = Input.GetAxisRaw("Horizontal");
float v = Input.GetAxisRaw("Vertical");
Vector3 inputDir = new Vector3(h, 0f, v).normalized;
Vector3 moveDir = Vector3.zero;
if (inputDir.magnitude > 0.01f && _camTransform != null)
{
// 카메라 Y 회전각을 더해 이동 방향 회전
float targetAngle = Mathf.Atan2(inputDir.x, inputDir.z) * Mathf.Rad2Deg + _camTransform.eulerAngles.y;
float angle = Mathf.SmoothDampAngle(transform.eulerAngles.y, targetAngle, ref _currentRotationVelocity, rotationSmoothTime);
transform.rotation = Quaternion.Euler(0f, angle, 0f);
moveDir = Quaternion.Euler(0f, targetAngle, 0f) * Vector3.forward;
}
// 2. 경사면 보정 벡터 계산
if (OnSlope(out Vector3 slopeNormal))
{
// 경사면 평면에 평행하게 이동 벡터 투영
moveDir = Vector3.ProjectOnPlane(moveDir, slopeNormal).normalized;
}
// 3. 중력 및 점프 처리
bool isGrounded = _controller.isGrounded;
if (isGrounded)
{
_verticalVelocity = groundedGravity;
if (Input.GetButtonDown("Jump"))
{
_verticalVelocity = Mathf.Sqrt(jumpHeight * -2f * gravity);
}
}
else
{
_verticalVelocity += gravity * Time.deltaTime;
}
// 4. 최종 이동 적용
float currentSpeed = Input.GetKey(KeyCode.LeftShift) ? runSpeed : walkSpeed;
Vector3 finalMotion = (moveDir * currentSpeed) + (Vector3.up * _verticalVelocity);
_controller.Move(finalMotion * Time.deltaTime);
}
private bool OnSlope(out Vector3 hitNormal)
{
hitNormal = Vector3.up;
if (!_controller.isGrounded) return false;
if (Physics.Raycast(transform.position, Vector3.down, out RaycastHit hit, slopeRayLength))
{
hitNormal = hit.normal;
float angle = Vector3.Angle(Vector3.up, hitNormal);
// 경사각이 0보다 크고 최대 등반각보다 작은지 검사
return angle > 0f && angle <= _controller.slopeLimit;
}
return false;
}
}
2. 주요 컴포넌트 파라미터 세팅
Slope Limit: 오를 수 있는 최대 경사도 (통상 45°). 이 각도를 초과하는 경사면에 서면 자동으로 미끄러집니다.Step Offset: 계단을 오를 수 있는 높이 (통상 0.3m). 캐릭터 키가 2m일 때 0.3~0.4m가 적절합니다.Skin Width: 충돌 떨림 방지 완충 폭. 캐릭터 반경의 약 10%(0.08m) 수준으로 지정해야 틈새 낌 현상이 예방됩니다.