| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | |||||
| 3 | 4 | 5 | 6 | 7 | 8 | 9 |
| 10 | 11 | 12 | 13 | 14 | 15 | 16 |
| 17 | 18 | 19 | 20 | 21 | 22 | 23 |
| 24 | 25 | 26 | 27 | 28 | 29 | 30 |
| 31 |
- 스파르타내일배움캠프
- 코테
- 뭐드라
- 연습
- ag 내일배움캠프
- 블렌더
- 취미
- 원신
- 게임 회사
- spritemask2d
- materialpropertyblock
- 코딩
- 스파르타내일배움캠프TIL
- til
- 게임분석
- 트러블슈팅
- 유니티
- 붕괴 스타레일
- 까먹기전에메모
- 나만의 견해
- 게임
- spritelibrary
- LookAt
- 내일배움캠프
- 게임 개발
- 게임용어
- 프로그래밍
- 셰이더
- 공부
- 붕괴스타레일
- Today
- Total
덴바의 노트
[유니티] 3D FPS 게임 구현 1 - 이동 구현 (Locomotion) 본문

이전 포스트
[유니티] 3D FPS 게임 구현 0 - 에셋 준비
최근 원신, 명조 등의 장르 구현에만 너무 몰두한 나머지 다른 장르 구현 실력이 바닥을 치는 것을 느꼈습니다.이번에는 오랜만에 FPS 게임의 일부를 구현해볼까 합니다. 오늘의 키워드에셋 준비
denbacodingnote.tistory.com
지난번에 에셋 준비를 모두 마쳤으니 이번에는 캐릭터 이동을 구현해보도록 하겠습니다.
오늘의 키워드
- 캐릭터 움직임
1. 이용할 모델링 파일 배치
먼저 준비한 캐릭터 에셋을 맵에 배치해둡니다. Transform은 Scale 빼고 모두 0으로 초기화 합니다.

플레이어 기능 스크립트 설계 및 구현

InputSystem : 플랫폼/디바이스에 상관없이 입력을 추상화해서, 이벤트 기반으로 처리하는 유니티 시스템
InputManager : Input System의 입력을 받아 Observable 스트림으로 배포하는 기능
FPSPlayerController : 입력을 받아서 해석 후 각 기능으로 전파 및 관리하는 기능
PlayerLocomotion : 플레이어의 움직임을 담당
INPUT SYSTEM의 Generate C# Class를 생성합니다.
또한 각 필요한 키 입력을 등록 합니다.



InputManager 구현
먼저 InputManager는 싱글톤으로 구현해줍니다. 특히 MonoBehaviour를 상속하는 싱글톤이 아닌, 일반 class, new()의 Singleton으로 구현합니다.
이유는 Update, FixedUpdate 등의 함수를 이용할 이유가 없기 때문입니다.
public class InputManager : Singleton<InputManager>
{
private GameInput_Actions m_Actions;
private Subject<Vector2> onMove = new();
}
InputSystem의 Actions를 C# 클래스로 생성했기에, 해당 클래스를 InputManager에 필드로 선언해둡니다.
또한 UniRx를 이용해서 Move Action의 입력값을 외부해서 받을 수 있게 OnMove Subject를 생성해둡니다.
private void Bind(InputAction action, Action<InputAction.CallbackContext> callback, bool performed = true, bool canceled = false, bool started = false)
{
if (performed) action.performed += callback;
if (canceled) action.canceled += callback;
if (started) action.started += callback;
}
private void Unbind(InputAction action, Action<InputAction.CallbackContext> callback)
{
action.performed -= callback;
action.canceled -= callback;
action.started -= callback;
}
우리가 등록한 Action에는 started, performed, canceled가 존재하기는데, 새로운 액션을 추가할 때 마다 위 내용을 다 넣기에는 반복적이고 귀찮기에 이를 함수로 묶어둡니다.
private void OnMoveInput(InputAction.CallbackContext context)
{
var move = context.ReadValue<Vector2>();
onMove?.OnNext(move);
}
이제 이동의 입력값을 InputAction.CallbackContext 파라미터를 통해서 받고, 이를 Vector2로 받아서,
아까 구현한 onMove에 발행할 수 있도록 합니다.
public override UniTask Initialize()
{
m_Actions?.Enable();
m_Actions?.AddTo(this);
if (m_Actions != null)
{
Bind(m_Actions.Player.Move, OnMoveInput, started: false, performed: true, canceled: true);
}
}
해당 InputManager를 초기화할 때 Actions를 Enable()를 반드시 호출해줍니다. 이를 활성화하지 않으면, Input 하드웨어로 부터의 입력에 대해서 Action이 일어나지 않는다고 판단해주시면 됩니다.
그리고 아까 구현한 기능을 Bind해줍니다.
저는 performed로 실시간으로 입력과 입력이 끝날 때에 대해서 액션을 구독해둡니다.
다른 입력을 추가할 때 또한 위와 같은 방식으로 처리합니다.
플레이어 컨트롤러

PlayerController는 MonoBehaviour를 상속하는 스크립트로 생성합니다.
Start 함수에서 아까 호출했던 InputManager에 접근해서 OnMove에 OnMove를 구독합니다.

InputContext를 struct로 가볍게 만들어줍니다.
예전에는 PlayerController에 필요한 필드를 하나씩 만들어 갔지만, 이렇게 하면 막상 필요한 필드를 하나씩 가져와야 하는 불편함이 있습니다.
그래서 위와 같이 Context로서 하나로 묶고 각 기능의 로직으로 이를 전달하는 방식을 채택했습니다.
PlayerLocomotion

이동 방식은 진짜 구현하는 방법이 다양하지만 대표적으로는 Rigidbody와 CharacterController가 존재합니다.
이번에는 굳이 물리적인 처리가 크게 필요하지 않기 때문에 CharacterController를 사용하겠습니다.
또한 PlayerLocomotion은 MonoBehaviour를 상속하지 않고 일반 C# 클래스로 구현했습니다.
실시간 기반 호출을 하려면 Update, FixedUpdate, LateUpdate 등을 사용해야 하지만, MonoBehaviour를 상속시켜 해당 스크립트 내에서 처리하면, 각 기능별 호출 타이밍을 개발자가 마음대로 조절할 수 없습니다.
예를들어서, Locomotion의 Update 함수가 맨마지막에 호출되고 StateMachine의 Update 함수가 호출되게 하고 싶을 때,
MonoBehaviour 내의 Update 함수로 제어를 가능한가? 라고 질문하면 그건 운적으로 그렇게 호출될 가능성은 있어도 제어는 불가능합니다.
물론 Script Execution Order를 사용하면 제어는 할 수 있지만, 복잡하고 확장성이 떨어진다고 생각합니다.
하지만 PlayerController의 Update 내부에서 실행할 함수를 직접 지정하도록 구성하면, 실행 순서를 비교적 간단하게 제어할 수 있습니다.
void Update()
{
_stateMachine?.LogicUpdate();
_weapon?.LogicUpdate();
_locomotion?.LogicUpdate(deltaTime, _inputContext);
}

스크립트 구현이 완료되었으니 캐릭터 오브젝트에 해당 컴포넌트를 부착하고
CharacterController 필드를 할당해줍니다.

결과 확인

이제 이동은 구현이 되었지만, 움직이는 모션이나 카메라 회전 등이 아직 부족합니다.
위 내용은 다음 포스팅에서 작성해보겠습니다.
'프로그래밍 노트' 카테고리의 다른 글
| [유니티] 버튼 연타 방지 확장 메소드 구현 (0) | 2026.05.27 |
|---|---|
| [유니티] 3D FPS 게임 구현 0 - 에셋 준비 (1) | 2026.04.22 |
| [유니티] 컴파일링(Compiling)과 어셈블리 정의 (Assembly Definition) (0) | 2026.04.16 |
| 유니티 VirtualCamera가 Live 상태로 변하지 않는 문제 (0) | 2025.11.23 |
| 유니티 C#, C++ 그리고 Null (1) | 2025.06.30 |