2 minute read

작업 내용

A* 알고리즘 적용 방식 변경

어제 계획한대로 LinkedList Path에 저장된 Node를 하나씩 이동할 때마다 이동하기 전에 이동 가능한지 확인하고 이동하는 방식으로 변경하였다.
일단 어제보다는 조금 나은 모습을 보이는 것 같다.
과정 중에 잠깐 기다리게 하는 방법으로 async를 사용하기도 해봤는데 오히려 멀티 스레드에서 이동 가능한지 확인을 하는게 의미가 없는게, 확인을 하고 이동을 해야하다보니 확인 과정이 따로 놀아버리니까 이동을 멋대로 해버리는 문제만 생겼다.
일단 MoveState 클래스는 다음과 같다.

if (pathNode == null)
{
    Debug.Log("이동 할 수 없는 지역입니다");
    userUnit.StateMachine.ChangeState(userUnit.IdleState);
}
else
{
    CheckAndFindPath();
    SetGridValueAtCurrentNode(false);
}

Enter 시에 pathNode가 있는지 확인하는건 같지만 없을 경우 새로운 길을 찾게 했다. 그리고 SetGridValueAtCurrentNode();는 그리드상에 현재 유닛 위치를 이동 가능하게, 불가능하게 설정하는 역할이다.

private void CheckAndFindPath()
{
    // 현재 pathNode가 이동 가능한지 판단
    while (!GridGenerator.Instance.Grid.IsGridAvailable(GridGenerator.Instance.Grid.World2Grid(pathNode.Value)))
    {
        path = GridGenerator.Instance.FindPath(userUnit.transform.position, targetPos);
        pathNode = path.First;

        if (pathNode == null)
        {
            userUnit.StateMachine.ChangeState(userUnit.IdleState);
            return;
        }
    }
}

CheckAndFindPath()는 보이는 것 처럼 현재 pathNode값이 이동 가능할 때 까지 새 길을 찾는 과정이다. 만약 pathNode == null이라면 return하는데, 이건 분명히 목적지를 이동 가능 지역으로 찾으라고 해줬을 텐 이런 일이 발생하면 안되지만 발생할 때가 있어서 해놓았다.
설마 A* 길을 찾는 동안 목적지가 점령당할일은 없을거라 보는게, 분명히 A* 길찾기 함수는 한 프레임에 끝날텐데 그 사이에 다른 누군가가 그 자리를 점령할리가 없을거라 보기 때문이다.
일단 그래서 이도 찾아봐야할 에러 사항이다.

public override void Update()
{
    if (!userUnit.Action.MoveToTarget(pathNode.Value, speed))
    {
        if (pathNode.Next != null)
        {
            SetGridValueAtCurrentNode(true);
            pathNode = pathNode.Next;
            CheckAndFindPath();

            if (pathNode != null)
            {
                SetGridValueAtCurrentNode(false);
            }
            else
            {
                userUnit.StateMachine.ChangeState(userUnit.IdleState);
            }
        }
        else
        {
            userUnit.StateMachine.ChangeState(userUnit.IdleState);
        }
    }
}

MoveState의 Update에서는 pathNode를 바꿀 때 CheckAndFindPath를 불러서 매번 이동 가능한지 확인을 한다.

public override void Exit()
{
    if (pathNode != null)
    {
        if ((Vector2)userUnit.transform.position != pathNode.Value)
        {
            SetGridValueAtCurrentNode(true);
            userUnit.transform.position = pathNode.Value;
            SetGridValueAtCurrentNode(false);
        }
    }
}

Exit 시에는 현재 위치가 이동하다 말아서 다음 위치에 도달 못했을 경우 그 자리가 이미 점령 당해있을 것이기에 거기에 맞춰서 위치를 바꿔주고 이전 위치는 이동 가능하게 풀어주고 다음 자리를 이동 불가로 해준다.
그리고 특히 문제는 TrackingState인데 Update에서 하는 작업이 많다 보니 에러가 자주 발생한다.

public override void Update()
{
    if (IsTargetEnemyInvalid())
    {
        userUnit.StateMachine.ChangeState(userUnit.IdleState);
        return;
    }

    if (IsTargetEnemyOutOfRange())
    {
        if (!isMoving)
        {
            SetNewTargetPosition();
            path = GridGenerator.Instance.FindPath(userUnit.transform.position, targetPosition);
            pathNode = path.First;
            userUnit.Action.TargetPosition = targetPosition;

            if (pathNode == null)
            {
                Debug.Log("이동 할 수 없는 지역입니다");
                userUnit.StateMachine.ChangeState(userUnit.IdleState);
                return;
            }

            CheckAndFindPath();
            SetGridValueAtCurrentNode(false);
            isMoving = true;
        }

        MoveAlongPath();
    }
    else
    {
        isMoving = false;
    }
}
private void MoveAlongPath()
{
    if (!userUnit.Action.MoveToTarget(pathNode.Value, speed))
    {
        if (pathNode.Next != null)
        {
            SetGridValueAtCurrentNode(true);
            pathNode = pathNode.Next;
            CheckAndFindPath();
            if (pathNode != null)
            {
                SetGridValueAtCurrentNode(false);
            }
            else
            {
                isMoving = false;
            }
        }
        else
        {
            isMoving = false;
        }
    }
}

이전이랑 비슷한데, 새 길을 찾을 때랑 이동 할 때 MoveState처럼 이동 가능여부를 확인하고 이동한다.
문제는 이게 의도와 다르게 지나온 자리에 그리드 값을 false인채로 남겨두는 경우들이 있어서 이를 수정해줘야한다.
MovingAttackState 클래스는 MoveState와 비슷하다.

public override void Update()
{
    if (userUnit.Action.TargetEnemy == null || userUnit.Action.TargetEnemy.Dead)
    {
        if (!userUnit.Action.MoveToTarget(pathNode.Value, speed))
        {
            if (pathNode.Next != null)
            {
                SetGridValueAtCurrentNode(true);
                pathNode = pathNode.Next;
                CheckAndFindPath();

                if (pathNode != null)
                {
                    SetGridValueAtCurrentNode(false);
                }
                else
                {
                    userUnit.StateMachine.ChangeState(userUnit.IdleState);
                }
            }
            else
            {
                userUnit.StateMachine.ChangeState(userUnit.IdleState);
            }
        }
    }
}

단지 적이 없을 때만 이동한다는 차이다.

마무리

코드 자체는 변한게 많지 않아보여도 생각보다 이것저것 많이 고치다가 결과로 나온 코드가 지금 코드다.
어디서 그리드를 설정해주고, 어디서 해제해주는지 다시 잘 살펴보면서 버그들을 해결해야할 것 같다.
그래도 안되면 어쩌면 내가 구조를 잘못 짠 것 일지도 모르겠다.