경로를 계산하는 방법에 대한 튜토리얼을 제공하겠습니다.

Introduction

경로 탐색은 경로를 계산하는 것입니다. 그러나 이에는 여러 가지 변형이 있습니다. 가장 기본적인 경로는 한 점 A에서 다른 점 B로 이동하는 것이지만, 다른 경로 유형에는 예를 들어 후보 지점 목록의 가장 가까운 지점으로의 경로를 계산하는 경로 유형이 있고, 무작위 방향으로 경로를 계산하는 경로 유형도 있습니다.

또한, 내장된 이동 스크립트를 사용할 수도 있으며, 이 경우에는 경로 탐색 요청을 처리하려고 할 것입니다. 또는 사용자 정의 이동 스크립트를 작성하려고 할 수도 있으며, 이 경우에는 경로 탐색 요청을 직접 처리해야 합니다. 게다가, 어떤 이동 스크립트도 사용하지 않고 경로를 계산할 수도 있습니다. 예를 들어, 어떤 이동도 발생하지 않은 채로 플레이어에게 미리 경로를 표시하려고 할 수 있습니다.

이 페이지에서는 여러 가지 다양한 방법으로 경로를 계산하는 방법에 대해 다룰 것입니다.

Using a built-in movement script

내장된 이동 스크립트 중 하나를 사용하는 경우, 에이전트는 주기적으로 경로를 완전히 자동으로 다시 계산합니다. 해야 할 일은 단순히  ai.destination 속성을 대상 지점으로 설정하거나  AIDestinationSetter 를 사용하여 기존 게임 오브젝트를 향해 이동하도록 만드는 것입니다. 에이전트가 경로를 얼마나 자주 다시 계산하는지는 이동 스크립트의 일부인  AutoRepathPolicy 의 속성을 사용하여 제어할 수 있습니다.

void PointAndClick (IAstarAI ai) {
    // 마우스 버튼이 눌렸는지 확인합니다.
    if (Input.GetMouseButton(0)) {
        var cam = Camera.main;
        // 레이캐스트가 모든 콜라이더에 충돌하도록 설정합니다.
        LayerMask mask = -1;
        // 커서에서 레이를 발사하여 세계에서 어디에 충돌하는지 확인합니다.
        if (Physics.Raycast(cam.ScreenPointToRay(Input.mousePosition), out var hit, Mathf.Infinity, mask)) {
            // AI의 목적지를 레이가 충돌한 지점으로 설정합니다.
            ai.destination = hit.point;
        }
    }
}

 

경우에 따라서는 ai.SearchPath 를 사용하여 에이전트가 즉시 경로를 다시 계산하도록 강제할 수 있습니다.

경로 탐색 요청에 대해 더 직접적인 제어를 원하는 경우 ai.SetPath 메서드를 사용하여 경로 탐색 요청 또는 이미 계산된 경로를 에이전트에 할당할 수 있습니다. 그러나 이렇게 할 경우 자동 경로 재계산을 비활성화해야 합니다. 그렇지 않으면 에이전트가 설정한 경로를 곧바로 자신의 경로로 덮어씁니다.

// 자동 경로 재계산을 비활성화합니다.
ai.canSearch = false;
var pointToAvoid = enemy.position;
// AI를 적으로부터 피하도록 설정합니다.
// 경로는 약 20개의 월드 단위일 것입니다 (1개의 월드 단위를 이동하는 기본 비용은 1000입니다).
var path = FleePath.Construct(ai.position, pointToAvoid, 1000 * 20);
ai.SetPath(path);
IAstarAI.SetPathIAstarAI.SearchPath 을 확인하십시오.

모든 경로 유형을 시연한 Path Types 를 참조하십시오.

 

Using the Seeker component

자체 이동 스크립트를 작성하거나 다른 이유로 경로를 직접 계산해야 하는 경우, Seeker 구성 요소를 사용하여 경로를 계산할 수 있습니다. Seeker 구성 요소는 모든 GameObject에 연결할 수 있으며 해당 GameObject에 대한 경로 탐색 요청을 처리합니다. 경로를 요청하기 쉽게 만드는 편리한 메서드 모음이 있으며, 다양한 경로 탐색 설정과  path modifiers를 처리할 수도 있습니다.

Writing a movement script 참조

built-in movement scripts는 경로를 계산하기 위해 내부적으로 Seeker 구성 요소를 사용합니다. FollowerEntity 구성 요소는 예외입니다. FollowerEntity는 Unity의 Entity Component System을 내부적으로 사용하며, 이것은 전통적인 Unity 구성 요소와 잘 호환되지 않기 때문입니다.

참고: Seeker는 한 번에 하나의 경로 탐색 요청만 처리합니다. 이전 경로 요청이 완료되기 전에 새로운 경로를 요청하면 이전 경로 요청이 취소됩니다. 동시에 여러 경로를 계산하려면 Seeker를 건너뛰고 AstarPath 클래스를 직접 사용해야 합니다.
 
void Start () {
    //  이 GameObject에 연결된 seeker 구성 요소를 가져옵니다
    var seeker = GetComponent<Seeker>();

    // 현재 위치에서 앞으로 10 단위의 위치로 새 경로 요청을 예약합니다.
    // 경로가 계산되면, 취소되지 않은 한 OnPathComplete 메서드가 호출됩니다.
    seeker.StartPath(transform.position, transform.position + Vector3.forward * 10, OnPathComplete);

    // 여기서 경로가 계산되는 것은 아닙니다.
    // 단지 계산을 위해 대기열에 추가된 것뿐입니다.
}

void OnPathComplete (Path path) {
    // 경로가 이제 계산되었습니다!

    if (path.error) {
        Debug.LogError("Path failed: " + path.errorLog);
        return;
    }

    // 사용 중인 경로 유형으로 경로를 캐스트합니다
    var abPath = path as ABPath;

    // 씬 뷰에 경로를 10초 동안 그립니다
    for (int i = 0; i < abPath.vectorPath.Count - 1; i++) {
        Debug.DrawLine(abPath.vectorPath[i], abPath.vectorPath[i+1], Color.red, 10);
    }
}

위의 코드는 GameObject의 현재 위치에서 앞으로 10 단위의 위치로 경로를 요청합니다. 경로가 계산되면 OnPathComplete 메서드가 경로를 인수로 호출됩니다. Seeker와 동일한 GameObject에 path modifiers를 추가하면 OnPathComplete 메서드가 호출되기 전에 경로에 적용됩니다.

자신의 씬에서 이를 시도해보세요. GameObject에 Seeker 구성 요소를 추가한 다음 위의 스크립트를 동일한 GameObject에 추가하세요. 경로의 복잡성 및 동시에 예약된 경로 수에 따라 경로가 계산되는 데 한 프레임 또는 두 프레임이 걸릴 수 있습니다.

경로 요청이 실패하는 경우 일반적인 Error messages목록과 해결 방법을 확인하십시오.

자주 하는 실수는 StartPath 호출 후에 경로가 이미 계산된 것으로 가정하는 것입니다. 그러나 이것은 잘못된 것입니다. StartPath 호출은 경로를 대기열에 넣기만 합니다. 이는 많은 단위가 동시에 경로를 계산할 때 FPS 하락을 피하기 위해 경로 계산을 여러 프레임에 걸쳐 분산하는 것이 바람직하기 때문입니다.  multithreading을 활성화한 경우 모든 경로가 별도의 스레드에서 비동기적으로 계산됩니다.

물론 즉시 경로를 계산해야 할 때도 있습니다. 그런 경우에는 Path.BlockUntilCalculated 메서드를 사용할 수 있습니다.

var path = seeker.StartPath(transform.position, transform.position + Vector3.forward * 10, OnPathComplete);
path.BlockUntilCalculated();

// 이제 경로가 계산되었으며 OnPathComplete 콜백이 호출되었습니다.

 Path.WaitForPath 를 사용하여 코루틴에서 경로가 계산될 때까지 기다릴 수도 있습니다.

IEnumerator Start () {
    // 이 GameObject에 연결된 seeker 구성 요소를 가져옵니다
    var seeker = GetComponent<Seeker>();

    var path = seeker.StartPath(transform.position, transform.position + Vector3.forward * 10, null);
    // 기다립니다... 이것은 경로의 복잡성에 따라 한 프레임 또는 두 프레임이 걸릴 수 있습니다
	// 기다리는 동안 게임의 나머지 부분은 계속 실행됩니다
    yield return StartCoroutine(path.WaitForPath());
    // 이제 경로가 계산되었습니다

    // 씬 뷰에 경로를 10초 동안 그립니다
    for (int i = 0; i < path.vectorPath.Count - 1; i++) {
        Debug.DrawLine(path.vectorPath[i], path.vectorPath[i+1], Color.red, 10);
    }
}

Seeker의 메서드 대신 자체 경로 객체를 만들 수도 있습니다. 이렇게 하면 경로를 계산하기 전에 경로 개체의 설정을 변경할 수 있습니다.

// 새 경로 객체를 생성합니다. 마지막 매개변수는 콜백 함수입니다
// 하지만 이것은 내부적으로 seeker에 의해 사용되므로 여기서는 null로 설정합니다
// 경로는 pooled paths를 사용할 수 있도록 정적 Construct 호출을 사용하여 생성됩니다.
// 이렇게 하면 자주 발생하는 GC 스파이크를 피할 수 있습니다.
var p = ABPath.Construct(transform.position, transform.position+transform.forward*10, null);

// 기본적으로 시작 및 종료 노드에 대한 가장 가까운 이동 가능한 노드를 검색합니다
// 그러나 예를 들어 턴 기반 게임에서는 가장 가까운 이동 가능한 노드를 검색하지 않고
// 대신 대상 지점이 이동할 수 없는 노드에 있으면 오류를 반환하도록 설정할 수 있습니다.
// NNConstraint를 None으로 설정하면 가장 가까운 이동 가능한 노드 검색이 비활성화됩니다.
p.nnConstraint = NNConstraint.None;

// 경로 요청을 예약하기 위해 Seeker에게 전송합니다
seeker.StartPath(p, OnPathComplete);

주의 깊게 보는 독자들은 OnPathComplete에 대한 대리자가 생성될 때마다 GC 할당이 발생하는 것을 알 수 있을 것입니다. 이것은 많지 않지만 누적됩니다. 지역 필드에 대리자를 캐싱하여 이 할당을 피할 수 있습니다.

protected OnPathDelegate onPathComplete;

void OnEnable () {
    onPathComplete = OnPathComplete;
}

public void SearchPath () {
   // 이제 GC 할당을 피하기 위해 직접적으로 OnPathComplete 대신에 onPathComplete 필드를 사용하세요.
    GetComponent<Seeker>().StartPath(transform.position, transform.position+transform.forward*10, onPathComplete);
}

void OnPathComplete (Path p) {
    // 이 구성 요소가 경로를 계산하는 동안 파괴되는 경우를 대비하여
    if (!this) return;
}

 

Seeker 구성 요소는 게임에서 단일 캐릭터의 경로 찾기 요청을 처리하기 위해 설계되었습니다. 이러한 이유로 한 번에 하나의 경로 요청만 처리합니다. 다른 경로가 이미 계산 중일 때 StartPath를 호출하면 이전 경로 계산이 중단되었다는 경고가 기록됩니다. 이전 경로 요청을 명시적으로 취소하려면 경고를 로깅하지 않고 Seeker.CancelCurrentPathRequest 를 호출할 수 있습니다.

여러 경로를 동시에 계산하려면 Seeker를 건너뛰어야 합니다. 대신 직접 AstarPath 클래스를 사용하는 것을 참조하세요.

Other types of paths

표준 경로 외에도  MultiTargetPath (Pro 기능)와 같은 다른 유형의 경로가 있습니다. 특히 MultiTargetPath는 Seeker에 특별한 기능이 있으므로 이들도 쉽게 예약할 수 있습니다.

var endPoints = new Vector3[] {
    transform.position + Vector3.forward * 5,
    transform.position + Vector3.right * 10,
    transform.position + Vector3.back * 15
};
// endPoints가 Vector3[] 배열인 다중 대상 경로를 시작합니다.
// pathsForAll 매개변수는 각 대상 지점까지의 경로를 모두 검색해야 하는지,
// 아니면 어느 대상 지점까지의 최단 경로만 찾아야 하는지를 지정합니다.
var path = seeker.StartMultiTargetPath(transform.position, endPoints, pathsForAll: true, callback: null);
path.BlockUntilCalculated();

if (path.error) {
    Debug.LogError("Error calculating path: " + path.errorLog);
    return;
}

Debug.Log("The closest target was index " + path.chosenTarget);

// 모든 대상 지점으로 경로를 그립니다.
foreach (var subPath in path.vectorPaths) {
    for (int i = 0; i < subPath.Count - 1; i++) {
        Debug.DrawLine(subPath[i], subPath[i+1], Color.green, 10);
    }
}

// 가장 가까운 대상 지점까지의 경로를 그립니다.
for (int i = 0; i < path.vectorPath.Count - 1; i++) {
    Debug.DrawLine(path.vectorPath[i], path.vectorPath[i+1], Color.red, 10);
}
참고
MultiTargetPath는 A* Pathfinding Project Pro 기능입니다. 따라서 프로젝트의 무료 버전을 사용하여 위의 코드를 시도하면 오류가 발생합니다.

어떤 유형의 경로든 예약하는 일반적인 방법은

Path p = MyPathType.Construct (...);    // 여기서 MyPathType은 예를 들어 MultiTargetPath입니다.
seeker.StartPath(p, OnPathComplete);

Construct 메서드는 생성자 대신 사용됩니다. 이렇게 하면 경로 풀링이 더 쉽게 수행될 수 있습니다.

Path TypesPooling 을 참조하세요.
모든 경로 유형의 데모를 보려면 Path Types 를 확인하세요.

 

Using the AstarPath class directly

각 경로를 더욱 더 세밀하게 제어하려면 AstarPath 구성 요소를 직접 사용할 수 있습니다. 그러면 사용하는 주요 함수는  AstarPath.StartPath 입니다. 이 함수는 동시에 많은 경로를 계산하려는 경우 유용합니다. Seeker는 한 번에 하나의 활성 경로만 갖는 캐릭터용이며, 동시에 여러 경로를 요청하려고 하면 마지막 경로만 계산하고 나머지는 취소합니다.

Seeker는 내부적으로 AstarPath.StartPath 메서드를 사용하여 경로를 계산합니다.

참고
AstarPath.StartPath 를 사용하여 계산된 경로는 후처리되지 않습니다. 그러나 특정 Seeker에 연결된 수정자를 사용하여 경로를 후처리하려면 경로가 계산된 후에  Seeker.PostProcess 를 호출할 수 있습니다.
// 씬에 AstarPath 인스턴스가 있어야 합니다
if (AstarPath.active == null) return;

// 여러 경로를 비동기적으로 계산할 수 있습니다
for (int i = 0; i < 10; i++) {
    var path = ABPath.Construct(transform.position, transform.position+transform.forward*i*10, OnPathComplete);

    // AstarPath 구성 요소를 직접 사용하여 경로를 계산합니다
    AstarPath.StartPath(path);
}

 

Path pooling

새 경로 객체를 생성할 때 할당을 피하려면 경로 풀링을 사용할 수 있습니다. 위의 예제에서는 이를 건너뛰었지만, 가비지 수집을 최소화하는 것이 중요하다면 사용하는 것이 좋습니다. 내장된 이동 스크립트는 이미 내부적으로 경로 풀링을 사용합니다.

경로 풀링에 대해 더 자세히 알아보려면 다음을 참조하세요: Pooling .

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

Local Avoidance  (0) 2024.05.25
Using Modifiers  (0) 2024.05.24
Movement scripts > Writing a movement script  (0) 2024.05.24
Movement scripts  (0) 2024.05.23
Agent Movement  (0) 2024.05.23

간단한 사용자 정의 이동 스크립트 작성 튜토리얼입니다.
이 튜토리얼은 시작하기 가이드의 일부입니다: Get Started Guide .
우리는 AI를 이동시키는 우리만의 정말 간단한 스크립트를 작성할 것입니다. 따라서 좋아하는 스크립트 편집기를 열고 따라하세요.

참고:
이 튜토리얼은 3D 게임을 만드는 경우와 2D 게임을 만드는 경우 모두 따를 수 있습니다. 경우에 따라 2D 및 3D에 대한 약간 다른 지침이 있습니다. 2D 및 3D를 위한 코드는 거의 동일하지만 몇 군데에서 차이가 있습니다. 이러한 경우는 주석을 사용하여 명확하게 나타납니다. 그러나 시작하기 튜토리얼에서 생성된 환경은 3D 환경입니다. 2D 환경에 대한 그래프를 만드는 방법에 대한 지침은 Pathfinding in 2D 를 참조하십시오.

첫 번째로 해야 할 일은 경로를 계산하는 것입니다. 이를 위해 Seeker 구성 요소의 StartPath 메서드를 사용합니다. Seeker 에 대한 호출은 정말 간단합니다. 시작 위치, 끝 위치 및 콜백 함수 세 가지 인수가 필요합니다(콜백 함수는 "void SomeFunction(Path p)" 형식이어야 합니다):

Path StartPath (Vector3 start, Vector3 end, OnPathDelegate callback = null)

 

그러면 시작할 때 경로 요청을 시작하는 간단한 코드 조각으로 스크립트를 시작해 보겠습니다:

using UnityEngine;
using System.Collections;
// 이 줄을 빼 놓으면 스크립트가 'Path' 클래스가 존재한다는 것을 알지 못하고 컴파일러 오류가 발생할 것입니다.
// 경로 찾기를 사용하는 스크립트의 맨 위에 이 줄이 항상 있어야 합니다.
using Pathfinding;

public class AstarAI : MonoBehaviour {
    public Transform targetPosition;

    public void Start () {
        // 이전에 추가한 Seeker 구성 요소에 대한 참조를 가져옵니다.
        Seeker seeker = GetComponent<Seeker>();

        // 새로운 경로를 targetPosition 개체로 계산을 시작하고 결과를 OnPathComplete 메서드에 반환합니다.
        //경로 요청은 비동기적으로 이루어지므로 OnPathComplete 메서드가 호출되는 시점은 경로를 계산하는 데 소요되는 시간에 따라 달라집니다. 
        // 일반적으로 다음 프레임에 호출됩니다.
        seeker.StartPath(transform.position, targetPosition.position, OnPathComplete);
    }

    public void OnPathComplete (Path p) {
        Debug.Log("Yay, we got a path back. Did it have an error? " + p.error);
    }
}

 

프로젝트 내의 AstarAI.cs라는 이름의 파일에 저장하고 스크립트를 AI GameObject에 추가합니다.
새로운 GameObject를 만들어 대상으로 사용할 수 있도록하고, 이를 (-20,0,22)와 같은 좌표로 이동한 다음 AI GameObject를 선택하고 대상을 targetPosition 필드에 드래그합니다. 이제 AI가 경로를 찾으려는 위치입니다.


재생을 누릅니다. 로그 메시지를 받아야 하며, 씬 뷰에 경로가 녹색 선으로 표시되어야 합니다(Seeker 구성 요소가 Gizmos를 사용하여 마지막으로 계산된 경로를 그립니다).

녹색 선이 보이지 않으면, Seeker 구성 요소의 'Show Gizmos' 확인란이 선택되어 있는지 확인하십시오. 최신 Unity 버전에서는 기즈모를 깊이 테스트하기도 하므로 지면 아래에 숨겨져 있을 수 있습니다. 기즈모 버튼을 클릭하여 시야 창 위에 있는 '3D 아이콘' 확인란을 해제하여 깊이 테스트를 비활성화합니다.


오류가 발생하는 경우, Seeker 구성 요소가 실제로 AstarAI 스크립트가 있는 동일한 GameObject에 연결되어 있는지 확인하십시오. 여전히 오류가 발생하는 경우, 대상 위치가 도달 가능하지 않을 수 있으므로 약간 변경해 보십시오.

이 페이지는 일반적인 오류 메시지를 설명합니다:  Error messages

그것은 매끄럽게 보이지는 않지만, 지금은 그렇게 할 수 있습니다. 왜냐하면 아마도 그 코드가 실제로 무엇을 했는지에 대한 설명을 기다리고 있을 것이기 때문입니다.

먼저 스크립트가 Seeker의  StartPath 메서드를 호출합니다. 그런 다음 seeker는 새로운  ABPath 인스턴스를 생성하고 그것을  AstarPath 스크립트로 보냅니다.  AstarPath 스크립트는 경로를 대기열에 넣을 것입니다. 가능한 한 빨리 스크립트는 그리드를 검색하여 경로를 처리합니다. 이 과정은 노드를 하나씩 확인하여 끝 노드를 찾을 때까지 진행됩니다.

검색 단계가 어떻게 작동하는지에 대한 자세한 정보는 이 위키피디아 페이지를 참조하십시오.

계산된 경로를 다시 받으면 어떻게 정보를 얻을 수 있을까요?

경로 인스턴스에는 그와 관련된 두 가지 목록이 있습니다. Path.vectorPath 는 경로를 보유하는 Vector3 목록이며, 이 목록은 경로를 스무딩하는 경우 수정될 수 있으며, 이것이 추천되는 경로를 얻는 방법입니다. 두 번째로는  Path.path 목록이 있는데, 이것은 GraphNode 요소 목록입니다. 이는 방문한 모든 노드를 보유하며, 방문한 경로에 대한 추가 정보를 얻는 데 유용할 수 있습니다.

먼저 항상  path.error 를 확인해야 합니다. 그것이 true인 경우, 어떤 이유로든 경로가 실패한 것입니다. path.error가 true인 경우 Path.errorLog 필드에 더 자세한 정보가 있습니다.

AI 스크립트를 확장하기 위해 이동 기능을 추가해 보겠습니다.

 

3D 2D
우리는 Unity 내장 구성 요소인 CharacterController를 사용하여 에이전트를 이동시킬 것입니다. 따라서 AI GameObject에 CharacterController 를 추가하세요. 우리는 간단히 Transform component 의 위치를 수정하여 에이전트를 이동시킬 것입니다.

 

스크립트는 현재 이동 중인 웨이포인트를 추적하고, 그것이 가까워지면 다음 웨이포인트로 변경합니다.

매 프레임마다 우리는 몇 가지 작업을 수행할 것입니다:

  • 먼저 우리가 따라야 할 계산된 경로가 있는지 확인합니다. 경로 요청은 비동기적으로 이루어지기 때문에 경로가 계산되기까지 몇 프레임(보통 하나)이 걸릴 수 있습니다.
  • 그런 다음 에이전트가 현재 이동 중인 웨이포인트에 가까운지 확인하고, 그렇다면 다음 웨이포인트로 전환하고 체크를 반복합니다.
  • 이동 방법을 계산하기 위해 현재 웨이포인트의 좌표를 가져와서 우리의 위치를 뺍니다. 이렇게 하면 웨이포인트를 향한 벡터가 얻어집니다. 이 벡터를 정규화하여 길이가 1이 되도록 만듭니다. 그렇지 않으면 웨이포인트에서 멀어질수록 더 빨리 이동하게 됩니다.
  • 그런 다음 그 벡터를 속도로 얻기 위해 속도로 곱합니다.
  • 마지막으로, CharacterController.SimpleMove 메서드를 사용하여 에이전트를 이동시킵니다(transform.position을 수정하는 것은 2D 게임을 만드는 경우입니다).
3D 2D
아래의 스크립트는 3D 게임에 대해 작동합니다. 2D 게임에서 아래의 스크립트를 작동시키려면 주석에서 'If you are writing a 2D game'를 찾아 필요한 변경 사항을 수행하십시오.

 

using UnityEngine;
// 이 줄은 필수입니다. 이 줄이 없으면 스크립트가 'Path' 클래스가 존재한다는 것을 인식하지 못하고 컴파일 오류가 발생합니다.
// 경로 탐색을 사용하는 스크립트의 맨 위에 항상 있어야 합니다.
using Pathfinding;

public class AstarAI : MonoBehaviour {
    public Transform targetPosition;

    private Seeker seeker;
    private CharacterController controller;

    public Path path;

    public float speed = 2;

    public float nextWaypointDistance = 3;

    private int currentWaypoint = 0;

    public bool reachedEndOfPath;

    public void Start () {
        seeker = GetComponent<Seeker>();
        // 만약 2D 게임을 작성 중이라면 이 줄을 제거하고 아래에 제안된 대체 이동 방법을 사용하십시오.
        controller = GetComponent<CharacterController>();

        // 타겟 위치로 새 경로를 시작하고 경로가 계산되면 OnPathComplete 함수를 호출합니다.
        // (복잡성에 따라 몇 프레임이 걸릴 수 있음)
        seeker.StartPath(transform.position, targetPosition.position, OnPathComplete);
    }

    public void OnPathComplete (Path p) {
        Debug.Log("A path was calculated. Did it fail with an error? " + p.error);

        if (!p.error) {
            path = p;
            // 경로의 첫 번째 지점으로 이동하도록 웨이포인트 카운터를 재설정합니다.
            currentWaypoint = 0;
        }
    }

    public void Update () {
        if (path == null) {
            // 아직 따라갈 경로가 없으므로 아무것도하지 마십시오.
            return;
        }

        // 현재 웨이포인트에 충분히 가까운지 여부를 반복하여 확인합니다.
        // 많은 웨이포인트가 서로 가까울 수 있으므로 한 프레임에 여러 개를 도달할 수 있습니다.
        reachedEndOfPath = false;
        // 경로의 다음 웨이포인트까지의 거리
        float distanceToWaypoint;
        while (true) {
            // 최대 성능을 원하는 경우 제곱 거리를 확인하여 제곱근 계산을 없앨 수 있습니다.
            // 그러나 이것은 이 튜토리얼의 범위를 벗어납니다.
            distanceToWaypoint = Vector3.Distance(transform.position, path.vectorPath[currentWaypoint]);
            if (distanceToWaypoint < nextWaypointDistance) {
                // 다음 웨이포인트가 있는지 또는 경로의 끝에 도달했는지 확인합니다.
                if (currentWaypoint + 1 < path.vectorPath.Count) {
                    currentWaypoint++;
                } else {
                    // 에이전트가 경로의 끝에 도달했음을 나타내는 상태 변수를 설정합니다.
                    // 필요한 경우 특별한 코드를 실행하는 데 사용할 수 있습니다.
                    reachedEndOfPath = true;
                    break;
                }
            } else {
                break;
            }
        }

        // 경로의 끝에 접근할 때 부드럽게 감속합니다.
        // 이 값은 에이전트가 경로의 마지막 웨이포인트에 접근할 때 1에서 0으로 부드럽게 변경됩니다.
        var speedFactor = reachedEndOfPath ? Mathf.Sqrt(distanceToWaypoint/nextWaypointDistance) : 1f;

        // 다음 웨이포인트로의 방향
        // 길이가 1의 월드 단위가되도록 정규화합니다.
        Vector3 dir = (path.vectorPath[currentWaypoint] - transform.position).normalized;
       // 원하는 속도로 방향을 곱하여 속도를 얻습니다.
        Vector3 velocity = dir * speed * speedFactor;

        // Rigidbody2D 구성 요소를 사용하여 에이전트를 이동합니다. // 만약 2D 게임을 작성 중이라면
        // MovePosition은 초당 미터 단위의 속도를 사용하므로 Time.deltaTime을 곱해서는 안됩니다.
        controller.SimpleMove(velocity);

        // 만약 2D 게임을 작성 중이라면 위의 Rigidbody2D 코드를 제거하고 
        // 대신 다음 줄을 주석 해제하여 변환을 직접 이동합니다.
        // transform.position += velocity * Time.deltaTime;
    }
}

 

이제 재생 버튼을 누르면 AI가 계산된 경로를 따라 이동합니다. 멋지지 않나요?

원한다면 GameObject에 SimpleSmoothModifier 를 추가하여 더 부드러운 경로를 얻을 수 있습니다. 수정자에 대한 자세한 내용은  Using Modifiers 을 참조하세요.

다음은 간단한 부드러운 수정자를 사용하여 2D 씬에서 이동 스크립트가 작동하는 비디오입니다:

 

몇 가지 추가적인 개선 사항을 찾을 수 있습니다. 정기적으로 경로를 다시 계산하는 등: AstarAI.cs.

이로써 이 튜토리얼은 끝났습니다. 나머지 시작 튜토리얼을 계속 진행하는 것을 권장합니다: started tutorial.

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

Using Modifiers  (0) 2024.05.24
Searching for paths  (0) 2024.05.24
Movement scripts  (0) 2024.05.23
Agent Movement  (0) 2024.05.23
Navmesh Graph  (0) 2024.05.23

패키지에 포함된 다양한 이동 스크립트의 간략한 개요


이 패키지에는 여러 이동 스크립트가 포함되어 있습니다. 이 스크립트들은 실제로 경로를 따라 장면 내의 어떤 객체, 보통은 캐릭터를 이동시키는 스크립트들입니다. 이동 스크립트의 주요 역할은 경로를 검색하고 그 경로를 따르는 것입니다.

이 스크립트들은 이 패키지에서 완전히 선택 사항이며, 이동 스크립트를 사용하지 않고 경로 찾기를 사용할 수도 있고, 직접 이동 스크립트를 작성할 수도 있습니다. 그러나 많은 게임에서는 이러한 스크립트들이 캐릭터를 만드는 좋은 기반을 제공하며, 개발 후반에 게임에 맞춰 특별히 제작된 것으로 교체할 수도 있습니다.

패키지에는 네 가지 이동 스크립트가 포함되어 있습니다: FollowerEntity , AIPath , RichAI ,  AILerp . 이 스크립트들의 이름은 나중에 더 잘 선택될 수 있었겠지만, 현재 많은 지원 자료와 포럼 게시물이 이 이름을 참조하고 있어 변경을 주저하고 있습니다.

이동 스크립트의 주요 차이점

FollowerEntity

  • 그리드, 내비메쉬 및 리캐스트 그래프에서 잘 작동하는 전천후 이동 스크립트.
  • 경로를 부드럽게 따르고 매우 반응성이 좋습니다.
  • 백그라운드에서 엔티티 컴포넌트 시스템을 사용합니다.
  • 로컬 회피와 잘 작동합니다.
  • 3D 게임과 2D 게임 모두에서 이동을 지원합니다.
  • 오프메쉬 링크와 잘 작동합니다.
  • 항상 현재 어떤 노드를 통과하고 있는지 알고 있습니다.
  • 경로 수정자와 호환되지 않습니다 (내부적으로 경로를 단순화함).
  • 다른 이동 스크립트에 비해 강력한 보증을 제공하며, 엣지 케이스를 더 잘 처리합니다.
  • 코드 복잡도가 높습니다.
  • Unity의 캐릭터 컨트롤러와 함께 사용할 수 없습니다.

AIPath

  • 모든 그래프 유형에서 잘 작동하는 전천후 이동 스크립트.
  • 경로를 부드럽게 따르고 물리에 반응합니다.
  • 로컬 회피와 잘 작동합니다.
  • 3D 게임과 2D 게임 모두에서 이동을 지원합니다.
  • 오프메쉬 링크를 통과할 수 있지만, 이를 위한 특별한 이동 로직을 사용할 수 없습니다.
  • 경로 수정자와 호환됩니다.

 

RichAI

  • 내비메쉬/리캐스트 그래프를 위해 특별히 설계되었으며 다른 그래프 유형과는 작동하지 않습니다.
  • 내비메쉬 기반 그래프에서 경로를 따르는 데 AIPath 스크립트보다 더 우수하며, 경로에서 밀려나는 상황을 더 잘 처리하고 보통 경로를 더 부드럽게 따릅니다.
  • AIPath에 비해 오프메쉬 링크에 대한 지원이 더 좋습니다.
  • 로컬 회피와 잘 작동합니다.
  • 3D 게임(XZ 평면에서의 이동)에서 이동을 지원하지만, 2D에서는 지원하지 않습니다.
  • 오프메쉬 링크와 잘 작동합니다.
  • 대부분의 경로 수정자와 호환되지 않습니다 (내부적으로 경로를 단순화함).

AILerp

  • 경로를 따라 선형 보간을 사용하여 이동(이름에서 'lerp'는 선형 보간을 의미). 물리를 전혀 사용하지 않습니다.
  • 경로를 정확하게 따르며, 어떤 편차도 없습니다.
  • 위의 이유로 인해 로컬 회피와 함께 사용하는 것이 말이 되지 않으므로 지원하지 않습니다.
  • 가장 빠른 이동 스크립트이며, 이동 자체가 훨씬 간단하지만 게임에서 물리적 현실성이 필요한 경우 다른 이동 스크립트를 사용하는 것이 좋습니다.
  • 3D 게임과 2D 게임 모두에서 이동을 지원합니다.
  • 경로 수정자와 호환됩니다.
  • 코드 복잡도가 낮습니다.

요약, 내비메쉬 또는 그리드 기반 그래프를 사용하는 경우: 먼저 FollowerEntity 컴포넌트를 시도해 보세요, 그렇지 않다면 게임의 이동 스타일에 따라 AIPath 또는 AILerp를 사용하세요. 패키지에 포함된 예제 장면을 확인하여 다양한 이동 스크립트가 어떻게 작동하는지 확인해 보세요.

패키지에 포함된 모든 이동 스크립트는 Pathfinding.IAstarAI 인터페이스를 구현합니다. 따라서 여러 다른 이동 스크립트와 상호작용할 필요가 있다면 이 인터페이스를 사용하는 것이 좋습니다.

예를 들어, 다음은 모든 이동 스크립트에서 유용할 수 있는 몇 가지 속성입니다:

destination
이 에이전트가 이동해야 하는 월드 위치입니다.

아직 목적지가 설정되지 않은 경우, (+무한대, +무한대, +무한대)가 반환됩니다.

이 속성을 설정한다고 해서 에이전트가 즉시 경로를 재계산하는 것은 아닙니다. 따라서 에이전트가 이 지점으로 이동하기 시작하기까지 시간이 걸릴 수 있습니다. 대부분의 이동 스크립트에는 에이전트가 새로운 경로를 찾는 빈도를 나타내는 `repathRate` 필드가 있습니다. 또한  SearchPath 메서드를 호출하여 즉시 새로운 경로 검색을 시작할 수도 있습니다. 경로는 비동기적으로 계산되므로 에이전트가 경로 검색을 시작할 때 결과가 나올 때까지 몇 프레임(보통 1 또는 2 프레임)이 걸릴 수 있습니다. 이 시간 동안  pathPending 속성은 true를 반환합니다.

목적지를 설정한 후 에이전트가 그 목적지에 도달했는지 알고 싶다면  reachedDestination (권장) 또는  pathPending reachedEndOfPath 를 모두 확인할 수 있습니다. 각각의 필드에 대한 차이점은 해당 문서를 참조하십시오.

IEnumerator Start () {
    ai.destination = somePoint;
    // 즉시 목적지로의 경로 검색을 시작합니다.
    ai.SearchPath();
    // 에이전트가 목적지에 도달할 때까지 기다립니다.
    while (!ai.reachedDestination) {
        yield return null;
    }
    // 에이전트가 이제 목적지에 도달했습니다.
}
IEnumerator Start () {
    ai.destination = somePoint;
    // 즉시 목적지로의 경로 검색을 시작합니다.
    // 경로 계산이 완료될 때까지 몇 프레임이 걸릴 수 있음을 유의하세요.
    // 경로가 계산되는 동안 ai.pathPending은 true를 반환합니다.
    ai.SearchPath();
    // 에이전트가 위에서 설정한 목적지로의 경로를 확실히 계산할 때까지 기다립니다.
    while (ai.pathPending || !ai.reachedEndOfPath) {
        yield return null;
    }
    // 에이전트가 이제 목적지에 도달했습니다.
}
참고
이 설정은 Pathfinding.IAstarAI.destination 멤버에 해당합니다.

 

 

reachedDestination
AI가 목적지에 도달했을 경우 true입니다.

이것은 목적지에 도달했는지 확인하기 위한 최선의 계산입니다. AIPath/RichAI 스크립트의 경우, 캐릭터가 목적지에서 AIPath.endReachedDistance 월드 단위 내에 있을 때를 의미합니다.  AILerp 스크립트의 경우, 캐릭터가 목적지에 정확히 도달했을 때를 의미합니다(아주 작은 오차 범위 내에서).

이 값은 목적지가 변경되면 즉시 업데이트됩니다( reachedEndOfPath 와 대조적). 그러나 경로 요청은 비동기적이므로 실제 경로 결과를 확인할 때까지 근사치를 사용합니다. 이 속성은 현재 경로의 끝까지의 거리와 그 끝에서 목적지까지의 거리를 더한 총 거리가  AIPath.endReachedDistance 보다 작은지 확인합니다. 즉, 현재 경로의 끝에서 목적지까지 직선으로 이동할 수 있다고 가정합니다. 따라서 이 속성은 최선의 노력일 뿐이지만, 대부분의 경우 잘 작동합니다.

또한 목적지가 캐릭터의 머리 위나 발 아래 반 이상의 높이에 있을 경우 도달하지 못했다고 보고합니다(다층 건물이 있는 경우 캐릭터의 높이를 올바르게 구성하는 것이 중요합니다).

문제가 될 수 있는 경우는 에이전트가 매우 얇은 벽 옆에 서 있고 목적지가 갑자기 그 얇은 벽의 반대편으로 변경되는 경우입니다. 경로가 계산되는 동안 에이전트는 목적지가 매우 작은 거리(벽이 얇았기 때문에)만큼 이동했기 때문에 이미 목적지에 도달한 것으로 볼 수 있습니다. 실제로는 벽 반대편으로 돌아가는 데 상당히 긴 거리가 있을 수 있습니다.

reachedEndOfPath 와는 달리, 이 속성은 목적지가 변경되면 즉시 업데이트됩니다.

IEnumerator Start () {
    ai.destination = somePoint;
    // 즉시 목적지로의 경로 검색을 시작합니다.
    ai.SearchPath();
    //에이전트가 목적지에 도착할 때까지 기다려주세요.
    while (!ai.reachedDestination) {
        yield return null;
    }
    // 에이전트가 이제 목적지에 도착했습니다.
}
AIPath.endReachedDistance, remainingDistance, reachedEndOfPath 참조

 

 

velocity
에이전트가 이동하는 실제 속도입니다.
세계 단위당 초당 속도입니다.

desiredVelocity 참조
이 설정은 Pathfinding.IAstarAI.velocity 멤버에 해당합니다.

 

desiredVelocity
이 에이전트가 이동하길 원하는 속도입니다.
적용되는 경우 중력과 지역 회피를 포함합니다. 세계 단위당 초당 속도입니다.

velocity 참조
참고
 Pathfinding.AILerp 이동 스크립트는 로컬 회피나 중력을 사용하지 않으므로 이 속성은 항상 해당 구성 요소의  velocity 와 동일합니다.
이 설정은 Pathfinding.IAstarAI.desiredVelocity 멤버에 해당합니다.

 

 

Pathfinding.IAstarAI 참조

특정 객체를 따르도록 캐릭터를 설정하려면 포함된 스크립트 AIDestinationSetter 를 사용할 수 있습니다. 이는 내부적으로 매 프레임마다 선택한 대상의 위치로 목적지 속성을 설정합니다.

movement script  작성하기

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

Searching for paths  (0) 2024.05.24
Movement scripts > Writing a movement script  (0) 2024.05.24
Agent Movement  (0) 2024.05.23
Navmesh Graph  (0) 2024.05.23
Spherical world  (0) 2024.05.23

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

Movement scripts > Writing a movement script  (0) 2024.05.24
Movement scripts  (0) 2024.05.23
Navmesh Graph  (0) 2024.05.23
Spherical world  (0) 2024.05.23
Hexagonal Turn Based  (0) 2024.05.23

예제 장면: 내비메쉬 그래프 사용


Graph setup

이 장면에서 사용된 그래프는  NavMeshGraph 입니다. 이는 완전히 수동으로 모델링된 그래프로, 자동 생성 기능을 사용하지 않습니다. 이러한 그래프는 생성하는 데 시간이 더 걸리지만, 그 대신 그래프의 모양을 완전히 제어할 수 있습니다.

대부분의 경우, 자동으로 생성되는 RecastGraph 가 더 나은 선택이지만, 특정 상황에서는 내비메쉬 그래프가 유용할 수 있습니다. 예를 들어 외부 소스로부터 메쉬를 가져오거나 구형 세계에서 경로 찾기를 수행하려는 경우, 내비메쉬 그래프가 좋은 선택이 될 수 있습니다.

또한 단순한 형태(예: 사각형)의 수동 내비메쉬를 생성한 후 NavmeshCut 컴포넌트를 사용하여 장애물을 나타내기 위해 구멍을 뚫을 수 있습니다. 이는 많은 건축이 필요한 게임에 유연한 시스템을 제공하지만, 많은 얇은 삼각형을 포함하는 고품질의 내비메쉬를 제공하지 않을 수 있습니다. Recast 그래프와 타일을 처음부터 재계산하는 그래프 업데이트를 사용하는 것이 일반적으로 더 높은 품질의 내비메쉬를 제공하지만, 이는 세계의 복잡성과 그래프 설정에 따라 더 느릴 수 있습니다.

Creating a navmesh manually, Spherical Worlds  참조

 

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

Movement scripts  (0) 2024.05.23
Agent Movement  (0) 2024.05.23
Spherical world  (0) 2024.05.23
Hexagonal Turn Based  (0) 2024.05.23
Path Types  (0) 2024.05.23

예제 장면: 구형 세계 및 기타 특이한 형태에서의 경로 찾기



 

이 예제 장면은 구형 세계와 상자 세계에서의 경로 찾기를 보여줍니다. 시작 시 두 명의 에이전트가 존재하며, P 키를 눌러 더 많은 에이전트를 생성할 수 있습니다. 또한 이 장면에서는 로컬 회피가 활성화되어 있어, 에이전트들이 서로를 피하면서 비평면 세계에서도 이동할 수 있습니다.

Graph setup

 

구형 세계의 경우, 내비게이션 메쉬(navmesh) 그래프가 유일한 옵션입니다. 그리드 그래프나 리캐스트 그래프는 구형 세계 또는 적어도 평면적이지 않은 다른 형태에서 자동으로 생성될 수 없습니다.

이 장면에는 두 개의 내비게이션 메쉬 그래프가 있습니다. 하나는 구형 세계용이고, 다른 하나는 붕괴된 상자 모양을 위한 것입니다. 이 내비게이션 메쉬들은 3D 모델링 애플리케이션에서 수동으로 모델링한 후 Unity로 가져왔습니다.

참고
구형 세계에서의 Graph Types 에 대한 자세한 내용은 Spherical Worlds 를 참조하세요.
Creating a navmesh manually

 

Movement scripts

이 예제에서는 AIPathAlignedToSurface이동 스크립트를 사용하여 구형 내비메쉬에서 이동할 수 있도록 합니다.

자세한 내용은 Movement scripts 를 참조하세요.

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

Agent Movement  (0) 2024.05.23
Navmesh Graph  (0) 2024.05.23
Hexagonal Turn Based  (0) 2024.05.23
Path Types  (0) 2024.05.23
Lightweight Local Avoidance Simulation  (0) 2024.05.23

턴 기반 이동 및 커스텀 이동 규칙을 보여주는 예제 장면.

 

Overview

이 예제 장면은 육각형 그리드에서 턴 기반 이동을 보여줍니다. 표준 이동 스크립트를 사용하지 않고, 대신 모든 캐릭터의 이동을 하나의 스크립트에서 처리합니다.

참고
턴 기반 이동은 물론 포함된 이동 스크립트로도 수행할 수 있습니다.
이 장면은 간단한 퍼즐 게임을 보여줍니다. 오렌지 콘을 클릭하면 이동 범위가 표시되고, 별도의 타일을 클릭하여 이동할 수 있습니다. 오렌지 콘을 보라색 육각형으로 이동시키면 초록색 "문" 육각형 세트를 토글할 수 있는 옵션이 주어집니다.

퍼즐의 목표는 하나의 오렌지 콘을 화면 오른쪽의 빨간 영역에 있는 보라색 육각형으로 이동시키는 것입니다.
 

 

Graph setup

 

이 장면에서는 육각형 그리드 그래프가 사용됩니다. GridGraph를 생성하고 Shape 드롭다운을 육각형으로 변경하여 구성합니다.

육각형 그래프의 경우, 크기를 조정할 때 사용할 수 있는 두 가지 측정 방식이 있습니다. 이 장면에서는 육각형의 폭(마주보는 면 사이의 거리)을 1 월드 단위로 설정하지만, 지름(마주보는 꼭짓점 사이의 거리)을 사용할 수도 있습니다.

참고
2D 게임을 Tilemap 컴포넌트를 사용하여 빌드하는 경우, 그래프를 타일맵에 직접 정렬할 수 있습니다. 자세한 내용은 Pathfinding on tilemaps 를 참조하세요.

육각형 그래프가 찌그러진 사각형처럼 보일 수 있습니다. 이는 육각형 그래프가 내부적으로는 찌그러지고 다른 방식으로 연결된 그리드 그래프이기 때문입니다. 일부 게임에서는 레벨 외부에 불필요한 노드가 생길 수 있지만, 성능 면에서는 일반적으로 문제가 되지 않습니다.

씬의 모든 육각형 메쉬를 그래프와 정렬하기 위해 SnapToNode라는 스크립트를 사용합니다. 이 스크립트는 편집 모드에서도 실행되며, 변형이 이동될 때마다 가장 가까운 노드에 변형을 맞춥니다.

void Update () {
    if (transform.hasChanged && AstarPath.active != null) {
        var node = AstarPath.active.GetNearest(transform.position, NNConstraint.None).node;
        if (node != null) {
            transform.position = (Vector3)node.position;
            transform.hasChanged = false;
        }
    }
}

이렇게 하면 씬 뷰에서 프리팹을 쉽게 이동할 수 있으며, 자동으로 가장 가까운 육각형 노드에 맞춰집니다.

AstarPath.GetNearest(Vector3,NNConstraint)  참조

 

Purple triggers

씬의 보라색 육각형은 유니티의 내장 함수인 OnTriggerEnter를 사용하여 트리거됩니다. 이는 HexagonTrigger 클래스에서 처리됩니다. 이 메서드에서 스크립트는 객체가 에이전트인지, 그렇다면 유닛이 이 노드를 목표 노드로 설정했는지 또는 단순히 통과 중인지를 확인합니다. 확인이 완료되면 애니메이션이 재생됩니다.

void OnTriggerEnter (Collider coll) {
    var unit = coll.GetComponentInParent<TurnBasedAI>();
    var node = AstarPath.active.GetNearest(transform.position).node;

    // 에이전트인지, 그리고 에이전트가 이 노드를 향하고 있는지 확인합니다.
    if (unit != null && unit.targetNode == node) {
        visible = true;
        anim.CrossFade("show", 0.1f);
    }
}

애니메이션은 버튼을 나타내거나, 마지막에는 승리 메시지를 보여줍니다. 나타난 버튼들은 초록색 육각형의 통과 가능 여부를 토글할 수 있습니다. 버튼을 클릭하면 관련된 초록색 육각형에서  TurnBasedDoor.Toggle 을 호출합니다. 그러면 애니메이션이 재생되고, SingleNodeBlocker 컴포넌트를 사용하여 초록색 육각형 아래의 노드를 차단하거나 차단 해제합니다.

참고
Utilities for turn-based games 에 관한 자세한 정보는 SingleNodeBlocker 컴포넌트의 작동 방식을 확인하세요.

 

Agent movement

이 예제 장면의 이동은 다른 예제들과 약간 다릅니다. 내장 이동 스크립트를 사용하지 않고, 대신 모든 이동을 처리하는 커스텀 스크립트 TurnBasedManager 를 사용합니다. 턴 기반 게임에 반드시 필요한 것은 아니지만, 이동의 대체 접근 방식을 보여줍니다.

  • 이 매니저는 몇 가지 작업을 수행합니다:
    마우스 클릭을 감지하여 클릭된 것에 따라 에이전트를 선택하거나 이동시킵니다.
  • 에이전트가 선택되면 한 턴 내에 이동할 수 있는 모든 노드를 표시합니다.
  • 에이전트가 이동할 때, 경로의 부드러운 버전을 따라 에이전트의 위치를 애니메이션으로 이동시킵니다.
  • 에이전트가 이동할 때마다 SingleNodeBlocker를 트리거하여 다른 에이전트가 동일한 육각형으로 이동할 수 없도록 합니다.

Movement animation

에이전트를 이동시키기 위해  TurnBasedManager.MoveAlongPath 메서드를 사용합니다. 이 메서드는  catmull-rom spline 을 사용하여 경로의 점들 사이를 부드럽게 보간합니다.

static IEnumerator MoveAlongPath (TurnBasedAI unit, ABPath path, float speed) {
    if (path.error || path.vectorPath.Count == 0)
        throw new System.ArgumentException("Cannot follow an empty path");

    // 매우 간단한 이동으로, 카멜 롬 스플라인(Catmull-Rom Spline)을 사용하여 보간합니다.
    float distanceAlongSegment = 0;
    for (int i = 0; i < path.vectorPath.Count - 1; i++) {
        var p0 = path.vectorPath[Mathf.Max(i-1, 0)];
        // 현재 구간의 시작점
        var p1 = path.vectorPath[i];
        // 현재 구간의 끝점
        var p2 = path.vectorPath[i+1];
        var p3 = path.vectorPath[Mathf.Min(i+2, path.vectorPath.Count-1)];

        // 스플라인의 길이를 근사합니다.
        var segmentLength = Vector3.Distance(p1, p2);

        // 각 프레임마다 에이전트를 앞으로 이동시키고, 구간의 끝에 도달할 때까지 반복합니다.
        while (distanceAlongSegment < segmentLength) {
            // 카멜 롬 스플라인을 사용하여 경로를 부드럽게 만듭니다. 자세한 내용은 [카멜 롬 스플라인](https://en.wikipedia.org/wiki/Cubic_Hermite_spline#Catmull%E2%80%93Rom_spline)을 참조하세요.
            var interpolatedPoint = AstarSplines.CatmullRom(p0, p1, p2, p3, distanceAlongSegment / segmentLength);
            unit.transform.position = interpolatedPoint;
            yield return null;
            distanceAlongSegment += Time.deltaTime * speed;
        }

        distanceAlongSegment -= segmentLength;
    }

    // 에이전트를 경로의 최종 지점으로 이동시킵니다.
    unit.transform.position = path.vectorPath[path.vectorPath.Count - 1];
}

 

Movement range

이동 범위를 표시하기 위해 ConstantPath 가 사용됩니다. 이는 주어진 최대 경로 비용까지 도달할 수 있는 모든 노드를 출력하는 특수 경로 유형입니다. 따라서 에이전트가 한 턴 내에 도달할 수 있는 모든 노드를 생성하는 데 완벽합니다.

참고
ConstantPath  유형에 대한 자세한 내용은 Path Types 예제 장면을 참조하세요.

이는 TurnBasedManager.GeneratePossibleMoves 메서드에서 처리됩니다. 이 메서드는 간단히 하기 위해 동기적으로 ConstantPath 를 계산하고, 경로의 모든 노드를 반복하여 각 노드의 위치에 프리팹을 인스턴스화합니다.

void GeneratePossibleMoves (TurnBasedAI unit) {
    var path = ConstantPath.Construct(unit.transform.position, unit.movementPoints * 1000 + 1);

    path.traversalProvider = unit.traversalProvider;

    // 경로 계산을 예약합니다.
    AstarPath.StartPath(path);

    // 경로 요청을 즉시 완료하도록 강제합니다
	// 이는 그래프가 충분히 작아서 지연을 일으키지 않을 것이라고 가정합니다.
    path.BlockUntilCalculated();

    foreach (var node in path.allNodes) {
        if (node != path.startNode) {
            // 도달할 수 있는 노드를 나타내는 새로운 노드 프리팹을 생성합니다
		// 참고: 실제 게임에서 이 기능을 사용할 경우, 
    	// 항상 새로운 GameObject를 인스턴스화하는 것을 피하기 위해 오브젝트 풀링을 사용하는 것이 좋습니다.
            var go = GameObject.Instantiate(nodePrefab, (Vector3)node.position, Quaternion.identity) as GameObject;
            possibleMoves.Add(go);

            go.GetComponent<Astar3DButton>().node = node;
        }
    }
}

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

Navmesh Graph  (0) 2024.05.23
Spherical world  (0) 2024.05.23
Path Types  (0) 2024.05.23
Lightweight Local Avoidance Simulation  (0) 2024.05.23
Infinite procedurally generated world  (0) 2024.05.23

다양한 경로 유형을 보여주는 예시 장면

Contents

Overview

이 장면은 화면 왼쪽에 GUI 인터페이스를 제공하여 다양한 경로 유형을 선택하고 그 동작을 확인할 수 있도록 합니다.

게임 뷰에서 기즈모(Gizmos)를 활성화하면 그래프와 계산 중인 경로를 볼 수 있습니다.

 AstarPath 컴포넌트는 경로 검색을 시각화하도록 설정되어 있습니다. 경로가 계산된 후, 모든 검색된 노드는 파란색으로 강조 표시됩니다. 경로 자체는 주황색으로 그려집니다. 얇은 파란색 선으로 검색 트리를 볼 수도 있습니다. 이 모든 선은 시작 노드에서 수렴하며, 이는 시작 노드에서 검색된 모든 다른 노드로의 최단 경로를 보여줍니다. 이는 A* Inspector -> Settings -> Debug -> Show Search Tree 를 true로 설정하고, 그리드 그래프의 "Show Connections" 옵션을 활성화하여 수행됩니다.

 

참조:
다양한 경로 유형을 계산하는 방법에 대한 자세한 내용은 " Path Types 에서 Searching for paths " 튜토리얼을 참조하세요.

 

Graph setup

 

이 예시 장면에서는 설명을 위해 그리드 그래프를 사용합니다. 다른 그래프 유형도 사용할 수 있지만, 경로 시각화 측면에서 그리드 그래프가 이해하기 쉽고 설명하기 좋습니다.

여기서 보여지는 모든 경로 유형은 모든 그래프 유형에서 지원되지만, RandomPathFleePath 경로 유형은 navmesh/recast 그래프보다 더 높은 세분성을 제공하는 그리드 그래프에서 더 잘 작동합니다.

Path types

이 장면에서는 다음과 같은 경로 유형을 보여줍니다.

ABPath


가장 일반적인 경로 유형으로, 지점 간 이동에 사용됩니다. 모든 이동 스크립트에서 기본적으로 사용됩니다.

게임에서 왼쪽 마우스 버튼을 누르고 있으면, 마우스를 움직일 때 경로가 어떻게 다시 계산되는지 볼 수 있습니다.

참조:
다양한 경로 유형을 계산하는 방법에 대한 자세한 내용은 "경로 검색에서 경로 계산하기" 튜토리얼을 참조하세요.
// 자동 경로 재계산을 비활성화하세요.
ai.canSearch = false;
// 참고: 특정 지점으로의 경로만 계산하려면, 
// `ai.destination`을 사용하는 것이 일반적으로 더 좋은 선택입니다.

var path = ABPath.Construct(ai.position, ai.position + Vector3.forward * 10);
ai.SetPath(path);
ABPath  참조

 

MultiTargetPath

 

MultiTargetPath는 두 가지 시나리오에서 사용할 수 있습니다:

  1. 하나의 지점에서 여러 다른 지점으로, 또는 여러 지점에서 하나의 목표 지점으로 가는 가장 짧은 경로를 찾고자 할 때.
  2. 여러 목표 중 가장 가까운 목표로 가는 경로를 찾고자 할 때.

게임 내 GUI에서 "Only Shortest Path" 토글을 통해 이 두 모드 사이를 전환할 수 있습니다.

위 이미지에서는 중심에서 4개의 다른 목표로 가는 단일 MultiTargetPath 가 계산되었습니다. 모든 경로를 동시에 계산하는 것은 하나씩 계산하는 것보다 빠릅니다. 특히 목표가 많거나 목표가 서로 가까운 경우, 경로가 검색된 노드의 많은 부분을 공유하기 때문에 더욱 그렇습니다.

예시 장면에서 이를 테스트하려면, shift+클릭으로 경로에 목표를 추가하고, 그런 다음 좌클릭으로 경로를 계산하세요.

var targets = new Vector3[] {
ai.position + Vector3.forward * 5,
ai.position + Vector3.right * 10,
ai.position + Vector3.back * 15
};
// 자동 경로 재계산을 비활성화하세요.
ai.canSearch = false;
var path = MultiTargetPath.Construct(ai.position, targets, null, null);
// 가장 가까운 목표로 가는 경로만 계산하세요.
path.pathsForAll = false;
ai.SetPath(path);
MultiTargetPath  참조

 

 

RandomPath

RandomPath는 방황하는 에이전트와 절차적으로 세계 특징을 생성하는 데 유용합니다.

RandomPath는 시작 지점에서 외부로 검색을 시작하여 [검색 거리]와 [검색 거리] + [분산] 사이의 경로 비용을 가진 목표 노드를 선택합니다. 이 값들은 GUI에서 설정할 수 있습니다. 또한, 목표 지점(커서로 지정)에 더 가까이 이동할 가능성을 높이는 목표 강도(aim strength)도 지정할 수 있습니다.

// 자동 경로 재계산을 비활성화하세요.
ai.canSearch = false;
// AI가 무작위 방향으로 이동하도록 설정하세요.
// 경로는 약 20 월드 유닛 길이로 설정됩니다 (기본적으로 1 월드 유닛 이동 비용은 1000입니다).
var path = FleePath.Construct(ai.position, 1000 * 20);
path.spread = 5000;
ai.SetPath(path);
RandomPath  wander 참조 

 

FleePath

FleePath는 본질적으로 RandomPath 와 동일하지만, 경로가 끝 지점에서 멀어지도록 시도합니다.

커서에서 멀어지도록 경로를 설정하려면 피하는 힘(flee strength)을 0보다 크게 설정하세요.

// 자동 경로 재계산을 비활성화하세요.
ai.canSearch = false;
var pointToAvoid = enemy.position;
// AI가 적으로부터 도망가도록 설정하세요.
// 경로는 약 20 월드 유닛 길이로 설정됩니다 (기본적으로 1 월드 유닛 이동 비용은 1000입니다).
var path = FleePath.Construct(ai.position, pointToAvoid, 1000 * 20);
ai.SetPath(path);
FleePath  참조

 

ConstantPath

ConstantPath는 경로를 계산하지 않고, 대신 주어진 최대 경로 비용으로 에이전트가 도달할 수 있는 모든 노드를 찾습니다.

이 경로 유형은 턴제 게임에서 에이전트가 자신의 턴에 도달할 수 있는 모든 노드를 시각화하려는 경우에 유용합니다.

ConstantPath.allNodes 필드에서 출력 노드를 가져올 수 있습니다. 이 노드는 시작 노드에서의 비용에 따라 정렬되어 있습니다. 그러나 이 경로 유형에서는 비용이 저장되지 않기 때문에 직접 비용을 접근할 수는 없습니다.

이 설정을 통해 에이전트가 도달할 수 있는 모든 노드를 시각화할 수 있습니다.

// 여기서 새로운 경로를 생성하고 검색할 최대 거리를 설정합니다.
ConstantPath cpath = ConstantPath.Construct(transform.position, 20000, null);
AstarPath.StartPath(cpath);

// 경로가 계산될 때까지 대기합니다. 
// 위의 생성자에서 콜백을 제공하여 비동기적으로 계산할 수도 있습니다.

cpath.BlockUntilCalculated();

// 범위 내의 모든 노드에서 위쪽으로 선을 그립니다.
for (int i = 0; i < cpath.allNodes.Count; i++) {
Debug.DrawRay((Vector3)cpath.allNodes[i].position, Vector3.up, Color.red, 2f);
}
참조:
ConstantPath , PathUtilities.BFS 는 유사한 방식으로 작동하지만 경로 비용을 사용하지 않고, 노드에 도달하는 데 필요한 단계 수만 사용합니다.

 

 

FloodPath and FloodPathTracer

FloodPath 는 자체적으로는 특별히 유용하지 않지만, 모든 노드로의 경로를 계산하여 그래프를 "홍수"처럼 덮습니다. 이 데이터는 경로에 저장되며, 이후 FloodPathTracer 경로를 계산할 수 있습니다. FloodPathTracer 경로는 시작 지점에서 원래 FloodPath가 시작된 지점까지의 경로를 추적합니다. FloodPathTracer 검색은 일반 경로 요청에 비해 매우 빠릅니다.

예시 장면에서 테스트하는 방법:

  1. FloodPath 모드를 선택합니다.
  2. FloodPath가 시작될 지점을 클릭합니다.
  3. FloodPathTracer 모드를 선택합니다.
  4. 클릭하고 홀드하여 커서에서 FloodPath의 시작 지점까지 경로를 계산합니다.

콘솔에서 FloodPathTracer의 계산 시간을 확인할 수 있습니다. ABPath에 비해 매우 빠르게 계산되는 것을 확인할 수 있습니다.

이 경로 유형은 많은 에이전트를 생성하고 모두 동일한 목표로 이동해야 하는 게임 (예: 타워 디펜스 게임)에서 유용할 수 있습니다.

FloodPath  FloodPathTracer 참조

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

Spherical world  (0) 2024.05.23
Hexagonal Turn Based  (0) 2024.05.23
Lightweight Local Avoidance Simulation  (0) 2024.05.23
Infinite procedurally generated world  (0) 2024.05.23
Recast graph with off-mesh links  (0) 2024.05.23

로컬 회피를 최소한의 렌더링과 경로 탐색 없이 구현하는 예시 장면

 


이 예시 장면은 로컬 회피를 사용하여 어떤 움직임 스크립트, 경로 탐색, GameObjects 또는 로컬 회피에 꼭 필요하지 않은 오버헤드를 전혀 사용하지 않고 구현하는 방법을 보여줍니다. 이는 다른 시스템이 개입하지 않은 상태에서 로컬 회피 시스템의 성능을 평가하고자 할 때 유용합니다.

이 장면에는 LightweightRVO 컴포넌트가 포함되어 있으며, 이를 통해 다양한 구성에서 10에서 30,000까지의 에이전트를 시뮬레이션할 수 있습니다.

에이전트는 다음과 같은 다양한 목표로 구성할 수 있습니다:

  • 원형 (Circle): 에이전트가 원형으로 무작위로 생성되어 원의 반대편에 도달하려고 합니다.
  • 선형 (Line): 에이전트가 두 개의 평행선에 생성되어 다른 선의 대응 지점에 도달하려고 합니다. 이 목표는 적은 수의 에이전트와 함께 사용할 때 가장 효과적입니다.
  • 점 (Point): 에이전트가 원형으로 무작위로 생성되어 원의 중심에 도달하려고 합니다.
  • 무작위 스트림 (Random streams): 에이전트가 원판 형태로 무작위로 생성되어 다른 무작위 지점에 도달하려고 합니다.
  • 교차 (Crossing): 에이전트가 별 모양으로 생성되어 중심의 반대편 지점에 도달하려고 합니다. 이 목표는 적은 수의 에이전트와 함께 사용할 때 가장 효과적입니다.

LightweightRVO  컴포넌트는 Entity Component System을 사용하여 각 에이전트에 대해 하나의 엔티티를 생성합니다. 그런 다음 두 개의 커스텀 시스템을 사용하여 에이전트를 이동시키고, 단일 메쉬로 렌더링합니다. 또한 이미 실행 중인 RVOSystem 을 사용하여 로컬 회피를 시뮬레이션합니다. 이는 FollowerEntity 컴포넌트와 매우 유사하게 작동하지만, 움직임과 경로 탐색에 대한 복잡성이 훨씬 적습니다.

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

Hexagonal Turn Based  (0) 2024.05.23
Path Types  (0) 2024.05.23
Infinite procedurally generated world  (0) 2024.05.23
Recast graph with off-mesh links  (0) 2024.05.23
Recast graph in a 2D game  (0) 2024.05.22

플레이어를 따라가는 그래프를 무한한 세계에서 구현하는 방법을 보여주는 예시 장면

 

 

 

 

 

Contents

Graph setup

 

이 예시에서는 플레이어를 따라가는 작은 그리드 그래프를 사용합니다. 무한한 세계에서는 전체 그래프를 한 번에 생성할 수 없으므로, 플레이어 주변의 그래프만 생성하고 플레이어가 이동할 때 이를 함께 이동시킵니다.

우리는 그래프가 세계의 장애물을 표현할 수 있을 만큼 충분한 해상도를 가지도록 하고, 플레이어 주변의 적당한 크기를 덮을 수 있도록 합니다. 작은 그래프는 이동 시 오버헤드가 적지만, 플레이어가 큰 장애물을 계획하여 피하는 경로를 설정하기 어렵게 만듭니다.

참조:
자세한 내용은 " Large worlds" 튜토리얼에서 읽어볼 수 있습니다.
참고:
장면의 성능을 테스트할 때, 그래프 시각화를 숨기는 것이 좋습니다. 그래프 시각화를 업데이트하는 것이 그래프 자체를 업데이트하는 것보다 훨씬 느릴 수 있기 때문입니다.

 

Procedural world generation

ProceduralWorld GameObject는 세계 생성 구성을 포함하고 있습니다. ProceduralWorld 컴포넌트는 단순한 perlin noise 기반 스크립트로, 이를 사용하여 세계에 나무, 덤불, 바위 등을 무작위로 배치합니다. 플레이어가 이동하면 플레이어 뒤에 있는 오래된 객체를 제거하고, 플레이어 앞에 새로운 객체를 추가합니다.

간단히 말해, 이 스크립트는 타일의 그리드를 생성하고, 각 타일 내에서 Perlin 노이즈 밀도를 기반으로 여러 객체를 생성합니다. Perlin 노이즈는 객체 수의 변동을 부드럽게 하기 위해 확대하거나, 더 불규칙한 변동을 위해 축소할 수 있습니다.

이러한 노이즈 기반 세계 생성 알고리즘은 많은 게임(예: Minecraft)에서 지형 및 기타 객체를 생성하는 데 사용됩니다. 실제 게임에서는 종종 훨씬 더 복잡하고, 동굴, 강, 생태계 등 더 많은 기능을 가지고 있습니다.

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

Path Types  (0) 2024.05.23
Lightweight Local Avoidance Simulation  (0) 2024.05.23
Recast graph with off-mesh links  (0) 2024.05.23
Recast graph in a 2D game  (0) 2024.05.22
Recast graph on a terrain  (0) 2024.05.22

+ Recent posts