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

간단한 사용자 정의 이동 스크립트를 작성하는 튜토리얼입니다.

이 튜토리얼은 시작하는 가이드 튜토리얼의 일부입니다: 시작하기 가이드.

우리는 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>();

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

    public void OnPathComplete (Path p) {
        Debug.Log("야호, 경로를 받았어. 오류가 있었나요? " + p.error);
    }
}

 

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

재생을 누르세요. 로그 메시지를 확인해야 하며, 씬 뷰에 경로가 녹색 선으로 나타납니다 (Seeker 구성 요소는 마지막으로 계산된 경로를 Gizmos를 사용하여 그립니다).

녹색 선이 보이지 않으면 Seeker 구성 요소의 "Show Gizmos" 확인란이 선택되었는지 확인하세요. 최신 버전의 Unity에서는 gizmo를 깊이 테스트하므로 지면 아래에 숨겨져 있을 수 있습니다. 깊이 테스팅을 비활성화하려면 씬 뷰 창 위의 Gizmos 버튼을 클릭하고 "3D 아이콘" 확인란의 선택을 해제하세요.

에러가 발생하는 경우 Seeker 구성 요소가 실제로 AstarAI 스크립트가 있는 동일한 GameObject에 연결되어 있는지 확인하세요. 여전히 오류가 발생하면 대상 위치에 도달할 수 없을 수 있으므로 조금 변경해 보세요.

참고
이 페이지에서는 몇 가지 흔한 오류 메시지를 설명합니다: Error messages .

 

 

매끄럽지 않아 보이지만, 지금은 코드가 무엇을 했는지에 대한 설명을 기다리고 있을 수 있습니다.

먼저 스크립트는 Seeker의 StartPath 메서드를 호출합니다. 그런 다음 시커는 새 ABPath 인스턴스를 만들어 AstarPath 스크립트에 보냅니다. AstarPath 스크립트는 경로를 대기열에 넣습니다. 가능한 빨리 스크립트는 경로를 처리하고 그리드를 노드 단위로 검색하여 끝 노드가 찾아질 때까지 진행합니다.

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

 

경로가 계산되고 나면, 수정자가 첨부되어 있다면 수정자가 경로를 후처리한 후 Seeker에게 반환됩니다. 그런 다음 Seeker는 호출 시 지정된 콜백 함수를 호출할 것입니다.

계산된 경로를 받았을 때 어떻게 정보를 얻을 수 있을까요?

 

Path 인스턴스에는 이와 관련된 두 가지 목록이 있습니다. Path.vectorPath는 경로를 보유하는 Vector3 목록으로, 이 목록은 어떤 smoothing이 사용되더라도 수정될 것입니다. 이것은 경로를 얻는 권장되는 방법입니다. 두 번째로 Path.path 목록은 GraphNode 요소의 목록으로, 이 목록은 경로를 방문한 모든 노드를 보유하며, 통과한 경로에 대한 추가 정보를 얻는 데 유용할 수 있습니다.

 

먼저 항상 path.error를 확인해야 합니다. 이 값이 true이면 어떤 이유로든 경로가 실패한 것입니다. path.error가 true인 경우 Path.errorLog 필드에 무엇이 잘못되었는지에 대한 자세한 정보가 있습니다.

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

 

3D 2D
우리는 Unity의 내장된 컴포넌트인 CharacterController를 사용하여 에이전트를 이동시킬 것입니다. 그러므로 AI GameObject에 CharacterController를 추가하세요. 우리는 에이전트를 간단히 Transform 컴포넌트의 위치를 수정하여 이동시킬 것입니다.

 

스크립트는 현재 이동 중인 경유지를 추적하고 거기에 근접하면 즉시 다음 경유지로 변경합니다.

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

  • 먼저 우리가 따라야 할 계산된 경로가 있는지 확인합니다. 경로 요청은 비동기적이기 때문에 경로가 계산되기까지 시간이 걸릴 수 있습니다(일반적으로 한 프레임).
  • 그런 다음 에이전트가 현재 이동 중인 경유지에 가까운지 확인하고, 그렇다면 다음 경유지로 전환하고 확인을 반복합니다.
  • 이동 방법을 계산하려면 현재 경유지의 좌표를 취하고 그것에서 우리의 위치를 뺍니다. 이렇게 하면 경유지를 향한 벡터가 얻어집니다. 이 벡터의 길이를 1로 만들어 정규화하면, 그렇지 않으면 경유지에서 멀어질수록 더 빨리 이동할 것입니다.
  • 그런 다음 그 벡터를 우리의 속도로 곱하여 속도를 얻습니다.
  • 마지막으로 CharacterController.SimpleMove 메서드를 사용하여 에이전트를 이동시킵니다(2D 게임을 만들고 있다면 transform.position을 수정합니다).
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;
        // The distance to the next waypoint in the path
        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;

        // CharacterController 구성 요소를 사용하여 에이전트를 이동합니다.
        // SimpleMove 메서드는 초당 미터 단위의 속도를 받기 때문에 
        // Time.deltaTime으로 곱하지 않아야 합니다.
        controller.SimpleMove(velocity);

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

 

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

원한다면 GameObject에 SimpleSmoothModifier를 추가하여 경로를 더 부드럽게 만들 수 있습니다. Modifier에 대한 자세한 내용은 Using Modifiers에서 확인할 수 있습니다.

 

 

여기서는 경로를 정기적으로 다시 계산하는 것과 같은 몇 가지 추가적인 개선을 찾을 수 있습니다:  AstarAI.cs.

이것으로 이 튜토리얼은 마무리됩니다. get started tutorial의 나머지 부분을 계속 진행하는 것을 권장합니다.

 

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

Using navmeshes > Automatically generating a navmesh  (0) 2024.05.20
Using navmeshes > Creating a navmesh manually  (0) 2024.05.20
Using navmeshes  (0) 2024.05.20
Installation Guide  (0) 2024.05.20
Get Started Guide  (0) 2024.05.20

+ Recent posts