A* Pathfinding Project의 모든 그래프는 시스템에 추가 기능으로 작성됩니다. 이를 통해 자신만의 특수화된 그래프 유형을 추가하는 것이 (상대적으로) 쉽습니다.

 

이 튜토리얼에서는 시스템에서 사용할 수 있는 기본 그래프 생성기를 설정하는 방법을 보여 드리겠습니다. 전체 스크립트는 여기에서 확인할 수 있습니다: PolarGraphGenerator.cs

 

A Basic Graph

가장 간단한 그래프는 다음과 같이 생겼습니다:

using System.Collections.Generic;
using UnityEngine;
using Pathfinding;
using Pathfinding.Serialization;
using Pathfinding.Util;
using Unity.Jobs;

// 기본 그래프 유형에서 새로운 그래프를 상속받습니다
[JsonOptIn]
// 코드 스트리핑을 사용할 때 클래스가 제거되지 않도록 합니다 (https://docs.unity3d.com/Manual/ManagedCodeStripping.html 참조)
[Pathfinding.Util.Preserve]
public class SimpleGraph : NavGraph {
    // 그래프가 스캔되었고 경로 찾기에 사용될 수 있으면 true를 반환해야 합니다
    public override bool isScanned => true;

    class SimpleGraphScanPromise : IGraphUpdatePromise {
        public SimpleGraph graph;

        // 이 메서드에서는 그래프를 업데이트하는 데 필요한 비동기 계산을 실행할 수 있습니다.
        // 이 코루틴이 완료된 후 Apply 메서드가 호출됩니다.
        // 이 메서드에서 생성된 모든 JobHandle은 다음 반복 및 Apply 메서드 호출 전에 대기합니다.
        public IEnumerator<JobHandle> Prepare() => null;

        public void Apply (IGraphUpdateContext ctx) {
            // 여기서 그래프를 스캔하는 코드를 작성합니다.

            // 이전 노드를 제거합니다(있는 경우).
            graph.DestroyAllNodes();
        }
    }

    protected override IGraphUpdatePromise ScanInternal () => new SimpleGraphScanPromise { graph = this };

    public override void GetNodes (System.Action<GraphNode> action) {
        // 이 메서드는 그래프의 모든 노드에 대해 대리자를 호출해야 합니다.
    }
}

이 그래프는 노드를 생성하지 않지만, 새 그래프 추가 목록에서 회색으로 표시되어야 합니다. 이제 스캔 로직을 생성해 봅시다!

 

Scanning

 먼저, 위의 코드를 포함하는 새 스크립트를 생성하고 이름을 PolarGraph로 변경하고 PolarGraph.cs로 저장합니다.

ScanInternal 메서드는 그래프를 스캔해야 할 때 호출됩니다. Scan 메서드는 여러 노드를 생성하고 노드 간의 연결을 생성해야 합니다. 우리는 극좌표 그래프를 생성할 것입니다. 즉, 행 대신 원이 있는 원형으로 배열된 그리드와 같은 것입니다. "circles"는 그래프에서 동심원의 수를 나타내고 "steps"는 각 원에서 노드의 수를 나타냅니다. 아래 이미지에서 노드 간의 연결을 볼 수 있습니다. 노드 자체는 해당 선분의 교차점에 배치됩니다.

 

첫 번째 단계는 노드를 저장할 임시 배열을 생성하고 그래프를 구성할 변수를 추가하는 것입니다. PointGraph에서 사용되는 PointNode 노드 유형을 사용할 것입니다. 이는 우리가 사용하려는 것과 본질적으로 동일한 내용을 포함하기 때문입니다.

public int circles = 10;
public int steps = 20;

public Vector3 center = Vector3.zero;
public float scale = 2;

// 여기서 그래프의 모든 노드를 저장할 것입니다.
PointNode[] nodes;

GraphTransform transform;

// 지정된 위치에 단일 노드를 생성합니다.
PointNode CreateNode (Vector3 position) {
    var node = new PointNode(active);
    // 노드 위치는 Int3로 저장됩니다. Vector3를 Int3로 변환할 수 있습니다.
    node.position = (Int3)position;
    return node;
}

public override IEnumerable<Progress> ScanInternal () {
    // 모든 노드를 포함하는 2D 배열을 생성합니다.
    // 이는 서로 다른 노드를 참조하기 쉽게 하기 위한 임시 배열입니다.
    PointNode[][] circleNodes = new PointNode[circles][];
    yield break;
}

 

Editor

그래프가 새 그래프 추가 목록에 아직 표시되지 않는 이유는 그래프 에디터가 필요하기 때문입니다. 이제 이를 생성하겠습니다. AstarPathfindingProject/Editor/GraphEditors/(또는 Js 지원을 활성화한 경우 AstarPathfindingEditor/Editor/GraphEditors/) 또는 다른 Editor 폴더에 새 스크립트를 만듭니다. 스크립트 이름을 PolarGeneratorEditor.cs로 지정합니다.

아래는 극좌표 그래프를 위한 매우 간단한 에디터입니다. 이 코드를 방금 만든 파일에 복사하세요. 이렇게 하면 그래프를 생성할 수 있어야 합니다.

using UnityEditor;
using Pathfinding;

[CustomGraphEditor(typeof(PolarGraph), "Polar Graph")]
public class PolarGeneratorEditor : GraphEditor {
    // GUI는 여기에 작성합니다.
    public override void OnInspectorGUI (NavGraph target) {
        var graph = target as PolarGraph;

        graph.circles = EditorGUILayout.IntField("Circles", graph.circles);
        graph.steps = EditorGUILayout.IntField("Steps", graph.steps);
        graph.scale = EditorGUILayout.FloatField("Scale", graph.scale);
        graph.center = EditorGUILayout.Vector3Field("Center", graph.center);
    }
}

CustomGraphInspector 속성은 이 클래스가 PolarGraph 유형의 그래프에 대한 사용자 정의 에디터임을 시스템에 알리며, 그래프 목록에 표시될 이름은 "Polar Graph"가 됩니다.

시도해 보세요!  AstarPath 인스펙터의 Graphs 탭을 열고, Add Graph를 클릭하여 이제 목록에 있어야 하는 Polar Graph를 추가합니다.

 

Adding nodes

 이제 실제로 그래프를 생성하는 작업을 시작해야 합니다. 이 지점 이후의 모든 코드 조각은 Scan 함수에 들어가야 합니다. 노드의 회전, 위치 지정 등을 가능하게 하기 위해 행렬을 사용할 것입니다. 배열의 첫 번째 노드는 Vector3.zero에 배치할 중앙 노드여야 합니다. 노드 위치는 Int3로 저장됩니다. 이는 Vector3와 유사하지만 부동 소수점 좌표 대신 정수 좌표를 사용합니다. Vector3와 Int3 간에는 명시적 캐스트가 가능합니다.

// 이전 노드 제거(있는 경우)
graph.DestroyAllNodes();

var circles = graph.circles;
var steps = graph.steps;

// 모든 노드를 포함하는 2D 배열 생성
// 이는 서로 다른 노드를 참조하기 쉽게 하기 위한 임시 배열입니다.
PointNode[][] circleNodes = new PointNode[circles][];

// 노드를 #center로 이동시키고
// 위치를 #scale로 스케일링하는 행렬 생성
// GraphTransform 클래스에는 이를 처리하기 위한 다양한 유틸리티 메서드가 있습니다.
graph.transform = new GraphTransform(Matrix4x4.TRS(graph.center, Quaternion.identity, Vector3.one * graph.scale));

// 중심에 중앙 노드 배치
circleNodes[0] = new PointNode[] {
    graph.CreateNode(CalculateNodePosition(0, 0, graph.transform))
};

이제 주어진 각도와 원에서 노드의 위치를 계산해야 합니다.

static Vector3 CalculateNodePosition (int circle, float angle, GraphTransform transform) {
    // 중심에서 노드 방향을 가져옵니다.
    var pos = new Vector3(Mathf.Sin(angle), 0, Mathf.Cos(angle));

    // 원 번호로 곱하여 그래프 공간에서 노드 위치를 가져옵니다.
    pos *= circle;

    // 행렬로 곱하여 월드 공간에서 노드 위치를 가져옵니다.
    pos = transform.Transform(pos);
    return pos;
}

이제 그래프의 나머지 부분을 설정합니다. 원별로, 노드별로 설정합니다.

// 각 스텝이 사용할 각도 크기(라디안)
float anglesPerStep = (2 * Mathf.PI) / steps;

for (int circle = 1; circle < circles; circle++) {
    circleNodes[circle] = new PointNode[steps];
    for (int step = 0; step < steps; step++) {
        // 중심에 대한 노드의 각도를 가져옵니다.
        float angle = step * anglesPerStep;

        Vector3 pos = CalculateNodePosition(circle, angle, graph.transform);
        circleNodes[circle][step] = graph.CreateNode(pos);
    }
}

이제 모든 노드를 올바른 위치에 설정했지만, 다른 노드와의 연결이 없습니다. 이를 추가해 봅시다:

// 모든 원을 반복합니다.
// 원 0은 중앙 노드뿐이므로 지금은 건너뜁니다.
for (int circle = 1; circle < circles; circle++) {
    for (int step = 0; step < steps; step++) {
        // 현재 노드를 가져옵니다.
        PointNode node = circleNodes[circle][step];

        // 여기 있는 노드는 항상 정확히 네 개의 연결을 가집니다. 그리드와 유사하지만 극좌표입니다.
        // 마지막 원에 있는 노드는 세 개의 연결만 가집니다.
        int numConnections = circle < circles - 1 ? 4 : 3;
        var connections = new Connection[numConnections];

        // 현재 원에서 시계 방향으로 다음 노드를 가져옵니다.
        // 각 원의 마지막 노드는 원의 첫 번째 노드에 연결되어야 하므로 모듈로 연산자를 사용합니다.
        connections[0].node = circleNodes[circle][(step + 1) % steps];

        // 반시계 방향 노드. 여기서는 언더플로우를 확인합니다.
        connections[1].node = circleNodes[circle][(step - 1 + steps) % steps];

        // 이전 원의 노드(중심 방향)
        if (circle > 1) {
            connections[2].node = circleNodes[circle - 1][step];
        } else {
            // 중앙 노드로 연결 생성, 특별한 경우
            connections[2].node = circleNodes[circle - 1][0];
        }

        // 이 외부에 다른 원이 있는지 확인합니다.
        if (numConnections == 4) {
            // 다음 원의 노드(중심에서 밖으로)
            connections[3].node = circleNodes[circle + 1][step];
        }

        for (int q = 0; q < connections.Length; q++) {
            // Node.position은 Int3입니다. 여기서는 두 위치 사이를 이동하는 비용을 가져옵니다.
            connections[q].cost = (uint)(node.position - connections[q].node.position).costMagnitude;
        }

        node.connections = connections;
    }
}

첫 번째 노드(중앙)는 특별한 경우로, 첫 번째 원(또는 두 번째, 어떻게 보느냐에 따라 다름)의 모든 노드와 연결됩니다. 즉, 첫 번째 원의 노드는 steps개의 연결을 갖게 됩니다.

// 중앙 노드는 특별한 경우이므로 따로 처리해야 합니다.
PointNode centerNode = circleNodes[0][0];
centerNode.connections = new Connection[steps];

// 첫 번째 원의 모든 노드를 중앙 노드의 연결로 지정합니다.
for (int step = 0; step < steps; step++) {
    centerNode.connections[step] = new Connection(
        circleNodes[1][step],
        // centerNode.position은 Int3입니다. 여기서는 두 위치 사이를 이동하는 비용을 가져옵니다.
        (uint)(centerNode.position - circleNodes[1][step].position).costMagnitude,
        isOutgoing: true,
        isIncoming: true
    );
}

이제 남은 것은 노드를 이동 가능하게 만드는 것입니다. 기본값은 이동 불가능입니다. 이 튜토리얼에서는 장애물 검사 등을 다루지 않지만, Unity의 Physics 클래스를 공부하면 이를 구현할 수 있습니다.

// 모든 노드를 nodes 배열에 저장합니다.
List<PointNode> allNodes = new List<PointNode>();
for (int i = 0; i < circleNodes.Length; i++) {
    allNodes.AddRange(circleNodes[i]);
}
graph.nodes = allNodes.ToArray();

// 모든 노드를 이동 가능하게 설정합니다.
for (int i = 0; i < graph.nodes.Length; i++) {
    graph.nodes[i].Walkable = true;
}

이전 코드 조각을 Scan 함수에 차례로 배치했다면, 이제 작동하는 스캔 함수를 가지게 됩니다.

마지막으로 그래프를 스캔할 수 있도록 GetNodes 메서드를 구현해야 합니다. 모든 노드가 배열에 저장되어 있으므로 매우 간단합니다.

public override void GetNodes (System.Action<GraphNode> action) {
    if (nodes == null) return;

    for (int i = 0; i < nodes.Length; i++) {
        // 대리자 호출
        action(nodes[i]);
    }
}

이제 그래프를 생성하고, 매개변수를 편집하고, Scan을 클릭하면 그래프가 장면 뷰에 나타납니다.

멋지죠?

 

하지만 인스펙터를 선택 해제한 다음 다시 선택하면 설정이 저장되지 않았음을 알 수 있습니다. 이는 마지막으로 직렬화를 추가해야 하기 때문입니다. 모든 그래프 설정은 JSON으로 직렬화됩니다(http://www.json.org/ 참조). 필드를 직렬화하려면 각 필드에 속성을 추가해야 합니다. JsonMember 속성은 해당 필드를 직렬화하려고 한다는 것을 직렬화 도구에 알립니다.

전체 스크립트는 여기에서 확인할 수 있습니다: PolarGraphGenerator.cs

 

More Stuff

다른 기능을 사용자 정의하려면 더 많은 메서드를 재정의할 수 있습니다. 특히 GetNearest 및 GetNearestForce 메서드입니다. 이는 지점에서 가장 가까운 노드를 찾는 방법을 제어합니다. 기본 구현이 있지만, 그래프에 많은 노드가 포함된 경우 검색 속도가 느릴 수 있습니다. OnDrawGizmos 메서드를 재정의하여 그래프를 기본 구현과 다르게 그릴 수도 있습니다.

더 많은 정보를 직렬화해야 하는 경우, SerializeExtraInfo, DeserializeExtraInfo를 재정의하고, 노드를 로드한 후 설정해야 하는 경우 PostDeserialization 함수를 재정의할 수 있습니다.

모든 기능을 설명하는 것은 이 튜토리얼의 범위를 벗어나므로 다른 그래프 유형이 이를 구현한 방법을 확인할 수 있습니다.

The End

이제 이 그래프를 다른 그래프처럼 사용할 수 있어야 합니다! 이 튜토리얼이 그래프 생성기를 작성하는 데 도움이 되었기를 바랍니다.

'유니티 에셋 > A* Pathfinding project pro' 카테고리의 다른 글

Editing graph connections manually  (0) 2024.05.28
Extending The System > Writing Modifiers  (0) 2024.05.28
Extending The System  (0) 2024.05.28
Creating graphs during runtime  (0) 2024.05.28
Off-mesh links  (0) 2024.05.28

시스템은 다양한 방식으로 확장할 수 있습니다.

자세한 내용은 하위 페이지를 확인하세요.

스크립트에서 런타임 중 그래프를 생성하는 방법

어떤 이유로 유니티 에디터에서 디자인 시간에 그래프를 생성하고 설정할 수 없는 경우, 스크립팅을 통해 직접 그래프를 생성할 수 있습니다.

참고 그래프 설정 및 완전히 스캔된 그래프를 파일에서 저장하고 로드할 수 있습니다. 자세한 내용은  Saving and Loading Graphs 를 참조하십시오. 일반적으로 유니티 에디터에서 대부분의 설정을 정의한 다음, 게임 시작 시 스크립트를 사용하여 몇 가지 값만 조정하고 그래프를 다시 스캔할 수 있습니다.

 

다음과 같이 그래프를 생성할 수 있습니다:

// 모든 그래프 데이터를 보관합니다.
AstarData data = AstarPath.active.data;

// 그리드 그래프를 생성합니다.
GridGraph gg = data.AddGraph(typeof(GridGraph)) as GridGraph;

그래프 객체는 그래프 에디터에서 노출된 모든 설정을 가지고 있으므로 원하는 값으로 설정한 후 그래프를 스캔할 수 있습니다.

// 모든 그래프 데이터를 보관합니다.
AstarData data = AstarPath.active.data;

// 그리드 그래프를 생성합니다.
GridGraph gg = data.AddGraph(typeof(GridGraph)) as GridGraph;

// 몇 가지 값으로 그리드 그래프 설정
int width = 50;
int depth = 50;
float nodeSize = 1;

gg.center = new Vector3(10, 0, 0);

// 위 값으로 내부 크기 업데이트
gg.SetDimensions(width, depth, nodeSize);

// 모든 그래프 스캔
AstarPath.active.Scan();

완전히 사용자 정의된 그래프를 사용자 노드와 연결로 생성하려면 다음과 같이 포인트 그래프를 사용할 수 있습니다.

// 모든 그래프 데이터를 보관합니다.
AstarData data = AstarPath.active.data;

// 포인트 그래프를 생성합니다.
PointGraph graph = data.AddGraph(typeof(PointGraph)) as PointGraph;

AstarPath.active.Scan(graph);

// 모든 경로 찾기 스레드가 일시 중지된 상태에서만 그래프를 수정해야 합니다.
AstarPath.active.AddWorkItem(new AstarWorkItem(ctx => {
    // 노드 2개를 추가하고 연결
    var node1 = graph.AddNode((Int3) new Vector3(1, 2, 3));
    var node2 = graph.AddNode((Int3) new Vector3(4, 5, 6));
    var cost = (uint)(node2.position - node1.position).costMagnitude;
    GraphNode.Connect(node1, node2, cost);
}));

// 위의 작업 항목을 즉시 실행
AstarPath.active.FlushWorkItems();

또는 사용자 정의 그래프 유형을 생성할 수 있으며, 이에 대한 자세한 내용은 그래프 생성기 작성(Writing Graph Generators)을 참조하십시오.

Graph Types   참조

 

'유니티 에셋 > A* Pathfinding project pro' 카테고리의 다른 글

Extending The System > Writing Graph Generators  (0) 2024.05.28
Extending The System  (0) 2024.05.28
Off-mesh links  (0) 2024.05.28
Moving a graph at runtime  (0) 2024.05.28
Large worlds  (0) 2024.05.27

오프-메시 링크를 사용하여 점프, 등반, 텔레포터 및 기타 특수 이동 로직을 구현하는 방법에 대한 튜토리얼

Contents

 

오프-메시 링크는 단일 그래프 내 또는 다른 그래프 간의 노드 간 사용자 정의 연결입니다. 이를 사용하여 점프, 등반, 문 열기, 텔레포터 및 기타 특수 이동 로직을 구현할 수 있습니다.

 

에이전트가 오프-메시 링크에 도달하면 일반 이동 코드를 중지하고 사용자 정의 함수를 호출합니다. 이 함수는 애니메이션 재생, 에이전트를 다른 위치로 이동 등 특수 이동 로직을 구현할 수 있습니다.

포함된 이동 스크립트는 오프-메시 링크에 대한 다양한 수준의 지원을 제공합니다.

  • FollowerEntity : 오프-메시 링크에 대한 좋은 지원.
  • RichAI : 오프-메시 링크에 대한 괜찮은 지원. Interactable 컴포넌트를 사용한 이동 로직은 지원하지 않음.
  • AIPath : 오프-메시 링크에 대한 제한된 지원. 에이전트는 이를 통과할 수 있지만, 오프-메시 링크를 통과한다는 것을 인식하지 못하고 특수 이동 로직을 사용할 수 없음.
  • AILerp : 오프-메시 링크에 대한 제한된 지원. 에이전트는 이를 통과할 수 있지만, 오프-메시 링크를 통과한다는 것을 인식하지 못하고 특수 이동 로직을 사용할 수 없음.

오프-메시 링크는 NodeLink2 컴포넌트를 사용하여 생성됩니다.

 


End: 

링크의 종료 위치.

참조: 이 설정은  Pathfinding.NodeLink2.end 멤버에 해당합니다.

 


Cost Factor: 

이동의 난이도/속도 배수.

비용 계수가 1이면 링크를 통과하는 비용이 동일한 거리의 일반 내비메시를 이동하는 것과 동일합니다. 하지만 비용 계수가 1보다 크면 비례적으로 더 비쌉니다.

비용 계수가 1보다 작은 값을 사용하지 않아야 합니다. 예외로, AstarPath.heuristicScale 필드(A* Inspector -> Settings -> Pathfinding)를 장면 내 어디서든 사용하는 최소 비용 계수만큼으로 설정하거나 휴리스틱을 완전히 비활성화해야 합니다. 이는 경로 찾기 알고리즘이 경로가 최소한 목표까지의 직선 거리만큼 비용이 든다고 가정하기 때문입니다. 비용 계수가 1보다 작으면 이 가정이 더 이상 성립하지 않습니다. 그 결과, 경로 찾기 검색이 일부 링크를 무시할 수 있습니다. 왜냐하면 해당 방향을 검색할 필요가 없다고 생각할 수 있기 때문입니다. 비록 그 링크가 더 낮은 경로 비용을 유도할 수 있더라도 말입니다.

경고
휴리스틱 스케일을 줄이거나 휴리스틱을 비활성화하면 특히 큰 그래프에서 경로 찾기의 CPU 비용이 크게 증가할 수 있습니다.

자세한 내용은 [Admissible heuristic](https://en.wikipedia.org/wiki/Admissible_heuristic)을 참조하세요.

참조: 이 설정은  Pathfinding.NodeLink2.costFactor 멤버에 해당합니다.



 

One Way:

단방향 연결 설정.

참조: 이 설정은 Pathfinding.NodeLink2.oneWay 멤버에 해당합니다.

 

 


Pathfinding Tag: 

링크에 적용할 태그.

이를 사용하여 특정 에이전트가 링크를 사용하지 못하게 하거나, 사용 비용을 더 비싸게 만들 수 있습니다.

참조:
Working with tags
이 설정은 Pathfinding.NodeLink2.pathfindingTag 멤버에 해당합니다.



Graph Mask: 

이 링크가 연결할 수 있는 그래프.

링크는 항상 연결이 허용된 그래프에서 시작점과 끝점에 가장 가까운 노드를 연결합니다.

참조: 이 설정은 Pathfinding.NodeLink2.graphMask 멤버에 해당합니다.



NodeLink2 는 자신의 위치와 대상 변환의 위치 사이에 링크를 생성합니다. 기본적으로 이 링크는 양방향이므로 에이전트는 양쪽 방향으로 이를 통과할 수 있습니다. 그러나 단방향으로 설정할 수도 있으며, 이는 절벽에서 아래로 점프하는 것과 같은 상황에 유용합니다.

링크에 태그를 적용할 수도 있습니다. 이를 통해 특정 에이전트만 통과할 수 있도록 하거나, 특정 에이전트가 통과하는 데 더 많은 비용이 들도록 만들 수 있습니다. 비용 계수는 에이전트가 링크를 통과하는 대신 다른 경로를 선호하도록 만들 수 있습니다. 비용 계수가 1이면 링크를 통과하는 비용이 동일한 거리의 일반 내비메시를 이동하는 것과 동일합니다. 그러나 1보다 큰 비용 계수는 비례적으로 더 비쌉니다.

1보다 낮은 비용 계수를 사용하려면(예: 텔레포터) 추가 구성이 필요합니다. 자세한 내용은  NodeLink2.costFactor 를 참조하세요.

다음 섹션에서는 오프-메시 링크의 일반적인 사용 사례를 구현하는 방법을 설명합니다.

 

 

캐릭터가 절벽에서 점프할 수 있어야 하는 경우, 절벽에서 아래로 이어지는 단방향 오프-메시 링크를 추가할 수 있습니다. 이를 통해 캐릭터는 링크를 통과할 수 있지만 반대 방향으로는 통과할 수 없습니다.


기본 오프-메시 링크 이동 로직은 일반적으로 이 경우에 잘 작동합니다. 에이전트를 링크 끝으로 이동시키면서 내비메시를 무시하기 때문입니다. 그러나 애니메이션을 재생하거나 캐릭터가 더 현실적으로 점프하도록 만들기 위해 맞춤 로직을 추가할 수 있습니다.

 

텔레포터도 오프-메시 링크를 사용하여 구현할 수 있습니다. 그러나 이는 에이전트를 새 위치로 이동시키는 맞춤 로직이 필요합니다. 여러 가지 방법이 있지만, 가장 간단한 방법은 예제 장면에 포함된  Interactable 컴포넌트를 사용하는 것입니다. Interactable 컴포넌트를 NodeLink2 컴포넌트가 있는 같은 GameObject에 부착하면 에이전트가 링크를 통과할 때 워크플로우가 트리거됩니다.


이 워크플로우는 에이전트를 링크 끝으로 이동시키고, 몇 가지 파티클 시스템을 트리거합니다.

그러나 에이전트가 텔레포트를 거리 비례 비용 대신 제로 비용 동작으로 처리하도록 하려면 몇 가지 작업을 수행해야 합니다. 먼저 링크 컴포넌트의 비용 계수를 0으로 설정해야 합니다.


둘째, A* Inspector -> Settings -> Pathfinding -> Heuristic 필드를 None으로 설정해야 합니다. 그렇지 않으면 경로 찾기 검색이 해당 방향으로 이동하는 것을 고려하지 않아 에이전트가 텔레포터 주변을 걸어갈 수 있습니다. 자세한 내용은 NodeLink2.costFactor 문서를 참조하세요.

 

에이전트가 문을 열 수 있도록 처리하는 여러 가지 방법이 있습니다.

  • 문이 자동인 경우 그래프 스캔에서 제외하고, 에이전트가 근처에 있는지에 따라 열림/닫힘 애니메이션을 재생하는 별도의 스크립트를 사용하는 것이 좋은 해결책입니다. 이 경우 오프-메시 링크를 사용하지 않아야 합니다.
  • 문이 처음에는 활성화가 필요하지만, 한 번 열리면 열려 있는 상태로 유지되는 경우 아래와 같이  NodeLink2 컴포넌트를 사용할 수 있습니다.

문 한쪽에서 다른 쪽으로 가는 NodeLink2 컴포넌트를 생성합니다. 링크에서 문 열림 애니메이션을 트리거하기 위해 Interactable 컴포넌트를 사용할 것입니다.

 

Interactable 컴포넌트는 에이전트가 몇 가지 작업을 수행하도록 만듭니다:

  1. 문 열림 애니메이션 시작(내비메시를 통과 가능하게 만듦, 자세한 내용은 Doors 참조).
  2. 에이전트를 링크 시작점으로 이동시키고 문을 향하도록 만듦(에이전트는 링크를 통과하기 시작할 때 이미 문에 있어야 하므로, 주로 회전을 위해 사용됨).
  3. 문이 열릴 때까지 몇 초간 대기.
  4. 오프-메시 링크 비활성화.

 

마지막 단계는 직관적이지 않을 수 있지만, 이 사용 사례에는 매우 잘 작동합니다. 문이 열리면 아래의 내비메시가 통과 가능해집니다. 그런 다음 링크를 비활성화하면 에이전트는 경로를 다시 계산하게 되고, 이번에는 링크를 사용하지 않고 문을 통해 새 경로를 찾게 됩니다. 이후로 문은 열려 있고 에이전트는 그냥 통과할 수 있습니다.

필요한 경우, 문을 잠시 후에 닫고 오프-메시 링크를 다시 활성화할 수도 있습니다.

맞춤 애니메이션을 재생하거나 게임에 특화된 방식으로 캐릭터를 이동시키는 등 맞춤 오프-메시 링크 이동 로직이 필요한 경우, 콜백에 등록하여 이동의 모든 측면을 제어할 수 있습니다.

사용 중인 이동 스크립트에 따라  FollowerEntity.onTraverseOffMeshLink 또는 RichAI.onTraverseOffMeshLink 속성에 핸들러를 등록하세요. 또는 특정 오프-메시 링크에 대한 콜백을 등록하려면 NodeLink2.onTraverseOffMeshLink 속성을 사용할 수 있습니다.

참고:
 AIPath  및  AILerp 이동 스크립트는 오프-메시 링크 이동 중에 맞춤 이동을 지원하지 않습니다.

이 섹션에서는 FollowerEntity 에 중점을 둡니다.
 RichAI 도 유사한 기능을 가지고 있지만, API는 약간 다르고 다소 제한적입니다.


IOffMeshLinkHandler  인터페이스를 구현하면 어떤 클래스든 핸들러로 지정할 수 있습니다. 이 인터페이스에는 IOffMeshLinkStateMachine 인터페이스를 구현하는 객체를 반환해야 하는 GetOffMeshLinkStateMachine 메서드가 있습니다. 상태 머신 인터페이스에는 이동의 다양한 부분을 제어할 수 있는 여러 메서드가 있습니다.

 


OnTraverseOffMeshLink (context)
에이전트가 오프-메시 링크를 통과할 때 호출됩니다.

이 메서드는 코루틴(즉, IEnumerable을 반환)이어야 하며, 완료될 때까지 또는 에이전트가 파괴될 때까지 반복됩니다. 코루틴은 에이전트가 링크를 통과할 때까지 매 프레임 null을 yield해야 합니다.

코루틴이 완료되면 에이전트가 링크의 끝에 도달한 것으로 간주되고, 제어는 정상적인 이동 코드로 반환됩니다.

코루틴은 일반적으로 에이전트를 일정 시간 동안 링크의 끝으로 이동시키고 필요한 다른 작업을 수행합니다. 예를 들어, 애니메이션을 재생하거나 에이전트를 특정 방식으로 이동시킬 수 있습니다.

참조: 이 설정은 Pathfinding.IOffMeshLinkStateMachine.OnTraverseOffMeshLink 멤버에 해당합니다.



OnFinishTraversingOffMeshLink (context)
에이전트가 오프-메시 링크 통과를 완료했을 때 호출됩니다.

이는 이동이 완료된 후 필요한 모든 정리 작업을 수행하는 데 사용할 수 있습니다.
 OnFinishTraversingOffMeshLink  또는 OnAbortTraversingOffMeshLink 중 하나가 호출되지만, 둘 다 호출되지는 않습니다.

참조: 이 설정은  Pathfinding.IOffMeshLinkStateMachine.OnFinishTraversingOffMeshLink 멤버에 해당합니다.



OnAbortTraversingOffMeshLink ()
에이전트가 오프-메시 링크 통과를 완료하지 못했을 때 호출됩니다.
이는 이동이 완료된 후 필요한 모든 정리 작업을 수행하는 데 사용할 수 있습니다.
에이전트가 링크를 통과하는 동안 파괴되거나 다른 곳으로 텔레포트된 경우 중단이 발생할 수 있습니다.
OnFinishTraversingOffMeshLink  또는  OnAbortTraversingOffMeshLink 중 하나가 호출되지만, 둘 다 호출되지는 않습니다.

경고
이 메서드가 호출될 때 에이전트가 이미 파괴되었을 수 있습니다. 핸들러 컴포넌트 자체도 이 시점에서 파괴되었을 수 있습니다.
참조: 이 설정은 Pathfinding.IOffMeshLinkStateMachine.OnAbortTraversingOffMeshLink 멤버에 해당합니다.

 

 

가장 간단한 경우, 에이전트가 링크를 통과할 때 이벤트를 수신하고 기본 이동 구현을 사용할 수 있습니다. 다음과 같이 할 수 있습니다:

using UnityEngine;
using Pathfinding;
using Pathfinding.ECS;

public class LogOffMeshLinkTraversal : MonoBehaviour, IOffMeshLinkHandler, IOffMeshLinkStateMachine {
    // 컴포넌트가 활성화될 때 이 클래스를 오프-메시 링크의 핸들러로 등록합니다.
    // 이 컴포넌트는 NodeLink2와 FollowerEntity 모두에 등록할 수 있습니다.
    void OnEnable () {
        if (TryGetComponent<NodeLink2>(out var link)) link.onTraverseOffMeshLink = this;
        if (TryGetComponent<FollowerEntity>(out var ai)) ai.onTraverseOffMeshLink = this;
    }
    void OnDisable () {
        if (TryGetComponent<NodeLink2>(out var link)) link.onTraverseOffMeshLink = null;
        if (TryGetComponent<FollowerEntity>(out var ai)) ai.onTraverseOffMeshLink = null;
    }

    IOffMeshLinkStateMachine IOffMeshLinkHandler.GetOffMeshLinkStateMachine (AgentOffMeshLinkTraversalContext context) {
        Debug.Log("에이전트가 오프-메시 링크를 통과하기 시작했습니다.");
        return this;
    }

    void IOffMeshLinkStateMachine.OnFinishTraversingOffMeshLink (AgentOffMeshLinkTraversalContext context) {
        Debug.Log("에이전트가 오프-메시 링크를 통과 완료했습니다.");
    }

    void IOffMeshLinkStateMachine.OnAbortTraversingOffMeshLink () {
        Debug.Log("에이전트가 오프-메시 링크 통과를 중단했습니다.");
    }

    // 기본 구현으로 돌아가기 위해 IOffMeshLinkStateMachine.OnTraverseOffMeshLink를 구현하지 않습니다.
    // System.Collections.IEnumerable IOffMeshLinkStateMachine.OnTraverseOffMeshLink(ECS.AgentOffMeshLinkTraversalContext context)
}

제공된 context 객체는 AgentOffMeshLinkTraversalContext.TeleportAgentOffMeshLinkTraversalContext.MoveTowards 와 같은 헬퍼 메서드를 제공하며, 다양한 에이전트 데이터에 접근할 수 있습니다.

참조 AgentOffMeshLinkTraversalContext

 

더 복잡한 작업도 수행할 수 있습니다. 예를 들어, 에이전트의 위치를 완전히 제어하는 것입니다. 아래 예제에서는 점프 링크가 구현되었습니다. 에이전트는 먼저 링크의 반대쪽을 향하도록 회전한 다음,  bezier curve 으로 제어되는 호를 따라 "점프"하여 반대쪽으로 이동합니다.

using UnityEngine;
using Pathfinding;
using System.Collections;
using Pathfinding.ECS;

namespace Pathfinding.Examples {
    public class FollowerJumpLink : MonoBehaviour, IOffMeshLinkHandler, IOffMeshLinkStateMachine {
        // 컴포넌트가 활성화될 때 이 클래스를 오프-메시 링크의 핸들러로 등록합니다.
        void OnEnable() => GetComponent<NodeLink2>().onTraverseOffMeshLink = this;
        void OnDisable() => GetComponent<NodeLink2>().onTraverseOffMeshLink = null;

        IOffMeshLinkStateMachine IOffMeshLinkHandler.GetOffMeshLinkStateMachine(AgentOffMeshLinkTraversalContext context) => this;

        void IOffMeshLinkStateMachine.OnFinishTraversingOffMeshLink (AgentOffMeshLinkTraversalContext context) {
            Debug.Log("에이전트가 오프-메시 링크를 통과 완료했습니다.");
        }

        void IOffMeshLinkStateMachine.OnAbortTraversingOffMeshLink () {
            Debug.Log("에이전트가 오프-메시 링크 통과를 중단했습니다.");
        }

        IEnumerable IOffMeshLinkStateMachine.OnTraverseOffMeshLink (AgentOffMeshLinkTraversalContext ctx) {
            var start = (Vector3)ctx.link.relativeStart;
            var end = (Vector3)ctx.link.relativeEnd;
            var dir = end - start;

            // 오프-메시 링크를 통과하는 동안 로컬 회피를 비활성화합니다.
            // 활성화된 경우, 에이전트가 링크를 통과하면 자동으로 다시 활성화됩니다.
            ctx.DisableLocalAvoidance();

            // 에이전트를 이동시키고 링크의 반대편을 향하도록 회전시킵니다.
            // 오프-메시 링크에 도달할 때, 에이전트가 잘못된 방향을 향하고 있을 수 있습니다.
            while (!ctx.MoveTowards(
                position: start,
                rotation: Quaternion.LookRotation(dir, ctx.movementPlane.up),
                gravity: true,
                slowdown: true).reached) {
                yield return null;
            }

            var bezierP0 = start;
            var bezierP1 = start + Vector3.up * 5;
            var bezierP2 = end + Vector3.up * 5;
            var bezierP3 = end;
            var jumpDuration = 1.0f;

            // AI가 링크의 시작부터 끝까지 점프하도록 애니메이션을 적용합니다.
            for (float t = 0; t < jumpDuration; t += ctx.deltaTime) {
                ctx.transform.Position = AstarSplines.CubicBezier(bezierP0, bezierP1, bezierP2, bezierP3, Mathf.SmoothStep(0, 1, t / jumpDuration));
                yield return null;
            }
        }
    }
}

위 스크립트를 NodeLink2 컴포넌트가 있는 동일한 GameObject에 첨부하면, 에이전트가 링크를 통과할 때 점프하게 됩니다. 이는 아래 비디오에서 볼 수 있습니다.

 

'유니티 에셋 > A* Pathfinding project pro' 카테고리의 다른 글

Extending The System  (0) 2024.05.28
Creating graphs during runtime  (0) 2024.05.28
Moving a graph at runtime  (0) 2024.05.28
Large worlds  (0) 2024.05.27
Accessing graph data  (0) 2024.05.27

런타임에 그래프를 이동하는 방법

런타임에 그래프를 이동해야 하는 이유는 무엇입니까?

런타임에 그래프를 이동해야 할 여러 가지 이유가 있습니다:

파일에서 그래프를 로드했지만 원하는 위치에 배치되지 않았습니다.

전체 세계를 새로운 위치로 이동하고 싶으며, 그래프도 함께 이동하기를 원합니다.

절차적으로 생성된 레벨을 커버하기 위해 그래프를 이동하고 크기를 조정한 다음, 새로운 위치에서 그래프를 스캔하고 싶습니다.

매우 크거나 무한한 절차적 세계에서 에이전트를 따라다니도록 그래프를 이동하고 싶습니다.

참고: 런타임에 그래프를 처음부터 생성하려면  Creating graphs during runtime 을 참조하세요.

 

런타임에 그래프를 어떻게 이동합니까?

그래프를 이동하는 방법은 그래프 유형에 따라 다르지만, 일반적으로 먼저 그래프에 새로운 설정을 할당한 다음, RelocateNodes 메서드를 사용하여 실제로 노드를 이동시킵니다. 일부 그래프에는 방향 설정을 직접 지정할 수 있는 RelocateNodes 메서드의 오버로드가 있습니다.

RelocateNodes 메서드에는 몇 가지 다른 오버로드가 있지만, 가장 일반적인 사용 예는 아래 코드 스니펫에서 볼 수 있습니다:

 

그리드 그래프/레이어드 그리드 그래프의 경우:

// 그래프를 원점으로 이동, 회전 없음, 노드 크기 1.0
var gg = AstarPath.active.data.gridGraph;
gg.RelocateNodes(center: Vector3.zero, rotation: Quaternion.identity, nodeSize: 1.0f);

 

 

Recast 그래프의 경우:

// 그래프를 (20, 10, 10) 지점으로 이동, X 축을 기준으로 45도 회전
var graph = AstarPath.active.data.recastGraph;
graph.forcedBoundsCenter = new Vector3(20, 10, 10);
graph.rotation = new Vector3(45, 0, 0);
graph.RelocateNodes(graph.CalculateTransform());

 

 

navmesh graph의 겨우:

// 그래프를 (20, 10, 10) 지점으로 이동, X 축을 기준으로 45도 회전
var graph = AstarPath.active.data.navmesh;
graph.offset = new Vector3(20, 10, 10);
graph.rotation = new Vector3(45, 0, 0);
graph.RelocateNodes(graph.CalculateTransform());

 

 

새 위치에서 그래프 스캔하기

그래프를 이동한 후 새 위치에서 스캔하려면 끝에 Scan 호출을 추가할 수 있습니다:

// 그래프를 원점으로 이동, 회전 없음, 노드 크기 1.0
var gg = AstarPath.active.data.gridGraph;
gg.RelocateNodes(center: Vector3.zero, rotation: Quaternion.identity, nodeSize: 1.0f);
// 새 위치에서 그래프 스캔
AstarPath.active.Scan();

 

 

에이전트를 따라 그래프 이동하기

큰 절차적 세계 또는 무한한 절차적 세계가 있는 경우, 그래프가 플레이어를 중심으로 하고 플레이어가 이동할 때 그래프가 따라다니도록 할 수 있습니다. 기술적으로 그래프를 자주 이동하고 스캔하여 수행할 수 있지만, 이는 매우 비효율적입니다. 대신 ProceduralGraphMover 컴포넌트를 사용하여, 실제로 이동에 영향을 받는 노드만 다시 계산함으로써 그리드, 레이어드 그리드 또는 Recast 그래프를 런타임에 훨씬 효율적으로 이동할 수 있습니다.

 

참조
ProceduralGraphMover
Large worlds

'유니티 에셋 > A* Pathfinding project pro' 카테고리의 다른 글

Creating graphs during runtime  (0) 2024.05.28
Off-mesh links  (0) 2024.05.28
Large worlds  (0) 2024.05.27
Accessing graph data  (0) 2024.05.27
Saving and Loading Graphs  (0) 2024.05.27

대규모 세계에서의 경로 찾기 처리 방법.
대규모 세계는 경로 찾기에 부담을 줄 수 있으며, 특히 설정 방법을 잘못 선택하면 문제가 발생할 수 있습니다. 이 튜토리얼에서는 대규모 세계와 관련된 가장 일반적인 절충점과 이를 처리하기 위한 솔루션에 대해 알아봅니다.

Constraints

먼저 게임이 충족해야 할 요구 사항을 살펴보는 것이 중요합니다. 다양한 솔루션이 다양한 게임에 적합합니다. 다음 제약

사항 목록을 살펴보고 게임에 적용되는 내용을 확인하세요:

  • 한 명 이상의 캐릭터가 있습니까?
  • 전체 세계에 걸쳐 많은 에이전트가 동시에 경로 찾기가 필요합니까?
  • 모든 경로 찾기가 단일 지점/캐릭터를 중심으로 이루어집니까?
  • 게임 실행 중에 그래프를 업데이트해야 합니까?
  • 세계가 빌드 시에 모두 알려져 있습니까, 아니면 절차적으로 생성됩니까?
  • 게임에 패널티나 태그가 필요합니까?

Graph type

대규모 세계의 경우 기본 선택은 GridGraph RecastGraph 사이에서 이루어집니다. Recast 그래프는 일반적으로 대규모 세계에 더 적합한데, 이는 적은 노드로도 세계를 정밀하게 표현할 수 있기 때문입니다. Grid 그래프는 적절한 크기의 세계까지 확장할 수 있지만, 대규모 그리드 그래프는 많은 노드로 인해 메모리와 성능 오버헤드가 큽니다. 500x500 노드의 그리드 그래프는 이미 큰 편이며, 1000x1000 노드를 초과하지 않는 것이 좋습니다. 그러나 전 세계에서 동시에 경로 찾기가 필요하지 않은 경우(예: 모든 것이 단일 플레이어 캐릭터를 중심으로 이동하는 경우) 이동하는 그리드 또는 Recast 그래프를 사용할 수 있습니다.

Solutions

다음은 일반적인 설정 몇 가지와 그 장단점입니다.

A single recast graph

단일 대형 Recast 그래프는 기본적으로 훌륭한 선택이 될 수 있습니다. Recast 그래프는 대형 빈 영역과 작은 세부 사항을 모두 잘 표현할 수 있습니다. 타일링이 활성화되어 있는지 확인하세요. 이는 대형 그래프 생성 및 업데이트를 훨씬 빠르게 만듭니다.


장점:

  • 설정이 간단합니다.
  • 작은 세부 사항과 큰 세부 사항 모두 잘 표현합니다.
  • 비교적 낮은 메모리 사용량을 가집니다.
  • 적은 노드 수로 인해 경로 찾기가 빠릅니다.
  • 전체 맵이 메모리에 동시에 존재하므로 여러 캐릭터가 세계의 다른 부분을 동시에 탐색할 수 있습니다.
  • Navmesh cutting 을 사용하면 빠르게 그래프를 업데이트할 수 있습니다.

단점:

  • 전체 그래프 업데이트는 그리드 그래프보다 느립니다(Navmesh 절단은 더 빠름).
  • Recast 그래프는 태그와 패널티를 잘 표현하지 못합니다.
  • 큰 기복이 있는 빈 영역, 예를 들어 나무나 장애물이 없는 언덕은 Recast 그래프로 잘 표현하기 어렵습니다. 타일링을 활성화하면 큰 노드를 분할하는 데 도움이 됩니다.
  • 매우 큰 Recast 그래프를 스캔하는 데 시간이 많이 걸릴 수 있습니다. 이는 절차적 세계에는 이상적이지 않을 수 있습니다.
  • 시간이 지나면서 부분별로 생성할 수 있어야 합니다.

참고:
Recast graph in a 3D game



A single large grid graph

단일 대형 그리드 그래프는 그리드 그래프가 잘 확장되지 않는 한계를 넘지 않는다면 매우 잘 작동할 수 있습니다. 그리드 그래프는 최대 500x500 노드까지 적절히 확장되며, 정말 필요하다면 1000x1000 노드까지 확장할 수 있습니다. 그 이상은 권장되지 않습니다.

 

장점:

  • 설정이 간단합니다.
  • 그리드 그래프는 패널티와 태그와 잘 작동합니다.
  • 그래프 업데이트가 빠릅니다.
  • 그래프 스캔이 비교적 빠릅니다(1000x1000 그래프를 스캔하는 데는 시간이 걸릴 수 있음).
  • 전체 맵이 메모리에 동시에 존재하므로 여러 캐릭터가 세계의 다른 부분을 동시에 탐색할 수 있습니다.

단점:

  • 메모리 사용량이 Recast 그래프보다 높습니다.
  • 긴 거리의 경로 찾기가 Recast 그래프보다 훨씬 느려질 수 있습니다.
  • 매우 큰 세계로 확장되지 않습니다.

참고:
예제 장면 "Example2"에서 그리드 그래프 예시를 찾을 수 있습니다.



A small moving graph

경로 찾기가 단일 지점 또는 캐릭터(예: 플레이어)를 중심으로 이루어지는 경우, 이동 그리드 또는 Recast 그래프를 사용할 수 있습니다.

이동 그리드/Recast 그래프는 ProceduralGraphMover 스크립트를 활용하는 그래프입니다. 작은 그리드 그래프(예: 100x100) 또는 Recast 그래프가 항상 캐릭터를 중심으로 합니다. 캐릭터가 이동함에 따라 ProceduralGraphMover 는 그래프를 이동시키고 시야에 들어오는 새로운 부분을 스캔하며, 오래된 부분은 버립니다.

여러 개의 작은 그래프가 필요하다면 이를 확장할 수도 있습니다. 각 작은 그래프는 다른 지점 또는 캐릭터를 중심으로 합니다. 그러나 2-4개의 그래프를 초과하면 모든 그래프를 이동하고 업데이트하는 오버헤드가 커질 수 있습니다.

 

Recast 그래프도 사용할 수 있습니다. 이 경우  ProceduralGraphMover는 타일 단위로 작동합니다.

 

장점:

  • 패널티와 태그에 잘 작동하는 그리드 그래프 또는 작은 세부 사항을 잘 캡처하는 Recast 그래프를 사용할 수 있습니다.
  • 그래프 업데이트가 빠르며, 그래프가 적절히 작게 유지되는 한 스캔 속도가 빠릅니다.
  • 이 설정을 사용하면 매우 크거나 심지어 무한히 큰 세계를 쉽게 지원할 수 있습니다.
  • 메모리 사용량이 낮습니다.

단점:

  • 경로 찾기는 지정된 캐릭터/지점 주변에서만 가능합니다.
  • 긴 거리의 경로 찾기는 불가능하며, 이는 산맥과 같은 큰 장애물을 우회하는 경로를 찾기 어렵게 만들 수 있습니다.
  • 캐릭터/지점이 이동할 때마다 그래프를 정기적으로 업데이트해야 하기 때문에 지속적인 오버헤드가 발생합니다.
  • 설정이 다소 복잡합니다.

Infinite procedurally generated world  참조

Conclusion

이 튜토리얼에서는 대규모 세계에서 경로 찾기를 원할 때 고려해야 할 사항과 게임에서 사용할 수 있는 몇 가지 예제 설정에 대해 살펴보았습니다. 다양한 그래프 유형에 대한 자세한 정보는 그래프 유형(Graph Types)을 참조하십시오.

'유니티 에셋 > A* Pathfinding project pro' 카테고리의 다른 글

Off-mesh links  (0) 2024.05.28
Moving a graph at runtime  (0) 2024.05.28
Accessing graph data  (0) 2024.05.27
Saving and Loading Graphs  (0) 2024.05.27
Working with tags  (0) 2024.05.27

그래프와 노드에 접근하는 방법

그래프의 모든 데이터에 접근할 수 있습니다. 예를 들어, API에서 GraphNode를 매개변수로 기대하는 경우가 많으므로 이를 찾을 수 있어야 합니다. 또한 게임에 맞게 그래프 데이터를 광범위하게 수정해야 하는 경우도 있을 수 있습니다. 예를 들어, 자체 제작한 타일맵 생성기가 있고 해당 데이터를 그리드 그래프에 전달해야 할 수 있습니다.

참고:
Graph Updates.
특정 지점 근처의 노드를 검색하는 방법에 대한 정보는 '노드 사용(Using nodes)'을 참조하세요.

Accessing graphs

모든 그래프는 Pathfinding.AstarData 클래스에 저장됩니다. 이 클래스는 모든 그래프가 포함된 배열을 가지고 있으며, 그래프를 찾기 위한 여러 편리한 메서드를 포함하고 있습니다. 그러나 가장 빠른 방법은 제공된 단축키를 사용하는 것입니다. 각 내장 그래프 유형에 대해 해당 유형의 첫 번째 그래프를 가리키는 필드가 있습니다.

GridGraph gridGraph = AstarPath.active.data.gridGraph;
PointGraph pointGraph = AstarPath.active.data.pointGraph;
NavMeshGraph navmeshGraph = AstarPath.active.data.navmesh;
RecastGraph recastGraph = AstarPath.active.data.recastGraph;
LayerGridGraph layerGridGraph = AstarPath.active.data.layerGridGraph;

NavGraph[] allGraphs = AstarPath.active.data.graphs;

// 그래프 인스펙터에서 그래프의 이름을 설정할 수 있습니다
var graph = AstarPath.active.data.FindGraph(g => g.name == "My Custom Graph Name");

Getting nodes in a graph.

다양한 그래프 유형은 노드를 각각 다른 방식으로 저장합니다. 예를 들어, 그리드 그래프는 모든 노드를 거대한 배열에 저장하여 특정 셀 인덱스에서 노드를 쉽게 가져올 수 있습니다. Recast 그래프는 모든 노드를 개별 타일의 배열에 저장합니다.

그래프의 모든 노드를 가져오는 일반적이고 매우 상위 수준의 방법은 모든 그래프 유형에 존재하는 GetNodes 메서드를 사용하는 것입니다. 이 메서드는 각 노드에 대해 호출되는 콜백을 받습니다. 이 메서드가 반복자 대신 콜백을 사용하는 이유는 콜백이 반복자보다 훨씬 빠르기 때문이며, 대규모 그래프의 경우 이는 큰 차이를 만듭니다.

var gg = AstarPath.active.data.gridGraph;

gg.GetNodes(node => {
    // 여기에 노드가 있습니다
    Debug.Log("I found a node at position " + (Vector3)node.position);
});

 

경고
노드의 데이터를 업데이트하지 마세요. 이는 동시에 실행될 수 있는 경로 찾기와 충돌할 수 있습니다(특히 멀티스레딩을 사용하는 경우). 노드 데이터를 안전하게 업데이트하려면 안전한 콜백 내에서 이를 수행해야 합니다. AstarPath.AddWorkItem 을 사용하여 안전한 콜백을 요청할 수 있습니다.
참고:
Graph Updates

 

GridGraph

그리드 그래프 노드는 큰 배열에 저장됩니다. 노드의 좌표에 의해 인덱싱되어 쉽게 접근할 수 있습니다. 노드에 접근하기 위한 편리한 메서드도 제공됩니다:

var gg = AstarPath.active.data.gridGraph;
int x = 5;
int z = 8;
GridNodeBase node = gg.GetNode(x, z);

런타임 중에 그리드 그래프의 크기를 변경해야 하는 경우가 있습니다. 이때는 월드 유닛 단위로 그리드의 크기를 나타내는 unclampedSize 필드를 수정할 수 있습니다. 또는 SetDimensions 메서드를 사용할 수 있습니다. 그래프의 너비나 깊이를 변경한 후에는  AstarPath.Scan 을 사용하여 다시 계산해야 합니다.

var gg = AstarPath.active.data.gridGraph;
var width = 80;
var depth = 60;
var nodeSize = 1.0f;

gg.SetDimensions(width, depth, nodeSize);

// 그래프를 다시 계산
AstarPath.active.Scan();

그리드 그래프를 다시 계산하지 않고도  RelocateNodes 메서드를 사용하여 이동할 수도 있습니다. 이 메서드는 다른 그래프에서도 사용할 수 있지만, 그 경우 필요한 행렬을 직접 계산해야 합니다.

Layered Grid Graph

레이어드 그리드 그래프는 그리드 그래프와 거의 동일하게 작동합니다. 그러나 레이어드 그리드 그래프는 여러 레이어를 포함할 수 있기 때문에 추가적인 레이어 좌표가 필요합니다.

var gg = AstarPath.active.data.layerGridGraph;
int x = 5;
int z = 8;
int layer = 0;
GridNodeBase node = gg.GetNode(x, z, layer);

NavmeshGraph/RecastGraph

Recast 그래프는 노드를 여러 타일로 나눕니다. 개별 타일은 그리드 그래프가 노드를 저장하는 방식과 동일하게 배열에 저장됩니다. 각 타일 내부에서 노드는 고유한 순서 없이 배열에 저장됩니다. 그러나 모든 노드는 축 정렬 경계 상자 트리(AABB 트리)에 추가되어 특정 지점에 가장 가까운 노드를 효율적으로 쿼리할 수 있습니다. Navmesh 그래프는 Recast 그래프와 동일하게 작동하지만 항상 단일 타일을 사용합니다.

var graph = AstarPath.active.data.recastGraph;
int tileX = 5;
int tileZ = 8;
NavmeshTile tile = graph.GetTile(tileX, tileZ);

for (int i = 0; i < tile.nodes.Length; i++) {
    // ...
}
// or you can access the nodes like this:
tile.GetNodes(node => {
    // ...
});

PointGraph

포인트 그래프는 노드를 고유한 순서 없이 배열에 저장합니다.

포인트 그래프 노드에는 생성된 GameObject에 대한 필드도 포함되어 있으며(있는 경우) 이는 다양한 용도로 유용할 수 있습니다.

var node = AstarPath.active.GetNearest(transform.position).node;
var pointNode = node as PointNode;

if (pointNode != null) {
    Debug.Log("That node was created from the GameObject named " + pointNode.gameObject.name);
} else {
    Debug.Log("That node is not a PointNode");
}

런타임 중에 포인트 그래프에 노드를 추가할 수도 있습니다:

AstarPath.active.AddWorkItem(new AstarWorkItem(ctx => {
    var graph = AstarPath.active.data.pointGraph;
    // 노드 2개를 추가하고 연결
    var node1 = graph.AddNode((Int3)transform.position);
    var node2 = graph.AddNode((Int3)(transform.position + Vector3.right));
    var cost = (uint)(node2.position - node1.position).costMagnitude;
    GraphNode.Connect(node1, node2, cost);
}));
Graph Updates  참조

'유니티 에셋 > A* Pathfinding project pro' 카테고리의 다른 글

Moving a graph at runtime  (0) 2024.05.28
Large worlds  (0) 2024.05.27
Saving and Loading Graphs  (0) 2024.05.27
Working with tags  (0) 2024.05.27
Navmesh Cutting  (0) 2024.05.27

그래프를 미리 계산하고 그 데이터를 저장하는 방법

모든 그래프는 파일로 저장하고 불러올 수 있습니다. 실제로 에디터가 항상 하는 작업이기도 하며, Unity 직렬화는 거의 사용되지 않고, 대신 모든 그래프 설정이 직렬화되어 바이트 배열에 저장됩니다. 그러나 설정뿐만 아니라 계산된 후 그래프는 모든 노드를 압축된 바이트 배열 표현으로 저장할 수 있으며, 이를 파일에 저장하고 다른 곳에서 불러올 수 있습니다.
A* 인스펙터의 Save & Load 탭에서 "Save to file"과 "Load from file"이라는 두 개의 버튼을 사용하여 그래프 파일을 저장하고 불러올 수 있습니다.

Caching Graph Calculation

시작할 때 그래프를 다시 계산하는 것이 일반적이지만, 때로는 특히 RecastGraph를 사용하거나 모바일용으로 개발하는 경우 시작 시 지연이 매우 성가실 수 있습니다. 또한 어떤 이유로 시작 시 그래프를 계산할 수 없는 경우도 있습니다.
이 경우 그래프 캐싱이 유용합니다. 에디터에서 그래프를 스캔하고 외부 파일에 저장한 다음 시작 시 로드할 수 있게 합니다. 대부분의 경우 그래프를 스캔하는 것보다 훨씬 빠르며, 그래프가 어떻게 보일지 정확히 알 수 있습니다.

캐시를 생성하려면 A* 인스펙터의 Save & Load 탭을 열고 Generate Cache를 클릭하십시오. 저장하기 전에 그래프를 다시 스캔할지 묻습니다. 이제 저장된 그래프가 시작 시 모든 노드 정보가 그대로 로드되므로 계산 시간이 필요하지 않습니다.



Saving Graphs to File and Loading them

나중에 불러올 수 있도록 그래프를 파일로 저장하고 싶을 수도 있습니다. 예를 들어 서버에서 런타임 중에 불러올 수도 있습니다.

그래프를 저장하려면 Save & Load 탭을 열고 Save to file 버튼을 클릭하십시오. 설정만 포함할지, 설정과 노드 데이터를 포함할지 선택할 수 있습니다. 설정만 포함하는 경우 그래프가 로드된 후 다시 계산해야 캐릭터가 내비게이션에 사용할 수 있습니다. 모든 그래프를 다시 계산하려면 다음을 사용할 수 있습니다:

AstarPath.active.Scan();

그래프를 다시 로드하려면 Load from file 버튼을 누르고 파일을 찾기만 하면 됩니다. 이 작업은 현재 그래프를 대체한다는 점에 유의하세요.

Loading and Saving using Code

런타임 중에 그래프를 로드하거나 저장하려면, 당연히 에디터 인터페이스를 사용할 수 없습니다.

파일을 저장하고 불러오는 쉬운 API가 있습니다. 이 API는 그래프 설정을 바이트 배열로 직렬화합니다. 기본적으로 노드 정보가 포함됩니다(그래프가 먼저 스캔되었다고 가정합니다).

byte[] bytes = AstarPath.active.data.SerializeGraphs();

더 많은 제어를 원한다면 몇 가지 설정을 추가할 수 있습니다.

var settings = new Pathfinding.Serialization.SerializeSettings();

// 설정만 저장
settings.nodes = false;
byte[] bytes = AstarPath.active.data.SerializeGraphs(settings);

저장된 데이터를 로드하려면 다음을 호출할 수 있습니다:

AstarPath.active.data.DeserializeGraphs(bytes);

설정만 로드하는 경우, 설정을 로드한 후 Scan을 호출하고 싶을 수 있습니다:

AstarPath.active.data.DeserializeGraphs(bytes);
AstarPath.active.Scan();

Additive Loading

현재 로드된 그래프를 대체하는 대신, 다음을 사용하여 그래프를 추가적으로 로드할 수 있습니다:

AstarPath.active.data.DeserializeGraphsAdditive(bytes);

다음 방법을 사용하여 그래프를 언로드할 수 있습니다:

var data = AstarPath.active.data;
var myGraph = data.gridGraph;
data.RemoveGraph(myGraph);

Including Data in a TextAsset

그래프 데이터를 텍스트 에셋에 포함하면 빌드에 쉽게 포함할 수 있습니다. 데이터를 파일로 저장한 후 해당 파일의 이름을 "myGraph.bytes"와 같이 변경하고 Unity 프로젝트에 넣으세요. 이렇게 하면 Unity가 해당 파일을 이진 정보로 처리하게 됩니다. 확장자가 .txt와 같은 경우, Unity가 텍스트로 읽으려고 하기 때문에 데이터가 손상될 수 있습니다. 일부 운영 체제는 확장자를 숨기는 것을 좋아하므로, Unity가 .bytes 확장자로 파일을 인식하지 않는다면 실제로 .bytes 확장자를 가지고 있는지 확인하십시오. 확장자가 .zip(또는 다른 확장자)일 경우 단순히 숨겨져 있을 수 있습니다. 그런 다음 변수에 참조하고 .bytes 필드에 접근하여 텍스트 에셋에서 그래프를 로드할 수 있습니다.

using UnityEngine;
using System.Collections;
using Pathfinding;

public class TestLoader : MonoBehaviour {
    public TextAsset graphData;

    // 게임 시작 시 그래프 로드
    void Start () {
        AstarPath.active.data.DeserializeGraphs(graphData.bytes);
    }
}

Internal Data Structure

모든 설정은 JSON으로 직렬화됩니다. 이는 전방 및 후방 호환성을 유지하는 좋은 방법입니다. 아래 언급된 모든 파일은 크기를 줄이고 데이터를 쉽게 처리할 수 있도록 단일 zip 파일로 압축됩니다. 즉, zip 파일을 열어 설정을 수동으로 편집할 수도 있습니다.

 

참고
아래에 언급된 많은 파일은 항상 zip에 포함되지 않습니다. 파일에 관련 정보가 포함되지 않는다고 판단되면(예: 사용자가 만든 연결을 저장하지만 연결이 생성되지 않은 경우) 해당 파일은 제외됩니다.

Meta

모든 직렬화에는 meta.json 파일이 포함됩니다. 이 파일은 특정 그래프와 연결되지 않은 정보나 다른 그래프를 로드하는 데 필요한 정보를 포함합니다.

메타 파일에는 다음 내용이 포함됩니다:

  • 시스템의 버전 번호
  • 저장된 그래프 수
  • 각 그래프를 식별하기 위한 GUID 값
  • 각 그래프의 유형

아래는 meta.json 파일의 예입니다:

{
    "version": "3.0.9.5",
    
    "graphs": 1,
    
    "guids": 
        [
            "0d83c93fc4928934-8362a8662ec4fb9d"
        ],
    
    "typeNames": 
        [
            "Pathfinding.GridGraph"
        ]
}

Graph Settings

각 그래프의 설정은 "graph#.json"으로 저장되며, 여기서 #는 그래프 번호를 나타냅니다. 다음은 그리드 그래프의 직렬화된 설정 예시입니다(길이를 줄이기 위해 일부 설정은 제거되었습니다):

{
   "aspectRatio":1,
   "rotation":{
      "x":0,
      "y":0,
      "z":0
   },
   "center":{
      "x":0,
      "y":-0.1,
      "z":0
   },
   "unclampedSize":{
      "x":100,
      "y":100
   },
   "nodeSize":1,
   "maxClimb":0.4,
   "maxClimbAxis":1,
   "maxSlope":90,
   "erodeIterations":0,
   "autoLinkGrids":false,
   "autoLinkDistLimit":10,
   "neighbours":"Eight",
   "cutCorners":true,
   "penaltyPositionOffset":0,
   "penaltyPosition":false,
   "penaltyPositionFactor":1,
   "penaltyAngle":false,
   "penaltyAngleFactor":100,
   "open":true,
   "infoScreenOpen":false
   ...
}

노드 정보가 직렬화된 데이터에 포함된 경우, JSON으로 포함하기에는 공간을 너무 많이 차지하므로 대신 이진 데이터로 작성됩니다. 각 그래프 유형에는 노드 데이터를 직렬화하는 자체 코드가 있습니다. 이는 각 그래프의 SerializeExtraInfo 및 DeserializeExtraInfo 메서드에 의해 처리됩니다.

User Created Connections

NodeLink2 컴포넌트도 직렬화되며, 직렬화 시 해당 컴포넌트의 ID를 저장하고 그래프가 역직렬화될 때 해당 컴포넌트를 찾으려고 시도합니다. 이는 정적 맵에서는 잘 작동하지만, 런타임 중에 생성된 객체의 경우 다양한 객체를 추적할 좋은 방법이 없으므로, 저장된 데이터에 링크를 포함하지 않는 것이 좋을 수 있습니다(링크가 비활성화되어 있는지 확인). 그런 다음 그래프가 로드된 후 링크를 활성화하면 됩니다.

'유니티 에셋 > A* Pathfinding project pro' 카테고리의 다른 글

Large worlds  (0) 2024.05.27
Accessing graph data  (0) 2024.05.27
Working with tags  (0) 2024.05.27
Navmesh Cutting  (0) 2024.05.27
Using nodes  (0) 2024.05.27

태그를 사용하여 다양한 캐릭터가 걸을 수 있는 위치를 제한하는 방법

태그를 사용하여 노드 및 영역을 태그 지정하는 것은 강력한 기능입니다. 이를 통해 어떤 유닛이 어떤 지형을 걸을 수 있는지 제한할 수 있습니다. 예를 들어, 당신의 세계에 일부 생물들, 플레이어, 그리고 몇몇 AI가 있다고 상상해 보세요. AI와 생물들이 랜덤하게 경로를 찾아 다니지만, 생물들이 집 안으로 들어가지 않도록 하고 싶다면 태그를 사용하는 것이 훌륭한 해결책이 될 수 있습니다. 실내 영역을 "Indoors"라는 태그로 지정하고, 생물들이 그 노드들을 걸을 수 없도록 설정하면 됩니다!

태그 지정과 GraphUpdateScene 컴포넌트의 기능을 보여주는 동영상이 있습니다. 이 컴포넌트는 영역을 태그 지정하는 데 큰 도움이 됩니다.

 

 

먼저 일부 노드를 태그로 표시합니다. 그런 다음 Seeker 컴포넌트에는 "Valid Tags"라는 설정이 있습니다. 이 설정은 팝업 메뉴로 제공되며, 유닛이 어떤 태그를 통과할 수 있는지를 설정할 수 있습니다. 첫 번째 태그(보통 "Basic Ground"라고 명명)는 처음에 모든 노드에 설정됩니다. 경로 찾기를 할 때, Seeker는 경로가 통과할 수 있는 태그를 설정하고(편집기에서 설정한 태그들) 해당 제약 조건을 사용하여 경로를 찾으려고 합니다. 가장 가까운 노드를 검색할 때도 유효한 태그가 설정된 노드를 선택하도록 합니다.
다음은 제한된 영역 태그를 제외한 모든 태그를 통과할 수 있는 Seeker 컴포넌트의 이미지입니다.


시스템에서 하는 최적화 중 하나는 영역을 플러드 필링하여 한 지점에서 다른 지점으로 유효한 경로를 찾을 수 있는지를 아는 것입니다. 그러나 모든 태그에 대해 그렇게 할 수는 없습니다. 두 영역이 다른 태그가 있는 영역에 의해 분리되어 있지만 여전히 걸을 수 있는 경우, 그 영역들은 동일한 영역 ID를 공유하게 됩니다. 이는 유닛이 한 영역에서 시작하여 다른 영역으로 가는 경로를 찾으려고 할 때 중간 영역을 통과할 수 없으면 모든 가능한 노드를 검색한 후에야 멈추게 되어 시간이 더 걸릴 수 있다는 것을 의미합니다.

위에서 언급했듯이, GraphUpdateScene 컴포넌트는 노드를 태그 지정할 때 큰 도움이 됩니다. 이 컴포넌트에는 "Modify Tags" 설정이 있어 다각형의 노드 태그를 수정할지 여부를 설정할 수 있고, "Set Tag"는 "Modify Tags"가 true로 설정된 경우 노드를 태그로 표시할 태그를 정의합니다. GraphUpdateScene 컴포넌트 사용 방법에 대한 도움말은 Graph Updates 페이지를 참조하십시오.

태그를 디버그하려면 A* Inspector –> Settings –> Debug –> Path Debug Mode에서 디버그 모드를 Tags로 변경하십시오(Show Graphs가 켜져 있는지 확인하십시오). 이렇게 하면 각 태그가 특정 색상으로 렌더링되어 쉽게 구분할 수 있습니다.

태그는 사용하기 쉽게 이름을 지정할 수도 있습니다. A* Inspector –> Settings –> Tags에서 태그의 이름을 설정할 수 있습니다. 이 이름은 모든 태그 선택 필드에서 사용됩니다.

'유니티 에셋 > A* Pathfinding project pro' 카테고리의 다른 글

Accessing graph data  (0) 2024.05.27
Saving and Loading Graphs  (0) 2024.05.27
Navmesh Cutting  (0) 2024.05.27
Using nodes  (0) 2024.05.27
Grid Graph Rules > Writing Custom Grid Graph Rules  (0) 2024.05.26

내브메시 컷팅 튜토리얼


내브메시 컷팅은 리캐스트 또는 내브메시 그래프에 의해 생성된 기존 내브메시에 구멍을 뚫는 데 사용됩니다. 리캐스트/내브메시 그래프는 일반적으로 기존 노드의 매개변수만 변경하거나(예: 삼각형 전체를 이동 불가능하게 만듦) 전체 타일을 다시 계산하는 것을 허용하는데, 이는 유연성이 떨어지거나 매우 느립니다. 내브메시 컷팅을 사용하면 RTS 게임의 새로운 건물과 같은 장애물로 인해 차단된 내브메시의 일부를 제거(컷)할 수 있지만, 내브메시에 새로운 것을 추가하거나 노드의 위치를 변경할 수는 없습니다. 이는 리캐스트 그래프에서 전체 타일을 처음부터 다시 계산하는 것보다 훨씬 빠릅니다.

 

NavmeshCut 컴포넌트는 2D 모양을 사용하여 내브메시를 컷팅합니다. 이 모양은 내장된 2D 모양(사각형/원) 중 하나 또는 3D 모양(큐브/구/캡슐) 중 하나로 생성될 수 있으며, 컷팅 시 2D 모양으로 투영됩니다. 또한 컷으로 사용할 사용자 지정 2D 메시를 지정할 수도 있습니다.


사각형/원 모양은 3D가 아니라는 점에 유의하십시오. 이를 회전하면 2D 모양이 회전한 후 XZ 평면에 투영됩니다. 따라서 대부분의 경우 3D 모양(큐브/구/캡슐)을 사용하는 것이 더 쉽기 때문에 이를 사용하는 것이 좋습니다.

씬 뷰에서  NavmeshCut 은 돌출된 2D 모양처럼 보입니다. 이는 내브메시 컷이 높이를 가지기 때문입니다. 컷팅은 내브메시와 닿는 부분만 자릅니다. 성능상의 이유로 내브메시의 삼각형의 바운딩 박스만 확인하므로, 돌출된 모양과 교차하지 않더라도 바운딩 박스와 교차하는 삼각형을 자를 수 있습니다. 그러나 대부분의 경우 이는 큰 차이를 만들지 않습니다.

또한 isDual 필드를 `true`로 설정하여 내브메시 컷을 듀얼 모드로 설정할 수 있습니다. 이는 내브메시에 구멍을 내는 대신 경계선을 따라 내브메시를 분할하지만 내부와 외부를 모두 유지합니다. 이는 예를 들어 내브메시 삼각형과 정확하게 일치하지 않는 영역의 패널티를 변경하려는 경우에 유용할 수 있습니다. 이는 종종 GraphUpdateScene 컴포넌트와 결합하여 사용됩니다(그러나 GraphUpdateScene 컴포넌트는 그래프가 다시 업데이트되면 자동으로 패널티를 재적용하지 않습니다).

기본적으로 내브메시 컷은 회전 또는 스케일링을 고려하지 않습니다. 이를 고려하려면 useRotationAndScale 필드를 `true`로 설정할 수 있습니다.

Custom meshes
대부분의 경우 내장된 모양을 사용할 수 있지만, 경우에 따라 사용자 지정 컷팅 메시가 유용할 수 있습니다. 사용자 지정 메시는 아래 그림과 같이 평평한 2D 모양이어야 합니다. 스크립트는 해당 메쉬의 윤곽을 찾아 그 모양을 컷으로 사용합니다. 모든 노멀을 부드럽게 하고 메시에 UV 정보가 포함되지 않도록 하십시오. 그렇지 않으면 Unity가 버텍스를 분할하여 스크립트가 올바른 윤곽을 찾지 못할 수 있습니다. 매우 고다각형 메쉬는 사용하지 않는 것이 좋습니다. 이는 내브메시 그래프에 많은 노드를 생성하여 경로 찾기 속도를 저하시킬 수 있습니다. 매우 고다각형 메쉬는 많은 얇은 삼각형을 추가하여 최적이 아닌 경로를 생성할 수도 있습니다.

 


Update frequency
내브메시 컷은 일반적으로 매우 빠르기 때문에 자주 내브메시를 업데이트하고 싶을 수 있습니다(예: 몇 프레임마다 한 번씩). 그러나 너무 자주 업데이트하면 그래프에서 경로를 따르는 에이전트에 영향을 미칠 수 있습니다.

내브메시 컷이 에이전트 근처에서 그래프를 업데이트하면 일반적으로 경로를 다시 계산해야 합니다. 이것이 너무 자주 발생하면 경로 찾기 작업 스레드가 경로 찾기 요청으로 과부하되어 개별 경로 찾기 요청의 대기 시간이 길어질 수 있습니다. 이는 에이전트의 반응성을 떨어뜨릴 수 있습니다.

따라서 업데이트 빈도를 적절히 유지하는 것이 좋습니다. 플레이어는 내브메시가 초당 20번 업데이트되는 것과 초당 2번(또는 그보다 적게) 업데이트되는 것을 눈치채지 못할 것입니다.

이는 주로 updateDistance updateRotationDistance 필드를 사용하여 제어할 수 있습니다. 그러나 업데이트의 전역 빈도를 제어할 수도 있습니다. 이는 다음 섹션에서 설명합니다.

Control updates through code
내브메시 컷은 주기적으로 적용되지만, 때로는 그래프가 최신 상태인지 즉시 확인하고 싶을 수 있습니다. 그런 경우 다음 코드를 사용할 수 있습니다.

```csharp
AstarPath.active.FlushGraphUpdates();
```

// 경로 찾기 스레드가 현재 수행 중인 작업을 완료하는 대로 예약된 업데이트를 실행하도록 설정합니다.
AstarPath.active.navmeshUpdates.ForceUpdate();
// 업데이트가 완료될 때까지 차단합니다.
AstarPath.active.FlushGraphUpdates();

스크립트가 내브메시 컷이 변경되었는지 확인하는 빈도를 제어할 수도 있습니다. 컷이 매우 많은 경우 성능을 위해 확인 빈도를 줄이는 것이 좋을 수 있습니다.

// 매 프레임마다 확인 (기본값)
AstarPath.active.navmeshUpdates.updateInterval = 0;

// 0.1초마다 확인
AstarPath.active.navmeshUpdates.updateInterval = 0.1f;

// 변경 사항을 절대 확인하지 않음
AstarPath.active.navmeshUpdates.updateInterval = -1;
// 업데이트를 수동으로 예약해야 합니다.
AstarPath.active.navmeshUpdates.ForceUpdate();

이 설정은  AstarPath 인스펙터의 설정(Settings) 아래에서도 찾을 수 있습니다.


Navmesh cutting and tags/penalties
내브메시 컷은 그래프가 처음 스캔될 때 또는 리캐스트 그래프 타일이 처음부터 다시 계산될 때 발생하는 업데이트에 대해서만 태그를 보존할 수 있습니다.

이것은 GraphUpdateScene 컴포넌트를 사용하여 동적으로 적용한 태그가 내브메시 컷이 적용될 때 손실될 수 있음을 의미합니다. 따라서 태그와 내브메시 컷팅을 결합해야 하는 경우,  RecastMeshObj 포넌트를 사용하여 태그를 적용하는 것이 좋습니다. 이 방법은 내브메시 컷팅과 원활하게 작동합니다.

내부적으로는 그래프가 스캔될 때 내브메시 컷팅 서브시스템이 그래프의 모든 삼각형과 태그의 스냅샷을 찍습니다. 그런 다음 컷팅이 발생할 때마다 이 데이터를 참조하여 컷팅이 끝난 후 새로운 삼각형에 스냅샷의 태그가 복사됩니다.

컷팅이 끝난 후 그래프 업데이트를 사용하여 태그와 패널티를 적용할 수도 있습니다. 예를 들어, 내브메시 컷을 서브클래싱하고  UsedForCut 메서드를 오버라이딩하여 적용할 수 있습니다. 그러나 앞서 언급한  RecastMeshObj 를 사용하는 것이 더 견고한 솔루션이므로 권장됩니다.

 A* Pro 기능
이 기능은 A* Pathfinding Project Pro 기능입니다. 이 기능/클래스/변수는 A* Pathfinding Project의 무료 버전에는 존재하지 않거나 기능이 제한될 수 있습니다.
Pro 버전은 여기에서 구매할 수 있습니다.
http://www.arongranberg.com/2013/08/navmesh-cutting/ 참조

 

'유니티 에셋 > A* Pathfinding project pro' 카테고리의 다른 글

Saving and Loading Graphs  (0) 2024.05.27
Working with tags  (0) 2024.05.27
Using nodes  (0) 2024.05.27
Grid Graph Rules > Writing Custom Grid Graph Rules  (0) 2024.05.26
Grid Graph Rules  (0) 2024.05.26

+ Recent posts