기본적인 지형과 플레이어를 생성한다.
Player에게 PlayerMove.cs와 Player Input을 생성한다.
다음과 같이 세팅해준다.

WASD키를 입력하여 이동할 수 있고 카메라가 바라보는 방향을 기준으로 전후좌우로 움직일 수 있다.
LEFT SHIFT키를 입력하여 달릴 수 있다
PlayerMove.cs
using UnityEngine;
#if ENABLE_INPUT_SYSTEM
using UnityEngine.InputSystem;
#endif
[RequireComponent(typeof(CharacterController))]
#if ENABLE_INPUT_SYSTEM
[RequireComponent(typeof(PlayerInput))]
#endif
public class PlayerMove : MonoBehaviour
{
[Header("Player")]
[Tooltip("Player의 움직이는 속도 m/s")]
public float MoveSpeed = 2.0f;
[Tooltip("Player의 달리는 속도 m/s")]
public float SprintSpeed = 5.335f;
[Tooltip("가속 및 감속")]
public float SpeedChangeRate = 10.0f;
[Tooltip("Player가 이동 방향을 향하도록 하는 회전속도")]
[Range(0.0f, 0.3f)]
public float RotationSmoothTime = 0.12f;
// player
private float _speed;
private float _animationBlend;
private float _targetRotation = 0.0f;
private float _rotationVelocity;
private float _verticalVelocity;
private Inputs _input;
private CharacterController _controller;
private GameObject _mainCamera;
private void Awake()
{
if (_mainCamera == null)
{
_mainCamera = GameObject.FindGameObjectWithTag("MainCamera");
}
}
void Start()
{
_input = GetComponent<Inputs>();
_controller = GetComponent<CharacterController>();
}
// Update is called once per frame
void Update()
{
Move();
}
private void Move()
{
//이동속도와 달리기 속도는 달리기를 누른 기준으로 목표속도를 설정한다.
float targetSpeed = _input.sprint ? SprintSpeed : MoveSpeed;
//(Vector2의 == 연산자는 근사값을 사용해 비교하기 때문에
//float-point-error(부동 소수점 오차 - 1.1+0.1 != 1.2같은 오류)가 발생하지 않으며
//magnitude를 사용하는 것보다 비용이 적다.)
if (_input.move == Vector2.zero)
{
targetSpeed = 0.0f;
}
//플레이어의 수평속도
float currentHorizontalSpeed = new Vector3(_controller.velocity.x, 0.0f, _controller.velocity.z).magnitude;
float speedOffset = 0.1f;
float inputMagnitude = _input.analogMovement ? _input.move.magnitude : 1f;
//목표 속도까지 가속 또는 감속
if (currentHorizontalSpeed < targetSpeed - speedOffset ||
currentHorizontalSpeed > targetSpeed + speedOffset)
{
// 선형적인 결과가 아닌 곡선적인 부드러운 속도 변화를 나타낸다.
// Lerp의 T는 클램프로 고정되어 있으므로 속도를 클램프할 필요가 없다.
_speed = Mathf.Lerp(currentHorizontalSpeed, targetSpeed * inputMagnitude,
Time.deltaTime * SpeedChangeRate);
// 속도를 소수점 3자리까지 반올림한다
_speed = Mathf.Round(_speed * 1000f) / 1000f;
}
else
{
_speed = targetSpeed;
}
_animationBlend = Mathf.Lerp(_animationBlend, targetSpeed, Time.deltaTime * SpeedChangeRate);
if (_animationBlend < 0.01f)
{
_animationBlend = 0f;
}
// 방향 정규화
Vector3 inputDirection = new Vector3(_input.move.x, 0.0f, _input.move.y).normalized;
//이동 입력이 있는 경우 플레이어가 이동할 때 플레이어를 회전시킨다
if (_input.move != Vector2.zero)
{
_targetRotation = Mathf.Atan2(inputDirection.x, inputDirection.z) * Mathf.Rad2Deg +
_mainCamera.transform.eulerAngles.y;
float rotation = Mathf.SmoothDampAngle(transform.eulerAngles.y, _targetRotation, ref _rotationVelocity,
RotationSmoothTime);
// 카메라 위치를 기준으로 입력 방향을 향하도록 회전한다.
transform.rotation = Quaternion.Euler(0.0f, rotation, 0.0f);
}
Vector3 targetDirection = Quaternion.Euler(0.0f, _targetRotation, 0.0f) * Vector3.forward;
// Player를 움직인다.
_controller.Move(targetDirection.normalized * (_speed * Time.deltaTime) +
new Vector3(0.0f, _verticalVelocity, 0.0f) * Time.deltaTime);
}
}
Inputs.cs
using UnityEngine;
#if ENABLE_INPUT_SYSTEM
using UnityEngine.InputSystem;
#endif
public class Inputs : MonoBehaviour
{
[Header("Character 입력값")]
public Vector2 move;
public bool sprint;
[Header("이동 설정")]
public bool analogMovement;
#if ENABLE_INPUT_SYSTEM
public void OnMove(InputValue value)
{
MoveInput(value.Get<Vector2>());
}
public void OnSprint(InputValue value)
{
SprintInput(value.isPressed);
}
#endif
public void MoveInput(Vector2 newMoveDirection)
{
move = newMoveDirection;
}
public void SprintInput(bool newSprintState)
{
sprint = newSprintState;
}
}