๋ณธ๋ฌธ ๋ฐ”๋กœ๊ฐ€๊ธฐ

Unity/AR

[Unity AR] AR ํ™˜๊ฒฝ ๋‚ด๋น„๊ฒŒ์ด์…˜ & ๊ธธ ์ฐพ๊ธฐ ์‹œ์Šคํ…œ ๊ฐ„๋‹จํžˆ ๊ตฌ์ถ•ํ•˜๊ธฐ - NavMesh

728x90
๋ฐ˜์‘ํ˜•

 

 

 

 


 

 

์•ˆ๋…•ํ•˜์„ธ์š”. ์˜ค๋Š˜์€ Unity AR ํ™˜๊ฒฝ์—์„œ ๋‚ด๋น„๊ฒŒ์ด์…˜ ์‹œ์Šคํ…œ์„ ๊ฐ„๋‹จํ•˜๊ฒŒ ๊ตฌ์ถ•ํ•ด ๋ณด๋Š” ๊ธ€์„ ์“ฐ๊ฒ ์Šต๋‹ˆ๋‹ค.  ํ˜„์‹ค์„ธ๊ณ„์— ์นด๋ฉ”๋ผ๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ๋Š” ํ”Œ๋ ˆ์ด์–ด์—๊ฒŒ Target์„ ์ง€์ •ํ•˜์—ฌ Target๊นŒ์ง€์˜ ๊ฒฝ๋กœ๋ฅผ ์‹œ๊ฐ์ ์œผ๋กœ ํ‘œ์‹œํ•˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. 

 

https://www.youtube.com/shorts/fxetmnKnh_M

 

 

 

 

 

 

 

 

 AR Navigation ์‹œ์Šคํ…œ ๊ตฌ์ถ•

 

๊ฐ ๋ณ€์ˆ˜์™€ ํ•จ์ˆ˜๋“ค์„ ๋‚˜๋ˆ  ๊ฐ„๋‹จํžˆ ์„ค๋ช…ํ•œ ๋’ค ์ „์ฒด์ ์ธ ์ฝ”๋“œ๊นŒ์ง€ ์˜ฌ๋ฆฌ๋Š” ๋ฐฉ์‹์œผ๋กœ ๊ธ€์„ ์ง„ํ–‰ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค. 

 

๋จผ์ €, ํ•„์š”ํ•œ ์˜ค๋ธŒ์ ํŠธ๋“ค ์ž…๋‹ˆ๋‹ค. AR ํ™˜๊ฒฝ์„ ์œ„ํ•ด AR Session Origin๊ณผ AR Session์„ ๋งŒ๋“ค์–ด ์ค๋‹ˆ๋‹ค. 
Line ์˜ค๋ธŒ์ ํŠธ๋Š” Line Renderer๋ฅผ ๋‹ด๊ณ  ์žˆ๋Š” ์˜ค๋ธŒ์ ํŠธ๋กœ ์šฐ๋ฆฌ์—๊ฒŒ ์‹œ๊ฐ์ ์œผ๋กœ ๊ฒฝ๋กœ๋ฅผ ํ‘œ์‹œํ•˜๊ธฐ ์œ„ํ•ด Line์„ ๊ทธ๋ ค์ค๋‹ˆ๋‹ค.
Collectable Spawner ์˜ค๋ธŒ์ ํŠธ๋Š” ๋นˆ ์˜ค๋ธŒ์ ํŠธ๋กœ, ๋‚ด๋น„๊ฒŒ์ด์…˜ ์‹œ์Šคํ…œ์„ ๊ด€๋ฆฌํ•˜๋Š” CollectableSpawn.cs ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋‹ด๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. 
Plane 3D ์˜ค๋ธŒ์ ํŠธ๋ฅผ ์ƒ์„ฑํ•˜์—ฌ ๋‚ด๋น„๊ฒŒ์ด์…˜ ์‹œ์Šคํ…œ์ด ์ง€ํ˜•์„ ๋”ฐ๋ผ ๊ฒฝ๋กœ๋ฅผ ๊ณ„์‚ฐํ•˜๋„๋ก ๋งŒ๋“ค์–ด ์ค๋‹ˆ๋‹ค.

 

 

์ด์–ด์„œ, Window - AI - Navigation์„ ์„ ํƒํ•ด์ค๋‹ˆ๋‹ค. 

 

๋งŒ๋“ค์—ˆ๋˜ Plane ์˜ค๋ธŒ์ ํŠธ๋ฅผ Static ์ฒดํฌํ•ด์ค๋‹ˆ๋‹ค.

 

 

 

์ด์ œ Navigation - Bake - Bake๋ฅผ ํด๋ฆญํ•˜์—ฌ NavMesh๋ฅผ ์ƒ์„ฑํ•ด ์ค๋‹ˆ๋‹ค. ์ƒ์„ฑํ•ด ์ฃผ์ง€ ์•Š์œผ๋ฉด ์ด๋™ ๊ฒฝ๋กœ๋ฅผ ๊ณ„์‚ฐํ•  ์ˆ˜ ์—†์–ด ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.

 

Bake๋ฅผ ํ•ด์„œ NavMesh๋ฅผ ์ƒ์„ฑํ–ˆ์„ ๋•Œ์™€ Clear๋ฅผ ํ•ด์„œ NavMesh๊ฐ€ ์—†์„ ๋•Œ ์ฐจ์ด

 

 

 

 

 

CollectableSpawn.cs ํด๋ž˜์Šค๋Š” ์ง€์ •ํ•œ Target ์˜ค๋ธŒ์ ํŠธ๋ฅผ ๋‚ด๋น„๊ฒŒ์ด์…˜ ์‹œ์Šคํ…œ์„ ์‚ฌ์šฉํ•˜์—ฌ ํ”Œ๋ ˆ์ด์–ด์—๊ฒŒ ๊ฒฝ๋กœ๋ฅผ ์‹œ๊ฐ์ ์œผ๋กœ ํ‘œ์‹œํ•˜๋Š” ์—ญํ• ์„ ํ•ฉ๋‹ˆ๋‹ค. NavMesh์™€ Line Renderer์ด ๊ทธ ์—ญํ• ์„ ํ•˜๋Š” ๋ฐ ๋„์›€์„ ์ค๋‹ˆ๋‹ค. ์•„๋ž˜ ๊ฐ„๋‹จํžˆ ์š”์•ฝํ•ด์„œ ์ •๋ฆฌํ•ฉ๋‹ˆ๋‹ค. 

1. using UnityEngine.AI๋Š” Unity์˜ ๋‚ด๋น„๊ฒŒ์ด์…˜ ์‹œ์Šคํ…œ ๊ด€๋ จ ํด๋ž˜์Šค ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•œ ๋„ค์ž„์ŠคํŽ˜์ด์Šค๋ฅผ ์ž„ํฌํŠธํ•ฉ๋‹ˆ๋‹ค.

2. ๋ชจ๋“  ๋ณ€์ˆ˜๋Š” ํ”„๋ผ์ด๋น— ๋ณ€์ˆ˜๋กœ ์„ ์–ธํ•˜๊ณ  SerializeField๋ฅผ ๋„ฃ์–ด์ฃผ์–ด ์ธ์ŠคํŽ™ํ„ฐ์—์„œ ์„ค์ • ๊ฐ€๋Šฅํ•˜๊ฒŒ ๋งŒ๋“ค์—ˆ์Šต๋‹ˆ๋‹ค.
๊ฒฝ๋กœ ๋†’์ด์™€ ์•„์ดํ…œ ์Šคํฐ ๋†’์ด์™€ ๊ฒฝ๋กœ ์—…๋ฐ์ดํŠธ ์†๋„๋Š” ์›ํ•˜๋Š” ๊ฐ’์œผ๋กœ ์„ค์ •ํ•ด ์ฃผ์…”๋„ ๋ฉ๋‹ˆ๋‹ค.

3. Awake() ํ•จ์ˆ˜ ์•ˆ์—์„œ Triangulation ๋ณ€์ˆ˜์— ๋‚ด๋น„๊ฒŒ์ด์…˜ ๋ฉ”์‹œ์˜ ์‚ผ๊ฐํ™” ์ •๋ณด๋ฅผ ๊ณ„์‚ฐํ•ด์„œ ํ• ๋‹นํ•ฉ๋‹ˆ๋‹ค.

4. Start() ํ•จ์ˆ˜ ์•ˆ์—์„œ SpawnNewObject()ํ•จ์ˆ˜๋ฅผ ํ˜ธ์ถœํ•˜์—ฌ ์ƒˆ๋กœ์šด ์•„์ดํ…œ (Target ์˜ค๋ธŒ์ ํŠธ)์„ ์Šคํฐํ•ฉ๋‹ˆ๋‹ค.

 

 

 

SpawnNewObject() ํ•จ์ˆ˜์˜ ์—ญํ• ์€ ์ƒˆ๋กœ์šด ์•„์ดํ…œ์„ ์Šคํฐํ•˜๊ณ , ๊ฒฝ๋กœ ๊ทธ๋ฆฌ๊ธฐ ์ฝ”๋ฃจํ‹ด์„ ๊ด€๋ฆฌํ•ฉ๋‹ˆ๋‹ค.

1. if (Triangulation.vertices.Length == 0) ์ผ ๋•Œ ์ฆ‰, ๊ณ„์‚ฐํ•  NavMesh ์ •๋ณด๊ฐ€ ์—†๋‹ค๋ฉด ๋””๋ฒ„๊ทธ ๋กœ๊ทธ๋ฅผ ํ†ตํ•ด ์—๋Ÿฌ ๋ฉ”์„ธ์ง€๋ฅผ ์ถœ๋ ฅํ•ฉ๋‹ˆ๋‹ค. ์ด ์—๋Ÿฌ ๋ฉ”์„ธ์ง€๋Š” ์œ„์˜ ์˜์ƒ์—์„œ Bake ํ•  ๋•Œ Clear ํ•  ๋•Œ ์ฐจ์ด์—์„œ ๋ณด์—ฌ์ง‘๋‹ˆ๋‹ค.

2. ์˜ค๋ธŒ์ ํŠธ์˜ ์œ„์น˜๋Š” Random.Range()๋ฅผ ํ†ตํ•ด ๋ฌด์ž‘์œ„ ์œ„์น˜์— ์ƒ์„ฑ๋˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. 

3. if (DrawPathCoroutine != null ) ๋ถ„๊ธฐ๋ฌธ์—์„œ ๊ฒฝ๋กœ ๊ทธ๋ฆฌ๊ธฐ ์ฝ”๋ฃจํ‹ด์ด ์‹คํ–‰ ์ค‘์ธ์ง€ ํŒ๋‹จํ•ด์„œ ์‹œ์ž‘ or ์ค‘์ง€ ์—ญํ• ์„ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค.

 

 

 

DrawPathToCollectable() ์ฝ”๋ฃจํ‹ด ๋ฉ”์„œ๋“œ์ž…๋‹ˆ๋‹ค.  ํ™œ์„ฑํ™”๋œ ์˜ค๋ธŒ์ ํŠธ์™€ ํ”Œ๋ ˆ์ด์–ด ์‚ฌ์ด์˜ ๊ฒฝ๋กœ๋ฅผ ๊ณ„์‚ฐํ•˜๊ณ  ์‹œ๊ฐ์ ์œผ๋กœ ํ‘œ์‹œํ•˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.

1. wati ๋ณ€์ˆ˜๋Š” ๊ฒฝ๋กœ ์—…๋ฐ์ดํŠธ ์†๋„์— ๋”ฐ๋ฅธ ๋”œ๋ ˆ์ด๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.
2. path ๋ณ€์ˆ˜๋Š” ๋‚ด๋น„๊ฒŒ์ด์…˜ ๊ฒฝ๋กœ๋ฅผ ์ €์žฅํ•  NavMeshPath ๊ฐ์ฒด์ž…๋‹ˆ๋‹ค. ์œ„์—์„œ using UnityEngine.AI ๋„ค์ž„์ŠคํŽ˜์ด์Šค๋กœ ์ €์žฅํ–ˆ๊ธฐ ๋•Œ๋ฌธ์— ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.
3. while() ๋ฌธ์—์„œ ์˜ค๋ธŒ์ ํŠธ๊ฐ€ ์กด์žฌํ•  ๋•Œ ๋™์•ˆ ์‹คํ–‰ํ•˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. 
4. ํด๋ž˜์Šค NavMesh์˜ CalculatePath() ํ•จ์ˆ˜๋ฅผ ํ†ตํ•ด ํ”Œ๋ ˆ์ด์–ด์˜ ์œ„์น˜์™€ Target์˜ ์œ„์น˜๋ฅผ ๊ณ ๋ คํ•˜์—ฌ ๊ฒฝ๋กœ๋ฅผ ๊ณ„์‚ฐํ•ฉ๋‹ˆ๋‹ค.
5. for() ๋ฐ˜๋ณต๋ฌธ์„ ์—์„œ ๊ฒฝ๋กœ๋ฅผ ์‹œ๊ฐ์ ์œผ๋กœ ํ‘œ์‹œํ•˜๊ธฐ ์œ„ํ•ด Line Renderer์˜ ๊ผญ์ง“์  ์œ„์น˜๋ฅผ ๊ฒฝ๋กœ ๊ผญ์ง“์ ์˜ ์œ„์น˜์— ์ ์šฉํ•˜์—ฌ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค.
6. ๋งŒ์•ฝ, ์ง€์ •๋œ NavMesh๋ฅผ ๋ฒ—์–ด๋‚ฌ์„ ๊ฒฝ์šฐ else() ๋ฌธ์„ ๋งŒ๋‚˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. ์ด๋Š” ๋งŒ๋“ค์–ด ๋†“์€ Plane ์˜ค๋ธŒ์ ํŠธ๋ฅผ ๋ฒ—์–ด๋‚ฌ์„ ๋•Œ ๋””๋ฒ„๊ทธ ๋กœ๊ทธ๋ฅผ ์ถœ๋ ฅํ•ฉ๋‹ˆ๋‹ค.

 

 

 

 

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.AI;
 
public class CollectableSpawn : MonoBehaviour
{
    [SerializeField]
    private Collectable collectable;   // ์ˆ˜์ง‘ ๊ฐ€๋Šฅํ•œ ์•„์ดํ…œ ํ”„๋ฆฌํŒน
    [SerializeField]
    private Transform Player; // ํ”Œ๋ ˆ์ด์–ด์˜ ์œ„์น˜
    [SerializeField]
    private LineRenderer Path;  // ๊ฒฝ๋กœ๋ฅผ ์‹œ๊ฐ์ ์œผ๋กœ ํ‘œ์‹œํ•  LineRenderer ์ปดํฌ๋„ŒํŠธ
    [SerializeField]
    private float PathHeightOffset = 1.25f;  // ๊ฒฝ๋กœ ๋†’์ด ์˜คํ”„์…‹
    [SerializeField]
    private float SpawnHeightOffset = 1.5f;  // ์•„์ดํ…œ ์Šคํฐ ๋†’์ด ์˜คํ”„์…‹
    [SerializeField]
    private float PathUpdateSpeed = 0.25f;  // ๊ฒฝ๋กœ ์—…๋ฐ์ดํŠธ ์†๋„
 
 
    private Collectable ActiveInstance;  // ํ˜„์žฌ ํ™œ์„ฑํ™”๋œ ์ˆ˜์ง‘ ๊ฐ€๋Šฅํ•œ ์•„์ดํ…œ ์ธ์Šคํ„ด์Šค
    private NavMeshTriangulation Triangulation;  // ๋‚ด๋น„๊ฒŒ์ด์…˜ ๋ฉ”์‹œ์˜ ์‚ผ๊ฐํ™” ์ •๋ณด
    private Coroutine DrawPathCoroutine;  // ๊ฒฝ๋กœ ๊ทธ๋ฆฌ๊ธฐ ์ฝ”๋ฃจํ‹ด ์ฐธ์กฐ
 
    private void Awake()
    {
        // ๋‚ด๋น„๊ฒŒ์ด์…˜ ๋ฉ”์‹œ์˜ ์‚ผ๊ฐํ™” ์ •๋ณด ๊ณ„์‚ฐ
        Triangulation = NavMesh.CalculateTriangulation();
 
    }
 
    private void Start()
    {
        // ์‹œ์ž‘ํ•  ๋•Œ ์•„์ดํ…œ ์Šคํฐ
        SpawnNewObject();
    }
 
    public void SpawnNewObject()
    {
        if (Triangulation.vertices.Length == 0)
        {
            Debug.LogError("Triangulation vertices array is empty!");
            return;
        }
 
        // ๋ฌด์ž‘์œ„ ์œ„์น˜์—์„œ ์ˆ˜์ง‘ ๊ฐ€๋Šฅํ•œ ์•„์ดํ…œ ์ƒ์„ฑ
        ActiveInstance = Instantiate(collectable,
            Triangulation.vertices[Random.Range(0, Triangulation.vertices.Length)] + Vector3.up * SpawnHeightOffset,
            Quaternion.Euler(9000)
        );
 
 
        // ๊ฒฝ๋กœ ๊ทธ๋ฆฌ๊ธฐ ์ฝ”๋ฃจํ‹ด ์ค‘์ง€
        if (DrawPathCoroutine != null )
        {
            StopCoroutine(DrawPathCoroutine);
        }
        // ๊ฒฝ๋กœ ๊ทธ๋ฆฌ๊ธฐ ์ฝ”๋ฃจํ‹ด ์‹œ์ž‘
        DrawPathCoroutine = StartCoroutine(DrawPathToCollectable());
    }
 
    private IEnumerator DrawPathToCollectable()
    {
        WaitForSeconds wait = new WaitForSeconds(PathUpdateSpeed);
        NavMeshPath path = new NavMeshPath();
 
        while(ActiveInstance != null)
        {
            // ํ”Œ๋ ˆ์ด์–ด์™€ ์•„์ดํ…œ ์‚ฌ์ด์˜ ๋‚ด๋น„๊ฒŒ์ด์…˜ ๊ฒฝ๋กœ ๊ณ„์‚ฐ
            if (NavMesh.CalculatePath(Player.position, ActiveInstance.transform.position, NavMesh.AllAreas, path))
            {
                // ๊ฒฝ๋กœ๋ฅผ LineRenderer๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์‹œ๊ฐ์ ์œผ๋กœ ํ‘œ์‹œ
                Path.positionCount = path.corners.Length;
 
                for (int i = 0; i < path.corners.Length; i++)
                {
                    Path.SetPosition(i, path.corners[i] + Vector3.up * PathHeightOffset);
                }
            }
 
            else
            {
                Debug.Log("ํ•ด๋‹น ๊ฒฝ๋กœ๋ฅผ ๊ณ„์‚ฐํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค..");
            }
 
            yield return wait;
        }
    }
}
cs

 

 

 

Target ์˜ค๋ธŒ์ ํŠธ๋Š” Prefab์œผ๋กœ ๋งŒ๋“ค์–ด ์ค๋‹ˆ๋‹ค. ๋˜ํ•œ, Colletable ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋„ฃ์–ด์ฃผ์„ธ์š”. Collectable ํด๋ž˜์Šค๋Š” ํ”Œ๋ ˆ์ด์–ด๊ฐ€ ์ฐพ์„ ์˜ค๋ธŒ์ ํŠธ์˜ ์ •๋ณด๋ฅผ ๋‹ด๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ์ €๋Š” ๊ฐ„๋‹จํžˆ ์˜ค๋ธŒ์ ํŠธ๊ฐ€ ํšŒ์ „ํ•˜๋Š” ๋กœ์ง์„ ๋„ฃ์—ˆ์Šต๋‹ˆ๋‹ค.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
 
public class Collectable : MonoBehaviour
{
    [SerializeField]
    private Vector3 SpinAmount = new Vector3(0200);
    public CollectableSpawn Spawner;
 
 
    // Update is called once per frame
    void Update()
    {
        transform.rotation = Quaternion.Euler(transform.rotation.eulerAngles + (SpinAmount * Time.deltaTime));
       
    }
}
cs

 

 

 

Colletable Spawn ์ปดํฌ๋„ŒํŠธ ์ •๋ณด์ž…๋‹ˆ๋‹ค. Colletable์—๋Š” Target ํ”„๋ฆฌํŒน ์ •๋ณด๋ฅผ ๋„ฃ์–ด์ฃผ๊ณ , Player์—๋Š” AR Camera๋ฅผ ๋„ฃ์–ด์ค๋‹ˆ๋‹ค. AR ํ™˜๊ฒฝ์—์„œ๋Š” ์นด๋ฉ”๋ผ๋ฅผ ๋“ค๊ณ  ์žˆ๋Š” ์ฃผ์ฒด๊ฐ€ ํ”Œ๋ ˆ์ด์–ด์ด๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค. Path์—๋Š” ๊ฒฝ๋กœ๋ฅผ ์‹œ๊ฐํ™” ํ•ด์ค„ Line Renderer๋ฅผ ๋„ฃ์–ด์ค๋‹ˆ๋‹ค.

 

728x90
๋ฐ˜์‘ํ˜•