Unity/로봇 체스 개발 일지

[Unity] 로봇 움직임 구현(2) : PlayerMovement 동작 준비 & 시작

suppresswisely 2025. 1. 10. 17:16

요약

플레이어가 움직이기 위해서 동작 준비하는 과정이 필요하다. 이를 위해 움직일 가능성이 있는 공간을 파악하고 이동할 수 있는 공간인지 판단한다. 이때 판단하는 공간이 플레이어 기준 좌표가 되면 안 되므로 월드 좌표로 수정하여 코드를 작성하였다. 판단이 끝나면 실질적으로 이동을 시작한다.


동작 준비

동작 준비 단계에서는 플레이어가 움직일 수 있는 공간을 표시한다. 이를 위해서 파란색 판을 준비하였다.

파란색 판은 내가 이동할 수 있는 공간을 나타내는 역할을 한다. 이를 위해 생성하는 규칙은 다음과 같다.
1. 플레이어의 이동 가능 거리를 통해 원을 만든다.
2. 원안에 들어온 판만 생성할 수 있게 한다.
3. 대각선의 경우 반올림을 통해 판정한다.


코드 작성 전 고려 사항

규칙을 통해 알고리즘을 작성하면 다음과 같다.
1. 플레이어 이동 가능 거리를 통해 지름을 만든다.
2. 지름을 통해 플레이어를 중심으로 하여 지름으로 정사각형 넓이를 구한다.
3. 정사각형의 넓이는 이동이 가능성이 있는 공간이 되므로 이동이 가능한지 확인한다.
4. 이동이 가능하면 파랑색 타일을 놓는다.

여기서 이동이 가능한 공간은 다음과 같다.
1. 맵 안이고 플레이어가 아닌 곳
2. 해당 공간에 벽이나 몬스터가 아닌 곳
3. 플레이어 원안에 존재하는 곳
4. A*을 통해 길을 찾을 수 있는 곳

 

위의 사항을 고려하여 알고리즘을 작성하게 된다면 또 다른 고려 사항이 발생한다.

* 검은색은 월드 좌표 파란색은 플레이어 기준좌표


이동이 가능한 공간을 확인하기 위해서 배열 Map2D를 통해 해당 위치에 무엇이 있는지 조건문을 작성할 것이다. 이때 월드 좌표가 아닌 플레이어 기준 좌표를 사용하게 되면 원치 않은 공간에 무엇이 있는지 확인하게 된다. 이 점을 유의하면서 작성해야 한다.


코드 작성

PlayerMovement.cs

public class PlayerMovement : AStar
{
    ... 생략 ...
    public void MoveReady()
    {
        // 실제 맵이랑 플레이어가 움직이는 임의 탐색 공간의 간격
        Vector3Int interval = new Vector3Int(
            Instance.PlayerPositionInt.x - Instance.player.MoveDistance, 
            Instance.PlayerPositionInt.y - Instance.player.MoveDistance, 
            0
        );

플레이어 기준 좌표를 월드 좌표로 바꾸기 위해 이에 대한 간격을 구한다.

        // 지름 계산
        int diameter = Instance.player.MoveDistance * 2 + 1;

        // 플레이어가 움직이는 공간을 먼저 추측하기 위해 이동거리를 기준으로 함
        for (int i = 0; i < diameter * diameter; i++)
        {
            // 플레이어 기준 좌표
            int playerAreaX = (i / diameter);
            int playerAreaY = (i % diameter);

            // 실제 맵공간 좌표
            int mapAreaX = interval.x + playerAreaX;
            int mapAreaY = interval.y + playerAreaY;

            int distanceX = playerAreaX - Instance.player.MoveDistance;
            int distanceY = playerAreaY - Instance.player.MoveDistance;

플레이어 기준 좌표를 월드 좌표로 변형하였다.

            // 맵안 이고 플레이어가 아닌곳
            if ((mapAreaX > 0 && mapAreaY > 0 && mapAreaX < Instance.MapSizeX && mapAreaY < Instance.MapSizeY) 
                && (playerAreaX != Instance.player.MoveDistance || playerAreaY != Instance.player.MoveDistance))
            {
                // 해당 공간에 벽이나 몬스터가 아닌곳
                if (Instance.Map2D[mapAreaX, mapAreaY] != (int)MapObject.wall && Instance.Map2D[mapAreaX, mapAreaY] != (int)MapObject.moster)
                {
                    // 플레이어가 움직일수 있는 거리 안 즉 원안에 있는지
                    if (Mathf.RoundToInt(Mathf.Sqrt((distanceX * distanceX) + (distanceY * distanceY))) <= Instance.player.MoveDistance)
                    {
                        // 경로 탐색
                        PathFinding(
                            Instance.PlayerPositionInt,
                            new Vector3Int(mapAreaX, mapAreaY, 0),
                            Vector3Int.zero,
                            new Vector3Int(Instance.MapSizeX, Instance.MapSizeY, 0),
                            isWall
                        );
                        // 경로 탐색이 잘 되었는지, 이동 거리가 적절한지
                        if (FinalNodeList.Count > 1 && FinalNodeList.Count <= Instance.player.MoveDistance + 1)
                        {
                            Instance.poolManager.SelectPool(PoolManager.Prefabs.MovePlane).Get().transform.position
                                = new Vector3Int(mapAreaX, mapAreaY, -1);
                        }
                    }
                }
            }
        }
    }
    ... 생략 ...
}

플레이어가 이동할 수 있는공간을 판단하고 파란색 판을 PoolManager을 통해 생성한다.


동작 시작

실질적으로 애니메이션을 사용하여 로봇이 이동하는 코드이다.

PlayerMovement.cs

public class PlayerMovement : AStar
{
    ... 생략 ...
    public bool UpdateMove()
    {
        if (updateMoveStart)
        {
            PathFinding(
                Instance.PlayerPositionInt,
                new Vector3Int(Instance.MyHit.positionInt.x, Instance.MyHit.positionInt.y, 0),
                Vector3Int.zero,
                new Vector3Int(Instance.MapSizeX, Instance.MapSizeY, 0),
                isWall
            );
            // 가끔씩 2번 동작이 들어가는 경우가 있음 이때 오류 방지를 위한 try catch
            try
            {
                targetPosition = new Vector2(FinalNodeList[count].x, FinalNodeList[count].y);
            }
            catch { }
            updateMoveStart = false;
        }

동작을 시작하면 Update함수에서 UpdateMoveStart변수를 통해 1번만 실행한다. 판을 놓기 위해 길을 찾은 값을 저장하기보단 한 번 더 실행하는 것이 효율적이라 판단했다.

 

        float distance = Vector2.Distance(transform.position, targetPosition);
        // 캐릭터가 최종적으로 움직이는 좌표의 거리, 좌표 오차는 여기서 수정
        if (distance > 0.01f)
        {
            RunAnimation(true, targetPosition.x - transform.position.x);
            // 캐릭터를 이동
            transform.position = Vector2.MoveTowards(transform.position, targetPosition, Instance.player.PlayerMoveSpeed * Time.deltaTime);
            return true;
        }
        else
        {
            // 도착하면 FinalNodeList가 남아 있는지 확인
            // count의 시작은 1이고, 시작하자 마자 1이 증가함으로 + 1을 함
            if (count + 1 < FinalNodeList.Count)
            {
                count++;
                targetPosition = new Vector2(FinalNodeList[count].x, FinalNodeList[count].y);
                return true;
            }
            else
            {
                RunAnimation(false);
                count = 1;
                updateMoveStart = true;
                return false;
            }
        }
    }
    ... 생략 ...
}

길을 찾으면 플레이어가 이동하고 애니메이션을 작동시키는 부분이다. RunAnimation()은 오브젝트 좌우 반전 문서를 확인하면 된다.

오브젝트 좌우 반전