스크립팅하는애님 2024. 5. 25. 22:33

이 페이지는 다양한 크기의 에이전트 유형을 처리하는 방법을 설명합니다.

다양한 크기의 에이전트를 가지고 있을 때, 이들은 보통 동일한 경로를 따라 목표 지점으로 이동할 수 없습니다. 다행히도 이 문제를 해결하는 것은 쉽습니다. 가장 쉬운 방법은 각 에이전트 유형에 대해 여러 개의 그래프를 만드는 것입니다. 다양한 유형의 에이전트가 많거나 연속적인 스펙트럼이 있는 경우, 그래프의 수가 많아지면 메모리 사용량이 증가하고 스캔 시간이 길어지므로 이를 그룹화하는 것이 좋습니다.  Seeker component에서는 Seeker가 사용할 수 있는 그래프를 설정할 수 있습니다.

참고
Seeker.graphMask
NNConstraint.graphMask

그리드 그래프의 경우 사용할 수 있는 추가적인 두 가지 방법이 있으며, 이는 그리드 그래프에 대한 다른 접근 방식 섹션에서 설명됩니다.

예시

다음과 같은 두 에이전트가 있다고 가정해 보겠습니다:


 AstarPath  인스펙터에서 우리는 두 개의 다른 그래프를 만들 수 있으며, 유일한 차이는 사용된 캐릭터 반경입니다(다른 매개변수도 필요에 따라 변경할 수 있습니다). 이 예에서는 리캐스트 그래프가 사용되었지만, 다른 그래프 유형으로도 쉽게 할 수 있습니다.


이 그래프들을 스캔할 때, 우리는 작은 에이전트를 위한 그래프는 파란색으로, 큰 에이전트를 위한 그래프는 보라색으로 된 결과를 얻게 됩니다.


그런 다음 Seeker 컴포넌트에서 각 에이전트가 사용할 그래프를 설정할 수 있습니다:

이제 두 에이전트 모두 자신들의 크기에 적합한 그래프를 사용하게 됩니다:

 

그리드 그래프에 대한 다른 접근 방식

그리드 그래프에서 대체 방법으로는 그래프가 가장 가까운 장애물까지의 거리와 상관없이 서로 다른 tags 를 생성하게 하는 방법이 있습니다. 이는 단 하나의 그래프만 필요로 합니다.

Pathfinding.GridGraph.erosionUseTags  참조

또한 매우 유연하지만 불행히도 계산 비용이 더 높은 다른 접근 방식도 있습니다. 경로 요청 시  ITraversalProvider 객체를 제공할 수 있는데 (이 튜토리얼도 참조:  ITraversalProvider ), 이를 통해 코드에서 정확히 어떤 노드를 통과할 수 없게 할지를 결정할 수 있습니다. 이를 사용하면 현재 노드가 통과 가능한지 확인하는 대신, 예를 들어 3x3 또는 5x5 정사각형 내의 모든 노드를 확인하는 사용자 정의 코드를 추가할 수 있습니다 (기본값은 1x1 정사각형과 동일).

아래 이미지에서는 3x3 정사각형을 사용한 예를 볼 수 있습니다. 장애물 바로 옆의 노드들이 걸을 수 있음에도 불구하고 통과하지 않으려는 것을 확인할 수 있습니다. 이는 3x3 정사각형 내의 노드들을 확인하기 때문입니다.

ITraversalProvider 접근 방식의 주요 장점은 노드를 통과할 수 없게 만드는 모든 요소들을 처리할 수 있다는 것입니다. 예를 들어, 에이전트의 움직임을 제한하기 위해 다른 태그를 사용할 수 있지만, 여러 그래프를 사용하더라도 다양한 에이전트가 태그 때문에 통과할 수 없는 영역까지 걸어갈 수 있습니다. 이 접근 방식을 사용하면, 태그가 있는 노드들이 통과할 수 없는 것으로 감지되기 때문에 에이전트의 크기가 매우 큰 경우에도 근처로 이동할 수 없습니다. 에이전트 주위의 NxN 노드들은 모두 통과 가능해야 합니다.

ITraversalProvider는 다음과 같이 구현할 수 있습니다:

class GridShapeTraversalProvider : ITraversalProvider {
    Int2[] shape;

    public static GridShapeTraversalProvider SquareShape (int width) {
        if ((width % 2) != 1) throw new System.ArgumentException("only odd widths are supported");
        var shape = new GridShapeTraversalProvider();
        shape.shape = new Int2[width*width];

        // 정수 좌표를 포함하는 배열을 생성합니다.
        int i = 0;
        for (int x = -width/2; x <= width/2; x++) {
            for (int z = -width/2; z <= width/2; z++) {
                shape.shape[i] = new Int2(x, z);
                i++;
            }
        }
        return shape;
    }

    public bool CanTraverse (Path path, GraphNode node) {
        GridNodeBase gridNode = node as GridNodeBase;

        // 그리드 노드가 아닌 경우 특별히 처리하지 않습니다.
        if (gridNode == null) return DefaultITraversalProvider.CanTraverse(path, node);
        int x0 = gridNode.XCoordinateInGrid;
        int z0 = gridNode.ZCoordinateInGrid;
        var grid = gridNode.Graph as GridGraph;

        // 현재 노드 주변의 모든 노드를 순회하며 해당 노드들이 모두 통과 가능한지 확인합니다.
        for (int i = 0; i < shape.Length; i++) {
            var inShapeNode = grid.GetNode(x0 + shape[i].x, z0 + shape[i].y);
            if (inShapeNode == null || !DefaultITraversalProvider.CanTraverse(path, inShapeNode)) return false;
        }
        return true;
    }

    public bool CanTraverse (Path path, GraphNode from, GraphNode to) {
        return CanTraverse(path, to);
    }

    public uint GetTraversalCost (Path path, GraphNode node) {
        // 기본 통과 비용을 사용합니다.
        // 선택적으로, 예를 들어, 도형 내부의 비용 평균을 계산하는 등으로 수정할 수 있습니다.
        return DefaultITraversalProvider.GetTraversalCost(path, node);
    }

    // Unity 2021.3 및 이후 버전에서는 기본 구현(참 값을 반환)이 사용되기 때문에 이 부분을 생략할 수 있습니다.
    public bool filterDiagonalGridConnections {
        get {
            return true;
        }
    }
}
주의
이 구현은 계층형 그리드 그래프가 아닌 그리드 그래프에서만 작동합니다.

 

그리고 다음과 같이 사용할 수 있습니다.

ABPath path = ABPath.Construct(currentPosition, destination, null);

path.traversalProvider = GridShapeTraversalProvider.SquareShape(3);

// 직접 이동 스크립트를 작성하는 경우
seeker.StartPath(path);

// 기존 이동 스크립트를 사용하는 경우 (ai.canSearch를 false로 설정할 수도 있음)

주의
두 가지 그리드 특정 접근 방식의 단점은 도달할 수 없는 목적지로 경로를 요청하려고 할 경우 경로가 완전히 실패하고, AI가 도달할 수 있는 가장 가까운 지점으로 이동하지 않는다는 점입니다. 경로를 계산하기 전에  calculatePartial 필드를 true로 설정하여 일부 경우에 이를 해결할 수 있습니다:

path.calculatePartial = true;

이렇게 하면 목표 노드가 ITraversalProvider에 따라 통과 가능한 노드인 경우, 도달할 수 있는 가장 가까운 노드로 이동하게 됩니다.