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

Bitmask Tutorial  (0) 2024.05.29
Upgrade Guide  (0) 2024.05.29
Deploying for mobile/uwp  (0) 2024.05.28
Optimization > Compiler Directives  (0) 2024.05.28
Optimization > Pooling  (0) 2024.05.28

iPhone/Android 또는 UWP(Universal Windows Platform)용 A* Pathfinding Project 작업

여기에서 설명하는 대부분의 사항은 iPhone과 Android 모두에 적용됩니다. 그러나 iPhone은 더 제한적이므로 호환성을 위해 더 큰 주의가 필요합니다.

Universal Windows Platform (UWP)

Unity는 일부 이유로 UnityPackage 파일과 함께 일부 .dll 파일의 메타데이터를 포함하지 않습니다. UWP에서 작동하도록 하려면 Unity 프로젝트에서 `Pathfinding.Ionic.Zip.dll` 파일을 찾아서 UWP 빌드 시 *제외*되도록 설정해야 합니다. 이것만 하면 UWP에서 제대로 작동할 것입니다.

iPhone

A* Pathfinding Project는 대부분 iPhone에서도 바로 작동합니다. 그러나 스트리핑(어셈블리 또는 바이트코드)을 사용하는 경우, `link.xml` 파일을 찾아야 합니다. 이 파일은 `Assets/AstarPathfindingProject/link.xml`에 있으며, `Assets/link.xml`에 배치해야 합니다. 이 파일은 AOT(사전 컴파일) 컴파일러에 파일에 언급된 클래스를 제거하지 않도록 지시합니다. 이 작업을 수행하지 않으면, 예를 들어 캐싱을 시도할 때 오류가 발생할 수 있습니다.

마이크로 mscorlib 사용은 너무 많은 .Net 부분이 제거되므로 지원되지 않습니다.

"Fast But No Exceptions" 옵션을 사용하는 경우, 일부 리플렉션 코드를 사용할 수 없으므로 `ASTAR_FAST_NO_EXCEPTIONS` 지시문을 활성화해야 합니다. 프로 버전을 사용하는 경우, A* Inspector의 Optimizations 탭을 열고 `ASTAR_FAST_NO_EXCEPTIONS`을 활성화한 후 Apply를 클릭하십시오. 무료 버전을 사용하는 경우, `AstarData.cs` 스크립트(Core 폴더에 있음)를 열고 상단에 다음 줄을 추가(또는 주석 해제)하십시오:

#define ASTAR_FAST_NO_EXCEPTIONS


또한  JsonSerializer.cs 에서도 동일한 작업을 수행하십시오.

이렇게 하면 기본 그래프 유형에 대한 참조가 하드코딩됩니다. 사용자 정의 그래프 유형을 작성한 경우, 해당 유형을  AstarData.cs 의 `DefaultGraphTypes` 변수에 정의된 하드코딩된 목록에 추가해야 합니다.

참조:
Pathfinding.AstarData.DefaultGraphTypes



Optimize for mobile

iPhone용으로 배포할 때 시작 시간을 줄이기 위해 캐싱 시작을 사용하는 것이 좋습니다. 특히 Recast 그래프를 사용하는 경우에 유용합니다.

참조:
Saving and Loading Graphs


또한 A* Inspector -> Optimizations 아래의 다양한 옵션을 확인하여 특정 경우에 성능을 향상시킬 수 있습니다.

참조:
Compiler Directives

 

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

Upgrade Guide  (0) 2024.05.29
Misc  (0) 2024.05.28
Optimization > Compiler Directives  (0) 2024.05.28
Optimization > Pooling  (0) 2024.05.28
Optimization > Heuristic Optimization  (0) 2024.05.28

http://www.arongranberg.com/astar/buy

패키지 최적화를 위한 컴파일러 지시문 사용 개요

A*  Pathfinding Project의 프로 버전에는 A* Inspector에 "Optimization"이라는 탭이 있습니다.


이 탭은 프로젝트에서 컴파일러 지시문이 나열된 위치를 검색합니다. 컴파일러 지시문은 다른 코드가 컴파일에 포함될지 여부를 제어하는 코드 조각입니다.

#define DEBUG

public void Start () {
#if DEBUG
    Debug.Log ("DEBUG 컴파일러 지시문이 정의되었습니다");
#else
    Debug.Log ("DEBUG 지시문이 정의되지 않았습니다");
#endif
}



보시다시피, 일반적인 IF 문처럼 작동합니다. 컴파일러 지시문의 장점은 런타임 오버헤드가 전혀 없으며, 모두 컴파일 시간에 평가된다는 점입니다. A*  Pathfinding Project는 몇 가지 사용자 지정 가능한 측면을 가지고 있으며, 이는 보통 코드 수정을 필요로 하지만, 컴파일러 지시문으로 해결할 수 있습니다.

지시문은 다양한 최적화를 적용하거나(각각의 설명 참조) 디버깅 메시지를 활성화합니다(개발자가 아닌 다른 사람에게는 별로 유용하지 않을 수 있습니다).

지시문이 활성화되었는지 여부는 플레이어 설정의 "Scripting Define Symbols" 필드에 저장되며, 이는 Unity 프로젝트의 모든 씬에서 설정이 공유됨을 의미합니다.

모바일을 대상으로 하는 경우, 빌드된 플레이어의 파일 크기를 최대한 줄이는 것이 좋습니다. 이 경우 `ASTAR_NO_ZIP` 옵션을 사용하여 DotNetZip 라이브러리에 대한 의존성을 제거할 수 있습니다. 최적화 패널에서 옵션을 활성화한 후, `Assets/AstarPathfindingProject/Plugins/DotNetZip` 폴더의 dll 파일을 제거하면 몇 백 KB를 줄일 수 있습니다. 그러나 이렇게 하면 캐시된 시작을 사용할 때 캐시 크기가 훨씬 커집니다.

A* Pro 기능
이 기능은 A* Pathfinding Project Pro 기능입니다. 이 함수/클래스/변수는 A* Pathfinding Project의 무료 버전에는 존재하지 않거나 기능이 제한될 수 있습니다. Pro 버전은 여기에서 구매할 수 있습니다.

 

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

Misc  (0) 2024.05.28
Deploying for mobile/uwp  (0) 2024.05.28
Optimization > Pooling  (0) 2024.05.28
Optimization > Heuristic Optimization  (0) 2024.05.28
Optimization  (0) 2024.05.28

Pooling

객체 풀링은 가비지 컬렉터의 부하를 줄이기 위해 사용됩니다.

풀링은 시스템을 많이 사용하거나 저사양 하드웨어(예: 모바일)에서 실행할 때 성능을 극대화하는 훌륭한 방법입니다. Mono 가비지 컬렉터는 더 이상 사용되지 않는 객체를 잘 수집하지만, 큰 문제는 GC가 실행될 때마다 게임을 일시적으로 멈추어야 한다는 점입니다. 이는 게임에서 몇 초마다 작은 끊김 현상으로 나타날 수 있습니다. 특히 메모리와 프로세서 성능이 제한된 모바일에서는 큰 문제가 됩니다.

이 문제를 해결하는 한 가지 방법은 대부분의 객체를 풀링하는 것입니다. 객체가 더 이상 사용되지 않으면 풀에 넣고, 이후 동일한 유형의 새 인스턴스가 필요할 때 풀에서 쉽게 얻을 수 있는 객체가 있는지 확인합니다. 풀을 사용하면 GC의 부하가 줄어들어 게임의 끊김 현상이 줄어듭니다.

Path Pooling

A* Pathfinding Project에서 풀링은 주로 경로와 리스트에 사용됩니다. 이는 가장 많이 할당되는 것이기 때문입니다. 스크립트에 풀링 로직을 추가하는 것은 매우 적은 코드로 가능하지만, 올바르게 수행하지 않으면 오류가 발생하기 쉽습니다.

A* Pathfinding Project의 풀링은 일종의 수동 참조 카운팅에 기반합니다. 경로 객체를 사용하기 시작할 때 .Claim 함수를 호출해야 하고, 더 이상 사용하지 않을 때(예: 새 경로로 대체될 때)는 Release 함수를 호출해야 합니다. 경로에 대한 Release 함수가 호출되고 다른 스크립트가 사용하지 않을 때, 경로는 언제든지 재활용될 수 있습니다. 이는 변수들이 언제든지 초기화될 수 있음을 의미합니다. 따라서 경로에 대해 Release를 호출한 후에는 해당 경로에 대한 참조를 유지해서는 안 됩니다. 경로 클래스의 vectorPath 및 path 리스트도 경로가 재활용될 때 재활용됩니다. 따라서 이를 다시 사용하지 않도록 주의해야 합니다. 만약 vectorPath/path 변수만 사용하고 경로를 재활용하고 싶다면, 이를 로컬 변수에 저장하고 경로 객체의 vectorPath/path 변수를 null로 설정한 후 경로를 릴리스하십시오. 변수가 null이므로 경로 객체는 이를 재활용할 수 없으며, 따라서 안전하게 사용할 수 있습니다.

참조
Pathfinding.Path.Claim
Pathfinding.Path.Release


경로에 대해 Claim이나 Release를 호출하지 않으면, 단순히 풀링되지 않습니다. 따라서 풀링을 신경 쓰고 싶지 않다면 신경 쓰지 않아도 됩니다. 경로에 대해 Claim을 호출하지만 Release를 호출하지 않고 모든 참조를 제거하면, 여전히 가비지 컬렉터에 의해 수집되지만 풀로 반환되지는 않습니다. Claim을 호출하지 않고 Release를 호출하거나, 동일한 객체에 대해 여러 번 Release를 호출하면 오류가 로그에 기록됩니다.

다음은 경로 풀링 사용의 예입니다:

public class SomeAI : MonoBehaviour {
    ABPath path;

    public IEnumerator Start () {
        while (true) {
            GetComponent<Seeker>().StartPath(transform.position, transform.position + transform.forward * 10, OnPathComplete);
            yield return new WaitForSeconds(1); // Added to prevent infinite loop in this example
        }
    }

    void OnPathComplete (Path p) {
        // 이전 경로를 풀로 반환
        if (path != null) path.Release(this);

        path = p as ABPath;

        // 새로운 경로 요청
        path.Claim(this);
    }

    void Update () {
        // 에디터에서 경로 그리기
        if (path != null && path.vectorPath != null) {
            for (int i = 0; i < path.vectorPath.Count - 1; i++) {
                Debug.DrawLine(path.vectorPath[i], path.vectorPath[i + 1], Color.green);
            }
        }
    }
}


경로가 풀링될 때, vectorPath 및 path 필드(및 경로 유형에 더 많은 리스트가 있는 경우)는 또한 풀링됩니다. 따라서 경로 객체에서 vectorPath를 가져오고 경로 객체를 풀링한 후에도 계속 vectorPath를 사용하는 것은 안전하지 않습니다.

Claim 및 Release 함수는 객체 참조를 가져옵니다. 이는 사용자 오류 가능성을 줄이기 위해 주로 사용됩니다. 동일한 객체를 사용하여 경로가 여러 번 릴리스되거나 여러 번 클레임되지 않도록 오류 검사가 수행됩니다.

List Pooling

대부분의 사용자에게는 중요하지 않을 수 있지만, 고급 사용자에게는 관련이 있을 수 있습니다. 이 프로젝트는 제네릭 리스트의 풀링도 포함합니다. 이는 시스템이 매번 새로운 리스트를 할당하지 않도록 내부적으로 사용됩니다. 리스트 풀링 클래스는 사용하기 매우 간단하며,  Pathfinding.Util.ListPool.Claim 을 사용하여 참조를 얻고, 완료되면  Pathfinding.Util.ListPool.Release 를 사용하여 해제합니다. ListPool 클래스는 타입 인수를 사용하므로 모든 리스트 타입에 사용할 수 있습니다.

// 리스트 참조 얻기
List<int> myList = Pathfinding.Util.ListPool<int>.Claim();

// 작업 수행
for (int i = 0; i < 100; i++) myList.Add(i);
int fiftytwo = myList[52];

// 해제
Pathfinding.Util.ListPool<int>.Release(ref myList);
// Release 호출 시 'ref' 매개변수를 사용하여 myList 변수를 동시에 null로 설정하여
// 리스트를 실수로 다시 사용하는 것을 방지합니다.



Debugging

풀링 설정이 복잡한 스크립트와 함께 사용되는 경우 설정이 어렵고 결과를 즉시 확인할 수 없기 때문에 디버거를 사용하는 것이 좋습니다. A*  Pathfinding Project에는 디버거가 포함되어 있습니다. Components ->  Pathfinding  -> Debugger를 GameObject에 추가하십시오.

Pathfinding.AstarDebugger 에 대한 자세한 정보는 참조하십시오.


또한 최적화 탭에서 ASTAR_POOL_DEBUG 정의를 활성화하는 것도 매우 유용합니다. 무료 버전만 있는 경우(최적화는 프로 전용 기능이므로),  Path.cs 파일을 열고 ASTAR_POOL_DEBUG를 언급하는 상단의 줄을 주석 해제하십시오.

이를 활성화하면 파괴된 모든 경로가 풀링되었는지, 잘못된 사용이 있었는지에 대한 정보를 로그에 기록합니다.

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

Deploying for mobile/uwp  (0) 2024.05.28
Optimization > Compiler Directives  (0) 2024.05.28
Optimization > Heuristic Optimization  (0) 2024.05.28
Optimization  (0) 2024.05.28
Deploying  (0) 2024.05.28

Heuristic Optimization

휴리스틱 최적화를 사용하여 상당한 속도 향상을 얻는 방법에 대한 튜토리얼입니다.

경로 찾기 검색이 이루어질 때 일반적으로  heuristic 이 사용됩니다. 휴리스틱은 목표까지 얼마나 멀리 있는지를 대략적으로 추정하는 방법입니다. 일반적으로 실제  euclidean distance 를 직접 사용합니다. 이는 빠르고 일반적으로 상대적으로 좋은 결과를 제공하기 때문입니다. 하지만 작은 장애물만 있는 열린 공간이 아닌 세계에서는 이 추정이 좋지 않습니다. 그 결과, 휴리스틱이 더 나았더라면 필요하지 않았을 많은 노드를 검색하게 됩니다.

A* Inspector -> Settings -> Heuristic Optimization
휴리스틱 최적화 설정을 보유합니다.

Heuristic Optimization 참조
A* Pro 기능
이 기능은 A* Pathfinding Project Pro 기능입니다. 이 함수/클래스/변수는 A* Pathfinding Project의 무료 버전에는 존재하지 않거나 기능이 제한될 수 있습니다. Pro 버전은 여기에서 구매할 수 있습니다.
이 설정은  AstarPath.euclideanEmbedding 멤버에 해당합니다.



Results

이 기술은 목표를 찾기 위해 검색해야 하는 노드 수를 크게 줄일 수 있습니다. 이는 성능을 크게 향상시킬 수 있습니다.

아래의 두 이미지를 비교해 보십시오. 첫 번째 이미지에서는 이 최적화가 비활성화되어 있고, 두 번째 이미지에서는 활성화되어 있습니다. 두 번째 이미지에서는 검색이 더 정보에 기반하여 불필요한 방향을 피할 수 있습니다.

휴리스틱 최적화 비활성화



휴리스틱 최적화 활성화



Background

세계가 정적이거나 업데이트가 매우 드문 경우, 노드 간의 거리를 미리 계산하고 훨씬 더 나은 휴리스틱을 얻을 수 있는 기술을 사용할 수 있습니다. 이는 많은 경우 경로 찾기 성능을 10배 향상시킬 수 있습니다.

최적의 휴리스틱은 물론 그래프의 모든 노드 간 거리를 알고 있을 때입니다. 하지만 이는 계산에 오랜 시간이 걸리고 많은 공간을 차지하기 때문에 실용적이지 않습니다.

따라서 몇몇 노드(이후 "피벗 포인트"라 부름)를 선택하고 그 노드에서 그래프의 모든 다른 노드까지의 거리를 계산합니다. 이를 통해 꽤 좋은 휴리스틱을 계산할 수 있습니다.

사용된 휴리스틱과 관계없이, A* 알고리즘은 항상 최단 경로에 있을 수 있는 모든 노드를 검색합니다. 따라서 이 기술은 예를 들어 큰 열린 그리드 그래프에서 성능 향상을 제공하지 않습니다. 왜냐하면 경로 검색은 어쨌든 최단 경로의 노드보다 훨씬 더 많이 검색하지 않기 때문입니다.

아래 이미지는 완전히 비어 있는 그리드 그래프와 3개의 다른 최적 경로를 보여줍니다. 알고리즘은 녹색 영역에 있는 모든 노드를 검색해야 합니다. 왜냐하면 그들은 모두 동일한 길이를 가지고 있기 때문입니다. 그리드에서는 많은 최적 경로가 있기 때문에 모든 노드가 최적 경로에 있습니다.


A*  Pathfinding Project는 수동으로 선택하는 것보다 더 좋은 피벗 포인트를 자동으로 선택하는 알고리즘을 가지고 있습니다. 피벗 포인트를 수동으로 배치하는 것보다 무작위로 선택하는 것이 더 나은 경우가 많습니다.

Placing Pivot Points Manually

이 피벗 포인트를 배치할 때 중요한 점은 이들이 세계의 막다른 곳에 있어야 한다는 것입니다. 많은 경로가 피벗 포인트로 확장될 수 있고 여전히 최단 경로가 될 때 가장 잘 작동합니다.

아래 이미지에서 보라색으로 표시된 피벗 포인트를 볼 수 있습니다. 왼쪽 하단의 에이전트가 녹색 원으로 경로를 요청했습니다.


게임에 특정한 지식을 적용할 수도 있습니다. 예를 들어 TD 게임에서는 거의 모든 유닛이 단일 목표로 이동합니다. 따라서 목표에 단일 피벗 포인트를 배치하면 모든 경로가 훨씬 빠르게 계산됩니다. 왜냐하면 그 지점까지의 최적 거리가 이미 알려져 있기 때문입니다.

Placing Pivot Points Automatically

두 가지 모드가 있습니다. 무작위로 모든 포인트를 선택하거나, 가능한 한 서로 멀리 떨어져 있는 노드를 선택하는 알고리즘을 적용하는 것입니다. 두 번째 방법은 훨씬 높은 품질의 휴리스틱을 제공하지만, 초기 거리 계산을 직렬로 수행해야 하므로 계산 속도가 느립니다.

아래 이미지에서 무작위로 선택한 5개의 포인트와 서로 멀리 떨어진 5개의 포인트를 비교할 수 있습니다. 서로 멀리 떨어진 포인트는 막다른 곳이나 맵의 구석에 배치되는 경향이 있어 매우 좋은 결과를 제공합니다.


사용할 피벗 포인트의 수는 다양합니다. 가장 좋은 방법은 다양한 값을 시도해보고 어떤 것이 게임에 가장 적합한지 확인하는 것입니다. 피벗 포인트가 100개를 넘으면 검색할 노드 수가 줄어드는 이득보다 오버헤드가 더 커집니다. 1에서 100 사이를 권장하며, 다양한 값을 시도해보고 어떤 것이 가장 적합한지 확인하십시오.

게임 시작 시 경로 찾기가 작동할 때까지 지연이 발생할 수 있습니다. 이는 휴리스틱 조회를 사전 처리하느라 바쁘기 때문입니다. 이 지연이 문제가 된다면 "Random" 모드를 사용하거나 피벗 포인트 수를 줄이십시오.

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

Optimization > Compiler Directives  (0) 2024.05.28
Optimization > Pooling  (0) 2024.05.28
Optimization  (0) 2024.05.28
Deploying  (0) 2024.05.28
Spherical Worlds  (0) 2024.05.28

성능을 향상시키기 위한 몇 가지 팁

시스템 성능을 높이는 다양한 방법이 있습니다.

하지만 먼저 프로파일링을 하세요! 최적화를 시도하기 전에, 프로파일러를 사용하여 게임의 어느 부분이 속도를 저하시키는지 파악해야 합니다. 멀티스레딩을 사용하는 경우, 경로 찾기는 별도의 경로 찾기 스레드에만 나타납니다(이것은 Unity 프로파일러에서 선택할 수 있습니다).

 

General tips

성능 평가 시 "Show Graphs"(A* 인스펙터 하단)가 비활성화되어 있는지 확인하십시오. 큰 그래프의 경우, 매 프레임마다 이를 그리는 오버헤드가 게임을 심각하게 느리게 할 수 있습니다(이는 에디터에만 적용되며 독립 실행형 게임에는 적용되지 않습니다).

Optimizing game startup

게임이 시작될 때 AstarPath 컴포넌트는 기본적으로 모든 그래프를 스캔합니다. 이는 그래프 설정 및 그래프 크기에 따라 시간이 걸릴 수 있습니다.

정적으로 고정된 월드가 있는 경우, 스캔된 그래프를 캐시하고 시작 시 캐시를 로드할 수 있습니다. 이에 대한 자세한 내용은 Saving and Loading Graphs 를 참조하십시오.

또는, 그래프가 스캔되는 동안 진행 막대를 표시하거나 게임을 완전히 멈추지 않으려면 비동기적으로 그래프를 스캔할 수 있습니다. 먼저 A* Inspector -> Settings -> Scan On Awake를 비활성화하십시오. 그런 다음 사용자 정의 스크립트에서 다음과 같이 비동기적으로 그래프를 스캔할 수 있습니다:

IEnumerator Start () {
    foreach (Progress progress in AstarPath.active.ScanAsync()) {
        Debug.Log("Scanning... " + progress.ToString());
        yield return null;
    }
}


그래프를 캐시하는 것이 옵션이 아닌 경우(예를 들어 동적 레벨이 있는 경우), 그래프 설정을 조정하여 스캔 속도를 높이거나 그래프 유형을 변경하는 것이 좋습니다. 일반적으로 그리드 그래프는 recast 그래프보다 스캔 속도가 빠르지만, 세계가 매우 큰 경우는 예외입니다. 그러나 경로 찾기는 보통 recast 그래프에서 더 빠릅니다. 다음 섹션에서는 다양한 그래프 유형에 대한 몇 가지 팁을 포함하고 있습니다.

Graph Types 에 대한 자세한 내용은 그래프 유형을 참조하십시오.

 

Optimizing Grid Graph Scanning

 

그리드 그래프를 스캔하는 시간을 줄이는 몇 가지 방법이 있습니다:

  • 가능하다면 해상도를 줄이십시오.
  • 완전히 평평한 세계가 있는 경우 높이 테스트를 비활성화하십시오.
  • 침식을 비활성화하십시오. 이는 작은 성능 오버헤드를 가집니다.
  • 충돌 테스트가 필요 없는 경우 이를 비활성화하십시오.

Optimizing Recast Graph Scanning

  • 셀 크기를 늘리십시오. 이는 그래프가 세계를 보컬화하는 해상도를 줄일 것입니다.
  • 타일링을 사용하고 적절한 크기의 타일을 사용하십시오. 64에서 256 보컬 사이의 값이 권장됩니다. 타일은 병렬로 스캔될 수 있어 다중 코어 컴퓨터에서 상당한 속도 향상을 제공할 수 있습니다. 그러나 각 타일에는 약간의 오버헤드가 있으므로 너무 작은 타일 크기는 스캔 속도를 느리게 합니다.
  • 콜라이더를 래스터화하십시오. 콜라이더는 일반적으로 메쉬보다 훨씬 간단하여 래스터화가 더 빠릅니다. 또한, 물리 엔진을 사용하여 효율적으로 쿼리할 수 있으므로 그래프 업데이트 성능이 향상됩니다.
  • 그래프의 경계 상자를 줄여 필요한 세계 부분만 덮도록 하십시오.

Optimizing Navmesh Graph Scanning

Navmesh 그래프를 스캔하는 데 걸리는 시간은 입력 메쉬의 삼각형 수에 거의 전적으로 의존합니다. 이를 줄일 수 있다면 그래프는 더 빠르게 스캔될 것입니다.

Optimizing Movement Scripts

이동 스크립트는 성능에 큰 영향을 미칠 수 있습니다. 포함된 다양한 이동 스크립트는 서로 다른 성능 특성을 가지고 있습니다.

  • AILerp 는 가장 빠르지만, 움직임이 매우 현실적이지 않아 게임에 적합하지 않을 수 있습니다.
  • AIPath 는 조금 느리지만 로컬 회피를 처리할 수 있으며 더 부드러운 움직임을 제공합니다(이는 게임에 따라 바람직할 수도 있습니다).
  • RichAI 는 가장 느리지만, 경로를 따를 때 AIPath보다 더 견고하며 메쉬 간 링크를 더 잘 지원합니다.
  • FollowerEntity 는 일반적으로 성능 측면에서 AILerp와 AIPath 사이에 있습니다. 멀티스레딩을 사용하여 메인 스레드를 차단하지 않지만, 여전히 많은 작업을 백그라운드에서 수행하며 다른 스크립트보다 더 복잡합니다. 현재 베타 상태이지만 대부분의 경우 AIPath 및 RichAI보다 추천합니다.

 

캐릭터에 CharacterController 또는 Rigidbody 컴포넌트를 부착하지 않는 것이 좋습니다. 이들은 상당히 느립니다. 이들이 부착되지 않은 경우, AIPath 및 RichAI 스크립트는 대신 단순한 레이캐스트를 사용하여 지면을 감지하며, 이는 일반적으로 훨씬 빠릅니다.

Movement scripts 에 대한 자세한 내용은 이동 스크립트를 참조하십시오.



Optimizing Pathfinding

경로 찾기는 실제로 성능 병목 현상이 아닐 때가 많습니다. 이는 큰 그리드 그래프와 많은 에이전트를 보유한 경우에만 병목 현상이 되기 쉽습니다. 프로파일러를 사용하여 경로 찾기 스레드가 매우 바쁜지 확인하십시오.

하지만 처리량이 병목 현상이 아니더라도 경로 찾기 지연 시간을 개선하는 것이 좋습니다. 지연 시간은 경로 요청이 발행된 후 단일 경로를 계산하는 데 걸리는 시간입니다. 많은 에이전트의 목적지를 한꺼번에 설정하면 높은 지연 시간으로 인해 일부 에이전트가 다른 에이전트보다 뒤처질 수 있습니다. 이들은 경로를 재계산하기 위해 차례를 기다려야 합니다.


위 그림에서 볼 수 있듯이, 210개의 에이전트가 동시에 새로운 목적지를 받으면서 경로 찾기 스레드가 경로를 재계산하느라 매우 바쁩니다. 그러나 지연 시간은 여전히 ​​매우 낮아 모든 경로가 다음 프레임이 시작되기 전에 계산됩니다. 이 경우 성능은 양호합니다.

경로 찾기 성능을 개선해야 하는 경우 다음 팁을 참고하십시오:

  • 가장 쉬운 방법은 멀티스레딩을 활성화하는 것입니다(A* Inspector -> Settings). 이렇게 하면 경로 찾기가 Unity 스레드 대신 별도의 스레드에서 실행되어 성능이 크게 향상될 수 있습니다. 요즘 거의 모든 컴퓨터와 심지어 전화기에도 많은 코어가 있기 때문입니다.
  • 경로 결과 로깅은 성능에 큰 영향을 미칩니다. 경로 찾기 처리량을 50%까지 줄이는 경우도 많습니다. 필요하지 않으면 경로 로깅을 비활성화해야 합니다. A* Inspector -> Settings -> Path Log Mode를 None으로 설정하십시오.
  • 새 경로가 계산되는 빈도를 줄이십시오.
    • 이동 스크립트의 Recalculate Paths Automatically 필드를  Dynamic 으로 설정하여 에이전트가 목표에서 멀리 떨어져 있을 때는 경로를 덜 자주 재계산하고, 목표가 많이 변경되었을 때는 더 자주 재계산하도록 합니다.
    •  EveryNSeconds 모드를 사용하는 경우 기간 필드를 늘려보십시오.
    • 자동 경로 재계산을 비활성화하고 필요할 때만 경로를 재계산하는 게임 전용 논리를 사용할 수도 있습니다.
  • 수정자의 품질 설정을 낮추십시오. 특히 SimpleSmoothModifier 가 경로를 나누는 세그먼트 수를 줄일 수 있습니다. AIPath 이동 스크립트는 경로 자체가 특히 부드럽지 않더라도 경로를 부드럽게 따르는 데 이미 매우 잘 작동합니다.
  • 그리드 그래프 대신 recast 그래프를 사용하는 것이 일반적으로 경로 찾기 성능을 향상시킵니다. recast 그래프는 동일한 영역을 덮기 위해 훨씬 적은 노드를 필요로 하기 때문입니다.
  • 정적 맵에서는 휴리스틱 최적화가 옵션이 될 수 있습니다.  Heuristic Optimization 를 참조하십시오.

More Tips

성능을 향상시키기 위한 더 많은 방법을 보려면 하위 페이지를 참조하십시오.

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

Optimization > Pooling  (0) 2024.05.28
Optimization > Heuristic Optimization  (0) 2024.05.28
Deploying  (0) 2024.05.28
Spherical Worlds  (0) 2024.05.28
Editing graph connections manually  (0) 2024.05.28

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

Optimization > Heuristic Optimization  (0) 2024.05.28
Optimization  (0) 2024.05.28
Spherical Worlds  (0) 2024.05.28
Editing graph connections manually  (0) 2024.05.28
Extending The System > Writing Modifiers  (0) 2024.05.28

비플래너 표면에서 경로 찾기를 사용하는 방법에 대한 튜토리얼

일부 게임에서는 행성이나 고리와 같이 항상 '위' 방향이 일정하지 않은 표면에서 경로 찾기 및 로컬 회피를 사용해야 할 수 있습니다. 아래 이미지에서 몇 가지 세계 형태의 예를 볼 수 있습니다. 하지만 원하는 만큼 복잡하게 만들 수 있습니다(아래의 몇 가지 팁을 참조하세요).

참고
패키지에는 구형 세계에 대해 경로 찾기 및 로컬 회피를 구성하는 방법을 보여주는 "Spherical"이라는 예제 장면이 포함되어 있습니다.

 

Configuring a graph


네비메시 그래프와 포인트 그래프 모두 사용할 수 있습니다. 그러나 대부분의 경우 네비메시 그래프를 권장합니다. 설정 방법은 네비메시 튜토리얼(예:  Creating a navmesh manually )을 따르며, 평면 대신 구와 같은 모양을 만든다는 점만 다릅니다.
네비메시 그래프 설정에서 'Recalculate Normals'를 비활성화해야 합니다. 이를 활성화한 상태에서는 Y+ 축을 기준으로 노멀을 재계산하기 때문에, 구형 세계에서는 올바르지 않게 됩니다.



Movement scripts

곡면을 따라 캐릭터를 이동시키려면 사용자 정의 이동 스크립트를 사용하거나 포함된 AIPathAlignedToSurface 컴포넌트를 사용할 수 있습니다. AIPathAlignedToSurface 스크립트는 AIPath에서 상속받아 곡면에 더 적합하도록 몇 가지를 변경합니다. 첫째, 바닥 아래의 노멀을 사용하여 표면에 맞게 정렬합니다. 둘째, 중력을 세계의 위 방향이 아닌 캐릭터의 위 방향에 상대적으로 만듭니다. 예를 들어 중력을 (0, -10, 0)으로 설정하면 캐릭터가 구의 아래쪽에 있어도 표면 쪽으로 끌립니다.

네비메시 그래프에서 높은 경로 품질을 위해  funnel modifier 를 사용하는 것이 좋습니다. 그러나 곡면에서 올바르게 작동하려면 splitAtEveryPortal 이 활성화되어 있어야 합니다.

메시 콜라이더를 지면으로 사용하는 경우, AIPathAlignedToSurface 스크립트는 표면 노멀을 보간하여 이동을 더 부드럽게 만듭니다. 그러나 이는 콜라이더에 사용된 메시가 읽기 가능한 경우에만 작동합니다. 메시 자산 가져오기에서 'Read/Write Enabled' 체크박스를 확인하세요.

Local avoidance

로컬 회피는 기본적으로 곡면에서 지원됩니다. 장면에 RVOSimulator를 추가하고 에이전트에 RVOController를 추가한 다음(자세한 내용은  Local Avoidance 참조) RVOSimulator의  movement plane option을 'Arbitrary'로 설정하면 됩니다. AIPathAlignedToSurface 스크립트는 RVOController에 표면의 방향을 알려주는 작업을 처리합니다. 사용자 정의 이동 스크립트를 작성하는 경우  RVOController.movementPlane 속성을 설정하여 방향을 업데이트해야 합니다.

Tips for good non-planar navmeshes

좋은 네비메시는 대략적으로 동일한 크기의 삼각형으로 구성되어야 하며, 삼각형이 너무 작으면 경로 찾기가 불필요하게 느려지고, 너무 크면 정밀도가 너무 낮아집니다. 내장된 이동 스크립트를 사용하는 경우 매우 급격한 표면 변화를 피해야 합니다. 예를 들어, 일반 큐브 대신 둥근 큐브를 사용하십시오.

아래에는 좋은 네비메시와 나쁜 네비메시의 예가 있습니다. 이들은 매우 간단한 예제 네비메시이지만, 피해야 할 일반적인 아이디어를 전달하기를 바랍니다.

추천하지 않음.
이 네비메시는 날카로운 모서리(이동하기 어려움)와 매우 큰 삼각형(좋은 경로를 찾기에는 너무 큼)이 포함되어 있습니다. 경로는 노드별로 검색됩니다.
추천하지 않음.
이 네비메시는 노드 크기가 더 적절하게 세분화되었지만 여전히 매우 날카로운 모서리가 있습니다.
추천하지 않음.
이 네비메시는 날카로운 모서리가 없지만 삼각형 크기가 매우 달라서 일반적으로 경로 품질에 좋지 않습니다.
추천.
이 네비메시는 좋습니다.
추천.
이 네비메시는 좋습니다.
추천하지 않음.
이 네비메시는 삼각형 크기가 매우 다릅니다(특히 극지방 근처). 일반적으로 경로 품질에 좋지 않습니다.
추천.
이 네비메시는 좋습니다.

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

Optimization  (0) 2024.05.28
Deploying  (0) 2024.05.28
Editing graph connections manually  (0) 2024.05.28
Extending The System > Writing Modifiers  (0) 2024.05.28
Extending The System > Writing Graph Generators  (0) 2024.05.28

그래프 구조 편집 방법

때로는 그래프가 거의 올바르지만, 약간 수정이 필요한 경우가 있습니다. 이는 포인트 그래프에서 가장 일반적이며, 이 페이지는 주로 해당 그래프 유형을 대상으로 합니다. 하지만 이 방법은 어느 정도 다른 그래프 유형에서도 작동합니다.

점프, 등반, 텔레포터 및 기타 특수 이동 로직을 처리하려면 Off-mesh links 링크를 참조하세요.

 

 

포인트 그래프에서 연결을 추가하거나, 기존 연결을 제거하거나, 일부 연결의 비용을 변경하려는 경우 NodeLink 컴포넌트를 사용할 수 있습니다. 포인트 그래프는 일반적으로 GameObjects의 컬렉션에서 생성됩니다. NodeLink 컴포넌트를 하나의 노드에 추가한 다음, 대상을 다른 노드로 설정할 수 있습니다. 옵션을 변경하여 두 노드 사이에 새 연결을 추가하거나, 이미 존재하는 연결을 제거하거나(있는 경우), 두 노드 사이의 연결 비용을 변경할 수 있습니다. 이 새로운 연결은 원래 포인트 그래프에 의해 생성된 것과 동일합니다.

 

그래프를 쉽게 수정할 수 있도록 몇 가지 키보드 단축키가 있습니다. Unity의 메뉴바 -> Edit -> Pathfinding에서 메뉴 항목을 찾을 수 있습니다.

  • alt+ctrl+L (macOS에서는 alt+cmd+L): 두 노드를 연결합니다. NodeLink 연결이 없으면 노드를 연결하고, 이미 연결된 경우 명시적으로 연결을 삭제합니다.
  • alt+ctrl+U (macOS에서는 alt+cmd+U): 두 노드의 연결을 해제합니다. NodeLink가 연결을 추가하거나 삭제하도록 구성되어 있는지와 상관없이 두 노드를 연결하는 NodeLink 컴포넌트를 제거합니다.
  • alt+ctrl+B (macOS에서는 alt+cmd+B): 선택한 GameObjects의 모든 NodeLink 컴포넌트를 제거합니다.

Editing graph connections using code

코드를 사용하여 그래프 연결을 수정할 수도 있습니다.

// 두 노드를 연결합니다.
var node1 = AstarPath.active.GetNearest(transform.position, NNConstraint.None).node;
var node2 = AstarPath.active.GetNearest(transform.position + Vector3.right, NNConstraint.None).node;
var cost = (uint)(node2.position - node1.position).costMagnitude;

GraphNode.Connect(node1, node2, cost, OffMeshLinks.Directionality.TwoWay);

 

참조
GraphNode.Connect
GraphNode.Disconnect
GraphNode.ContainsOutgoingConnection
Graph Updates
Creating graphs during runtime
Graph Types

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

Deploying  (0) 2024.05.28
Spherical Worlds  (0) 2024.05.28
Extending The System > Writing Modifiers  (0) 2024.05.28
Extending The System > Writing Graph Generators  (0) 2024.05.28
Extending The System  (0) 2024.05.28

Modifiers는 경로를 후처리하여 단순화하거나 부드럽게 하는 작은 스크립트입니다.

Modifiers는 확장 가능한 애드온 아키텍처를 사용하여 시스템에 내장되어 있어 자신만의 modifier를 쉽게 추가할 수 있습니다. 이 튜토리얼에서는 프로젝트에 포함된 경로 부드럽게 하는 모듈(Components-> Pathfinding ->Modifiers->SimpleSmooth)과 유사한 간단한 경로 부드럽게 하는 모듈을 작성하는 방법을 보여드리겠습니다.

New Script

 프로젝트 내에 새 C# 스크립트를 만들고, 이름을 ModifierTutorial로 지정합니다. 좋아하는 스크립트 편집기에서 열면 다음과 같은 기본 클래스가 있습니다:

using UnityEngine;
using System.Collections;

public class ModifierTutorial : MonoBehaviour {

    // 초기화에 사용
    void Start () {
    
    }
    
    // 매 프레임 호출
    void Update () {
    
    }
}

이제 Seeker가 있는 모든 GameObject에 부착할 수 있는 modifier를 만들 것입니다. 이를 위해 MonoModifier 클래스를 상속받아야 합니다.

이 클래스는 Seeker와 Modifier 간의 기본 통신을 처리하며, modifier 작성에 큰 도움이 됩니다.

추상 클래스이므로 컴파일러 오류를 피하기 위해 일부 함수를 구현해야 합니다:

using UnityEngine;
using System.Collections;
using Pathfinding;

public class ModifierTutorial : MonoModifier {
    public override int Order { get { return 60; } }
    public override void Apply (Path p) {
    }
}

여기까지 작성한 것은 가장 기본적인 modifier로, 실제로 아무것도 하지 않지만, 나중에 modifier를 작성할 때 템플릿으로 사용할 수 있습니다.

MonoModifier는 Pathfinding 네임스페이스에 존재하므로 "using Pathfinding" 구문을 추가했습니다.

Order 속성은 다른 modifier와의 관계에서 이 modifier가 호출될 시점을 결정합니다. 값이 클수록 나중에 호출됩니다. Order가 10인 modifier와 20인 modifier가 있으면, 10인 modifier가 먼저 호출됩니다. 내장된 modifier는 0에서 50 사이의 값을 사용하므로, 이 modifier의 order를 60으로 설정하면 내장된 모든 modifier 이후에 실행됩니다(있는 경우).

Apply 함수는 코드가 들어갈 곳이며, 경로가 후처리될 때 호출됩니다. 제공된 Path 객체는 후처리할 경로입니다.

 

Smoothing

사용할 부드럽게 하는 알고리즘은 매우 간단합니다. 각 포인트를 일정량씩 서로 가깝게 그립니다. Pathfinding.Path.vectorPath 배열에서 작업할 것이며, Pathfinding.Path.path 배열은 변경하지 않아야 합니다. 그렇지 않으면 다른 modifier가 깨질 수 있습니다. 먼저 경로가 성공했는지와 vectorPath 배열이 null이 아닌지 확인해야 합니다. 그렇지 않으면 NullReferenceException이 발생할 수 있습니다. 또한 경로가 단일 세그먼트로만 구성된 경우(즉, 포인트가 2개 이하인 경우) 부드럽게 할 수 없으므로 이 경우도 건너뜁니다.

public override void Apply (Path path) {
    if (path.error || path.vectorPath == null || path.vectorPath.Count <= 2) {
        return;
    }

이제 실제 부드럽게 하기 알고리즘을 작성합니다: 경로를 더 작은 세그먼트로 나누고, 경로 배열을 루프하여 첫 번째와 마지막 포인트를 제외한 각 포인트를 인접한 포인트에 가깝게 이동시킵니다. 이 작업을 몇 번 반복하여 경로가 충분히 부드러워질 때까지 진행합니다.

public int iterations = 5;

public int subdivisions = 2;

public override void Apply (Path path) {
    if (path.error || path.vectorPath == null || path.vectorPath.Count <= 2) {
        return;
    }
    // 분할 횟수는 0보다 작을 수 없습니다.
    subdivisions = Mathf.Max(subdivisions, 0);

    // 잘못된 값을 입력하지 않도록 합니다.
    if (subdivisions > 12) {
        Debug.LogWarning("경로를 12번 이상 분할하는 것은 많습니다. 메모리 문제가 발생할 수 있으며 게임이 느려질 수 있습니다.\n" +
            "이 메시지가 기록되면 부드럽게 처리가 적용되지 않습니다.");
        subdivisions = 12;
        return;
    }

    // 부드럽게 처리된 경로를 저장할 새 리스트 생성
    List<Vector3> newPath = new List<Vector3>();
    List<Vector3> originalPath = path.vectorPath;

    // 원래 배열의 한 세그먼트(선)는 더 작은 세그먼트로 나누어집니다.
    int subSegments = (int)Mathf.Pow(2, subdivisions);
    float fractionPerSegment = 1F / subSegments;
    for (int i = 0; i < originalPath.Count - 1; i++) {
        for (int j = 0; j < subSegments; j++) {
            // Vector3.Lerp를 사용하여 포인트를 선을 따라 올바른 위치에 배치합니다.
            newPath.Add(Vector3.Lerp(originalPath[i], originalPath[i+1], j*fractionPerSegment));
        }
    }

    // 마지막 포인트 추가
    newPath.Add(originalPath[originalPath.Count-1]);

    // [iterations] 횟수만큼 경로를 부드럽게 합니다.
    for (int it = 0; it < iterations; it++) {
        // 첫 번째와 마지막 포인트를 제외한 모든 포인트를 루프합니다.
        for (int i = 1; i < newPath.Count-1; i++) {
            // 현재 포인트와 인접한 두 포인트의 평균으로 새로운 포인트 설정
            Vector3 newpoint = (newPath[i] + newPath[i-1] + newPath[i+1]) / 3F;
            newPath[i] = newpoint;
        }
    }

    // 새 경로를 p.vectorPath 필드에 할당합니다.
    path.vectorPath = newPath;
}

새 경로가 p.vectorPath 필드에 할당되므로 다른 스크립트에서 이를 찾을 수 있습니다.

Pooling

메모리 효율성을 높이기 위해 리스트를 풀링하여 가비지 컬렉터의 작업을 줄일 수 있습니다.

// 풀에서 빈 리스트 가져오기
List<Vector3> newPath = Pathfinding.Util.ListPool<Vector3>.Claim();
List<Vector3> originalPath = p.vectorPath;

...

// 새 경로를 p.vectorPath 필드에 할당합니다.
p.vectorPath = newPath;
// 이전 경로는 더 이상 필요하지 않으므로 풀에 반환합니다.
Pathfinding.Util.ListPool<Vector3>.Release(originalPath);
Pooling  참조

 

Conclusion

이 튜토리얼의 끝입니다. 이 튜토리얼이 경로 modifier 작성에 도움이 되기를 바랍니다. 전체 스크립트는 여기에서 확인할 수 있습니다:  ModifierTutorial.cs

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

Spherical Worlds  (0) 2024.05.28
Editing graph connections manually  (0) 2024.05.28
Extending The System > Writing Graph Generators  (0) 2024.05.28
Extending The System  (0) 2024.05.28
Creating graphs during runtime  (0) 2024.05.28

+ Recent posts