[Go-Unity 3D] 15. Navigation Mesh - Off Mesh Link โญโญโญ

์—…๋ฐ์ดํŠธ:

์นดํ…Œ๊ณ ๋ฆฌ:

ํƒœ๊ทธ: , ,


Navigation Mesh , Off mesh Link(์ˆ˜๋™,์ž๋™), Nav Mesh Obstacle, Patrol

๊ณ ๋ฐ•์‚ฌ์˜ ์œ ๋‹ˆํ‹ฐ ๊ธฐ์ดˆ๋ฅผ ์ •๋ฆฌ.

์‚ฌ๋‹ค๋ฆฌ ์ ˆ๋ฒฝ์‚ฌ์ด ์ด๋™, ๋‚™ํ•˜

1. Off Mesh Link

  • ์‚ฌ๋‹ค๋ฆฌ, ์•”๋ฒฝ๊ณผ ๊ฐ™์ด ์ˆ˜์ง์œผ๋กœ ์˜ฌ๋ผ๊ฐ€๊ฑฐ๋‚˜ ๋‚ด๋ ค์˜ค๋Š” ๊ธธ
  • ์ ˆ๋ฒฝ ์‚ฌ์ด๋ฅผ ๋›ฐ์–ด์„œ ๋„˜์–ด๊ฐ€๊ฑฐ๋‚˜ ๋‚ญ๋– ๋Ÿฌ์ง€ ์•„๋ž˜๋กœ ๋–จ์–ด์ง€๋Š” ๊ธธ
  • ๋ฉ”์‹œ๊ฐ€ ๋Š์–ด์ ธ ์žˆ๋Š” ๊ณณ์„ ์ด๋™ํ•  ์ˆ˜ ์žˆ๊ฒŒ ์„ค์ •ํ•˜๋Š” ๊ฒƒ
  • ์„ค์ • : ์ž๋™ , ์ˆ˜๋™
  • auto traverse off mesh : ์ด๋™์˜ค๋ธŒ์ ํŠธ์˜ nav mesh agent ์ปดํฌ๋„ŒํŠธ์— ์žˆ์œผ๋ฉฐ,
    ์ฒดํฌํ•ด์ œ์‹œ off mesh link ๊ตฌ๊ฐ„์—์„œ ๋ฉˆ์ถ˜๋‹ค, ํ•ด์ œ์‹œ ๋”ฐ๋กœ ๋™์ž‘์„ ๋งŒ๋“ค์–ด ์‚ฌ์šฉํ•œ๋‹ค ex) Off Mesh Link Climb.cs offmesh2
  1. Navigation view - Objectํƒญ -> Generate OffMeshLinks ์ฒดํฌ
    offmesh
  2. Navigation view - Bakeํƒญ์˜ ๋‚™ํ•˜ ๋†’์ด, ์ ํ”„ ๊ฑฐ๋ฆฌ ์„ค์ • ํ›„ Bake๋กœ ๋ฐ์ดํ„ฐ ์ €์žฅ
    offmesh1

์žฅ์ 

  • ๊ฒŒ์ž„์›”๋“œ์— ๋ฐฐ์น˜๋œ ๋งŽ์€ ์˜ค๋ธŒ์ ํŠธ์˜ Off Mesh Link๋ฅผ ํ•œ๊บผ๋ฒˆ์— ์„ค์ • ๊ฐ€๋Šฅ

๋‹จ์ 

  • ๋‚™ํ•˜ ๋†’์ด์™€ ์ ํ”„ ๊ฑฐ๋ฆฌ๋ฅผ ํ•˜๋‚˜๋งŒ ์„ค์ •ํ•  ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ๋‹ค์–‘ํ•œ ์ง€ํ˜•์„ ์„ธ์„ธํ•˜๊ฒŒ X
  • ์œ„๋กœ ์˜ฌ๋ผ๊ฐ€๋Š” Off Mesh Link ์„ค์ • X


  1. ์—ฐ๊ฒฐ๋˜๋Š” ๋‘ ์ง€์ ‘์œผ๋กœ ์‚ฌ์šฉํ•  ์˜ค๋ธŒ์ ํŠธ ์ƒ์„ฑ ๋ฐ ๋ฐฐ์น˜ (Start, End)
  2. off mesh link ์ปดํฌ๋„ŒํŠธ๋ฅผ ์ƒ์„ฑํ•˜๊ณ , Start, End ๋ณ€์ˆ˜์— ์—ฐ๊ฒฐ๋˜๋Š” ๋‘ ์ง€์ ์„ ์„ค์ •
    image

์žฅ์ 

  • ์ง€ํ˜•์— ๋”ฐ๋ผ ์„ธ์„ธํ•œ ์„ค์ •์ด ๊ฐ€๋Šฅ
  • ์‚ฌ๋‹ค๋ฆฌ/์•”๋ฒฝ๊ณผ ๊ฐ™์ด ์œ„๋กœ ์˜ฌ๋ผ๊ฐ€๋Š” ์„ค์ • ๊ฐ€๋Šฅ

๋‹จ์ 

  • Off Mesh Link๋กœ ์—ฐ๊ฒฐ์ด ํ•„์š”ํ•œ ๋ชจ๋“  ๋ถ€๋ถ„์„ ์ง์ ‘ ์„ค์ •ํ•ด์•ผํ•จ





2. ๊ตฌ์—ญ(Area)

์–ด๋Š๊ธธ์ด ๋” ๋น ๋ฅด๊ณ  ๋Š๋ฆฐ์ง€ ํ™•์ธํ•˜๊ธฐ ์œ„ํ•ด์„œ

  • ์ผ๋ฐ˜๊ตฌ์—ญ์€ ObJectํƒญ์—์„œ ์„ค์ •๊ฐ€๋Šฅ
    image

  • Navigation view - Areaํƒญ์—์„œ ๊ตฌ์—ญ ์ถ”๊ฐ€
    image

  • ์‚ฌ๋‹ค๋ฆฌ์— Climb area๋ฅผ ์ถ”๊ฐ€ ํ•˜๊ธฐ์œ„ํ•ด Area์— Climb ์ถ”๊ฐ€
    image

  • ์ƒˆ๋กœ์šด ๊ตฌ์—ญ์ด ์ƒ๊ธฐ๊ฒŒ ๋˜๋ฉด ์ด๋™ํ•˜๋Š” ์˜ค๋ธŒ์ ํŠธ์˜ nav mesh agent์ปดํฌ๋„ŒํŠธ์˜ Area Mask์— ํ™•์ธํ•ด๋ณด๊ธฐ image





3. OffMeshLinkClimb.cs

์‚ฌ๋‹ค๋ฆฌ ์‚ฌ์šฉ์‹œ ๋™์ž‘

OffMeshLinkClimb.cs

using System.Collections;
using UnityEngine;
using UnityEngine.AI;

public class OffMeshLinkClimb : MonoBehaviour
{
    [SerializeField] 
    private int             offMeshArea = 3;   // ์˜คํ”„๋ฉ”์‹œ์˜ ๊ตฌ์—ญ (Climb)
    [SerializeField] 
    private float           climbSpeed = 1.5f; // ์˜ค๋ฅด๋‚ด๋ฆฌ๋Š” ์ด๋™ ์†๋„ 
    private NavMeshAgent    navMeshAgent;

    private void Awake() {
        navMeshAgent = GetComponent<NavMeshAgent>();
    }

    IEnumerator Start(){
        while (true)
        {
            // IsOnClimb() ํ•จ์ˆ˜์˜ ๋ฐ˜ํ™˜ ๊ฐ’์ด true์ผ ๋•Œ ๊นŒ์ง€ ๋ฐ˜๋ณต ํ˜ธ์ถœ 
            yield return new WaitUntil(() => IsOnClimb());
            // ์˜ฌ๋ผ๊ฐ€๊ฑฐ๋‚˜ ๋‚ด๋ ค์˜ค๋Š” ํ–‰๋™
            yield return StartCoroutine (ClimbOrDescend());
        }
    }

    public bool IsOnClimb(){

        //ํ˜„์žฌ ์˜ค๋ธŒ์ ํŠธ์˜ ์œ„์น˜๊ฐ€ OffMeshLink์— ์žˆ๋Š”์ง€ (true / False)
        if (navMeshAgent.isOnOffMeshLink){
            // ํ˜„์žฌ ์œ„์น˜์— ์žˆ๋Š” OffMeshLink์˜ ๋ฐ์ดํ„ฐ
            
            OffMeshLinkData linkData = navMeshAgent.currentOffMeshLinkData;
            // ์„ค๋ช… : navMeshAgent.currentOffMeshLinkData.offMeshLink๊ฐ€
            // true์ด๋ฉด ์ˆ˜๋™์œผ๋กœ ์ƒ์„ฑํ•œ offMeshLink
            // false์ด๋ฉด ์ž๋™์œผ๋กœ ์ƒ์„ฑํ•œ offMeshLink
            
            // ํ˜„์žฌ ์œ„์น˜์— ์žˆ๋Š” OffMeshLink๊ฐ€ ์ˆ˜๋™์œผ๋กœ ์ƒ์„ฑํ•œ OffMeshLink์ด๊ณ , ์žฅ์†Œ ์ •๋ณด๊ฐ€ "climb"์ด๋ฉด 
            if (linkData.offMeshLink != null && linkData.offMeshLink.area == offMeshArea){
                return true;
            }
        }
        return false;
    }

    private IEnumerator ClimbOrDescend(){
        // ์˜ฌ๋ผ๊ฐ€๊ฑฐ๋‚˜ ๋‚ด๋ ค๊ฐ€๋Š” ํ–‰๋™

        // ๋„ค๋น„๊ฒŒ์ด์…˜์„ ์ด์šฉํ•œ ์ด๋™์„ ์ž ์‹œ ์ค‘์ง€ํ•œ๋‹ค 
        navMeshAgent.isStopped = true;

        // ํ˜„์žฌ ์œ„์น˜์— ์žˆ๋Š” offMeshLink์˜ ์‹œ์ž‘/์ข…๋ฃŒ ์œ„์น˜
        OffMeshLinkData linkData = navMeshAgent.currentOffMeshLinkData; 
        Vector3 start   = linkData.startPos; 
        Vector3 end     = linkData.endPos;

        // ์˜ค๋ฅด๋‚ด๋ฆฌ๋Š” ์‹œ๊ฐ„ ์„ค์ •
        float climbTime     = Mathf. Abs(end.y - start.y) / climbSpeed;
        float currentTime   = 0.0f;
        float percent       = 0.0f;

        while( percent <1){
            // ๋‹จ์ˆœํžˆ deltaTime๋งŒ ๋”ํ•˜๋ฉด ๋ฌด์กฐ๊ฑด 1์ดˆ ํ›„์— percent๊ฐ€ 1์ด ๋˜๊ธฐ ๋•Œ๋ฌธ์— 
            // climbTime ๋ณ€์ˆ˜๋ฅผ ์—ฐ์‚ฐํ•ด์„œ ์‹œ๊ฐ„์„ ์กฐ์ ˆํ•œ๋‹ค.
            currentTime += Time.deltaTime;
            percent = currentTime/climbTime;

            // ์‹œ๊ฐ„ ๊ฒฝ๊ณผ(์ตœ๋Œ€ 1)์— ๋”ฐ๋ผ ์˜ค๋ธŒ์ ํŠธ์˜ ์œ„์น˜๋ฅผ ๋ฐ”๊ฟ”์ค€๋‹ค 
            transform.position= Vector3. Lerp(start, end, percent);

            yield return null;
        }
        // OffMeshLink๋ฅผ ์ด์šฉํ•œ ์ด๋™ ์™„๋ฃŒ
        navMeshAgent.CompleteOffMeshLink();
        // offMeshLink ์ด๋™์ด ์™„๋ฃŒ๋˜์—ˆ์œผ๋‹ˆ ๋„ค๋น„๊ฒŒ์ด์…˜์„ ์ด์šฉํ•œ ์ด๋™์„ ๋‹ค์‹œ ์‹œ์ž‘ํ•œ๋‹ค 
        navMeshAgent.isStopped = false;
    }
}
  • offmeshlink ์‚ฌ๋‹ค๋ฆฌ ๋™์ž‘์„ ๋งŒ๋‚˜๋ฉด ํ–‰๋™์„ ๋ฉˆ์ถ˜ํ›„ ์‚ฌ๋‹ค๋ฆฌ๋™์ž‘์„ ์‹คํ–‰ํ•˜๊ณ  ๋‹ค์‹œ ์ด๋™





4. OffMeshLinkJump.cs

๋–จ์–ด์ง€๊ฑฐ๋‚˜ ์ ˆ๋ฒฝ์‚ฌ์ด๋ฅผ ์ ํ”„ํ• ๋•Œ

OffMeshLinkJump.cs

using System.Collections;
using UnityEngine;
using UnityEngine.AI;
public class OffMeshLinkJump : MonoBehaviour
{

    [SerializeField] 
    private float           jumpSpeed =  10.0f;     // ์ ํ”„ ์†๋„
    [SerializeField] 
    private float           gravity = -9.81f;       // ์ค‘๋ ฅ ๊ณ„์ˆ˜
    private NavMeshAgent    navMeshAgent;

    private void Awake(){
        navMeshAgent = GetComponent<NavMeshAgent>();
    }
     
    IEnumerator Start(){
        while ( true )
        {
            // IsOnJump() ํ•จ์ˆ˜์˜ ๋ฐ˜ํ™˜ ๊ฐ’์ด true์ผ ๋•Œ ๊นŒ์ง€ ๋ฐ˜๋ณต ํ˜ธ์ถœ 
            yield return new WaitUntil(() => IsOnJump());

            // ์ ํ”„ ํ–‰๋™
            yield return StartCoroutine(JumpTo());
        }
    }

    public bool IsOnJump()
    {
        if( navMeshAgent.isOnOffMeshLink )
        {
            // ํ˜„์žฌ ์œ„์น˜์— ์žˆ๋Š” offMeshLink์˜ ๋ฐ์ดํ„ฐ
            OffMeshLinkData linkData = navMeshAgent.currentOffMeshLinkData;

            // OffMeshLinkType์€ Manual=0, DropDown=1, JumpAcross=2๋กœ
            // ์ž๋™์œผ๋กœ ์ƒ์„ฑํ•œ OffMeshLink์˜ ์†์„ฑ ๊ตฌ๋ถ„์„ ์œ„ํ•ด ์‚ฌ์šฉ(1, 2)

            // ํ˜„์žฌ ์œ„์น˜์— ์žˆ๋Š” offMeshLink์˜ offMeshLinkType์ด JumpAcross ์ด๋ฉด 
            if (linkData.linkType == OffMeshLinkType.LinkTypeJumpAcross ||
                linkData.linkType == OffMeshLinkType.LinkTypeDropDown )
            {
                return true;
            }
        }
        return false;
    }

    IEnumerator JumpTo(){

        // ๋„ค๋น„๊ฒŒ์ด์…˜์„ ์ด์šฉํ•œ ์ด๋™์„ ์ž ์‹œ ์ค‘์ง€ํ•œ๋‹ค 
        navMeshAgent.isStopped = true;

        // ํ˜„์žฌ ์œ„์น˜์— ์žˆ๋Š” offMeshLink์˜ ์‹œ์ž‘/์ข…๋ฃŒ ์œ„์น˜ 
        OffMeshLinkData linkData = navMeshAgent.currentOffMeshLinkData; 
        Vector3 start = transform.position;
        Vector3 end = linkData.endPos;

        // ๋›ฐ์–ด์„œ ์ด๋™ํ•˜๋Š” ์‹œ๊ฐ„ ์„ค์ •
        float jumpTime      = Mathf.Max(0.3f, Vector3.Distance(start, end) / jumpSpeed);
        float currentTime   = 0.0f;
        float percent       = 0.0f;

        // y ๋ฐฉํ–ฅ์˜ ์ดˆ๊ธฐ ์†๋„
        float v0 = (end-start).y - gravity;

        while (percent < 1){

            // ๋‹จ์ˆœํžˆ deltaTime๋งŒ ๋”ํ•˜๋ฉด ๋ฌด์กฐ๊ฑด 1์ดˆ ํ›„์— percent๊ฐ€ 1์ด ๋˜๊ธฐ ๋•Œ๋ฌธ์—
            // jumpTime ๋ณ€์ˆ˜๋ฅผ ์—ฐ์‚ฐํ•ด์„œ ์‹œ๊ฐ„์„ ์กฐ์ ˆํ•œ๋‹ค
            currentTime += Time.deltaTime; 
            percent=currentTime/jumpTime;

            // ์‹œ๊ฐ„ ๊ฒฝ๊ณผ(์ตœ๋Œ€ 1)์— ๋”ฐ๋ผ ์˜ค๋ธŒ์ ํŠธ์˜ ์œ„์น˜(x, z)๋ฅผ ๋ฐ”๊ฟ”์ค€๋‹ค 
            Vector3 position = Vector3. Lerp(start, end, percent);

            // ์‹œ๊ฐ„ ๊ฒฝ๊ณผ์— ๋”ฐ๋ผ ์˜ค๋ธŒ์ ํŠธ์˜ ์œ„์น˜(y)๋ฅผ ๋ฐ”๊ฟ”์ค€๋‹ค.
            // ํฌ๋ฌผ์„  ์šด๋™: ์‹œ์ž‘์œ„์น˜+ ์ดˆ๊ธฐ์†๋„*์‹œ๊ฐ„+ ์ค‘๋ ฅ*์‹œ๊ฐ„์ œ๊ณฑ
            position.y = start.y + (v0 * percent) + (gravity * percent * percent); 

            // ์œ„์—์„œ ๊ณ„์‚ฐํ•œ x, y, z ์œ„์น˜ ๊ฐ’์„ ์‹ค์ œ ์˜ค๋ธŒ์ ํŠธ์— ๋Œ€์ž…
            transform.position = position;

            yield return null;
        }   
        // OffMeshLink๋ฅผ ์ด์šฉํ•œ ์ด๋™ ์™„๋ฃŒ
        navMeshAgent.CompleteOffMeshLink();
        // offMeshLink ์ด๋™์ด ์™„๋ฃŒ๋˜์—ˆ์œผ๋‹ˆ ๋„ค๋น„๊ฒŒ์ด์…˜์„ ์ด์šฉํ•œ ์ด๋™์„ ๋‹ค์‹œ ์‹œ์ž‘ํ•œ๋‹ค. 
        navMeshAgent.isStopped = false;
    }
}

  • offmeshlink ์ ˆ๋ฒฝ์„ ๋งŒ๋‚˜๋ฉด ํ–‰๋™์„ ๋ฉˆ์ถ˜ํ›„ ์‚ฌ๋‹ค๋ฆฌ๋™์ž‘์„ ์‹คํ–‰ํ•˜๊ณ  ๋‹ค์‹œ ์ด๋™





5. Nav Mesh Obstacle ์ปดํฌ๋„ŒํŠธ

์ด๋™ ์˜ค๋ธŒ์ ํŠธ์˜ ๋„ค๋น„๊ฒŒ์ด์…˜ ๋ฉ”์‹œ ์„ค์ •์— ์‚ฌ์šฉ๋˜๋Š” ์ปดํฌ๋„ŒํŠธ

image

  • Shape : ์žฅ์• ๋ฌผ์˜ ๋ชจ์–‘
  • Center : shape ์˜ ์ค‘์‹ฌ
  • Carve : navigation mesh์— ๊ณต๊ฐ„์„ ๋น„์šธ์ง€ (true:๋น„์šด๋‹ค)
    move Threshold
    ์„ค์ •๋œ ๊ฑฐ๋ฆฌ๋ฅผ ์ด๋™ํ•˜๋ฉด ์˜ค๋ธŒ์ ํŠธ์˜ Navigation mesh ๋ฐ์ดํ„ฐ ๊ฐฑ์‹ 

time to stationary : ์„ค์ •๋œ ์‹œ๊ฐ„๋งŒํผ ์›€์ง์ž„์ด ์—†์œผ๋ฉด โ€œ๋ฉˆ์ถคโ€์œผ๋กœ ์ธ์‹

carve Only stationary : ๋ฉˆ์ถค ์ผ๋•Œ ๊ณต๊ฐ„์„ ๋น„์šธ์ง€
(true : ๋ฉˆ์ถœ๋•Œ๋งŒ ๋น„์›€, flase : ์‹ค์‹œ๊ฐ„์œผ๋กœ ๋น„์›€)

Patrol ์ˆœ์ฐฐ

SimplePatrol.cs

using UnityEngine;

public class SimplePatrol : MonoBehaviour
{
    [SerializeField]
    private Transform[] paths;              // ์ˆœ์ฐฐ ๊ฒฝ๋กœ
    private int         currentPath = 0;    // ํ˜„์žฌ ๋ชฉํ‘œ์ง€์  ์ธ๋ฑ์Šค
    private float       moveSpeed = 3.0f;   // ์ด๋™ ์†๋„

    private void Update()
    {
        // ์ด๋™ ๋ฐฉํ–ฅ ์„ค์ • : (๋ชฉํ‘œ์œ„์น˜-๋‚ด์œ„์น˜). ์ •๊ทœํ™”
        Vector3 direction= (paths[currentPath].position - transform.position).normalized; 

        // ์˜ค๋ธŒ์ ํŠธ ์ด๋™
        transform.position += direction * moveSpeed * Time.deltaTime;

        // ๋ชฉํ‘œ ์œ„์น˜์— ๊ฑฐ์˜ ๋„๋‹ฌํ–ˆ์„ ๋•Œ                        (a-b)sqrMagnitude : a-b ๊ฑฐ๋ฆฌ ๊ฐ’์„ ์ œ๊ณฑํ•œ ๊ฒฐ๊ณผ ์—ฐ์‚ฐ์†๋„ ๋น ๋ฆ„
        if ( (paths[currentPath].position - transform.position).sqrMagnitude < 0.1f )
        {                           

            // ๋ชฉํ‘œ ์œ„์น˜ ๋ณ€๊ฒฝ (์ˆœ์ฐฐ ๊ฒฝ๋กœ ์ˆœํ™˜)
            if ( currentPath < paths.Length - 1) currentPath ++;
            else                                 currentPath = 0;
        }
    }
}
  • patrol ์ฝ”๋“œ ์ ์šฉ ํ›„ cube ์ด๋™ ์ž๋ฆฌ๋Š” ์ด๋™ํ•  ์ˆ˜ ์—†๋Š” ์ž๋ฆฌ๋กœ ๋ณด์ง€ ์•Š๋Š”๋‹ค.
  • nav mesh obstacle ์ปดํฌ๋„ŒํŠธ๋ฅผ ์ถ”๊ฐ€ํ•˜์—ฌ ํ๋ธŒ๋กœ ์ด๋™ ์œ„์น˜๋ฅผ ์ด๋™ํ•  ์ˆ˜ ์—†๋Š” ๊ฒฝ๋กœ๋กœ๋ณธ๋‹ค.

nav mesh obstacle ์ถ”๊ฐ€ X
image

nav mesh obstacle ์ถ”๊ฐ€ ํ›„
image
nav mesh obstacle ์ปดํฌ๋„ŒํŠธ๋ฅผ ์ถ”๊ฐ€ํ•˜์—ฌ ํ๋ธŒ๋กœ ์ด๋™ ์œ„์น˜๋ฅผ ์ด๋™ํ•  ์ˆ˜ ์—†๋Š” ๊ฒฝ๋กœ๋กœ๋ณธ๋‹ค.





6. ์ •๋ฆฌ

  • Off Mesh Link ์— ๋Œ€ํ•ด์„œ (์‚ฌ๋‹ค๋ฆฌ, ์ ˆ๋ฒฝ์‚ฌ์ด ์ ํ”„, ์ ˆ๋ฒฝ ๋‚™ํ•˜)
  • Area ์ถ”๊ฐ€ , ์ฝ”์ŠคํŠธ, ๋” ๋น ๋ฅธ๊ธธ ์ฐพ๊ฒŒ ๋„์™€์คŒ.
  • nav Mesh Obstacle์„ ์ด์šฉํ•ด ์‹ค์‹œ๊ฐ„์œผ๋กœ navํ™•์ธ


์ฐธ๊ณ  : ์œ ๋‹ˆํ‹ฐ TOP


๐Ÿ“”

๋Œ“๊ธ€๋‚จ๊ธฐ๊ธฐ