반응형

드래그 이동을 구현한 뒤 방향을 확인하면서 한 가지 문제가 있었다. 플레이어를 오른쪽 위로 끌었는데 의도한 대각선 경로 대신 오른쪽과 위쪽 이동이 번갈아 들어가는 현상이 나타났다.

 

목적지를 터치해서 BFS로 경로를 만드는 방식에서는 대각선 이동이 정상적으로 나왔고, 문제는 직접 드래그하는 입력에서만 발생했다. 처음에는 대각선 판정값을 조정하면 될 거라고 생각했지만, 실제로는 포인터 입력을 경로로 바꾸는 기준 자체를 수정해야 했다.

1. 수정 전후 비교

왼쪽 : 수정 전 / 오른쪽 : 수정 후

기존에는 대각선 방향으로 드래그할 때도 경로가 → ↑ → ↑처럼 직교 방향으로 분해될 수 있었다. 수정 후에는 현재 Cell 중심에서 포인터가 향하는 방향을 직접 판정하도록 변경했다.

 

아래부터는 왜 이런 현상이 발생했고 어떻게 코드를 수정했는지 정리했다.

2. 기존 코드에서는 무엇을 기준으로 경로를 만들었나?

기존 UpdateDrag()는 포인터의 월드 좌표를 먼저 Cell 좌표로 변환했다.

Vector2 pointerWorld = ScreenToWorld(currentScreenPosition);
Vector3Int targetCell = gridMap.WorldToCell(pointerWorld);
Vector3Int currentCell = pathBuilder.CurrentCell;

그다음 GridLineRasterizer.GetCells(currentCell, targetCell)로 현재 경로 끝점부터 포인터가 들어간 Cell까지 연결하고, TryEnterCell()을 이용해 Path에 넣었다.

Pointer World 위치
    ↓ WorldToCell()
포인터가 들어간 Target Cell
    ↓ GridLineRasterizer.GetCells()
중간 Cell 목록
    ↓ DragPathBuilder.TryEnterCell()
경로 확정

중간 Cell을 빠뜨리지 않는다는 점에서는 유용하지만, 사용자가 어느 방향으로 드래그하려 했는지와 포인터가 어느 Cell에 먼저 들어갔는지는 항상 같지 않았다.

3. 왜 대각선이 직선 두 칸으로 나뉘었을까?

예를 들어 (0,0) Cell 중심에서 오른쪽 위로 포인터를 움직인다고 해보자. 완벽하게 45도로 움직이는 것이 아니라면 오른쪽 경계와 위쪽 경계를 지나는 시점이 조금씩 달라질 수 있다.

사용자 의도:  (0,0) ↗ (1,1)

실제 Cell 진입 순서의 예:
(0,0) → (1,0) ↑ (1,1)

처음 오른쪽 Cell (1,0)으로 판단되는 순간 이를 Path에 추가하면, 이후 포인터가 (1,1)에 들어갔을 때는 이미 시작점이 (1,0)으로 변경된 상태다. 결국 대각선 한 칸 대신 직선 두 칸으로 경로가 만들어질 수 있었다.

따라서 처음 선택한 Cell을 보정하는 것보다 Cell 좌표로 바꾸기 전에 포인터 방향 자체를 판단하는 편이 낫다고 봤다.

4. 현재 Cell 중심을 기준으로 방향을 판정하도록 변경

수정한 코드에서는 포인터가 들어간 targetCell을 곧바로 사용하지 않는다.

Vector3Int currentCell = pathBuilder.CurrentCell;
Vector2 currentCellCenter = gridMap.CellToWorld(currentCell);
Vector2 worldDelta = pointerWorld - currentCellCenter;
Vector2 cellDelta = gridMap.WorldDeltaToCellDelta(worldDelta);

Vector3Int direction = DragDirectionResolver.Resolve(
    cellDelta,
    diagonalStartThrehold,
    straightStartThreshold,
    diagonalRatio
);

여기서 worldDelta는 현재 Cell 중심에서 포인터까지의 상대적인 변위이고, cellDelta는 그 변위를 Cell 크기 기준으로 환산한 값이다.

 

예를 들어 Cell 크기가 2 × 1이고, 두 축의 World 변위가 (1.0, 0.5)라면 실제로는 둘 다 Cell의 절반만큼 이동한 것이다.

worldDelta = (1.0, 0.5)
cellDelta  = (1.0 / 2, 0.5 / 1)
           = (0.5, 0.5)

이처럼 크기가 다른 Grid에서도 같은 Cell 단위 기준으로 판정하기 위해 cellDelta를 사용했다.

 

방향 판정만 담당하는 DragDirectionResolver를 별도 클래스로 분리했기 때문에, 입력 이벤트 처리나 Path 수정 로직과는 독립적으로 판정 기준을 조정할 수 있다.

5. DragDirectionResolver의 세 가지 설정값

현재 설정한 테스트값은 다음과 같다.

[SerializeField, Range(0.2f, 0.49f)]
private float diagonalStartThrehold = 0.35f;

[SerializeField, Range(0.5f, 0.9f)]
private float diagonalRatio = 0.65f;

[SerializeField, Range(0.5f, 0.9f)]
private float straightStartThreshold = 0.60f;

이 값은 역할이 서로 다르다.

설정값 무엇을 비교하는가?
diagonalStartThrehold X와 Y 두 축 모두 최소 0.35 Cell 이상 움직였는가
diagonalRatio 작은 이동량 ÷ 큰 이동량이 0.65 이상인가
straightStartThreshold 대각선이 아니라면, 가장 많이 움직인 축이 0.60 Cell 이상인가

대각선 조건은 다음 코드에서 검사한다.

float absX = Mathf.Abs(delta.x);
float absY = Mathf.Abs(delta.y);
float maxValue = Mathf.Max(absX, absY);

if (maxValue <= Mathf.Epsilon)
    return Vector3Int.zero;

float minValue = Mathf.Min(absX, absY);
float ratio = minValue / maxValue;

bool enoughForDiagonal =
    absX >= diagonalStartThreshold &&
    absY >= diagonalStartThreshold;

bool diagonalDirection = ratio >= diagonalRatio;

대각선 판정은 두 축의 크기가 충분한지 먼저 확인하고, 그다음 X/Y 변위가 얼마나 비슷한지를 확인하는 방식이다.

예시 ① 오른쪽 위 대각선

cellDelta = (0.45, 0.40)
두 축 모두 0.35 이상 → 통과
ratio = 0.40 / 0.45 ≈ 0.89
0.89 ≥ 0.65 → 대각선

결과: (1, 1, 0)

예시 ② 방향을 확정하기에 부족한 입력

cellDelta = (0.50, 0.10)
Y가 0.35 미만 → 대각선 아님
가장 큰 이동량 0.50 < 0.60 → 직선도 아직 아님

결과: (0, 0, 0)

예시 ③ 오른쪽 직선

cellDelta = (0.70, 0.10)
Y가 0.35 미만 → 대각선 아님
가장 큰 이동량 0.70 ≥ 0.60
X가 더 큼 → 오른쪽

결과: (1, 0, 0)

이렇게 대각선을 먼저 판정하고, 조건을 만족하지 못했을 때만 직선 판정을 진행한다.

if (enoughForDiagonal && diagonalDirection)
    return new Vector3Int(xDirection, yDirection, 0);

if (maxValue < straightStartThreshold)
    return Vector3Int.zero;

if (absX >= absY)
    return new Vector3Int(xDirection, 0, 0);

return new Vector3Int(0, yDirection, 0);

xDirection과 yDirection은 원래 변위의 부호를 보고 각각 -1, 0, 1로 만들어 둔 값이다. 그래서 Resolve()가 반환하는 것은 실제 거리나 목적지 좌표가 아니라 다음 한 칸의 방향이다.

6. 방향만 바꾸고 이동 규칙은 유지

판정 결과가 (1,1,0)이라면 UpdateDrag()에서는 이를 CurrentCell에 더해 다음 후보 Cell을 계산한다.

Vector3Int nextCell = currentCell + direction;
bool changed = pathBuilder.TryEnterCell(nextCell);

이후 DragPathBuilder가 기존 경로로 돌아가는 입력인지, 새 Cell인지 판정한다. 새 Cell이라면 GridMap.CanMove()로 이동 가능 여부도 검사한다.

 

중요한 건 이번 수정에서 실제 이동 규칙까지 변경하지 않았다는 점이다. 대각선이 허용되는 경우에도 벽 모서리를 뚫는 Corner Cutting은 기존 CanMove() 규칙에 따라 막는다.

또한 BFS를 이용하는 그리드 터치 이동은 정상 동작하고 있었으므로 건드리지 않았다. MovementPlan과 PlayerMover 역시 그대로 사용한다.

구분 기존 수정 후
드래그 판단 기준 Pointer가 진입한 Cell 현재 Cell 중심에서 Pointer까지의 변위
경로 연결 방식 GridLineRasterizer 8방향 Resolver + 반복적 TryEnterCell()
대각선 의도 Cell 진입 순서의 영향을 받음 X/Y 크기·비율로 방향을 먼저 판정
BFS 탭 이동 변경 없음 변경 없음
Corner Cutting 금지 금지 유지

7. 결과와 남은 확인 사항

수정 전에는 대각선으로 드래그해도 직교 방향 Cell이 먼저 확정되는 경우가 있었고, 수정 후에는 현재 Cell 중심에서 포인터가 향하는 방향을 기준으로 대각선을 우선 판단하도록 개선했다.

 

다만 0.35, 0.60, 0.65는 현재 사용 중인 테스트값이지 모든 기기에서 최적이라고 검증된 값은 아니다. 마우스와 실제 모바일 터치에서 작은 손가락 흔들림, 빠른 대각선 이동, 인접 장애물 근처 동작을 더 확인할 필요가 있다.

 

이번에 확인한 것은 대각선 입력이 잘 안 된다고 해서 Threshold만 조절할 문제가 아니었다는 점이다. 포인터가 먼저 들어간 Cell로 방향을 추측하는 것과, 현재 Cell을 기준으로 실제 포인터의 방향을 계산하는 것은 달랐다. 이후에는 방향 판정을 DragDirectionResolver가 담당하도록 분리했기 때문에, 드래그 경로 생성 로직과 별개로 민감도를 조정할 수 있게 됐다.

 

관련 글: 2026.09.27 - [Project/Project_P] - [Unity2D / TileMap] 캐릭터 Grid 이동 (Pointer Drag)

반응형