게임 속 캐릭터에게 생동감을 불어넣는 것은 단순한 이동을 넘어섭니다. 이제 캐릭터가 벽을 타고 달리고, 가파른 경사면을 자유롭게 오르내리는 기능을 추가하여 게임의 액션성을 극대화할 때입니다. 이 글은 WASD 이동, 마우스 시점 제어, 점프 및 달리기 기능에 벽타기와 경사면 이동 로직을 더한, 훨씬 더 역동적인 캐릭터 컨트롤러 코드를 소개합니다. 이 코드는 복잡한 파쿠르나 플랫폼 게임의 핵심 기반이 될 것입니다.
1. 확장된 캐릭터 컨트롤 스크립트 작성
AdvancedPlayerController.cs
라는 이름의 스크립트를 새로 만들거나 기존 코드를 수정하여 아래 내용을 입력해 주세요. 이 스크립트는 플레이어 캐릭터 오브젝트에 부착됩니다.
C#
using UnityEngine;
[RequireComponent(typeof(Rigidbody))]
public class AdvancedPlayerController : MonoBehaviour
{
[Header("기본 이동 설정")]
public float baseMoveSpeed = 5f;
public float sprintSpeedMultiplier = 1.5f;
private float currentMoveSpeed;
private Rigidbody rb;
private Vector3 moveDirection;
[Header("점프 설정")]
public float jumpForce = 5f;
private bool isGrounded = false;
[Header("시점 제어 설정")]
public Transform playerCamera;
public float lookSensitivity = 2f;
public float verticalLookLimit = 90f;
private float horizontalRotation = 0f;
private float verticalRotation = 0f;
[Header("벽타기 설정")]
public float wallRunSpeedMultiplier = 1.2f;
public float wallRunForce = 2f;
public float wallCheckDistance = 0.8f;
public float wallJumpForce = 7f;
private bool isWallRunning = false;
[Header("경사면 이동 설정")]
public float maxSlopeAngle = 45f;
private RaycastHit slopeHit;
void Awake()
{
rb = GetComponent<Rigidbody>();
Cursor.lockState = CursorLockMode.Locked;
currentMoveSpeed = baseMoveSpeed;
}
void Update()
{
// === 입력 처리 및 상태 업데이트 ===
if (Input.GetKey(KeyCode.LeftShift))
{
currentMoveSpeed = baseMoveSpeed * sprintSpeedMultiplier;
}
else
{
currentMoveSpeed = baseMoveSpeed;
}
// === 마우스 입력으로 시점 회전 ===
float mouseX = Input.GetAxis("Mouse X") * lookSensitivity;
float mouseY = Input.GetAxis("Mouse Y") * lookSensitivity;
horizontalRotation += mouseX;
transform.localRotation = Quaternion.Euler(0f, horizontalRotation, 0f);
verticalRotation -= mouseY;
verticalRotation = Mathf.Clamp(verticalRotation, -verticalLookLimit, verticalLookLimit);
if (playerCamera != null)
{
playerCamera.localRotation = Quaternion.Euler(verticalRotation, 0f, 0f);
}
// === 점프 및 벽타기 입력 ===
if (Input.GetButtonDown("Jump"))
{
if (isGrounded)
{
rb.AddForce(Vector3.up * jumpForce, ForceMode.Impulse);
isGrounded = false;
}
else if (isWallRunning)
{
// 벽에서 점프
Vector3 jumpDirection = Vector3.up * wallJumpForce;
rb.AddForce(jumpDirection, ForceMode.Impulse);
}
}
}
void FixedUpdate()
{
// === 이동 및 경사면 로직 ===
float moveX = Input.GetAxis("Horizontal");
float moveZ = Input.GetAxis("Vertical");
moveDirection = transform.forward * moveZ + transform.right * moveX;
if (isGrounded && OnSlope())
{
// 경사면에 맞는 이동 벡터 적용
Vector3 slopeMoveDirection = Vector3.ProjectOnPlane(moveDirection, slopeHit.normal);
rb.velocity = new Vector3(slopeMoveDirection.x * currentMoveSpeed, rb.velocity.y, slopeMoveDirection.z * currentMoveSpeed);
}
else
{
rb.velocity = new Vector3(moveDirection.x * currentMoveSpeed, rb.velocity.y, moveDirection.z * currentMoveSpeed);
}
// === 벽타기 로직 ===
if (!isGrounded && WallDetected())
{
// 벽을 타고 달릴 때
isWallRunning = true;
rb.velocity = new Vector3(rb.velocity.x, 0, rb.velocity.z); // 중력 무시
// 벽타기 시 위로 밀어주는 힘 추가
rb.AddForce(transform.up * wallRunForce, ForceMode.Force);
currentMoveSpeed *= wallRunSpeedMultiplier; // 벽타기 속도 증가
}
else
{
isWallRunning = false;
}
}
// 바닥과 충돌했을 때 isGrounded 상태를 true로 변경
void OnCollisionEnter(Collision collision)
{
if (collision.gameObject.CompareTag("Ground"))
{
isGrounded = true;
}
}
// 경사면에 있는지 확인하는 함수
private bool OnSlope()
{
if (Physics.Raycast(transform.position, Vector3.down, out slopeHit, 1.5f))
{
float angle = Vector3.Angle(Vector3.up, slopeHit.normal);
return angle < maxSlopeAngle && angle != 0;
}
return false;
}
// 벽을 감지하는 함수
private bool WallDetected()
{
// 플레이어 좌우에 벽이 있는지 레이캐스트로 확인
return Physics.Raycast(transform.position, transform.right, wallCheckDistance) ||
Physics.Raycast(transform.position, -transform.right, wallCheckDistance);
}
}
2. 코드 해설 및 심화 포인트
이 코드는 FixedUpdate()
와 Raycast
를 활용하여 더욱 복잡하고 물리적인 움직임을 구현합니다.
- 경사면 이동:
OnSlope()
함수는Raycast
를 이용해 플레이어 아래의 표면을 감지하고, **Vector3.Angle
**을 이용해 경사각을 측정합니다. 만약 경사각이maxSlopeAngle
보다 작으면, **Vector3.ProjectOnPlane()
**을 이용해 이동 벡터를 경사면에 투사하여 미끄러짐 없이 부드럽게 이동하게 합니다. - 벽타기 로직: 공중에 있을 때
WallDetected()
함수로 좌우에 벽이 있는지Raycast
로 확인합니다. 벽을 감지하면isWallRunning
상태를true
로 바꾸고,Rigidbody
의 중력 (rb.velocity.y
)을 무시하며 위로 힘을 가해 벽을 타는 듯한 효과를 연출합니다. - 벽 점프:
isWallRunning
상태에서 점프를 누르면,rb.AddForce()
를 이용해 위로 강력한 힘을 가하여 벽에서 뛰어오르는 동작을 구현합니다. FixedUpdate()
: 물리 연산은FixedUpdate()
에서 처리하는 것이 유니티의 공식적인 권장 사항입니다. 이동 벡터와 힘을FixedUpdate()
에서 적용하면 프레임 속도에 관계없이 일관되고 안정적인 물리 연산을 보장합니다.
이 코드는 단순히 캐릭터를 이동시키는 것을 넘어, 게임 환경과 상호작용하는 복합적인 움직임을 구현하는 데 초점을 맞췄습니다. 이 기반을 활용하여 벽에서 점프하는 애니메이션을 추가하거나, 경사면의 종류에 따라 다른 효과를 주는 등 다양한 게임 플레이 요소를 확장해 보세요.