- [게임 개발 일지 #1] 일단 만들어보자 - 간단한 플레이어 움직이기2025년 05월 14일
- 우밍
- 작성자
- 2025.05.14.:54
반응형유니티 기본 세팅 3D 형태 유니티 버전은 2022.3.55f1를 사용했다. (그냥 컴퓨터에 깔려있어서... 사용했다.)
일단 기획은 없지만 <견습 기사 모험기>라는 게임을 보고 간단한 프로토타입을 만들어보려고 한다.먼저 3D 형태부터 만들어보도록 하자.
3D 플레이어에 쓸 모델 레벨에 쓸 그리드 텍스쳐도 같이~ 유니티 에셋스토어를 찾아보니 괜찮은 모델이 있어서 가져왔다.
핑꾸핑꾸하당 항상 에셋스토어에서 임포트해오면 핑꾸핑꾸해진다.
메테리얼에 쉐이더가 제대로 연결 안돼서 (렌더 파이프라인 불일치)
핑꾸로 보이는 거라고 하는데 이럴 때 마다 하는 방법이 있다.
상단 메뉴에서
1. Window > Rendering > Render Piperline Converter을 열고2. Built-in to URP 선택 후
3. Material Update 체크 후 Initialize And Convert을 눌러주면 된다.
그러면 URP용으로 자동 변환 해준다.
(근데 커스텀 쉐이더는 지원을 안해주는 것 같다.)
1. Window 클릭! 2. Rendering 클릭 3. Render Pipeline Converter 클릭! 4. Material Update에 체크를 하고 Initialize And Convert 클릭! 핑꾸 삭제~ 초기 세팅 자 이제 Player를 움직여보자.
Figma로 클래스 설계 Figma로 대충 클래스 설계를 했다.
플레이어 상태를 일단 (대기, 이동, 공격) 3종류가 설계했고
각 상태별로 입력 처리, 애니메이션 재생, 이동, 공격 로직을 분리 시켰다.
자 이제 작성을 해보자
PlayerInput.cs
using System.Collections; using System.Collections.Generic; using UnityEngine; /// <summary> /// 플레이어 입력 처리 클래스 /// </summary> public class PlayerInput : MonoBehaviour { public Vector3 MoveInput { get; private set; } private const string HORIZONTAL = "Horizontal"; private const string VERTICAL = "Vertical"; private void Update() { HandleMoveInput(); } private void HandleMoveInput() { float horizontal = Input.GetAxisRaw(HORIZONTAL); float vertical = Input.GetAxisRaw(VERTICAL); MoveInput = new Vector3(horizontal, 0, vertical).normalized; } }
PlayerInput
클래스는
플레이어의 키보드 입력을 감지하고 현재 프레임의 이동 방향 벡터를 계산해주는 역할을 한다.
Unity안에 있는Input System
을 사용해도 되는데 간단한 프로토타입이니 사용을 안했다.PlayerData.cs
using System.Collections; using System.Collections.Generic; using UnityEngine; public class PlayerData : MonoBehaviour { public float MoveSpeed = 5f; public float RotationSpeed = 720f; }
PlayerData
클래스는 플레이어의 데이터를 담는 컨테이너다.
지금은MonoBehaviour
를 상속했지만 추후에ScriptableObject
로 바꿀 예정이다.
현재는 이동속도와 회전속도밖에 없다.PlayerMovement.cs
using System.Collections; using System.Collections.Generic; using UnityEngine; public class PlayerMovement : MonoBehaviour { private PlayerData _playerData; private void Awake() { _playerData = GetComponent<PlayerData>(); } public void Move(Vector3 moveInput) { Vector3 moveDirection = moveInput; transform.position += moveDirection * _playerData.MoveSpeed * Time.deltaTime; } public void Rotate(Vector3 MoveInput) { if (MoveInput == Vector3.zero) { return; } Quaternion targetRotation = Quaternion.LookRotation(MoveInput); Quaternion newRotation = Quaternion.RotateTowards(transform.rotation, targetRotation, _playerData.RotationSpeed * Time.deltaTime); transform.rotation = newRotation; } }
PlayerMovement
클래스는 현재는 이동 및 회전 동작을 처리한다.
외부 입력(MoveInut)을 받아 플레이어를 움직이고, 바라보는 방향으로 회전시킨다.PlayerAnimaton.cs
using System.Collections; using System.Collections.Generic; using UnityEngine; public class PlayerAnimation : MonoBehaviour { private Animator _animator; private static readonly int MOVE_PARAM = Animator.StringToHash("Move"); private void Awake() { _animator = GetComponent<Animator>(); } public void SetMoveAnimation(bool isMoving) { _animator.SetBool(MOVE_PARAM, isMoving); } }
PlayerAnimation
클래스는 플레이어 애니메이션을 관리한다.
아직Movement
동작만 적용했다.
(추후 많은 애니메이션을 추가하겠지?)PlayerStateMachine.cs
using UnityEngine; /// <summary> /// 플레이어 상태 머신 클래스 /// </summary> public class PlayerStateMachine : MonoBehaviour { public PlayerState CurrentState { get; private set; } public PlayerIdleState PlayerIdleState { get; private set; } public PlayerMoveState PlayerMoveState { get; private set; } public void Start() { TransitionState(PlayerIdleState); } public void FixedUpdate() { if (CurrentState != null) { CurrentState.FixedUpdate(); } } public void Update() { if (CurrentState != null) { CurrentState.Update(); } } public void TransitionState(PlayerState newState) { if (CurrentState == newState) { return; } if (CurrentState != null) { CurrentState.Exit(); } CurrentState = newState; if (CurrentState != null) { CurrentState.Enter(); } } }
PlayerStateMachine
클래스는 플레이어의 상태를 관리하는 상태 머신 역할을 한다.
현재 상태를 추적하고 새로운 상태로 전환할 때Enter()
,Exit()
메서드를 자동으로 호출해준다.- 예전에는
CurrentState?.Exit()
이런 방식으로 널 체크를 했었는데 처음 보는 사람이 이 키워드를 보면 잘 몰라해서?.
를 쓰지 않는다. - 웬만하면 처음 보는 키워드 같이 생기면 잘 안 쓰려고 한다.
PlayerState.cs
using System.Collections; using System.Collections.Generic; using UnityEngine; public abstract class PlayerState { protected PlayerStateMachine _playerStateMachine; protected PlayerInput _playerInput; protected PlayerMovement _playerMovement; protected PlayerAnimation _playerAnimation; public PlayerState(PlayerStateMachine playerStateMachine) { _playerStateMachine = playerStateMachine; _playerInput = playerStateMachine.GetComponent<PlayerInput>(); _playerMovement = playerStateMachine.GetComponent<PlayerMovement>(); _playerAnimation = playerStateMachine.GetComponent<PlayerAnimation>(); } public abstract void Enter(); public abstract void FixedUpdate(); public abstract void Update(); public abstract void Exit(); public abstract void Transition(); } public class PlayerIdleState : PlayerState { public PlayerIdleState(PlayerStateMachine playerStateMachine) : base(playerStateMachine) { } public override void Enter() { } public override void FixedUpdate() { } public override void Update() { Transition(); } public override void Exit() { } public override void Transition() { if (_playerInput.MoveInput != Vector3.zero) { _playerStateMachine.TransitionState(_playerStateMachine.PlayerMoveState); } } } public class PlayerMoveState : PlayerState { public PlayerMoveState(PlayerStateMachine playerStateMachine) : base(playerStateMachine) { } public override void Enter() { _playerAnimation.SetMoveAnimation(true); } public override void FixedUpdate() { } public override void Update() { _playerMovement.Move(_playerInput.MoveInput); _playerMovement.Rotate(_playerInput.MoveInput); Transition(); } public override void Exit() { _playerAnimation.SetMoveAnimation(false); } public override void Transition() { if (_playerInput.MoveInput == Vector3.zero) { _playerStateMachine.TransitionState(_playerStateMachine.PlayerIdleState); } } }
PlayerState
클래스는 플레이어 상태 관리 시스템의 핵심이다.추상 클래스
PlayerState
를 기반으로 각 상태 (Idle, Move)를 상속하고,
상태에 따라 입력, 이동, 회전, 애니메이션을 처리한다.- 원래는 추상 메서드를 작성할 때
public virtual Update() {}
를 자주 사용했지만 처음 보는 사람이 이 코드를 봤을 때 왜 갑자기 업데이트를 하고 Exit()는 왜 작동을 안하는 지 질문을 많이 받았다. virtual
를 사용하면- 꼭 구현하지 않아도 되는 선택지를 줄 수 있지만
- 처음보는 사람들에게는 구조 파악하기가 힘들어했다.
abstract
를 사용하면- 반드시 구현해야 하고
- 직접 호출해줘야 작동하는 구조임을 의도적으로 말할 수 있어서
virtual
대신abstract
를 사용한다. - 몇 줄 더 쓰는 건 귀찮지만 가독성을 위해서 귀찮아도 해야 한다.
애니메이터 컨트롤러도 하나 만들어주었다.
현재는Idle
상태와Move
상태밖에 없기 때문에 애니메이션 2개와 1개(Move) param이 있다.
자 이제 한번 플레이 해보자.플레이 하기전에 Unity엔진안에 Recorder라는 패키지를 다운을 받았다.
(영상을 찍고 저장할 수 있는 툴이다.)다운을 받았으면
Window > General > Recorder > Recorder Window 들어가면 된다
Add Recorder를 눌러서 원하는 것을 생성한다. (나는 Movie로 생성했다.)
빨간색 재생 버튼을 누르면 녹화가 시작된다.
영상확인은 Output File 쪽에서 확인하면 된다.잘 작동한다. 오늘은 간단하게 플레이어 움직이는 것을 구현해보았다.
근데 오늘 진짜 깨달은 게 있다.
블로그 글쓰는 게 생각보다 오래 걸린다... ㅋㅋㅋㅋㅋㅋㅋㅋ
짧게 한 줄 썼다고 생각했는데캡처하고, 설명 달고, 코드 정리하고 나면
시간이 훅 지나가 있닼ㅋㅋㅋㅋㅋㅋㅋ
그래도 재미있어서 한다
고생했다.
다음엔 무슨 기능을 추가해볼까나
반응형'게임엔진 > 개발 일지' 카테고리의 다른 글
[게임 개발 일지 #0] 프로젝트 WindSong 시작하기 - 픽셀과 복셀 사이에서 (2) 2025.05.14 다음글이전글이전 글이 없습니다.댓글