반응형

플레이어 이동 처리 흐름도

 

플레이어 이동은 Grid를 선택하면 해당 위치까지 이동하는 방식과, 캐릭터를 직접 드래그해서 원하는 경로를 지정하는 방식 두 가지로 구현했습니다.

 

두 방식 모두 Pointer 입력과 Grid 좌표를 공통적으로 사용하기 때문에, 이번 포스팅에서는 먼저 Input System을 이용한 입력 처리와 Grid 좌표 변환 과정을 정리해보겠습니다.


1. Input System을 통한 플레이어 입력 받기

Input Actions 설정

 

Point는 현재 Pointer가 화면의 어디에 있는지 받기 위한 액션으로 PC에서는 마우스 위치가 될 수 있고, 모바일에서는 터치 포인터 위치가 될 수 있습니다.

값 자체는 화면 좌표로 받게 되며, 포인터 위치가 변할 때마다 그 값을 그대로 전달받기 위해서 Pass Through를 사용했습니다.

 

Press는 Pointer가 눌렸는지 / 떼졌는지를 표현하는 입력입니다.

 

따라서 Point는 현재 Pointer가 화면의 어디에 있는지를 확인하고, Press는 Pointer를 누르기 시작하거나 손을 떼는 입력을 확인하는 데 사용했습니다.


2. PlayerDragController에서 입력 처리

Input Action을 만들어두었다고 해서 바로 캐릭터가 움직이는 것은 아닙니다.

 

Input System에서는 현재 Pointer의 위치나 Press 상태만 전달해주기 때문에, 실제로 이 입력을 받아 캐릭터를 선택했는지, Grid를 선택했는지, 현재 Drag 중인지 등을 판단하는 부분이 필요합니다.

 

이 역할은 PlayerDragController에서 처리했습니다.

 

먼저 Input Action Asset에서 Generate C# Class를 사용해 PlayerInputActions 클래스를 생성했습니다.

private PlayerInputActions inputActions;
 

Generate C# Class를 사용하면 Input Actions에 만들어둔 Action Map과 Action을 C# 코드에서 바로 사용할 수 있습니다.

 

제가 만들어둔 GamePlay Action Map 안에는 앞에서 설명한 Point, Press가 있기 때문에 코드에서는 다음과 같이 접근할 수 있습니다.

inputActions.GamePlay.Point
inputActions.GamePlay.Press
 

 

그리고 Awake()에서 PlayerInputActions 객체를 생성했습니다.

private void Awake()
{
    inputActions ??= new PlayerInputActions();
    
    /*
    여기서 ??=는 왼쪽 값이 null일 때만 오른쪽 값을 대입하는 연산자입니다.
    따라서 위 코드는 아래 코드와 같은 의미입니다.
    if (inputActions == null) { inputActions = new PlayerInputActions(); }
    */
}
 

이후 OnEnable()에서 실제 Input Action과 메서드를 연결했습니다.

private void OnEnable()
{
    inputActions.GamePlay.Point.performed += OnPointerPerformd;

    inputActions.GamePlay.Press.started += OnPressStarted;
    inputActions.GamePlay.Press.canceled += OnPressCanceled;

    inputActions.GamePlay.Enable();
}

private void OnDisable()
{
    inputActions.GamePlay.Point.performed -= OnPointerPerformd;

    inputActions.GamePlay.Press.started -= OnPressStarted;
    inputActions.GamePlay.Press.canceled -= OnPressCanceled;

    inputActions.GamePlay.Disable();
}

//	OnEnable()에서 이벤트를 등록했다면 OnDisable()에서는 반대로 이벤트 연결을 해제해야합니다.
//	오브젝트가 다시 활성화됐을 때 같은 이벤트가 중복으로 등록되는 것을 방지하기 위해서입니다.
 

 

여기서 +=는 해당 이벤트가 발생했을 때 실행할 메서드를 연결하는 의미입니다.

정리하면 다음과 같습니다.

Point.performed → Pointer 위치가 변경되었을 때 위치 갱신

Press.started → Pointer를 누르기 시작했을 때

Press.canceled → Pointer에서 손을 뗐을 때

이제 각각의 입력이 PlayerDragController에서 어떻게 사용되는지 확인해보겠습니다.

  • Point.performed는 현재 Pointer의 위치가 변경됐을 때 발생합니다.
private void OnPointerPerformd(InputAction.CallbackContext context)
{
    pointerScreenPosition = context.ReadValue<Vector2>();
}
 

앞에서 Point의 Control Type을 Vector2로 설정했기 때문에 ReadValue<Vector2>()를 통해 현재 Pointer 위치를 받아올 수 있습니다.

 

예를 들어 화면의 다음 (620, 1240) 위치를 터치하고 있다면, 이 값이 pointerScreenPosition에 저장됩니다.

 

여기서 중요한 점은 이 값이 아직 TileMap의 좌표가 아니라 화면을 기준으로 한 Screen Position이라는 점입니다.

즉 Point.performed는 캐릭터를 직접 움직이는 것이 아니라, 현재 Pointer가 화면 어디에 있는지를 계속 확인하는 역할을 합니다.

이 값은 이후 Grid 선택이나 Drag 경로를 만들 때 사용하게 됩니다.


  • Press.started는 Pointer를 누르기 시작했을 때 발생합니다.

PC에서는 마우스 버튼을 누른 순간이고, 모바일에서는 화면에 손가락을 대기 시작한 순간입니다.

이때 바로 캐릭터를 움직이는 것은 아니고, 먼저 현재 Pointer가 무엇을 누르고 있는지 확인합니다.

 

우선 현재 Pointer의 Screen Position을 World Position으로 변환한 뒤, playerTouchCollider.OverlapPoint()를 이용해서 실제로 플레이어 캐릭터를 누른 것인지 확인합니다.

bool isPlayerTouched = playerTouchCollider.OverlapPoint(worldPosition);
 

OverlapPoint()는 전달한 World Position이 Collider 내부에 포함되어 있는지를 확인하는 함수입니다.

 

즉 현재 손가락이나 마우스 위치가 실제 플레이어 캐릭터 위에 있는지를 확인하기 위해 사용했습니다.

Pointer 위치
    ↓
Screen Position
    ↓
World Position
    ↓
playerTouchCollider.OverlapPoint()
    ↓
플레이어를 눌렀는지 확인
 

플레이어를 눌렀다고 해서 바로 Drag를 시작하는 것은 아닙니다.

 

캐릭터를 짧게 터치해서 선택하려는 것인지, 아니면 일정 시간 이상 눌러서 Drag하려는 것인지를 구분해야 하기 때문입니다.

따라서 플레이어를 누른 경우에는 이후 입력 시간을 확인해서 Tap과 Long Press를 구분하도록 처리했습니다.

    캐릭터 Press
         ↓
   입력 시간 확인
   ┌─────┴──────┐
   │            │
 짧은 입력  Long Press
   │            │
 선택 처리   Drag 준비
 

반대로 캐릭터가 이미 선택되어 있는 상태에서 Grid를 눌렀다면, 해당 위치를 이동 목적지로 사용할 수 있도록 처리했습니다.

 

즉 Press.started에서는 바로 이동을 실행하는 것이 아니라, 현재 무엇을 눌렀는지 확인하고 이후 어떤 입력으로 처리할지 판단하기 위한 시작 상태를 만드는 역할을 합니다.

 

실제 Long Press 이후 Drag 경로를 만드는 과정은 Pointer Drag 포스팅에서 따로 정리하겠습니다.


  • Press.canceled는 눌렀던 Pointer에서 손을 뗐을 때 발생합니다.

Press.started에서 입력이 시작됐다면 Press.canceled에서는 현재 입력 상태에 맞게 해당 입력을 마무리합니다.

 

예를 들어 캐릭터를 짧게 터치했다면 선택 또는 선택 해제로 처리하고, Grid를 누르고 있었다면 해당 Grid를 목적지로 사용할 수 있도록 처리합니다.

Press.started → 무엇을 눌렀는지 확인

Point.performed → Pointer 위치 갱신

Press.canceled → 현재 입력 상태에 맞게 처리
 

결국 PlayerDragController에서는 직접 캐릭터를 움직이기보다는, Input System에서 들어온 입력을 받아서 어떤 입력인지 구분하고, 이후 이동 처리로 넘겨주는 역할을 담당합니다.


3. Screen Position을 Grid Cell로 변환하기

여기까지 Input System을 통해 Pointer의 위치는 받아올 수 있게 됐습니다.

하지만 한 가지 문제가 있습니다. Point를 통해 받아오는 위치는 다음과 같은 Screen Position(620, 1240)입니다.

 

반면 실제 캐릭터 이동은 TileMap의 Cell을 기준으로 처리하고 있습니다.

따라서 Pointer가 화면의 어느 위치에 있는지만 알아서는 해당 위치가 실제로 어느 Grid인지 알 수 없습니다.

그래서 다음과 같은 좌표 변환이 필요합니다.

Screen Position → World Position → Grid Cell

 

이 부분을 처리하기 위해 GridMap 클래스를 만들어 사용했습니다.


4. GridMap

GridMap은 TileMap을 기준으로 Grid와 관련된 기능을 한 곳에서 처리하기 위해 만든 클래스입니다.

현재 사용하고 있는 주요 기능은 다음과 같습니다.

World Position → Grid Cell 변환

Grid Cell → World Position 변환

해당 Cell이 이동 가능한지 확인
 

이렇게 따로 클래스를 만든 이유는 이후 PlayerDragController뿐만 아니라 BFS, Drag 이동, 실제 캐릭터 이동에서도 계속해서 Grid 정보를 사용하기 때문입니다.

각 클래스에서 각각 TileMap을 확인하도록 만들기보다는 GridMap을 통해 같은 기준을 사용하도록 만들었습니다.

 

  • WorldToCell

WorldToCell()은 월드 좌표를 Grid Cell 좌표로 변환할 때 사용합니다.

예를 들어 Pointer 위치를 ScreenToWorldPoint()를 통해 World Position으로 변환했다면, 그 위치가 TileMap의 몇 번째 Cell인지 다시 확인해야 합니다.

Vector3Int cell = gridMap.WorldToCell(worldPosition);
 

정리하면 다음과 같습니다.

World Position
      ↓
WorldToCell()
      ↓
Grid Cell
 

즉, 현재 위치가 어느 Grid에 속하는지 확인할 때 사용하는 함수입니다.

Pointer로 특정 Tile을 선택하거나, 현재 플레이어가 어느 Cell에 있는지 확인할 때 주로 사용합니다.

 

  • CellToWorld

CellToWorld()은 반대로 Grid Cell 좌표를 실제 World Position으로 변환할 때 사용합니다.

Vector3 worldPosition = gridMap.CellToWorld(cell);
 

정리하면 다음과 같습니다.

Grid Cell
    ↓
CellToWorld()
    ↓
World Position
 

Grid 이동에서는 경로를 Vector3Int 형태의 Cell 좌표로 관리하고 있기 때문에, 실제 캐릭터 Transform을 움직이려면 해당 Cell의 World Position이 필요합니다.

즉, 어느 Grid로 이동할지는 Cell 좌표로 관리하고, 실제 캐릭터를 움직일 때는 World 좌표로 변환해서 사용합니다.

 
  • IsWalkable

마지막으로 해당 Cell이 실제로 이동 가능한 위치인지 확인하는 기능도 GridMap에서 처리했습니다.

bool canMove = gridMap.IsWalkable(cell);
 

캐릭터 이동에서는 단순히 해당 Cell이 존재하는지만 확인하면 안 됩니다.

TileMap 안에 있더라도 벽이나 장애물이 있는 위치라면 이동할 수 없기 때문입니다.

따라서 IsWalkable()을 통해 해당 Cell이 실제 이동 가능한 위치인지 공통적으로 확인하도록 만들었습니다.

Grid Cell
   ↓
IsWalkable()
   ↓
이동 가능 / 이동 불가능

이렇게 이동 가능 여부를 GridMap에서 관리하면 이후 BFS에서도 같은 기준을 사용할 수 있고, Drag를 통해 직접 경로를 만들 때도 동일한 판정을 사용할 수 있습니다.


정리

여기까지 구현하면서 입력부터 Grid Cell을 구하기까지의 흐름은 다음과 같이 구성했습니다.

PlayerInputActions
        ↓
  Point / Press
        ↓
PlayerDragController
        ↓
  Screen Position
        ↓
  World Position
        ↓
     GridMap
        ↓
    Grid Cell
 

Input System에서는 현재 Pointer의 위치와 Press 상태를 받아오고,

PlayerDragController에서는 해당 입력을 바탕으로 현재 무엇을 누르고 있는지와 어떤 입력인지 판단합니다.

그리고 GridMap을 통해 Screen Position을 실제 TileMap에서 사용할 수 있는 Grid Cell로 변환했습니다.

여기까지 하면 이제 플레이어가 어떤 Grid를 선택했는지 확인할 수 있는 상태가 됩니다.

 

하지만 목적지 Cell을 알게 됐다고 해서 바로 이동할 수 있는 것은 아닙니다.

현재 위치와 목적지 사이에 벽이나 장애물이 있을 수 있기 때문에 실제로 이동 가능한 Cell을 따라 경로를 찾아야 합니다.

 

다음 포스팅에서는 BFS를 이용해 선택한 Grid까지 이동 경로를 만드는 과정을 정리해보겠습니다.

반응형

'Project > Project_P' 카테고리의 다른 글

Unity2D - TileMap 캐릭터 Grid 이동 (BFS)  (0) 2026.09.23