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

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

+ Recent posts