유니티 에셋/A* Pathfinding project pro

Movement scripts > Writing a movement script

스크립팅하는애님 2024. 5. 24. 00:26

간단한 사용자 정의 이동 스크립트 작성 튜토리얼입니다.
이 튜토리얼은 시작하기 가이드의 일부입니다: 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.