플레이어 캐릭터 이동 구현에 대한 글이다.
다양한 방법으로 구현해볼 수 있겠지만
총 세 가지 방법에 대해 다뤄보도록 하겠다.
1. Input.GetAxis
수평, 수직 키 값을 입력받아 추출해주는 함수이다.
GetAxis
public class TopDownCharacterController : MonoBehaviour
{
[SerializeField] private float speed = 5f;
void Update()
{
float x = Input.GetAxis("Horizontal");
float y = Input.GetAxis("Vertical");
transform.position += new Vector3(x, y) * speed * Time.deltaTime;
}
}
“Horizental”
을 넣어주면 가로 이동 (왼쪽, 오른쪽) 의 값을,
”Vertical”
을 넣어주면 세로 이동 (위, 아래) 의 값을 반환한다.
왼쪽은 -1f
, 오른쪽은 1f
를 반환하는데
살짝 특이한 것이, 반환값이 float
이라는 것이다.
GetAxis
로 입력을 받아올 때
오른쪽을 입력해주면,
처음부터 1을 받아오는 것이 아니라
어느정도의 시간 간격을 두고
0f
에서 1f
로 서서히 변하는 것이다.
우리가 평소 캐릭터의 이동을 생각해보면 시작부터 정해진 속도로 가는 것이 아니라, 최대 속도까지 일정하게 가속이 붙는 경우가 많지 않은가.
이러한 부드러운 이동을 위해 서서히 증가하는 float
값을 반환해준다.
GetAxisRaw
처음부터 1f의 속도를 가지도록 구현하고 싶을 수도 있겠다.
그럴 때 사용하는 것이 바로 GetAxisRaw이다.
public class TopDownCharacterController : MonoBehaviour
{
[SerializeField] private float speed = 5f;
void Update()
{
float x = Input.GetAxisRaw("Horizontal");
float y = Input.GetAxisRaw("Vertical");
transform.position += new Vector3(x, y) * speed * Time.deltaTime;
}
}
여전히 float을 반환해주는 건 똑같지만, 부드러운 이동이 아닌 시작부터 바로 1f의 값을 반환해준다.
Input Manager
Input.GetAxis
를 사용할 때
이러한 부드러운 이동의 가속과 감속 정도를 조절하고 싶다거나,
WASD or 화살표 조작이 아닌 다른 키를 이동할 때 쓰고 싶은 경우가 있을 것이다.
이때 GetAxis의 프로퍼티를 따로 바꿔줄 수가 있다.
이곳에서 가속 값이나 키 설정을 바꿔줄 수 있다.
심지어 나만의 Axis를 추가하여 새롭게 정의할 수도 있다.
2. Input.GetKey
아예 GetKey
로 다이렉트로 키 입력을 받아올 수도 있다.
하지만 추천하지는 않는 방식이다.
public class TopDownCharacterController : MonoBehaviour
{
private Rigidbody2D playerRigidbody;
[SerializeField] private float speed = 5f;
private void Awake()
{
playerRigidbody = GetComponent<Rigidbody2D>();
}
void Update()
{
if (Input.GetKey(KeyCode.UpArrow) == true)
{
playerRigidbody.AddForce(new Vector2(0f, speed));
}
if (Input.GetKey(KeyCode.DownArrow) == true)
{
playerRigidbody.AddForce(new Vector2(0f, -speed));
}
if (Input.GetKey(KeyCode.RightArrow) == true)
{
playerRigidbody.AddForce(new Vector2(speed, 0f));
}
if (Input.GetKey(KeyCode.LeftArrow) == true)
{
playerRigidbody.AddForce(new Vector2(-speed, 0f));
}
}
}
AddForce로 움직임을 구현했는데, 이 역시 추천하지 않는다.
의도한 대로 동작하지 않을 확률이 높으며, 어떤 식으로 물리가 동작하는 지 추적하기도 힘들다.
만약 Transform의 변수들을 직접 조작하는 것이 아닌
메서드를 이용하여 캐릭터의 위치를 변경하고 싶다면
Transform.Translate
를 적극 사용하도록 하자.
3. Input System 패키지와 event 활용
마지막 방법이자 오늘의 결론.
앞에서 소개했던 방식들은 특정한 키나 특정한 axis만 처리할 수 있기 때문에 직관성이 떨어진다.
따라서 C#의 event
키워드를 활용하여 콜백 함수를 불러오자.
Input System 패키지 설치
사용하기 전에 먼저 Input System 패키지를 설치해줘야 한다.
Input System 패키지가 보이지 않으면
상단의 Packages를 In Project
에서 Unity Registry
로 바꿔주면 보인다.
Install해주고 재시작 창이 뜨면 유니티를 재시작해주자.
Input Actions 오브젝트 생성
이 후에는 Create
메뉴에 Input Actions
라는 새로운 오브젝트를 만들 수 있다.
Input Actions 편집
더블클릭을 하면 애니메이션처럼 새로운 편집창이 뜨게 된다.
이게 초기 상태이다.
Control Scheme이 없기 때문에 새로 추가해주자.
Control Scheme의 이름을 짓고 List에 Keyboard와 Mouse까지 추가해주자.
만약 JoyStick의 Control Scheme이 필요하다면 해당 방식의 Control Scheme을 새로 만들면 되는 것이다.
이제 Action Maps의 +
버튼을 눌러 새로운 Action map을 추가해주자.
이제 Actions를 수정해주자.
Action Properties에서
Action Type을 Value
로 바꾸고
Control Type을 Vector 2
로 해주자.
그러고 Action
에 바인딩을 추가할 수 있다.
새로 만든 액션 옆의 +
버튼을 누르면
Add Up\Down\Left\Right Composite
이라는 것이 있다.
추가한 바인딩에 키를 매핑해주자.
여기서 WASD말고 화살표로도 조작받고 싶으면 어떻게 하나요? 할 수 있는데 Action을 한 개 더 추가해서 똑같은 방식으로 만들고 키매핑만 화살표로 해주면 된다.
움직임 뿐만 아니라 여러 조작에 대한 기능을 구현할 수 있다.
마우스가 있는 방향을 바라본다던가, 좌클릭을 인식하면 들고 있는 총을 쏜다던가 하는 등의 기능 말이다.
Input Actions
를 편집한 뒤에 Save Asset
을 눌러 저장해주자.
스크립트 작성
키 입력 받는 스크립트 작성
이제 event와 Action을 활용하는 코드를 작성할 것이다.
event
와 Action
은 C#의 키워드인데, 자세한 내용은 다음의 글을 참고하자.
두 개의 스크립트를 생성하여 다음과 같이 작성해주자.
TopDownCharacterController.cs
public class TopDownCharacterController : MonoBehaviour
{
// event 외부에서는 호출하지 못하게 막는다
public event Action<Vector2> OnMoveEvent;
public void CallMoveEvent(Vector2 direction)
{
OnMoveEvent?.Invoke(direction);
}
}
PlayerInputController.cs
public class PlayerInputController : TopDownCharacterController
{
public void OnMove(InputValue value)
{
Vector2 moveInput = value.Get<Vector2>().normalized;
CallMoveEvent(moveInput);
}
}
OnMove
라고 함수를 작성하는데,
이는 우리가 Input Actions
에서 추가해준 Action
의 이름이 Move라서 그렇다.
조금 있다 Input Actions
를 컴포넌트로 추가할 때 더 자세히 설명하겠다.
moveInput
으로 넣어주는 Vector2
를 normalized
해주는데, 그 이유는
W와 D를 동시에 입력했을 때 Vector2
는 (1, 1)
이다.
이 경우 크기가 1보다 크기 때문에 대각선으로 이동할 때는 속도가 더 빨라지는 증상이 발생한다.
따라서 Vector2
의 크기를 1로 만들어주기 위해 normalized
라는 키워드를 붙여주게 된다.
코드의 구조를 조금 자세히 설명하자면, WASD의 입력이 있을 때마다 Input Actions에 Action을 추가해놓은대로 OnMove 함수의 호출이 된다.
이 때 OnMove
함수 안에서
OnMoveEvent
라는 event Action
을 인보크해주는 CallMoveEvent
를 호출해주면
이 의미는 잘 생각해보면
WASD의 입력이 감지될 때마다 OnMoveEvent가 발생하는 것이다!
이러면 이제 움직임에 필요한 추가적인 작업이 있다면
모두 OnMoveEvent에다가 +=
로 넣어주기만 하면 된다.
참고) event를 사용했을 때의 이점
이러한 방식은 코드의 기능 분리가 매우 깔끔해진다는 점이다.
다른 스크립트에서 캐릭터가 움직일 때만 호출되어야 하는 함수가 있다고 해보자.
event를 사용하지 않았을 때는 캐릭터의 움직임을 알기 위한 정보를 해당 스크립트에 일일히 다 작성해야 한다.
하지만 이런식으로 움직임을 관장하는 스크립트의 event
에
내가 호출하고자 하는 다른 스크립트의 함수를 더해줘버리기만 한다면?
아무리 많은 스크립트에서 움직임 감지가 필요하다고 하더라도
그냥 해당 함수를 event
에 더해줘 버리면 된다.
마치 유튜브의 구독과도 같다.
그러고 나선 Move가 감지될 때마다
event
에 구독된 함수들이 모조리 호출되는 것이다.
이러한 디자인 패턴을 옵저버 패턴이라고 한다.
자세히 다룰 일이 있다면 추가로 게시글을 작성하도록 하겠다.
Entity 이동 스크립트 작성
지금까지 키 입력을 받는 부분을 구현했으니 실제로 오브젝트가 이동하는 스크립트를 작성해야한다.
다음과 같이 스크립트를 생성하여 코드를 작성해주자.
TopDownMovement.cs
public class TopDownMovement : MonoBehaviour
{
private TopDownCharacterController _controller;
private Vector2 _movementDirection = Vector2.zero;
private Rigidbody2D _rigidbody;
private void Awake()
{
_controller = GetComponent<TopDownCharacterController>();
_rigidbody = GetComponent<Rigidbody2D>();
}
private void Start()
{
_controller.OnMoveEvent += Move;
}
private void FixedUpdate()
{
ApplyMovement(_movementDirection);
}
private void Move(Vector2 direction)
{
_movementDirection = direction;
}
private void ApplyMovement(Vector2 direction)
{
direction = direction * 5;
_rigidbody.velocity = direction;
}
}
오브젝트를 움직이는 함수 Move
를
WASD 키가 감지되었을 때 트리거되는 OnMoveEvent
에 구독을 해준 것이다.
그림으로 표현하자면 다음과 같다.
컴포넌트 추가
이제 Player 오브젝트에서 지금까지 만든 컴포넌트들을 추가해주자.
인스펙터를 자세히 보면
호출 가능한 이벤트 함수 목록이 있는데,
우리가 Move
라는 이름의 액션을 추가해줬기 때문에 OnMove
가 호출 가능한 모습이다.
앞서 언급한 방법처럼 Look
과 Fire
를 추가해준다면
마찬가지로 OnLook
과 OnFire
함수 역시 호출 가능하도록 추가되는 것을 확인할 수 있다.
그 다음 실제로 캐릭터를 움직여주는 부분을 만들자.
참고로 4방향의 움직임을 구현 중이기 때문에
Rigidbody2D
에서 Gravity Scale
은 0
으로 만들어주자.
값이 있으면 게임 실행시 캐릭터가 아래로 뚝 떨어진다.
이제 완성이다!
지금까지 기존에 주로 유니티에서 이동을 구현하던 방법과 Input System과 event를 활용한 이동 구현 방법이었다.
Input System과 event 모두 유지 보수나 기능 변경이 매우 편리한 구조이기 때문에 앞으로 이동 구현이 필요할 때면 해당 게시글을 적절히 참고하는 것이 좋겠다.
Uploaded by N2T
'개발 > Unity 내일배움캠프 TIL' 카테고리의 다른 글
Unity2D 셀 애니메이션 (0) | 2023.09.07 |
---|---|
Unity2D 타일맵 (Tilemap) (0) | 2023.09.06 |
Unity MonoBehaviour (모노비헤이비어)와 스크립트 라이프 사이클 (1) | 2023.09.04 |
C# 패턴 일치 (Pattern Matching) (0) | 2023.09.01 |
C# 중첩 클래스와 partial 클래스 (0) | 2023.08.31 |