A.1 삼각함수란?

직각삼감형의 각도에 따른 변 길이의 비율을 반환하는 함수 입니다. 

삼각함수로 사인, 코사인, 탄젠트라는 단어를 들어본 적이 있을 겁니다.

 

A.2 각도로 좌표 구하기 

플레이어가 적 캐릭터에게 사격을 하기위해 적과의 각도를 알면 다음과 같이 계산할수 있습니다.

float x = Mathf.Cos(라디안)
float y = Mathf.Sin(라디안)
Vector3 v = new Vector3(x,y) * shootSpeed;

유니티가 제공하는 삼각함수.

Mathf.cos, Mathf.sin Mathf.Tan

유니티가 제공하는 삼각함수는 각도를 매개변수로 사용하지 않고 라디안(호도법)을 사용합니다.

각도 = 라디안 x(180/원주율)

라디안=각도x(원주율/180)입니다.

유니티는 계산을 편하게 하기위해 Mathf.Deg2Rad, Mathf.Rad2Deg를 마련해 놨습니다.

 

라디안

아래 그림은 각도는 한바퀴를 360도라고 정의했을때 비율이다.

라디안은 반지름1인 원이 한바퀴 도는 둘레가 2πr인데 반지름이 1이름로 2π=360도 이다.

라디안은 결국 쉽게 말하면 한바퀴도는데 2π니까 1도 = 2π/360 = π/180 임.

역으로하면 360도 = 2π 니까 π=180도 뭐 그닥 와닫지는 않지만.

어려운건 아님.  각도는 바퀴고 라디안은 굴러간거리라고 생각하면 편함.

가만 생각해보면 원이 한바퀴도는건 파이=3.141592아니었나 생각되지만 그건 지름이 1일 경우이고 라디안은 반지름이1 지름이 2일 경우이다. ㅠㅠ

삼각함수도 반지름이 1일경우로 원을 그려 생각하면 엄청 쉬움

cosθ+sinθ=1 이게 원을 뜻한다.

x*x+y*y=1 도 원이지만. 

[네이버 지식백과]라디안[Radian] (물리학백과)

 

A.3 좌표에서 각도 구하기 

플레이어의 애니메이션을 선택하기 위해 플레이어의 이동각도를 알아야 할 때가 있습니다.

플레이어가 점 p1에서 p2로 이동할때 각도는 다음과 같습니다.

float dx = p2.x - p1.x;
float dy = p2.y - p1.y;
float angle = Mathf.Atan2(dy,dx)*Mathf.Rad2Deg;

Atan2는 좌표를 입력하면 각도를 구해주는 함수입니다.

 

A.4 자주쓰는 삼각함수 메서드

특정 게임 오브젝트 추적하기

Vector2 GetToVector(Vector2 pos) {
	float dx = pos.x-transform.position.x;
    float dy = pos.y-transform.position.y;
    float rad = Mathf.Atan2(dy,dx);
    //벡터만들기
    float x = Mathf.Cos(rad);
    float y = Mathf.Sin(rad);
    Vector2 v = new Vector2(x,y);  //방향
    return v;
}

특정 게임 오브젝트를 향하도록 회전시키기

GetAngle(Vector2 from, Vector2 to) {
	float dx = to.x-from.x;
    float dy = to.y-from.y;
    float rad = Mathf.Atan2(dy,dx);
    return rad * Mathf.Rad2Deg;
}

위함수를 Update()에서 호출해서 게임오브젝트을 로테이션 시킵니다.

Update(){
	float angle = GetAngle(transform.position, 이동 위치의 position);
    transform.rotation = Quaternion.Euler(0,0,angle);
}

다음 메소드는 이동속도를 회전각도로 반환합니다.

float velocityToAngle(){
	float r = 0;
    Rigidbody2D body = GetComponent<Rigidbody2D>();
    if(body !=null) {
    	Vector2 v = body.velocity;
        r = Mathf.Atan2(v.y, v.x) * Mathf.Rad2Deg;
    }
    retur r;
}

Update()에서 다음같이 사용됩니다.

Update() {
	float angle = velocityToAngle();
    transform.rotation = Quaternion.Euler(0,0,angle);
}

'유니티스크립팅 > 게임수학' 카테고리의 다른 글

벡터  (0) 2023.04.25

게임 오브젝트 이름으로 찾습니다.

GameObject obj = GameObject.Find("게임 오브젝트 이름")

 

게임 오브젝트 이름으로 자식 오브젝트를 찾습니다. 

Transform transform = this.transform.Find("게임오브젝트이름");

GameObject obj = transform.gameObject;

 

Tag 이름으로 오브젝트 하나를 찾습니다.

GameObject obj = GameObject.FindGameObjectwithTag("태그 이름");

 

같은 Tag의 모든 오브젝트를 찾습니다. 배열로 리턴합니다.

GameObject [] objects = GameObject.FindGameObjectwithTag("태그 이름");

 

https://terms.naver.com/entry.naver?docId=3405101&cid=47324&categoryId=47324 

 

벡터

물체에 힘을 가할 때는 어느 방향으로 얼마만큼 세게 미는지를 같이 이야기한다. 속도라는 개념도 어느 방향으로 얼마나 빠르게 가는지를 동시에 이야기한다. 이와 같이 크기와 방향을 동시에

terms.naver.com

일반적인 벡터의 정의는 이미 대략은 알고 계실것이다.

유니티도 벡터를 많이 이용하고 툴을 제공한다.

1. 두 지점사이의 거리

현재위치 curPos와 목적지인 destPos가 있다면 현재위치에서 목적지로 가는 벡터는 delta = destPos-curPos 이다.

2지점의 거리는 delta.magnitude이다. Vector3.Distance(curpos,destPos)로도 구할 수 있다.

 

2. 단위거리 이동

목적지로 10만큼의 거리를 가고 싶을때는 정규화후 이동하면된다.

newPos = curPos;

newPos += delta.normalized * 10f

 

3. 쿼터니언

인스펙터뷰의 transform의 rotation은 Vector3로 보이지만 스크립트에서 수정하고자 하면 에러가 난다. 왜냐하면 내부적으로는 짐벌락의 문제로 쿼터니언으로 처리하기 때문이다. 

오일러각으로 읽어오기 위해서는 rotation.eulerAngles로 읽어와야하고 변경할때는

유니티는 오일러각을 Quaternion으로 변환해주는 함수를 제공한다.

rotation = Quaternion.Euler(Vector3);처럼 변경해야 한다.

 

현재 회전 상태에서 더 회전하기

현재 rotation1 = Quaternion(30,0,0)상태에서 rotation2 = Quaternion(0.60,0) 만큼 더 회전시키려면 rotation1+rotation2가 아니고 rotation1*rotation2이다. 쿼터니언 사이의 연산은 행렬을 사용하기 때문입니다.

 

4.벡터의 내적 : vector3.Dot(a,b) 벡터b를 벡터a로 투영한 길이를 구합니다. 수직일 경우 0이 됩니다.

5. 벡터의 외적 : Vector3.Cross(a,b) 두 벡터 모두에 수직인 벡터를 구합니다. 앞뒤가 있습니다.

 

'유니티스크립팅 > 게임수학' 카테고리의 다른 글

게임에서 사용되는 삼각함수  (2) 2023.05.25

3D 오브젝트를 하나 만들고 Plane을 Scale(5,5,5) Player를 다음과 같이 Cube, Shphere를 이용해 만들어준다. Green, Black Material을 만들어 Plane과 Eye에 적용한다.

프로젝트뷰에 우클릭 [Create]후 Animator를 하나 만들고 PlayerAnimCtrl로 이름을 바꾸고, 다시 [Create]후 Animation을 만들고 Idel로 이름을 바꾼다.

하이라키뷰에서 Player의 Head를 선택하면 Animation뷰에서 Create하라고 하면 새로 만들고

AddProperty를 클릭하고 Position옆 + 를 클릭한다.

0:00 ◆ 클릭후 Ctrl-C한후 타임라인 0:30밑을 선택해 Ctrl-V해서 카피한다.

씬뷰탭을 누르고 하이라키뷰의 Head를 선택후 살짝 위로 올린다.

Preview 옆 빨간 ◉ 클릭해 녹화를 한후 다시 클릭해 녹화를 마친다. ▶를 눌러 머리가 위아래로 움직이는지 확인해본다   

Idel옆 ▼를 클릭해 Create New Clip을 선택한후 RunFoward를 만든다

하이라키뷰에서 Player의 Head를 선택하면 Animation뷰에서 Create하라고 하면 새로 만들고 AddProperty를 클릭하고 Position옆 + 를 클릭한다

 

Preview 옆 빨간 ◉ 클릭해 녹화를 시작하고 하이라키뷰의 PlayerHead를 선택하고 TimeLine 0:00을 클릭후 Z Position을 0.5로 변경한후 Preview 옆 빨간 ◉ 클릭해 녹화를 마친다. 혹시 0:00외 다른 타임라인이 있다면 제거해준다. Head가 이동 방향쪽으로만 향해 있으면 되므로 한프레임만 있으면 된다

같은 요령으로 RunLeft, RunRight, RunBackward를 만들오 첫프레임의 Head위치를 변경후 저장해준다. 

 

Animator탭을 선택한후 idle Animation을 끌어다 놓으면 자동으로 Entry와 연결되면서 Default State가 된다.

NewblendTree를 만든다. 이름을 Move로 바꾼다.

Move를 클릭해 들어간후 BlendTree를 클릭한후 인스펙터뷰의 BlendType 속성을 1D를 2D Simple로 변경한다.

Animator탭밑 Parameters를 선택하고 +를 눌러 float incX, float incZ, Bool isMove를 생성한다. Inspector뷰의 Parameters를 incX, incZ를 선택한다.

Motion List +를 4번 눌러 리스트를 4개 만들어 Animation을 끌어다 놓고 PosX와 PosY를 다음과 같이 설정한다.

Animator탭밑 BaseLayer탭을 선택해 다시 돌아가자

Idel과 Move사이 Transition을 만들어준다.

Idle->Move 속성은 다음과 같다. Conditions는 isMove==true, Has Exit time은 언체크 Duration은 0.1

Move->Idle 속성은 다음과 같다. Conditions는 isMove==false, Has Exit time은 언체크 Duration은 0.1

하이라키뷰에서 Player를 선택후 지금만든 PlayerAnimCtrl을 끌어다 Animator 컴포넌트의 Controller에 적용해준다.

이제 PlayerCtrl 스크립트를 만들어 Player에 적용시켜주자.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class PlayerCtrl : MonoBehaviour {
    // Start is called before the first frame update
    private Animator anim;  // 애니메이터 컴포넌트를 담을 변수
    private bool isMove;  // Move State 체크용 파라메터
    void Start() {
        anim = GetComponent<Animator>();  //애니메이터 컴포넌트를 받아온다
    }

    // Update is called once per frame
    void Update() {
        float moveSpeed = 3.0f;
        float _x = Input.GetAxisRaw("Horizontal");  //키임력을 받아
        float _z = Input.GetAxisRaw("Vertical");
        Vector3 direction = new Vector3(_x, 0, _z);  //벡터를 만들고
        isMove = false;

        if (direction != Vector3.zero) {
            isMove = true;  //움직임이 있으면 Move State로 간다
            //큐브를 이동시켜주고
            this.transform.Translate(direction.normalized * moveSpeed * Time.deltaTime);
        }
        //애니메이터 파라메터를 설정해준다
        //애니메이터 파라메터를 설정해준다
        anim.SetBool("isMove", isMove);
        anim.SetFloat("incX", direction.x);
        anim.SetFloat("incZ", direction.z);
    }
}

플레이 해보면 허름한 이동 애니메이션이 실행된다.

 

'유니티스크립팅' 카테고리의 다른 글

Beginner Scripting  (0) 2023.02.25

Windows - Package Manager에서 Animation Rigging 패키지를 검색하고 Install 한다.

 

패키지 설치가 끝났으면 애니메이션 리깅을 설정해서 캐릭터를 구성하는 뼈대를 나타내는 것으로 시작한다.

 

던전 스켈레톤을 누르고 상단에 Animation Rigging - Bone Renderer Setup을 클릭하면 된다.

 

Bone Renderer Setup를 클릭하면 던전 스켈레톤에 Bone Renderer라는 컴포넌트가 추가되며 동시에 Scene 뷰에 던전 스켈레톤의 뼈대가 표시되고 마우스로 쉽게 클릭도 가능해진다!

 

Bone Renderer는 던전 스켈레톤의 릭을 구성하는 뼈대 오브젝트들의 트랜스폼을 참조하고 있으며 뼈대의 크기나 색을 조정할 수도 있다.

 

여기까지 됐다면 Rig을 만들어야 한다. 참고로 애니메이션 리깅은 별도의 Rig을 만듦으로써 동작한다.

던전 스켈레톤을 클릭하고 상단의 Animation Rigging - Rig Setup을 클릭하면 두 가지 변화가 생긴다.

  1. 던전 스켈레톤에 Rig Layer를 가진 Rig Builer 컴포넌트가 추가된다.
  2. 던전 스켈레톤의 자식으로 Rig 컴포넌트를 가진 한 오브젝트가 생긴다.

Rig 1 오브젝트에 제약조건(Constraint)를 추가함으로써 애니메이션 리깅이 가능해진다.

Rig 1에 Empty 오브젝트를 하나 추가해서 이름을 HeadAim으로 변경하고 HeadAim에 Multi-Aim Constraint 컴포넌트를 추가해 보자. 

Multi-Aim Constraint에 몇 가지 정보를 넘겨줘야 한다.

  1. Constrained Object 제약조건이 걸릴 뼈대를 넘겨주면 되는데, 본 실습에선 머리에 애니메이션 리깅을 적용하고 싶으니 Constrained Object에 던전 스켈레톤의 Head 뼈대를 넘겨주고 Aim Axis를 Y로 변경해 주자.
  2. Source Object는 Constrained Object와 상호작용할 오브젝트로 하이라키뷰의 Fox를 선택해서 Head가 보이게 펼쳐준후 Head를 끌어 연결해준다. Aim Axis는 Y로 변경해 준다.

추가로 Rig 컴포넌트에 weight가 있다. 이는 타겟이 캐릭터에게 영향을 주는 정도로 weight가 줄어들수록 target과 상호작용은 약해진다. 

 

3. 머리만 움직이면 어색하니 흉부도 함께 움직이도록 Rig1에 ChestAim 빈오브젝트에 Multi-airm Constraint를 추가하고 Spine1을 연결해준다. Source는 똑같이 Fox의Head를 연결해주고 머리와 흉부에 weight 값을 다르게 줘서 자연스러운 애니메이션을 연출해 보자. Aim Axis는 Y로 변경해 준다. Min Limit과 Max Limit을 각각 -100, 100으로 지정하면 멋진 결과물이 탄생한다.

 

해보면 흉부도 함께 움직인다. Fox가 움직여서  테스트가 어려우면 Fox의 스크립트를 언체크해도 된다.

 

같은 방법으로 Fox도  Animation Rigging - Bone Renderer Setup후 Animation Rigging - Rig Setup을 클릭 Rig1에 HeadAim을 추가하고 Multi-Aim Constraint 컴포넌트를 추가후 Constrained Object를 Fox Head로 하고

Source를 +를 누르고 Skeleton 다리를 추가하면 Fox 가 고개를 움직인다. Skeleton Head는 높이가 안 맞는다  Aim Axis Y t설정및 리미트 설정을 꼭한다. 

 

 

https://assetstore.unity.com/packages/3d/characters/creatures/dungeon-skeletons-demo-71087

 

Dungeon Skeletons Demo | 3D 생물 | Unity Asset Store

Elevate your workflow with the Dungeon Skeletons Demo asset from Polygon Blacksmith. Find this & other 생물 options on the Unity Asset Store.

assetstore.unity.com

Asset Store에서 Dungeon Skeletons를 다운 받아 인포트해서 하이라키에 끌어다 놓는다.  프리팹이 없기때문에 Models폴더에서 끌어다 놓는다

애니메이터가 연결 안되어 있기 때문에 Fox와 동일한 방법으로 만든다.

프로젝트뷰에서 우클릭 [Create]-[Animator Controller] 를 만든후 이름을 NewFoxAnim으로 한다.

더블클릭해서 Animator를 연다.

다음 폴더에서 idel을 끌어다 놓는다

Walk와 attack도 끌어다 놓고 transition을 연결한다.

애니메이션이 walk 밖에 없어서 브랜드트리는 안 만드는 것이다.

파라미터는 Trigger attack, bool move만 만들어준다.

idel>walk 트랜지션속성 Has Exit Time 언체크 Condition>move>false

walk>idel 트랜지션속성 Has Exit Time 언체크 Condition>move>false

 

완성된 SkeletonAnim을 DungeonSkeletonDemo  인스펙터 Animator컴포넌트의 Controller속성과 연결해준다.

SkeletonCtrl.cs를 다음과 같이 만들고 DungeonSkeletonDemo 에 연결해 준다.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class SkeletonCtrl : MonoBehaviour {
    // Start is called before the first frame update
    private Animator anim;  // 애니메이터 컴포넌트를 담을 변수
    private bool isMove;  // Move State 체크용 파라메터
    void Start() {
        anim = GetComponent<Animator>();  //애니메이터 컴포넌트를 받아온다
    }

    // Update is called once per frame
    void Update() {
        float moveSpeed = 3.0f;
        float _x = Input.GetAxisRaw("Horizontal");  //키임력을 받아
        float _z = Input.GetAxisRaw("Vertical");
        Vector3 direction = new Vector3(_x, 0, _z);  //벡터를 만들고
        isMove = false;

        if (direction != Vector3.zero) {
            isMove = true;  //움직임이 있으면 Move State로 간다
            //큐브를 이동시켜주고
            this.transform.Translate(direction.normalized * moveSpeed * Time.deltaTime);
        }
        //애니메이터 파라메터를 설정해준다
        //애니메이터 파라메터를 설정해준다
        anim.SetBool("move", isMove);
    }
}

유니티를 실행해서 WASD를 눌러보면 Fox와 DungeonSkeleton이 같이 움직인다.

3D 프로젝트를 하나 만든다.

3D Object Plane을 하나 만들고 Scale을 5로 한다.

Assetstore에서 Toon Fox를 다운받고 import한다. 하이라키에 끌어다 놓는다.

https://assetstore.unity.com/packages/3d/characters/animals/toon-fox-183005

 

Toon Fox | 3D 동물 | Unity Asset Store

Elevate your workflow with the Toon Fox asset from Pxltiger. Find this & other 동물 options on the Unity Asset Store.

assetstore.unity.com

기본적으로 애니메이터가 설정되어 있어 플레이하면 모든 애니메이션을 돌아가면서 보여준다.

프로젝트뷰에서 우클릭 [Create]-[Animator Controller] 를 만든후 이름을 NewFoxAnim으로 한다.

더블클릭해서 Animator를 연다.

Fox/animations폴더에서 Fox_Idle Animation을 끌어다 놓는다.

우클릭후 New Blend Tree를 만들고 이름을 Move로 만든다

Idle과 Transition을 만들어 준다.

Idle과 Move 사이 트랜지션의 컨디션을 다음과 같이 설정하고

양쪽다 Has Exit Time은 언체크하고 Transition Duration은 0.1로 한다

Parameter

s뷰에서 float incX, float incZ, bool move를 만든다.

Move State를 클릭한다.

가운데 Blend Tree를 선택하면 인스펙터가 다음 같이 바뀌고 Blend Type을 2D Simple Directional로 바꾼다.

Parameters를 다음과 같이 바꿔준다.

그다음과 +를 눌러 Add Motion Field를 4개 넣어주고

 

같은 창이 아래 모션을 애니메이션과 연결해준후 PosX PosY값을 설정한다.

BlendTree.cs를 다음과 같이 만들고 Fox에 연결해 준다.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class BlendTree : MonoBehaviour {
    // Start is called before the first frame update
    private Animator anim;  // 애니메이터 컴포넌트를 담을 변수
    private bool isMove;  // Move State 체크용 파라메터
    void Start() {
        anim = GetComponent<Animator>();  //애니메이터 컴포넌트를 받아온다
    }

    // Update is called once per frame
    void Update() {
        float moveSpeed = 3.0f;
        float _x = Input.GetAxisRaw("Horizontal");  //키임력을 받아
        float _z = Input.GetAxisRaw("Vertical");
        Vector3 direction = new Vector3(_x, 0, _z);  //벡터를 만들고
        isMove = false;

        if (direction != Vector3.zero) {
            isMove = true;  //움직임이 있으면 Move State로 간다
            //큐브를 이동시켜주고
            this.transform.Translate(direction.normalized * moveSpeed * Time.deltaTime);
        }
        //애니메이터 파라메터를 설정해준다
        //애니메이터 파라메터를 설정해준다
        anim.SetBool("move", isMove);
        anim.SetFloat("incX", direction.x);
        anim.SetFloat("incZ", direction.z);
    }
}

 

유니티 마우스 클릭 이동 방법 – 오늘은 유니티 C# 스크립트를 이용하여 마우스 클릭에 의한 플레이어 캐릭터의 이동 및 회전을 구현해 보겠습니다. 다음은 결과물의 영상입니다.클릭 앤 무브 작동 영상

위와 같은 동작을 위해 필요한 유니티 C# 클래스는 단 하나입니다. ClickToMove.cs 라는 이름의 모노비헤이비어 기반의 클래스를 아래와 같은 방법으로 만든 뒤, 플레이어 캐릭터 역할을 하는 게임 오브젝트에 붙여 넣으면 바로 실행됩니다.

다음은 코드를 작성하는 방법에 대한 설명입니다.

ClickToMove 유니티 C# 스크립트 생성

우선 유니티에서 C# 스크립트를 하나 만들고 이름을 ClickToMove 라고 변경하였습니다. 이 스크립트에서는 Start() 메소드를 사용하지 않을 것이므로 Update() 를 제외하고는 일단 지웠습니다. (그냥 놔두셔도 물론 됩니다.)

필드 선언

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
 
public class ClickToMove : MonoBehaviour{
    public float movementSpeed = 10f;
    public float rotationSpeed = 10f;
    private void Update(){
 
    }
}

제일 먼저 플레이어 캐릭터의 이동 속도 및 마우스 클릭 지점으로의 회전 속도를 지정하기 위한 변수 두 개를 선언하였습니다.  movementSpeed 는 이동 속도를, rotationSpeed 는 회전 속도를 나타냅니다. 각각 기본 값 10f 를 부여하였고, 이는 나중에 얼마든지 변경 가능합니다.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
 
public class ClickToMove : MonoBehaviour{
    public float movementSpeed = 10f;
    public float rotationSpeed = 10f;
    private Vector3 destinationPoint;
    private void Update(){ 
    }
}

다음으로 이동 목표 지점의 좌표를 저장할 변수 destinationPoint 를 선언하였습니다.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
 
public class ClickToMove : MonoBehaviour
{
 
    public float movementSpeed = 10f;
 
 
    public float rotationSpeed = 10f;
 
 
    private Vector3 destinationPoint;
 
 
    private bool shouldMove = false;
 
 
    private void Update()
    {
  
    }
}

다음으로 플레이어 캐릭터의 움직임을 제어하기 위한 플래그(flag)로 부울 타입 변수인 shouldMove 를 추가하였습니다. 플레이어 캐릭터가 이동하다가 목표지점에 도달하면 멈춰야 하기 때문에 이를 제어하기 위한 것입니다.

목표 지점 위치 구하기

마우스 클릭 여부 확인

이제 마우스 클릭을 통해 이동 지점의 좌표를 구하는 부분을 만들어 보겠습니다.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
 
public class ClickToMove : MonoBehaviour
{
    public float movementSpeed = 10f;
 
    public float rotationSpeed = 10f;
 
    private Vector3 destinationPoint;
 
    private bool shouldMove = false;
 
    private void Update()
    {
        if (Input.GetMouseButtonDown(0))
        {
            
        }
    }
}

Input.GetMouseButtonDown(0)은 마우스 왼쪽 버튼 클릭 여부를 확인하는 방법입니다. 마우스 왼쪽 버튼을 클릭하면 true를 반환하고 클릭하지 않으면 false를 반환합니다.

if 조건문은 마우스 왼쪽 버튼이 클릭되었는지 확인합니다. 클릭하면 조건문이 참이 되어서, if 블록 내부의 코드가 실행됩니다. 클릭하지 않으면 거짓이 되므로 if 블록 내부의 코드가 실행되지 않습니다.

목표 지점 위치 구하기

이제 마우스 클릭을 기반으로 실제 3차원 월드 상에서의 목표 이동 지점을 구하는 코드를 작성해 보겠습니다.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
 
public class ClickToMove : MonoBehaviour
{
    public float movementSpeed = 10f;
 
    public float rotationSpeed = 10f;
 
    private Vector3 destinationPoint;
 
    private bool shouldMove = false;
 
    private void Update()
    {
        if (Input.GetMouseButtonDown(0))
        {
            Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
            RaycastHit hit;
            if (Physics.Raycast(ray, out hit, 100f))
            {
                
            }
        }
 
    }
}

위의 코드는 3차원 월드 상에서의 마우스 클릭 지점을 구할 때, 일종의 패턴처럼 외워서 사용하시면 됩니다. 각 행의 의미는 다음과 같습니다.

Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);

이 명령문이 하는 일은 Ray 타입의 개체인 ray를 만드는 것입니다. 이렇게 만든 ray 는 다음 부분에서 마우스 클릭 지점을 구할 때 사용됩니다.

ray를 만들기 위해서는 Camera.main 개체의 ScreenPointToRay 메서드를 사용합니다. Camera.main 은 현재 씬(scene)에 존재하는 메인 카메라를 나타냅니다.

ScreenPointToRay 메서드는 Vector3를 매개 변수로 사용하며, 이 매개 변수는 화면상의 점(point)을 나타냅니다. 위의 코드에서는 Input.mousePosition이 매개 변수로 사용되었는데, 이 Input.mousePosition은 마우스 커서의 현재 (화면상의) 위치를 나타냅니다.

이렇게 마우스 커서의 화면상의 위치를 매개 변수로 전달하면, 이제 ScreenPointToRay 메서드는 메인 카메라의 위치에서 시작하여 화면의 포인트 방향으로 확장되는 광선(ray)을 생성합니다. 다음의 그림을 보시면 이해가 되실 겁니다.

위의 그림에서 플레이어의 눈(메인 카메라)로부터 2차원 화면상의 마우스 클릭 지점을 관통하는 광선(ray)이 쭉 나아가다가 3차원 월드상의 바닥에 충돌하고 있습니다. 위의 명령문에서 만들어 낸 Ray 개체가 가리키는 것이 그것이라고 이해하시면 됩니다.

RaycastHit 이해하기

이제 다음 코드를 보겠습니다.

RaycastHit hit;

이 코드는 Raycast 타입의 변수인 hit 를 선언하고 있습니다. RaycastHit 는 광선이 충돌하는 지점(3D 공간의 한 점)과 관련한 정보를 저장하는데 사용되는 데이터 구조(data structure)입니다.

if (Physics.Raycast(ray, out hit, 100f))

이 코드는 물리학(Physics) 클래스의 Raycast 메서드를 사용하여 레이캐스팅을 수행합니다. Raycast 메서드는 위의 코드에서 보시는 것처럼 세 가지 파라미터를 사용합니다.

  • 캐스팅할 광선을 나타내는 Ray 개체. 이 경우에는 앞에서 만들어 낸 ray 가 매개 변수로 전달됩니다.
  • RaycastHit. 즉, 광선이 충돌하는 지점에 대한 데이터 개체입니다. 이 경우에는 바로 위에서 구한 hit 가 매개 변수로 전달됩니다.
  • 광선이 도달할 수 있는 최대 거리를 나타내는 실수(float) 값. 이 경우 100f가 매개 변수로 전달되었습니다. 더 크거나 작은 숫자를 입력하셔도 되지만 너무 짧으면 래이캐스트로 인한 충돌 자체가 일어나지 않을 수 있으니 주의하시기 바랍니다.

Physics.Raycast 메서드는 광선(ray)이 충돌체를 가진 물체와 충돌하면 true를 반환하고 그렇지 않으면 false를 반환합니다. 위의 코드에서 충돌이 감지되면 if 조건문 블록 내부의 코드가 실행되고, 그렇지 않으면 if 블록 내부의 코드가 실행되지 않을 것입니다.

따라서 위와 같은 방법으로 마우스 클릭이 이동 목표 지점에 대한 유효한 정보를 가져 오는 지 여부를 알 수 있는 것입니다.

Physics.Raycast 메서드 사용

그럼 이제부터 조건문 내부 코드를 작성하도록 하겠습니다.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
 
public class ClickToMove : MonoBehaviour
{
    public float movementSpeed = 10f;
 
    public float rotationSpeed = 10f;
 
    private Vector3 destinationPoint;
 
    private bool shouldMove = false;
 
    private void Update()
    {
        if (Input.GetMouseButtonDown(0))
        {
            Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
            RaycastHit hit;
            if (Physics.Raycast(ray, out hit, 100f))
            {
                destinationPoint = new Vector3(hit.point.x, transform.position.y, hit.point.z);
 
                shouldMove = true;
            }
        }
 
    }
}

만약 Physics.Raycast 메서드가 true 를 반환한다면, hit 라는 매개 변수에는 이제 충돌 지점에 대한 여러 가지 데이터가 자동으로 저장되게 됩니다. 따라서 이 hit 를 분석해서 목표 이동 지점을 파악할 수 있습니다.

destinationPoint = new Vector3(hit.point.x, transform.position.y, hit.point.z);

목표 이동 지점은 위와 같은 방식으로 hit 에 들어 있는 정보를 통해 생성해 낼 수 있습니다.

참고로 위의 코드에서는 Vector3 개체를 직접 만들었지만, 원래는 다음과 같이 hit.point 를 직접 대입하는 것이 정석입니다.

destinationPoint = hit.point;

하지만 우리의 경우에는 y 값을 hit.point의 y 좌표가 아닌, 플레이어 캐릭터 게임 오브젝트의 y 좌표인 transform.position.y 를 이용했습니다. 그 이유는 현재 플레이어 캐릭터가 지면에 닿아 있지 않고, 지면에서 약간 위에 떠 있기 때문에 이 고도를 유지하기 위해서 그런 것입니다.

만약에 여러분의 캐릭터가 위와 달리 지면에 붙어 있다면, destinationPoint = hit.point; 라고 하셔도 무방합니다.

움직임과 관련한 플래그

이제 다음 코드를 보겠습니다.

shouldMove = true;

이 코드의 의미는 명확합니다. shouldMove 플래그를 true로 설정하여 개체가 대상 지점을 향해 이동을 시작할 수 있도록 허용하는 것입니다. 이 값이 나중에 false 로 변하면, 다시 말해서 플레이어 캐릭터가 목표지점에 도달하게 되면 더 이상 이동을 허용하지 말아야 합니다. 이에 대해서는 이후의 코드에서 확인하실 수 있을 것입니다.

이제 목표 이동 지점을 구했으니 다음으로 해야 할 일은 캐릭터를 해당 지점을 향해 회전시키고, 지정된 속도에 맞춰 이동시켜야 합니다. 이를 위한 코드를 추가해 보겠습니다.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
 
public class ClickToMove : MonoBehaviour
{
    public float movementSpeed = 10f;
 
    public float rotationSpeed = 10f;
 
    private Vector3 destinationPoint;
 
    private bool shouldMove = false;
 
    private void Update()
    {
        if (Input.GetMouseButtonDown(0))
        {
            Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
            RaycastHit hit;
            if (Physics.Raycast(ray, out hit, 100f))
            {
                destinationPoint = new Vector3(hit.point.x, transform.position.y, hit.point.z);
 
                shouldMove = true;
            }
        }
 
        if (shouldMove)
        {
            
        }
    }
}

이제부터의 코드는 플레이어 캐릭터의 이동이 허용된 경우에만 의미가 있습니다. 따라서 위와 같이 조건문을 만든 것입니다. 이제 이 안에 회전과 관련된 코드를 먼저 작성해 넣겠습니다.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
 
public class ClickToMove : MonoBehaviour
{
    public float movementSpeed = 10f;
 
    public float rotationSpeed = 10f;
 
    private Vector3 destinationPoint;
 
    private bool shouldMove = false;
 
    private void Update()
    {
        if (Input.GetMouseButtonDown(0))
        {
            Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
            RaycastHit hit;
            if (Physics.Raycast(ray, out hit, 100f))
            {
                destinationPoint = new Vector3(hit.point.x, transform.position.y, hit.point.z);
 
                shouldMove = true;
            }
        }
 
        if (shouldMove)
        {
            Quaternion targetRotation = Quaternion.LookRotation(destinationPoint - transform.position);
            transform.rotation = Quaternion.Slerp(transform.rotation, targetRotation, rotationSpeed * Time.deltaTime);            
        }
    }
}

캐릭터의 회전과 관련한 이 코드 블록도 하나의 패턴으로 암기해서 사용하시는 것이 좋습니다. 처음에는 원리를 이해하기 어려워도 반복해서 사용하다 보면 자신도 모르게 의미를 이해하게 될 것입니다. 일단 각각의 코드에 대해 설명 드리겠습니다.

Quaternion targetRotation = Quaternion.LookRotation(destinationPoint - transform.position);

이 명령문은 개체가 대상 지점을 바라보기 위해 얼마만큼 회전해야 하는지를 계산합니다. Quaternion.LookRotation 매서드는 매개 변수로 정면 방향(forward direction)을 받습니다. 플레이어 캐릭터의 현재 위치에서 목표 지점을 바라보기 위한 정면 방향은 destinationPoint에서 캐릭터 개체의 현재 위치(transform.position)를 빼면 구할 수 있습니다.

이렇게 해서 구한 회전 값을 Quaternion 타입의 변수인 targetRotation 에 저장하면 됩니다.

부드러운 회전 구현

그럼 다음 코드를 보겠습니다.

transform.rotation = Quaternion.Slerp(transform.rotation, targetRotation, rotationSpeed * Time.deltaTime);

이 코드는 바로 위에서 계산한 목표 회전 값을 향해 개체를 부드럽게 회전시키는 일을 합니다. 이를 위해서는 Quaternion 클래스의 Slerp(구면 선형 보간) 메서드를 사용합니다.

Quaternion.Slerp() 매서드는 개체의 현재 회전(transform.rotation), 목표 회전(targetRotation) 및 회전 속도(rotationSpeed)를 매개 변수로 받습니다. (이 때 Time.deltaTime 값을 회전 속도에 곱하면 게임의 프레임 속도에 관계없이 일정한 속도로 회전할 수 있습니다.)

그리고 이렇게 얻어진 매 프레임 당 캐릭터가 회전할 목표 값을 transform.rotation 에 할당하면 됩니다. 여기까지 코드를 작성하고 실행하면 캐릭터가 이동은 못해도 회전은 잘 수행하게 될 것입니다.

마우스를 클릭하면 회전한다

캐릭터 이동 구현

이제 마지막으로 이동 부분을 작성하도록 하겠습니다. 지금까지 작성한 코드에 다음과 같은 명령문을 추가합니다.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
 
public class ClickToMove : MonoBehaviour
{
    public float movementSpeed = 10f;
 
    public float rotationSpeed = 10f;
 
    private Vector3 destinationPoint;
 
    private bool shouldMove = false;
 
    private void Update()
    {
        if (Input.GetMouseButtonDown(0))
        {
            Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
            RaycastHit hit;
            if (Physics.Raycast(ray, out hit, 100f))
            {
                destinationPoint = new Vector3(hit.point.x, transform.position.y, hit.point.z);
 
                shouldMove = true;
            }
        }
 
        if (shouldMove)
        {
            Quaternion targetRotation = Quaternion.LookRotation(destinationPoint - transform.position);
            transform.rotation = Quaternion.Slerp(transform.rotation, targetRotation, rotationSpeed * Time.deltaTime);
 
            transform.position = Vector3.MoveTowards(transform.position, destinationPoint, movementSpeed * Time.deltaTime);
 
        }
    }
}

이 코드는 플레이어 캐릭터 개체를 앞에서 구한 목표 지점을 향해 이동시킵니다.

Vector3.MoveTowards() 메서드에 개체의 현재 위치(transform.position), 목표 지점(destinationPoint) 및 ‘이동 속도(movementSpeed) 곱하기 Time.deltaTime’의 세 가지 매개 변수를 전달하면 각 프레임당 개체가 얼마나 이동해야 원하는 속도로 목표 지점에 도달할 수 있을 지를 알 수 있습니다. 이렇게 구한 값을 다시 플레이어 캐릭터 개체의 transform.position 에 할당하면 됩니다.

목표 지점 도달 처리

이 코드를 추가하고 게임을 실행하면 마우스 클릭을 통해 캐릭터를 목표 지점으로 이동시킬 수 있습니다. 하지만 목표 지점에서 멈추지 않고 계속 움직이게 됩니다. 정지와 관련된 코드를 작성하지 않았기 때문입니다.

따라서 다음과 같은 코드를 추가함으로써 이 문제를 해결할 수 있습니다.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
 
public class ClickToMove : MonoBehaviour
{
    public float movementSpeed = 10f;
 
    public float rotationSpeed = 10f;
 
    private Vector3 destinationPoint;
 
    private bool shouldMove = false;
 
    private void Update()
    {
        if (Input.GetMouseButtonDown(0))
        {
            Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
            RaycastHit hit;
            if (Physics.Raycast(ray, out hit, 100f))
            {
                destinationPoint = new Vector3(hit.point.x, transform.position.y, hit.point.z);
 
                shouldMove = true;
            }
        }
 
        if (shouldMove)
        {
            Quaternion targetRotation = Quaternion.LookRotation(destinationPoint - transform.position);
            transform.rotation = Quaternion.Slerp(transform.rotation, targetRotation, rotationSpeed * Time.deltaTime);
 
            transform.position = Vector3.MoveTowards(transform.position, destinationPoint, movementSpeed * Time.deltaTime);
 
            if (transform.position == destinationPoint)
            {
                shouldMove = false;
            }
        }
    }
}

이 코드는 개체의 위치가 대상 지점과 동일한지 여부를 확인합니다. 만약 그럴 경우, 이동 플래그가 false로 설정되고 플레이어 캐릭터 개체는 이동을 멈추게 됩니다.

위 코드에서 보완해야 할 부분

참고로 이 부분은 자칫 문제를 야기할 수도 있습니다. 예를 들어 게임의 프레임이 낮을 경우(너무 느릴 경우) 자칫 위의 조건문 (==)이 참인 순간을 지나쳐 버릴 수 있습니다.

이런 경우를 대비하기 위해 플레이어 캐릭터의 현재 위치와 목표 지점까지의 거리를 계산해서, 거리가 예를 들어 1미터 이상 떨어졌을 때만 이동하고, 그 보다 줄어들면 멈추게 하는 방법으로 코드를 변경할 수도 있습니다. 이 부분은 여러분이 직접 해 보시기 바랍니다.

지금까지 작성한 ClickToMove 스크립트는 어떤 게임 오브젝트에 붙여도 작동됩니다. 위의 안내에 따라 스크립트를 작성하고 확장, 변형시켜 보시기 바랍니다. 꽤 재미 있는 유니티 C# 스크립트 공부가 될 것입니다.

 

https://cjwoov.tistory.com/18#:~:text=%5BUnity%5D%20%EB%A7%88%EC%9A%B0%EC%8A%A4%20%ED%81%B4%EB%A6%AD%ED%95%9C%20%EC%A7%80%EC%A0%90%EC%9C%BC%EB%A1%9C%20%EC%BA%90%EB%A6%AD%ED%84%B0%20%EC%9D%B4%EB%8F%99%EC%8B%9C%ED%82%A4%EA%B8%B0%201%20Control.cs%3A,%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8%202%20Behavior.cs%3A%20%EC%8B%A4%EC%A0%9C%20%EC%BA%90%EB%A6%AD%ED%84%B0%EC%9D%98%20%ED%96%89%EB%8F%99%EC%9D%84%20%EB%8B%B4%EB%8B%B9%ED%95%98%EB%8A%94%20%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8

'유니티스크립팅 > 유니티팁' 카테고리의 다른 글

게임 오브젝트를 찾는 Find 메서드  (0) 2023.05.24
알아두면 편리한 기능  (0) 2023.03.04

라이트매핑

라이트매핑은 씬의 표면 밝기를 미리 계산하고 계산 결과를 나중에 사용하기 위해 라이트맵이라고 불리는 텍스처에 저장하는 프로세스입니다.

왼쪽: 라이트맵이 적용된 간단한 씬. 오른쪽: Unity가 생성한 라이트맵 텍스처. 그림자 및 광원 정보가 모두 캡처됩니다.

라이트맵에는 직접광과 간접광이 모두 포함될 수 있습니다. 이 조명 텍스처는 오브젝트의 머티리얼과 연결된 셰이더에서 컬러(알베도) 및 릴리프(노멀) 같은 표면 정보와 함께 사용될 수 있습니다.

라이트맵에 베이크된 데이터는 런타임 시 변경할 수 없습니다. 실시간 광원은 라이트맵이 적용된 씬 위에 오버레이시켜 추가적으로 사용할 수 있지만, 라이트맵 자체를 대화식으로 변경할 수 없습니다.

이 방식을 사용할 경우 게임플레이 시에 광원을 실시간으로 움직일 수 있는 유용성을 잠재적인 성능 향상과 맞바꾸는 것이므로, 모바일 플랫폼처럼 성능이 낮은 하드웨어에 적합합니다.

Unity는 라이트맵 생성을 위해 다음의 라이트매퍼를 제공합니다.

 

인라이튼 베이크된 전역 조명을 사용한 라이트매핑 - Unity 매뉴얼

인라이튼 베이크된 전역 조명은 사전 계산된 실시간 전역 조명 데이터에 기반하여 간접 조명을 생성합니다. 이렇게 하면 씬의 조명을 변경한 후 새로운 라이트맵을 매우 빠르게 생성하는 장점

docs.unity3d.com

인라이튼 베이크된 전역 조명을 사용한 라이트매핑

인라이튼 베이크된 전역 조명은 사전 계산된 실시간 전역 조명 데이터에 기반하여 간접 조명을 생성합니다. 이렇게 하면 씬의 조명을 변경한 후 새로운 라이트맵을 매우 빠르게 생성하는 장점이 있습니다. 그러나 인라이튼 베이크된 전역 조명은 프로그레시브 라이트매퍼보다 UV 레이아웃 제한이 더 많습니다.

인라이튼 베이크된 전역 조명은 지원 중단 예정입니다.(인라이튼 실시간 전역 조명은 계속 지원합니다.)

렌더 파이프라인 지원

렌더 파이프라인의 인라이튼 베이크된 전역 조명 지원에 대한 자세한 내용은 렌더 파이프라인 기능 비교를 참조하십시오.

인라이튼 라이트매퍼 사용

인라이튼 베이크된 전역 조명을 사용하려면 Window > Rendering > Lighting에서 Lightmapping Settings로 이동한 후 Lightmapper Enlighten으로 설정합니다.

LightingSettings API를 사용하여 스크립트를 통해 이 창에서 사용할 수 있는 많은 기능을 수행할 수 있습니다.

다음 프로퍼티는 인라이튼에만 해당됩니다. 이것을 노출시키려면 Lightmapper control에서 Enlighten을 선택합니다.

프로퍼티:기능:

Final Gather 베이크된 라이트맵과 동일한 해상도로 전역 조명 최종 광원 바운스를 계산합니다. 이렇게 하면 화질이 향상되지만 조명 베이크 시간이 늘어납니다. Final Gather가 활성화되면 Ray Count 및 Denoising 설정이 노출됩니다.
Indirect Resolution 이 설정을 사용하면 라이트매퍼가 간접 조명 계산에 사용하는 샘플 수를 지정할 수 있습니다. 값이 높을수록 라이트맵 품질이 향상되지만, 그만큼 베이크하는 데 걸리는 시간이 증가합니다.
  Ray Count 최종 수집 포인트마다 라이트매퍼가 방출하는 광선의 수를 지정합니다.
  Denoising 최종 수집 출력에 노이즈 제거 필터를 적용합니다.

기본 환경 기여 비활성화

Unity는 자동으로 앰비언트 프로브 기본 반사 프로브를 생성하여 환경 조명이 기본적으로 씬과 씬의 게임 오브젝트에 영향을 주도록 합니다.

라이트맵과 라이트 프로브를 수동으로 생성하지 않은 게임 오브젝트 또는 씬의 조명 결과에서 환경 기여를 비활성화하려면 기본 반사 프로브와 앰비언트 프로브를 비활성화합니다. 자세한 내용은 [SkyManager 비활성화]를 참조하십시오.

라이트매핑: 시작하기

씬 준비 및 라이트맵 베이킹

Unity 에디터 메뉴에서 Window > Rendering > Lighting 을 선택하여 Lighting 창을 엽니다. 라이트맵을 적용할 메시를 검토하여 라이트맵에 적합한 UV가 있는지 확인합니다. 메시 임포트 설정을 열고 Generate Lightmap UVs 설정을 활성화하면 가장 쉽게 확인할 수 있습니다.

다음, 라이트맵 해상도를 설정하기 위해 라이트맵 설정(Lightmapping Settings) 섹션으로 이동하여 라이트맵 해상도(Lightmap Resolution) 값을 조정합니다.

라이트맵에 포함되려면 렌더러가 다음 조건을 충족해야 합니다.

  • Mesh Renderer 또는 Terrain 컴포넌트가 있습니다.
  • Contribute GI로 표시되어 있습니다.
  • 빌트인 Unity 머티리얼, 스탠다드 셰이더 또는 메타 패스가 포함된 셰이더를 사용합니다.

광원 탐색기에서 광원에 대한 설정을 조정할 수 있습니다. 광원 탐색기를 열려면 Window > Rendering > Light Explorer로 이동합니다.

씬의 라이트맵을 생성하는 방법:

  1. Lighting 창(메뉴: Window > Rendering > Lighting)을 엽니다.
  2. Lighting 창의 Scene 탭 하단에서 Generate Lighting 을 선택합니다(또는 Auto Generate 가 활성화되어 있는지 확인합니다).
  3. Unity 에디터 상태 표시줄의 오른쪽 하단에 진행 표시줄이 나타납니다.

라이트매핑이 완료되면 Unity의 씬 뷰와 게임 뷰가 자동으로 업데이트되며 Lighting 창의 Baked Lightmaps 탭으로 이동하여 결과가 적용된 라이트맵을 볼 수 있습니다.

조명을 수동으로 생성하면 조명 데이터 에셋, 베이크된 라이트맵, 반사 프로브 Assets 폴더에 추가됩니다.

 

라이트맵 파라미터 에셋

라이트맵 파라미터 에셋에는 Unity의 조명 기능을 제어하는 파라미터 값 세트가 들어 있습니다. 이러한 에셋을 사용하면 다양한 조명 값 세트를 정의하고 저장하여 다양한 상황에서 사용할 수 있습니다.

라이트맵 파라미터 에셋을 사용하면 다양한 게임 오브젝트 타입이나 여러 플랫폼과 여러 씬 타입(예: 실내 또는 야외 씬)에 최적화된 프리셋을 빨리 만들 수 있습니다.

라이트맵 파라미터 에셋 생성

새 라이트맵 파라미터 에셋을 생성하려면 프로젝트 창에서 마우스 오른쪽 버튼을 클릭하고 생성(Create) > 새 파라미터 에셋(New Parameters Asset) 으로 이동합니다. Unity는 프로젝트 폴더에 생성된 에셋을 저장합니다.

프로퍼티

프로젝트 창에서 라이트맵 파라미터 에셋을 클릭하면 해당 에셋에 정의된 값이 인스펙터 창에 표시됩니다. 아래 표에는 각 파라미터와 해당 설명이 나열되어 있습니다.

실시간 GI

이 파라미터는 인라이튼 실시간 전역 조명을 설정합니다.

렌더 파이프라인의 인라이튼 실시간 전역 조명 지원에 대한 자세한 내용은 렌더 파이프라인 기능 비교를 참조하십시오.

프로퍼티기능

Resolution 이 값은 라이팅 창의 Scene 탭(메뉴: Window > Rendering > Lighting > Scene)에 있는 Realtime Resolution 값을 조정하여 라이트맵의 최종 해상도를 단위 거리당 텍셀 수로 표시합니다.
Cluster Resolution 클러스터 해상도(광원 바운스가 내부적으로 계산되는 해상도) 대 최종 라이트맵 해상도의 비율입니다. 자세한 내용은 씬 뷰에서 GI 시각화 문서를 참조하십시오.
Irradiance Budget 값은 라이트맵의 각 텍셀에 광원을 비추는 데 사용되는 유입 광원 데이터의 정밀도를 결정합니다. 각 텍셀의 조명은 텍셀 포지션에서 씬의 “뷰”를 샘플링하여 얻습니다. 복사 조도의 계산 정밀도 값이 낮을수록 샘플이 더 흐릿합니다. 값이 높을수록 샘플의 선명도가 높아집니다. 복사도 값이 높을수록 조명이 개선되지만, 런타임 메모리 사용량이 증가하고 CPU 사용량도 증가할 수 있습니다.
Irradiance Quality 슬라이더를 사용해 캐스트되고 주어진 출력 라이트맵 텍셀에 영향을 미치는 클러스터를 계산하는 데 사용되는 레이의 수를 정의합니다. 값이 높을수록 라이트맵이 시각적으로 개선되지만, Unity 에디터에서 미리 계산하는 시간이 늘어납니다. 이 값은 런타임 성능에 영향을 미치지 않습니다.
Modelling Tolerance 값은 광원이 메시 지오메트리를 통과할 수 있는 틈새의 최소 크기를 설정합니다. 환경에서 광원이 더 작은 틈새를 통과할 수 있게 하려면 이 값을 더 낮게 설정해야 합니다.
Edge Stitching 활성화할 경우, 프로퍼티는 불필요한 시각적 결함을 방지하기 위해 라이트맵의 UV 차트를 완벽하게 결합해야 함을 나타냅니다.
Is Transparent 활성화하면 오브젝트가 전역 조명 계산 중에 투명하게 표시됩니다. 후면은 계산에 포함되지 않고, 광원이 표면을 통해 지나갑니다. 보이지 않는 이미시브 표면에 유용합니다.
System Tag 라이트맵 텍스처가 “시스템”이라는 동일한 라이트맵 아틀라스에 결합된 오브젝트 그룹입니다. Unity 에디터는 모든 라이트맵이 아틀라스 하나에 들어가지 않는 경우 추가 시스템과 각각의 아틀라스를 함께 정의합니다. 하지만 때로는 (예를 들어 서로 다른 룸 안에 있는 오브젝트가 룸당 시스템 하나로 그룹화되게 하기 위해) 별도의 시스템을 직접 정의하면 유용합니다. System Tag 번호를 변경하여 새로운 시스템과 라이트맵을 강제로 만들 수 있습니다. 태그의 정확한 숫자 시퀀스 값은 중요하지 않습니다.

베이크된 GI

이 파라미터는 라이트매핑을 설정합니다.

렌더 파이프라인의 라이트매핑 지원에 대한 자세한 내용은 렌더 파이프라인 기능 비교를 참조하십시오.

프로퍼티기능

  인라이튼 베이크된 전역 조명 프로그레시브 라이트매퍼(Progressive Lightmapper)
Blur Radius 텍셀에서 사후 처리 중에 직접 조명에 적용되는 블러 필터의 반지름입니다. 반지름은 본질적으로 인접 텍셀 거리의 평균입니다. 반지름이 클수록 블러 효과가 더 많이 제공됩니다. 블러 레벨이 높을수록 시각적 결함이 감소하지만 섀도우의 가장자리가 부드러워지는 경향이 있습니다. 프로그레시브 라이트매퍼를 사용하는 경우 블러 반지름(Blur Radius) 은 이용할 수 없습니다.
안티앨리어싱 샘플(Anti-aliasing Samples) 적용되는 안티앨리어싱(“블록 모양”의 결함 감소) 정도입니다. 숫자가 높을수록 품질이 향상되고 베이크 시간이 늘어납니다. 앨리어싱을 줄이기 위해 텍셀을 슈퍼샘플링하는 횟수입니다. [1;3] 샘플은 슈퍼샘플링을 비활성화하고, [4;8] 샘플은 2x 슈퍼샘플링을 제공하고, [9;256] 샘플은 4x 슈퍼샘플링을 제공합니다. 주로 포지션과 노멀 버퍼에 사용되는 메모리 양에 영향을 미칩니다. (2x 시 메모리가 4배 더 많이 사용되고, 4x 시 메모리가 16배 더 많이 사용됩니다.)
Direct Light Quality 직접 조명을 측정하는 데 사용되는 레이의 수입니다. 레이 수가 많을수록 더 정확하고 부드러운 섀도우가 생성되지만 베이크 시간이 늘어납니다. 프로그레시브 라이트매퍼를 사용하는 경우 직접광 품질(Direct Light Quality) 은 이용할 수 없습니다.
Backface Tolerance 때로는 메시 구조로 인해 일부 텍셀에 후면 방향 지오메트리가 포함된 “뷰”가 있을 수 있습니다. 후면에서 유입되는 광원은 씬에서 무의미하므로, 프로퍼티를 통해 텍셀을 유효한 것으로 간주하기 위해서는 전체 광원 중 전면 지오메트리에서 나온 광원이 얼마나 되어야 하는지를 나타내는 백분율의 임계값을 선택할 수 있습니다. 유효하지 않은 텍셀은 인접 텍셀의 값으로부터 조명이 어림됩니다. 값을 낮추면 후면에서 유입되는 광원으로 인해 발생하는 조명 문제를 해결할 수 있습니다. 출력 텍셀에서 투사된 광선이 유효한 것으로 간주되기 위해 전면에 닿아야 하는 광선의 비율입니다. 텍셀에서 투사된 광선 중에 후면에 닿는 광선이 너무 많으면(텍셀이 어떤 지오메트리 안에 있음) 텍셀을 무효화할 수 있습니다. 이 경우 주위 텍셀에서 유효한 값을 복제하여 결함을 방지합니다. 예를 들어 후면 허용치를 0.0으로 설정하면, 텍셀에서 후면만 보일 때에만 텍셀이 거부됩니다. 후면 허용치를 1.0으로 설정하면, 후면에 닿는 광선이 하나만 있어도 광선 원점이 거부됩니다. 베이크된 텍셀 유효성(Baked Texel Validity) 씬 뷰 모드에서 유효한 텍셀은 녹색으로, 유효하지 않은 텍셀은 빨간색으로 나타납니다. 씬에 단면 메시가 있는 경우 이 기능을 영(0)으로 설정하여 비활성화하는 것이 좋습니다. 나중에 Unity 에디터에서 양면 플래그를 추가하여 이 문제를 해결할 수 있습니다.
Pushoff 모델링 유닛에서 레이 트레이스를 시작하기 전에 표면 지오메트리에서 밀어낼 거리입니다. 모든 베이크된 라이트맵에 적용되므로 직접광, 간접광 및 AO에 영향을 미칩니다. Pushoff는 원치 않는 AO나 섀도우를 없애는 데 유용합니다. 이 설정은 오브젝트 표면에 오브젝트 자체의 섀도우가 생겨 분명한 소스 없이 표면에 얼룩진 섀도우 패턴이 나타나는 문제를 해결하는 데 사용합니다. 이 설정을 사용하여 미세한 디테일을 정확하게 레이 트레이스할 수 있을 만큼 부동 소수점 정밀도가 충분히 높지 않은 경우에 큰 오브젝트에서 원치 않는 결함을 제거할 수도 있습니다. 레이트레이싱을 위해 노멀을 따라 레이 원점을 밀어서 지오메트리에서 멀리 떨어트릴 거리(모델링 단위)입니다. 모든 베이크된 라이트맵에 적용되므로 직접광, 간접광, 베이크된 앰비언트 오클루전에 영향을 미칩니다. 원치 않는 오클루전/그림자를 없애는 데 유용합니다.
Baked Tag 위의 시스템 태그(System Tag) 프로퍼티와 유사하며, 오브젝트 집합을 별도의 베이크된 라이트맵으로 그룹화하는 데 사용할 수 있습니다. 시스템 태그와 마찬가지로, 정확한 숫자 값은 중요하지 않습니다. 베이크된 태그 값이 서로 다른 오브젝트는 절대로 같은 아틀라스 안에 포함되지 않습니다. 하지만 태그가 같은 오브젝트가 라이트맵 하나에 들어가지 않을 수 있으므로 같은 아틀라스에 포함된다는 보장은 없습니다(예는 아래 이미지 A 참조). 멀티씬 베이크 API를 사용하는 경우 그룹화가 자동으로 수행되므로 이 프로퍼티를 설정하지 않아도 됩니다. 베이크된 태그(Baked Tag) 를 사용하여 아틀라스 잠금(Lock Atlas) 옵션의 일부 동작을 모사할 수 있습니다. 자세한 내용은 아래의 베이크된 태그: 세부 정보를 참조하십시오.
Limit Lightmap Count Limit Lightmap Count 는 인라이튼 베이크된 전역 조명을 사용할 때는 제공되지 않습니다. Limit Lightmap Count는 동일한 베이크된 전역 조명 설정을 가진 게임 오브젝트를 패킹할 때 Unity가 사용할 수 있는 최대 라이트 맵 수를 적용합니다. Limit Lightmap Count를 활성화하면 아래에 Max Lightmaps라는 이름의 설정이 나타납니다. 이 설정을 사용하여 Unity가 사용할 수 있는 최대 라이트 맵 수를 설정하십시오.

Unity는 게임 오브젝트들이 동일한 Anti-aliasing Samples, Pushoff, Baked Tag  Backface Tolerance 값을 가진 경우 동일한 베이크된 전역 조명 설정을 사용한다고 간주합니다. 즉 Unity가 다른 라이트맵 파라미터 에셋과 연결된 게임 오브젝트들을 함께 패킹할 수 있습니다. 게임 오브젝트를 패킹하기 위해 Unity는 모든 게임 오브젝트가 지정된 라이트맵 수 안에 들어갈 때까지 UV 레이아웃을 점차적으로 스케일다운합니다. 라이트매퍼 설정은 이러한 라이트맵의 크기를 정의합니다. 이 프로세스는 게임 오브젝트의 라이트맵 해상도를 떨어뜨릴 수 있습니다.

 

베이크된 태그: 세부 정보

 

위 두 이미지에는 동일한 씬에 대한 두 가지 뷰가 나와 있습니다.

  1. 위: 모든 게임 오브젝트의 Baked Tag 가 같으므로 모두 하나의 아틀라스에 있습니다.
  2. 아래: 한 게임 오브젝트에 다른 Baked Tag 가 할당되어 있어 두 번째 라이트맵으로 들어갑니다.

베이크된 AO

이 파라미터는 베이크된 앰비언트 오클루전을 설정합니다.

프로퍼티기능

Quality 베이크된 앰비언트 오클루전(AO) 측정 시에 캐스트되는 레이의 수입니다. 레이 수가 많을수록 AO 품질이 향상되지만 베이크 시간도 늘어납니다.
안티앨리어싱 샘플(Anti-aliasing Samples) AO 안티앨리어싱을 수행할 때 사용할 샘플 수입니다. 샘플 수가 많을수록 AO 품질이 향상되지만 베이크 시간도 늘어납니다.

일반 GI

프로퍼티기능

Backface Tolerance 출력 텍셀에서 투사된 광선이 조명 시스템에서 사용 가능한 것으로 간주되기 위해 전면에 닿아야 하는 광선의 비율입니다. 이를 통해 Unity는 후면에 닿는 광선이 너무 많은 경우(예: 텍셀이 일부 지오메트리 내에 있는 경우) 텍셀을 무효화할 수 있습니다. 조명 시스템은 주위 텍셀에서 유효한 값을 복제하여 의도치 않은 결함을 방지합니다.

후면 허용치(Backface Tolerance) 를 0.0으로 설정하면 조명 시스템은 후면만 보일 때만 텍셀을 리젝트합니다. 1.0으로 설정하면 조명 시스템은 후면에 닿는 광선이 하나만 있어도 광선 원점을 리젝트합니다.

라이트맵 파라미터 에셋 할당(Assigning Lightmap Parameters Assets)

씬(Scenes)

전체 씬에 라이트맵 파라미터 에셋을 할당하려면 다음 단계를 따르십시오.

  1. 조명 창(Window > Rendering > Lighting)을 엽니다.
  2. Scene 탭을 클릭합니다.
  3. Lightingmapping Settings 로 이동합니다.
  4. Lightmap Parameters 드롭다운을 사용하여 디폴트 라이트맵 파라미터 에셋을 할당합니다. 드롭다운 리스트에는 사용 가능한 라이트맵 파라미터 에셋이 모두 나열됩니다.

게임 오브젝트(GameObject)

라이트맵 파라미터 에셋을 단일 게임 오브젝트에 할당하려면 게임 오브젝트에 Mesh Renderer 또는 Terrain 컴포넌트가 연결되어 있어야 합니다.

라이트맵 파라미터 에셋을 Mesh Renderer 컴포넌트에 할당하려면 다음 단계를 따르십시오.

  1. 인스펙터에서 Mesh Renderer > Lighting으로 이동합니다.
  2. Contribute Global Illumination 을 활성화합니다.
  3. Mesh Renderer 컴포넌트에서 Lightmapping > Lightmap Parameters로 이동합니다.
  4. 메뉴에서 옵션을 선택합니다. Scene Default Parameter 를 선택하여 전체 씬에 할당된 동일한 라이트맵 파라미터 에셋을 사용합니다.

라이트맵 파라미터 에셋을 Terrain 컴포넌트에 할당하려면 다음 단계를 따르십시오.

  1. 인스펙터에서 Terrain > Terrain Settings > Lighting으로 이동합니다.
  2. Contribute Global Illumination 을 활성화합니다.
  3. Terrain Settings에서 Lightmapping > Lightmap Parameters로 이동합니다.
  4. 메뉴에서 옵션을 선택합니다. Scene Default Parameter 를 선택하여 전체 씬에 할당된 동일한 라이트맵 파라미터 에셋을 사용합니다.

라이트맵 UV

일반적으로 UV라고 불리는 텍스처 좌표는 지오메트리 주위에 텍스처를 “래핑”하는 방법을 말합니다. 라이트맵은 텍스처이므로 Unity에서 씬을 올바르게 사용하려면 UV가 필요합니다.

이 섹션에서는 다음의 정보를 제공합니다.

페이지설명

라이트맵 UV 소개 베이크된 전역 조명 시스템과 실시간 전역 조명 시스템의 라이트맵 UV에 대한 정보를 소개합니다.
라이트맵 UV 생성 직접 라이트맵 UV를 제공하거나 Unity에서 자동으로 생성하도록 하는 방법.
라이트맵 UV 시각화 Unity에서 사용 중인 라이트맵 UV를 보는 방법.
라이트맵 UV 오버랩 해결 라이트맵 UV의 일반적인 문제를 해결하는 방법.

https://docs.unity3d.com/kr/2021.3/Manual/LightingOverview.html

 

조명 - Unity 매뉴얼

Unity에서 다양한 아트 스타일에 적합한 사실적인 조명을 구현할 수 있습니다.

docs.unity3d.com

 

Unity의 내비게이션 시스템

내비게이션 시스템을 통해 게임 월드에서 이동할 수 있는 캐릭터를 생성할 수 있습니다. 2층으로 가기 위해 계단을 오르거나 배수로를 넘기 위해 점프해야 하는지를 이해할 수 있는 능력을 캐릭터에 부여합니다. Unity 내비메시 시스템은 다음으로 구성되어 있습니다.

  • 내비메시(NavMesh)는 내비게이션 메시의 줄임말로, 게임 월드에서 걸을 수 있는 표면을 뜻하며, 내비메시를 사용하여 게임 월드 안에 있는 움직일 수 있는 한 위치에서 다른 위치로 이동할 수 있는 경로를 찾을 수 있습니다. 데이터 구조는 레벨 지오메트리에서 자동으로 빌드 또는 베이크됩니다.
  • 내비메시 에이전트(NavMesh Agent) 컴포넌트를 사용하여 각자의 목적지로 이동하는 동안 서로를 피할 수 있는 캐릭터를 생성할 수 있습니다. 에이전트는 내비메시를 사용하여 게임 월드를 추론하며, 움직이는 장애물뿐만 아니라 서로를 피하는 방법을 알게 됩니다.
  • 오프 메시 링크(Off-Mesh Link) 컴포넌트를 사용하여 걸을 수 있는 표면만으로는 정의할 수 없는 내비게이션 단축키를 통합할 수 있습니다. 예를 들어 배수로나 울타리를 뛰어넘거나 문을 지나가기 전에 여는 행동 등은 모두 오프 메시 링크로 정의할 수 있습니다.
  • 내비메시 장애물(NavMesh Obstacle) 컴포넌트를 사용하여 에이전트가 월드를 탐색하는 동안 회피해야 하는 움직이는 장애물을 정의할 수 있습니다. 이에 대한 예로는 물리 시스템이 제어하는 통이나 상자를 들 수 있습니다. 움직이는 장애물이라면 에이전트가 이를 피하도록 하고, 장애물이 정지한 경우 내비메시에 구멍을 카빙하여 에이전트가 장애물을 돌아가도록 경로를 변경하거나, 정지한 장애물이 경로를 완전히 차단할 경우 에이전트가 다른 경로를 찾게 할 수 있습니다.

내비게이션 시스템의 내부 작업

게임에서 캐릭터(AI 써클에서는 에이전트라 함)를 지능적으로 움직이려면 목적지를 찾기 위해 필요한 레벨 추론과 목적지까지 이동하는 방법이라는 두 가지 문제를 해결해야 합니다. 이 두 문제는 밀접하게 연관되어 있지만 실제로는 아주 다릅니다. 레벨을 추론하는 문제는 씬 전체를 고려해야 한다는 점에서 훨씬 전역적이고 정적입니다. 목적지까지의 이동은 지역적이고 동적이며, 이동하는 방향과 이동 중인 다른 에이전트와의 충돌을 방지하는 방법에 대해서만 고려하면 됩니다.

걸을 수 있는 영역

게임 씬에서의 걸을 수 있는 영역을 나타내기 위해 내비게이션 시스템은 자체적인 데이터가 필요합니다. 걸을 수 있는 영역은 씬에서 에이전트가 서거나 움직일 수 있는 장소를 정의합니다. Unity에서 에이전트는 실린더로 정의됩니다. 걸을 수 있는 영역은 씬의 지오메트리에서 에이전트가 설 수 있는 위치를 테스트하여 자동으로 빌드됩니다. 그런 다음, 이러한 위치가 씬 지오메트리의 맨 위에 위치한 표면에 연결됩니다. 이 표면을 내비게이션 메시(줄여서 내비메시라고 함)라고 합니다.

내비메시는 이 표면을 Convex 폴리곤으로 저장합니다. Convex 폴리곤은 폴리곤 안의 두 지점 사이에 아무런 장애물이 없기 때문에 이런한 목적으로 유용하게 사용됩니다. 폴리곤의 경계를 비롯하여 서로 이웃한 폴리곤에 대한 정보도 저장합니다. 이렇게 하면 걸을 수 있는 모든 영역을 추론할 수 있습니다.

경로 찾기

씬에서 두 위치 사이의 경로를 찾기 위해서는 먼저 시작 위치와 목적지 위치를 가장 가까운 폴리곤에 매핑해야 합니다. 그런 다음, 시작 위치에서 탐색을 시작하여 모든 이웃 폴리곤을 거쳐 목적지 폴리곤에 도달합니다. 이동 중에 방문한 모든 폴리곤의 경로를 추적하여 시작에서 목적지까지 연결해주는 폴리곤의 시퀀스를 찾을 수 있습니다. 이렇게 경로를 찾는 일반적인 알고리즘을 A*(“에이스타”라고 발음)라고 하며, Unity도 바로 이 방법을 사용합니다.

경로 따라가기

시작 폴리곤에서 목적지 폴리곤까지의 경로를 정의하는 폴리곤의 시퀀스를 통로라고 합니다. 에이전트는 통로를 따라 보이는 가장 가까운 코너를 향해 움직이는 방법으로 목적지에 도달합니다. 씬에서 에이전트가 하나만 움직이는 간단한 게임이라면 한 번에 통로 선상의 모든 코너를 찾은 뒤 코너를 연결하는 라인 세그먼트를 따라 캐릭터가 움직이도록 애니메이션화할 수 있습니다.

여러 에이전트가 동시에 움직인다면 에이전트끼리의 충돌을 피하기 위해 원래 경로에서 벗어나야 할 필요가 있습니다. 라인 세그먼트로 이루어진 경로를 사용하여 이러한 경로 이탈을 수정하는 것은 매우 어렵고 오류가 쉽게 발생합니다.

각 프레임에서 에이전트의 움직임이 매우 적기 때문에 우회할 필요가 있을 경우 폴리곤의 연결을 사용하여 통로를 수정할 수 있습니다. 이후 에이전트가 향할 가장 가까운 보이는 코너를 검색하면 됩니다.

장애물 회피

스티어링 로직은 통로 선상에 있는 다음 코너의 포지션을 파악하고, 이에 기반하여 목적지에 도달하기 위한 원하는 방향과 속도를 계산합니다. 원하는 속도를 사용하여 에이전트를 움직일 때 다른 에이전트와의 충돌이 발생할 수 있습니다.

장애물 회피는 원하는 방향으로 나아가되 다른 에이전트 및 내비게이션 메시의 가장자리와 충돌하지 않게 적절한 균형점을 찾아 새로운 속도를 선택합니다. Unity는 상호간 속도 장애물(RVO)을 사용하여 충돌을 예견하고 방지합니다.

에이전트 이동

스티어링과 장애물 회피가 적용된 이후에 최종 속도가 계산됩니다. Unity에서 에이전트는 간단한 동적 모델을 사용하여 시뮬레이션되며, 자연적이고 부드러운 움직임을 위해 가속도까지 고려됩니다.

이 단계에서 시뮬레이션된 에이전트의 속도를 애니메이션 시스템에 제공하여 루트 모션을 사용해 캐릭터를 이동하거나 내비게이션 시스템이 이동을 처리하도록 할 수 있습니다.

두 메서드 중 하나를 사용하여 에이전트가 이동되면 시뮬레이션된 에이전트 위치가 이동되며 내비메시에 적용됩니다. 이 간단한 마지막 단계는 확실한 내비게이션 과정에서 중요한 역할을 합니다.

글로벌 및 로컬

내비게이션과 관련하여 이해해두어야 할 가장 중요한 사항 중의 하나는 글로벌 내비게이션과 로컬 내비게이션의 차이입니다.

글로벌 내비게이션은 월드에서 통로를 찾는 데 사용됩니다. 월드에서 경로를 탐색하려면 상당한 프로세싱 능력과 메모리가 필요합니다.

경로를 정의하는 폴리곤의 리니어 리스트는 스티어링을 위한 유연한 데이터 구조이며, 에이전트의 포지션이 움직임에 따라 로컬하게 조정될 수 있습니다. 로컬 내비게이션은 다른 에이전트나 움직이는 오브젝트와 충돌 없이 어떻게 효율적으로 다음 코너를 향해 나아갈 수 있는지를 결정합니다.

장애물의 두 가지 사례

내비게이션에는 다른 에이전트보다 다른 타입의 장애물이 많이 응용됩니다. 슈팅 게임에서 일반적으로 볼 수 있는 상자나 통을 예로 들 수 있고, 차량도 이에 해당합니다. 이러한 장애물은 로컬 장애물 회피 또는 글로벌 경로 탐색을 통해 처리할 수 있습니다.

움직이는 장애물이라면 로컬 장애물 회피를 사용하는 것이 좋습니다. 이 경우 에이전트가 장애물을 예측하여 회피합니다. 정지해 있을 수 있고 모든 에이전트의 경로를 차단하는 장애물이라면 글로벌 내비게이션인 내비게이션 메시에 영향을 주어야 합니다.

내비메시를 변경하는 것을 카빙이라고 합니다. 이 프로세스는 장애물의 어떤 부분이 내비메시에 닿는지를 감지한 다음, 내비메시에서 해당 부분을 깎아내어 구멍을 만듭니다. 이를 계산할 때 매우 많은 비용이 소모되기 때문에 움직이는 장애물은 충돌 회피를 통해 처리하는 이유이기도 합니다.

로컬 충돌 회피는 종종 흩어진 장애물을 피하는 데 사용되기도 합니다. 알고리즘이 로컬이므로 바로 직면한 충돌만 회피할 수 있으며, 함정을 피하거나 장애물이 경로를 차단하는 경우를 처리할 수 없습니다. 이러한 경우는 카빙을 통해 해결할 수 있습니다.

오프 메시 링크 정의

내비메시 폴리곤 사이의 연결은 경로 탐색 시스템 내부의 링크를 통해 정의됩니다. 가끔은 에이전트가 걸어 다닐 수 없는 장소(예: 펜스를 뛰어 넘거나 닫힌 문을 지나가는 경우)를 지날 수 있어야 합니다. 이러한 경우에는 액션이 일어나는 위치를 알아야 합니다.

오프 메시 링크 설정을 추가하여 이러한 액션을 정의하면, 특정한 링크를 통해 연결되는 경로가 있다는 것을 경로 탐색자가 알 수 있습니다. 해당 경로를 따라갈 때 이 링크가 사용되며, 링크 지점에서 특정한 액션이 이루어집니다.

 

내비메시 빌드

레벨 지오메트리에서 내비메시(NavMesh) 생성 과정을 내비메시 베이킹이라 부릅니다. 이 프로세스는 내비게이션 정적으로 마크된 메시 렌더와 모든 게임 오브젝트의 터레인을 수집한 후 처리하여 내비메시를 생성합니다. 메시는 레벨의 걸을 수 있는 표면과 비슷합니다.

Unity에서 내비메시 생성은 Navigation 창(메뉴: Window > AI > Navigation)에서 이루어집니다.

씬에서는 네 가지 단순한 단계를 거쳐 내비메시를 빌드합니다.

  1. 내비게이션에 영향을 주는 씬 지오메트리를 Select합니다. - 걸을 수 있는 표면과 장애물입니다.
  2. 내비게이션 스태틱을 Check하여 내비메시 베이킹 프로세스 안에 선택한 오브젝트를 포함시킵니다.
  3. 베이크 설정을 Adjust하여 에이전트의 크기에 맞춥니다.
  • 에이전트 반경 은 벽이나 낭떠러지 같은 지형에 에이전트 센터가 얼마나 가깝게 다가갈 수 있는지를 정의합니다.
  • 에이전트 높이 는 에이전트가 다가갈 수 있는 공간의 높이를 정의합니다.
  • 최대 슬로프 는 에이전트가 걸어 올라갈 수 있는 경사의 기울기가 어느 정도 되는지 정의합니다.
  • 스텝 높이 는 에이전트를 멈춰 세우는 장애물의 높이가 어느 정도 되는지 정의합니다.
  1. 베이크를 Click해서 내비메시를 빌드합니다.

빌드된 내비메시 결과는 내비게이션 창이 열리고 사용자의 눈에 씬이 들어올 때마다 기본 지오메트리 레벨 위에 파란 오버레이로 나타납니다.

위 그림에서 볼 수 있듯이, 생성된 내비메시에서 걸을 수 있는 영역이 더 작아 보입니다. 내비메시는 에이전트의 센터가 움직일 수 있는 영역을 나타냅니다. 개념적으로는 에이전트를 작아진 내비메시의 포인트로 여기든 풀 사이즈 내비메시의 원으로 여기든 둘은 같은 요소이므로 상관이 없습니다. 하지만 포인트로 해석하면 런타임 효율을 개선할 수 있고, 에이전트가 반지름에 상관없이 틈 사이로 지나갈 수 있는지 여부를 설계자가 즉시 확인할 수도 있습니다.

또 한 가지 유의해야 할 점은 내비메시는 걸을 수 있는 표면에 대한 근사치라는 점입니다. 예를 들어 계단의 경우 평면으로 표현되지만 소스 표면은 계단으로 되어 있을 수 있습니다. 이것은 내비메시 데이터 크기를 작게 유지하기 위해서입니다. 근사치의 부작용은 가끔씩 레벨 지오메트리에 추가 영역을 가져야 에이전트가 좁은 지점을 통과할 수 있습니다.

베이킹이 끝나면 내비메시 에셋 파일이 씬 이름과 같은 이름을 가지고 폴더 안에 생성됩니다. 예를 들어 씬을 Assets 폴더 안에

 첫 번째 레벨 로 만들었다면 내비메시는 Assets > First Level > NavMesh.asset 에 위치하게 됩니다.

베이킹을 위한 오브젝트 마킹 추가 워크플로

내비게이션 창에서 오브젝트를 내비게이션 스태틱 으로 마킹하기 외에도 위에서 설명한 대로 인스펙터 맨 위의 정적 메뉴를 이용할 수 있습니다. 내비게이션 창을 열어 두지 않았다면 편리한 기능입니다.

 

NavMesh 빌딩 컴포넌트

NavMesh 빌딩 컴포넌트는 런타임 시 Unity 에디터에서 NavMesh를 생성하고 사용하는 데 필요한 추가 컨트롤을 제공합니다.

NavMesh 빌딩 컴포넌트는 패키지 관리자로는 볼 수 없는 실험 패키지를 통해 사용할 수 있습니다. 이 실험 NavMesh 패키지를 설치하려면 이름별 레지스트리 패키지 추가의 지침을 따르고 com.unity.ai.navigation 패키지를 추가하십시오.

NavMesh 빌딩 컴포넌트에 관한 자세한 내용은 실험 패키지 문서를 참조하십시오.

 

고급 내비메시 베이크 설정

최소 영역(Min Region Area)

Min Region Area 고급 빌드 설정을 사용하여 연결되지 않은 작은 내비메시 영역을 제거할 수 있습니다. 표면 영역이 특정 값보다 작은 내비메시 영역은 제거됩니다.

일부 영역은 Min Region Area 설정에도 불구하고 제거되지 않을 수 있습니다. 내비메시는 타일 격자로 병렬로 구축됩니다. 영역이 타일 경계를 넘으면 그 영역은 제거되지 않습니다. 그 이유는 주변의 타일에 접근할 수 없는 빌드 프로세스의 단계에서 영역이 제거되기 때문입니다.

복셀 크기(Voxel Size)

수동 복셀 크기를 사용하면 베이크 프로세스가 작동하는 정확도를 변경할 수 있습니다.

내비메시 베이크 프로세스는 복셀화를 사용하여 임의의 레벨 지오메트리에서 내비메시를 작성합니다. 알고리즘의 첫 번째 단계에서 씬을 복셀에 래스터화한 다음 걷기 쉬운 표면을 추출하고 마지막으로 걷기 쉬운 표면을 탐색 메시로 바꿉니다. 복셀 크기는 결과 내비메시가 씬 지오메트리를 얼마나 정확하게 표현하는지를 나타냅니다.

디폴트 정확도는 에이전트 반지름당 3복셀, 즉 에이전트 전체 너비가 6복셀이 되도록 설정되어 있습니다. 정확도와 베이크 속도 사이에는 상쇄 효과가 존재합니다. 복셀 크기를 반으로 줄이면 메모리 사용이 4배 증가하고 씬을 빌드하는 데 4배 더 오래 걸립니다.

일반적으로 복셀 크기를 조정할 필요는 없습니다.

 더 작은 에이전트 반지름을 생성 하거나 보다 정확한 내비메시 를 만드는 두 가지 시나리오가 필요합니다.

더 작은 에이전트 반지름

인위적으로 더 작은 에이전트 반지름으로 베이크하면 내비메시 베이크 시스템은 복셀 크기를 같이 줄입니다. 다른 에이전트 크기가 동일하다면 내비메시 빌드 해상도를 높이지 않아도 됩니다.

가장 손쉬운 방법은 다음과 같습니다.

  1. 에이전트 반지름 을 실제 에이전트 반지름에 설정합니다.
  2. Manual Voxel Size 를 체크하면 현재 복셀 크기가 적용되고 “고정”됩니다.
  3. 에이전트 반지름 을 인위적으로 작게 설정합니다. Manual Voxel Size 를 체크했기 때문에 복셀 크기가 변하지 않습니다.

보다 정확한 내비메시

레벨에 많은 단점이 있는 경우 복셀을 작게 만들어 정확도를 높이는 것이 좋습니다. 복셀 크기 아래의 레이블에는 복셀 크기와 에이전트 반지름 간의 관계가 표시됩니다. 28 사이가 적절하며, 일반적으로 빌드 시간이 길어지는 것보다 더 나아갑니다.

게임에서 의도적으로 타이트한 통로를 빌드할 때는 에이전트 반지름에 더해 최소한 4 * voxelSize 간격을 남겨둬야 합니다. 특히 통로가 각을 이루고 있다면 더욱 그렇습니다.

내비메시 베이킹이 지원할 수 있는 것보다 작은 복도가 필요한 경우 오프 메시 링크를 사용합니다. 이 링크는 사용 시 감지할 수 있고, 특정 애니메이션을 재생할 수 있는 등의 장점이 있습니다.

 

내비메시 에이전트 생성

게임 레벨의 내비메시를 베이크한 경우 씬 안에서 이동할 캐릭터를 생성합니다. 실린더로 프로토타입 에이전트를 빌드한 뒤 움직여 보겠습니다. 이 작업은 NavMesh Agent 컴포넌트와 간단한 스크립트를 통해 이루어집니다.

먼저 캐릭터를 생성합니다.

  1. 원기둥을 생성합니다. 게임 오브젝트(GameObject) > 3D 오브젝트(3D Object) > 원기둥(Cylinder).
  2. 디폴트 원기둥 크기(높이 2와 반경 0.5)는 휴머노이드 형태의 에이전트에 적합하므로 원기둥 형태로 둡니다.
  3. 내비메시 에이전트 컴포넌트를 추가합니다. 컴포넌트(Component) > 내비게이션(Navigation) > 내비메시 에이전트(NavMesh Agent).

이제 간단한 내비메시 에이전트를 생성했으며 커맨드를 내릴 수 있습니다.

내비메시 에이전트로 실험을 시작하려면 캐릭터 크기와 속도에 맞춰 크기를 조정해야 합니다.

내비메시 에이전트 컴포넌트가 경로 탐색과 캐릭터의 움직임 조절까지 다룹니다. 스크립트에서 내비게이션으로 원하는 목표 포인트를 설정하면 내비메시 에이전트가 나머지 항목을 자동으로 처리합니다.

    // MoveTo.cs
        using UnityEngine;
        using UnityEngine.AI;
    
        public class MoveTo : MonoBehaviour {
       
           public Transform goal;
       
           void Start () {
              NavMeshAgent agent = GetComponent<NavMeshAgent>();
              agent.destination = goal.position; 
           }
        }

다음으로 간단한 스크립트를 빌드하여 또 다른 게임 오브젝트로 지정된 목표와 목표로 설정된 구체에 캐릭터를 보낼 수 있습니다.

  1.  C# 스크립트(MoveTo.cs)를 생성하고 위의 스크립트로 콘텐츠를 작성합니다.
  2. 생성한 캐릭터에 MoveTo 스크립트를 할당합니다.
  3. sphere를 생성하여 에이전트가 도달할 목표로 만듭니다.
  4. 캐릭터에서 구체를 NavMesh Surface에 가까운 위치로 옮깁니다.
  5. 캐릭터를 선택하고 MoveTo 스크립트를 지정한 뒤 구체를 Goal 프로퍼티에 할당합니다.
  6. Play 버튼을 누릅니다. 에이전트가 구체의 위치로 이동하는 것을 볼 수 있습니다.

즉, 스크립트에서 NavMesh Agent 컴포넌트의 레퍼런스를 가지고 에이전트를 움직이도록 설정하려면 에이전트의 destination 프로퍼티로 포지션을 할당합니다. 내비게이션 방법에서 내비메시 에이전트로 일반적인 게임플레이 시나리오 해결법에 대한 다양한 예제를 볼 수 있습니다

 

\

내비메시 장애물 생성

내비메시 장애물(NavMesh Obstacle) 컴포넌트는 에이전트가 이동하는 동안 피해야 하는 장애물을 정의하는 데 사용합니다. 예를 들어, 에이전트가 이동하는 동안 피해야 하는 상자나 통을 지정할 수 있습니다.

상자를 하나 추가하여 최상단의 경로를 차단해보겠습니다.

  1. 우선 게임 오브젝트(Game Object) > 3D 오브젝트(3D Object) > 큐브(Cube) 에서 큐브를 만들어 상자를 생성합니다.
  2. 큐브를 상단의 플랫폼으로 이동합니다. 큐브의 디폴트 크기가 상자의 크기로 적당하므로 그대로 둡니다.
  3. 큐브에 내비메시 장애물 컴포넌트를 추가합니다. 인스펙터에서 컴포넌트 추가(Add Component)를 선택한 다음 내비게이션(Navigation) > 내비메시 장애물(NavMesh Obstacle)을 선택합니다.
  4. 장애물의 모양을 박스(box)로 설정합니다. 모양이 변경되면서 큐브 중심과 크기가 렌더 메시에 자동으로 맞춰집니다.
  5. 장애물에 리지드바디를 추가합니다. 인스펙터에서 컴포넌트 추가(Add Component)를 선택한 다음 물리(Physics) > 리지드바디(Rigid Body)를 선택합니다.
  6. 마지막으로 에이전트가 장애물을 피하여 경로를 찾을 수 있도록 내비메시 장애물(NavMesh Obstacle) 인스펙터에서 Carve 설정을 활성화합니다.

이제 물리적으로 제어되는 상자가 활성화되었으며, AI는 이 상자를 피해 경로를 찾습니다.

 

오프 메시 링크 생성

오프 메시 링크(Off-Mesh Link)는 걸을 수 있는 내비게이션 메시 표면 외 지나갈 수 있는 경로를 만드는 데 사용됩니다. 예를 들어, 배수로나 펜스 뛰어넘기 또는 지나가기 전에 문 열기 등이 오프 메시 링크로 묘사될 수 있습니다.

오프 메시 링크 컴포넌트를 추가해 위 플랫폼에서 지상으로 점프하는 움직임을 구현해 보겠습니다.

  1. 먼저 원기둥 2개를 생성합니다. 게임 오브젝트(Game Object) > 3D 오브젝트(3D Object) > 원기둥(Cylinder).
  2. 원기둥의 크기를 (0.1, 0.5, 0.1) 로 조정해서 더 쉽게 작업할 수 있습니다.
  3. 첫 번째 원기둥을 위 플랫폼의 모서리인 내비메시 표면 가까이로 옮깁니다.
  4. 두 번째 원기둥을 내비메시 주변, 링크가 착지할 지점의 지상에 배치합니다.
  5. 왼쪽의 원기둥을 선택하고 오프 메시 링크 컴포넌트를 추가합니다. 인스펙터에서 컴포넌트 추가(Add Component)를 선택하고 내비게이션(Navigation) > 오프 메시 링크(Off-Mesh Link)를 선택합니다.
  6. 왼쪽의 원기둥을 시작(Start) 필드에 할당하고 오른쪽의 원기둥을 끝(End) 필드에 할당합니다.

오프 메시 링크 설정을 마쳤습니다. 만약 오프 메시 링크를 통한 경로가 내비메시를 통한 경로보다 짧을 경우 오프 메시 링크가 사용됩니다.

오프 메시 링크 컴포넌트를 지속시키기 위해 씬에서 모든 게임 오브젝트를 이용할 수 있습니다. 예를 들어 펜스 프리팹에 오프 메시 링크 컴포넌트를 포함시킬 수 있습니다. 또한 변환이 포함된 모든 게임 오브젝트를 시작과 끝 마커로 사용할 수 있습니다.

내비메시 베이크 프로세스는 공통의 건너뛰기 및 떨어지기 링크를 자동으로 탐지하고 생성할 수 있습니다. 자세한 내용은 오프 메시 링크 자동으로 빌드하기를 참조하십시오.

 

자동으로 오프 메시 링크 빌드

오프 메시 링크의 일부는 자동으로 감지되어 생성됩니다. 가장 일반적인 경우는 떨어지기(Drop-Down)  건너뛰기(Jump-Across) 입니다.

  • 떨어지기 링크는 플랫폼에서 떨어질 때 만들어집니다.
  • 건너뛰기 링크는 틈을 건너뛸 때 만들어집니다.

점프 위치를 자동으로 찾기 위해 빌드 프로세스는 내비메시의 가장자리를 따라 걸어가고 내비메시 위에 착지 위치를 확인합니다. 점프 궤도에 별다른 장애물이 없을 경우 오프 메시 링크가 생성됩니다.

이제 오프 메시 링크가 자동으로 생성되도록 설정해 보겠습니다. 내비메시 베이킹에 익숙하지 않다면 내비메시 빌드를 참조하십시오.

먼저 씬에서 점프가 시작될 수 있는 오브젝트를 표시해야 합니다. 오브젝트 탭 아래의 _ 내비게이션 창_에 있는 Generate Off-Mesh Links 옵션을 체크하면 됩니다.

그런 다음, 떨어지기 및 건너뛰기 궤도를 다음과 같이 설정합니다.

  • 떨어지기 링크의 생성은 Drop Height 파라미터에 의해 제어됩니다. 이 파라미터는 연결이 유지되는 가장 높은 낙하 거리를 제어하며, 이 값을 0으로 설정하면 링크가 생성되지 않습니다.
  • 떨어지기 링크의 궤도가 정의되면 수평 경로 (A)는 2\agentRadius + 4\voxelSize 가 됩니다. 이는 떨어질 경우 플랫폼 가장자리 바로 뒤에 착지한다는 것을 뜻합니다. 또한 단순히 걸어서 내려가지 않게 하려면 수직 경로 (B)는 베이크 설정의 Step Height 보다는 높아야 합니다. 그리고 복셀 크기를 통해 조정이 이루어지더라도 복셀화 과정 동안 발생하는 반올림 오류로 인해 링크가 생성되지 않는 일이 없도록 하려면 수직 경로가 Drop Height 보다는 작아야 합니다. Drop Height 의 값은 현재 레벨에서 측정한 값보다 약간 더 높게 설정해야 링크가 올바르게 연결됩니다.
  • 건너뛰기 링크 생성은 Jump Distance 파라미터에 의해 제어됩니다. 이 파라미터는 연결이 유지되는 최장 거리를 제어합니다. 이 값을 0으로 설정하면 링크가 생성되지 않습니다.
  • 건너뛰기 링크의 궤도가 정의되면 수평 경로 (C)는 2\*agentRadius 보다는 크지만 Jump Distance 파라미터의 값보다는 작습니다. 또한 착지 지점 (D)는 시작 지점의 레벨에 있는 복셀의 크기보다 높아서는 안 됩니다.

이제 오브젝트가 표시되었고 설정이 조정되었으므로 Bake 를 누르면 자동으로 오프 메시 링크가 생성됩니다. 씬을 변경하고 다시 베이크하는 경우 이전 링크는 제거되고 새로운 링크가 새로운 씬에 기반하여 생성됩니다.

문제 해결

오프 메시 링크가 의도하지 않은 위치에 생성되는 경우 다음 사항에 유의해야 합니다.

  • Drop Height 값은 현재 레벨에서 측정한 실제 거리보다 약간 더 커야 합니다. 이렇게 해야 내비메시 베이킹 프로세스 동안 약간의 오차가 발생하더라도 링크가 연결됩니다.
  • Jump Distance 값은 현재 레벨에서 측정한 실제 거리보다 약간 더 커야 합니다. Jump Distance는 내비메시의 한 지점에서 다른 지점까지의 거리로 측정되므로 2\*agentRadius 보다 더 큰 값으로 설정해야 틈을 건너뛸 수 있습니다.

정확한 캐릭터 배치를 위한 하이트 메시 빌드

하이트 메시(Height Mesh)를 이용하여 걸을 수 있는 표면에 캐릭터를 더 정확하게 배치할 수 있습니다.

길을 찾는 동안 내비메시 에이전트는 내비메시 표면에 있어야 합니다. 내비메시는 걸을 수 있는 공간을 대략적으로 추정한 것이므로, 내비메시가 빌드되는 동안 일부 기능이 안정화됩니다. 예를 들어 계단은 내비메시에서는 경사로 표현될 수도 있습니다. 게임에서 에이전트의 위치를 정확하게 지정해야 하는 경우에는 내비메시를 베이크할 때 하이트 메시 빌딩을 활성화해야 합니다. 이 설정은 내비게이션 창의 고급 옵션에 있습니다. 이 기능을 활성화하면 런타임 도중 메모리와 처리 시간을 추가적으로 요구하게 되며, 내비메시를 베이크할 때 더 오랜 시간이 필요하게 된다는 점을 기억해야 합니다.

 

내비게이션 영역 및 비용

Navigation Areas 는 특정 영역을 걸어서 지나가는 데 드는 비용(어려움)을 뜻하며, 경로를 탐색할 때에는 낮은 비용 영역순으로 선택됩니다. 또한 각각의 내비메시 에이전트에는 Area Mask 가 있어 에이전트가 이동할 수 있는 영역을 지정할 수 있습니다.

위의 예제에서, 영역 타입은 아래 두 개의 사용 사례에 사용됩니다.

  • Water 영역에는 높은 비용을 할당되어 있어 걸어가는 데 더 많은 비용이 소요됩니다. 이를 통해 물이 얕은 곳을 느리게 걸어가는 시나리오를 처리할 수 있습니다.
  • Door 영역에는 특정 캐릭터만 접근할 수 있습니다. 이를 통해 사람은 통과할 수 있지만 좀비는 통과할 수 없는 시나리오를 처리할 수 있습니다.

영역 타입은 내비메시 베이킹에 포함된 모든 오브젝트에 할당할 수 있으며, 각각의 오프 메시 링크에는 영역 타입을 지정하는 프로퍼티가 있습니다.

경로 탐색 비용

비용을 조절하여 경로 탐색자가 경로를 탐색할 때 선호하는 영역을 제어할 수 있습니다. 예를 들어 한 영역의 비용을 3.0으로 설정하면, 해당 영역을 지나가기 위해서는 다른 경로로 지나갈 때보다 세 배의 시간이 걸립니다.

비용의 작동 방식을 완전히 파악하기 위해 경로 탐색자의 작동 방식을 살펴보겠습니다.

경로 탐색 도중 거쳐가는 노드와 링크

Unity는 A*를 사용하여 내비메시에서 최단 경로를 계산합니다. A*는 연결된 노드 그래프에서 작동합니다. 이 알고리즘은 경로 시작점에서 가장 가까운 노드에서부터 시작하여 여러 연결 노드를 거쳐 목적지에 도달합니다.

Unity 내비게이션은 폴리곤 메시로 나타나기 때문에 경로 탐색자는 우선 각 폴리곤에 노드의 위치가 되는 포인트를 배치해야 합니다. 최단 경로는 이 노드들 사이에서 계산됩니다.

위 그림의 노란 점과 선은 A*를 따라 가로지르는 순서에 따라 노드와 링크가 내비메시에 배치되는 방식을 보여줍니다.

두 노드 사이를 이동하는 비용은 이동하는 거리와 링크 아래의 폴리곤 영역 타입에 설정된 비용에 따라 달라지며, 이를 거리 * 비용 이라고 합니다. 실제로 영역의 비용이 2.0인 경우 해당 폴리곤을 지나가는 거리는 두 배로 표시됩니다. A* 알고리즘에서는 모든 비용이 1.0 이상이어야 합니다.

최종 경로에서의 비용의 영향은 조절하기 어려우며, 특히 경로가 길수록 더 어렵습니다. 비용을 다루는 가장 좋은 방법은 비용을 힌트로 취급하면 됩니다. 예를 들어, 에이전트가 오프 메시 링크를 너무 자주 사용하지 않게 하려면 이 비용을 늘리면 됩니다. 하지만 에이전트가 보도 위를 걷는 것을 선호하는 동작을 조절하기는 어려울 수 있습니다.

어떤 레벨에서 경로 탐색자는 항상 최단 경로를 선택하지 않는다는 점도 알아두어야 합니다. 이는 노드 배치 때문입니다. 작은 장애물 옆에 큰 열린 영역이 있을 때, 즉 내비게이션 메시에 아주 큰 폴리곤과 아주 작은 폴리곤이 포함될 때 이런 현상을 쉽게 볼 수 있습니다. 큰 폴리곤의 노드는 해당 폴리곤의 아무 곳에나 배치될 수 있는데, 경로 탐색자의 관점에서 볼 때 이것이 우회 경로로 보일 수 있습니다.

영역 타입  비용  Areas 탭에서 전역으로 설정하거나 스크립트를 사용하여 에이전트별로 오버라이드할 수 있습니다.

영역 타입

영역 타입은 내비게이션 창  영역 탭에 있습니다. 29 개의 커스텀 타입과 Walkable, Not Walkable, Jump 등 3개의 빌트인 타입이 있습니다.

  • Walkable은 걸을 수 있는 영역을 의미하는 일반적인 영역 타입입니다.
  • Not Walkable은 내비게이션을 방지하는 일반적인 영역 타입입니다. 내비메시가 해당 영역을 지나가지 못하게 하면서 특정 오브젝트를 장애물로 표시할 때 사용할 수 있습니다.
  • Jump는 자동으로 생성된 모든 오프 메시 링크에 할당되는 영역 타입입니다.

다른 영역 타입의 오브젝트가 여러 개 중첩되는 경우 일반적으로 최종 생성된 내비메시 영역 타입이 우선적으로 적용됩니다. 하지만 Not Walkable 은 예외적으로 항상 최우선적으로 적용됩니다. 이를 통해 특정 영역을 차단해야 하는 경우 확실하게 차단할 수 있습니다.

영역 마스크

각 에이전트에는 내비게이션에 사용할 영역을 정의할 수 있는 Area Mask 가 있습니다. 영역 마스크는 에이전트 프로퍼티에서 설정할 수 있으며, 런타임 중에는 스크립트를 사용하여 비트마스크를 조작할 수 있습니다.

영역 마스크는 특정 타입의 캐릭터만 해당 영역을 지나갈 수 있게 할 때 유용합니다. 예를 들어 좀비에게서 도망치기 게임에서 각각의 문 아래 영역을 Door 영역 타입으로 표시하고, 좀비 캐릭터의 영역 마스크에서 Door 영역을 선택 해제하면 좀비가 해당 영역을 통과하지 못합니다.

 

추가 로딩을 사용하여 여러 내비메시 로드

다른 씬의 내비메시는 기본적으로 연결되지 않습니다. Application.LoadLevelAdditive() 를 사용하여 다른 레벨을 로드하는 경우 오프 메시 링크를 사용하여 여러 씬의 내비메시를 연결해야 합니다.

위의 예제에 Scene 1  Scene 2 가 있습니다. Scene 1 에는 걷기 쉬운 영역에서 시작하여 Scene 2 의 걷기 쉬운 영역에 착륙하는 오프 메시 링크가 있습니다. 필요에 따라 씬을 연결하는 만큼 많은 오프 메시 링크가 있을 수 있습니다.

작성 시 오프 메시 링크를 연결하는 씬의 다른 끝 지점은 연결되지 않습니다. 새로운 씬이 로드된 후 오프 메시 링크가 다시 연결됩니다.

여러 씬의 내비메시가 동일한 영역에서 겹쳐져 있는 경우 위치 선택은 해당 위치에서 임의의 내비메시일 수 있습니다. 이는 내비메시 API를 사용하는 에이전트, 오프 메시 링크 및 포지션 선택에 적용됩니다. 오프 메시 링크가 하나의 내비메시에서만 시작되고 끝날 수 있게 씬을 만들어야 합니다. 겹치는 내비메시 영역은 자동으로 연결되지 않습니다.

내비메시 에이전트를 다른 컴포넌트와 함께 사용

내비메시 에이전트(NavMesh Agent), 내비메시 장애물(NavMesh Obstacle), 오프 메시 링크(Off Mesh Link) 컴포넌트는 다른 Unity 컴포넌트와 함께 사용할 수 있습니다. 다른 컴포넌트를 함께 사용할 때에는 다음과 같은 규칙을 따라야 합니다.

내비메시 에이전트 및 물리

  • 서로 충돌하지 않도록 내비메시 에이전트에 물리 콜라이더를 추가하지 않아도 됩니다.
    내비게이션 시스템이 에이전트뿐만 아니라 장애물과 정적 월드에 대한 에이전트의 반응까지 시뮬레이션하기 때문입니다. 여기서 정적 월드는 베이크된 내비메시입니다.
  • 내비메시 에이전트가 물리 오브젝트를 푸시하거나 물리 트리거를 사용하도록 하려면 다음과 같이 수행해야 합니다. 
    콜라이더 컴포넌트를 추가합니다(없을 경우).
    리지드바디 컴포넌트를 추가합니다.
    키네마틱(물리 법칙에 영향을 받지 않음)을 활성화합니다. 이 작업은 매우 중요합니다!
    키네마틱이란 리지드바디가 물리 시뮬레이션 이외의 다른 요소에 의해 제어된다는 뜻입니다.
  • 내비메시 에이전트와 리지드바디(키네마틱이 아님)를 동시에 활성화하면 경합 조건이 발생합니다.
    두 컴포넌트가 동시에 에이전트를 움직이려고 하여 정의되지 않은 동작이 발생할 수 있습니다.
  • 내비메시 에이전트를 사용하여 물리 없이 플레이어 캐릭터 등을 움직일 수 있습니다.
    플레이어 에이전트의 회피 우선 순위를 작은 숫자(우선 순위가 높음)로 설정하여 플레이어가 군중 사이를 헤치고 지나갈 수 있게 합니다.
    다른 에이전트가 플레이어의 움직임을 예측하여 회피할 수 있도록 NavMeshAgent.velocity를 통해 플레이어 에이전트를 움직입니다.

내비메시 에이전트 및 애니메이터

  • 내비메시 에이전트와 루트 모션이 있는 애니메이터는 경합 조건을 유발할 수 있습니다.
    두 컴포넌트가 각 프레임에서 트랜스폼을 이동시키려고 합니다.
    두 가지 해결 방법 -
  • 정보는 항상 한 방향으로 흘러야 합니다.
    에이전트가 캐릭터를 움직이고 애니메이션이 이를 따르거나,
    시뮬레이션 결과에 기반하여 애니메이션이 캐릭터를 움직여야 합니다.
    그렇지 않으면 피드백 루프를 디버그하기가 어려워집니다.
  • 애니메이션이 에이전트를 따르는 경우 NavMeshAgent.velocity를 애니메이터의 입력값으로 사용하면 에이전트의 이동과 애니메이션이 대략적으로 일치합니다.
    확실하고 손쉬운 방법이지만, 애니메이션이 속도를 따라가지 못하면 발이 미끄러지는 결과가 나타날 수 있습니다.
  • 에이전트가 애니메이션을 따르는 경우
    NavMeshAgent.updatePosition 및 NavMeshAgent.updateRotation을 비활성화하여 게임 오브젝트의 위치에서 시뮬레이션을 분리합니다.
    시뮬레이션된 에이전트의 포지션(NavMeshAgent.nextPosition)과 애니메이션 루트(Animator.rootPosition)의 차이를 사용하여 애니메이션 제어를 계산할 수 있습니다.

내비메시 에이전트 및 내비메시 장애물

  • 혼합해서는 안 됩니다!
    둘 다 활성화하면 에이전트가 스스로 회피하게 됩니다.
    추가적으로 카빙이 활성화된 경우 에이전트는 카빙된 구멍의 가장자리로 지속적으로 다시 매핑하려고 하여 더 잘못된 동작이 발생합니다.
  • 언제든지 둘 중 하나만 활성화되도록 합니다.
    멈춘 상태라면 에이전트를 끄고 장애물을 활성화하여 다른 에이전트가 이를 피하도록 합니다.
    또는 우선 순위를 설정하여 특정 에이전트를 더 회피하도록 할 수도 있습니다.

내비메시 장애물 및 물리

  • 물리 제어되는 오브젝트가 내비메시 에이전트의 동작에 영향을 주도록 하려면 다음과 같이 수행해야 합니다.
    에이전트가 인지해야 하는 오브젝트에 내비메시 장애물 컴포넌트를 추가합니다. 이렇게 하면 회피 시스템이 장애물을 추론할 수 있습니다.
  • 게임 오브젝트에 리지드바디와 내비메시 장애물이 추가된 경우 장애물의 속도는 자동으로 리지드바디에서 구해집니다.
    이렇게 하면 내비메시 에이전트가 움직이는 장애물을 예측하고 회피할 수 있습니다.

내비메시 에이전트

NavMeshAgent 컴포넌트는 목표를 향해 움직일 때 서로를 피해가는 캐릭터 생성에 유용합니다. 에이전트는 내비메시를 이용하여 게임 월드에 대해 추론하고 서로 또는 기타 움직이는 장애물을 피할 방법을 이해하고 있습니다. 내비메시 에이전트의 스크립팅 API를 이용하여 경로를 찾거나 공간을 추론할 수 있습니다.

프로퍼티

프로퍼티기능

에이전트 크기
Radius 에이전트의 반경은 장애물과 다른 에이전트 간의 충돌 계산하기 위해 사용됩니다.
Height 에이전트가 장애물 밑으로 지나갈 수 있는 높이 간격입니다.
Base offset 트랜스폼 피봇 포인트와 관련한 충돌 실린더의 오프셋입니다.
스티어링
Speed 최대 이동 속도(초당 월드 단위로)
Angular Speed 최대 회전 속도(초당 각도)
Acceleration 최대 가속(제곱 초당 월드 단위로)
Stopping distance 에이전트는 목표 위치에 가까워졌을 시 정지합니다.
Auto Braking 활성화 시 에이전트는 목적지에 다다를 때 속도를 줄입니다. 에이전트가 멀티플 포인트 사이에서 부드럽게 움직여야 하는 순찰과 같은 동작을 할 때에는 반드시 비활성화 시켜야 합니다.
장애물 회피
Quality 장애물 회피 품질입니다. 에이전트의 수가 많다면 장애물 회피 품질을 줄임으로써 CPU 시간을 절약할 수 있습니다. 회피를 없음으로 설정할 경우 충돌만 해결할 수 있을 뿐 다른 에이전트와 장애물에 대한 적극적인 회피는 하지 않습니다.
Priority 낮은 우선 순위의 에이전트는 이 에이전트의 회피 대상에서 제외됩니다. 값은 0에서 99사이에서 설정되어야 하며 낮은 숫자가 높은 우선 순위임을 의미합니다.
경로 찾기
Auto Traverse OffMesh Link 자동적으로 오프 메시 링크를 횡단하려면 트루로 설정해야 합니다. 애니메이션을 사용하거나 오프메시 링크를 횡단하는 특정한 방법을 사용하고 싶다면 반드시 이를 꺼놔야 합니다.
Auto Repath 활성화 시 에이전트가 경로 일부분의 끝에 도달하면 경로를 재탐색 합니다. 목적지까지 경로가 없다면 목적지에서 제일 가깝게 도달할 수 있는 위치까지 부분적인 경로가 생성됩니다.
Area Mask 영역 마스크는 에이전트가 경로 탐색에 어떠한 영역 타입을 고려할 것인지를 설명합니다. 내비메시 베이킹를 위해 메시를 준비할 때 각각의 메시 영역 타입을 설정할 수 있습니다. 예를 들어 계단을 특별한 영역 타입으로 표시하고 몇몇 캐릭터 타입의 계단 이용을 금지할 수 있습니다.

세부 정보

에이전트는 수직으로 서 있는 실린더에 의해 정의되며 실린더의 크기는 Radius  Height 프로퍼티에 의해 특정됩니다. 실린더는 오브젝트와 함께 움직이지만 오브젝트가 회전한다 해도 계속 수직으로 서 있습니다. 실린더의 모양은 다른 에이전트와 장애물간의 충돌 감지와 대응에 사용됩니다. 게임 오브젝트의 앵커 포인트가 실린더의 베이스에 없을 때 높이의 차이를 메우기 위해 베이스 오프셋 프로퍼티를 사용할 수 있습니다.

실린더의 높이와 반경은 실제로 두 개 의 다른 장소에 특정됩니다. 내비메시 베이크 설정과 각 에이전트의 프로퍼티 입니다.

-내비메시 베이크 설정 은 내비메시 에이전트가 어떻게 정적인 월드 지오메트리와 충돌하고 또는 어떻게 회피하는지를 설명합니다. 메모리 여유량을 유지하고 CPU 로드를 지속적으로 체크하기 위해 오직 하나의 크기만이 베이크 설정에서 특정될 수 있습니다. -내비메시 에이전트 프로퍼티 값은 에이전트가 움직이는 장애물 및 다른 에이전트와 어떻게 충돌하는지를 설명합니다.

대부분의 경우 두 장소 모두에 에이전트 크기를 똑같이 설정합니다. 하지만 예를 들어 크기가 큰 에이전트의 반경이 더 넓다면 다른 에이전트는 그 에이전트 주변에 공간을 더 많이 남겨둡니다. 하지만 그렇지 않다면 크기가 큰 에이전트도 마찬가지로 환경을 무시합니다.

 

내비메시 장애물

Nav Mesh Obstacle 컴포넌트로 내비메시 에이전트가 월드를 탐색하는 동안 피해야 하는 움직이는 장애물(예: 물리 시스템에 의해 제어되는 배럴 또는 크레이트)을 설명할 수 있습니다. 장애물이 움직일 때 내비메시 에이전트는 장애물을 피하기 위해 최선을 다합니다. 장애물이 정지 상태일 때는 내비메시에 구멍을 팝니다. 그러면 내비메시 에이전트가 경로를 바꿔 장애물을 돌아가거나, 장애물로 인해 경로가 완전히 차단될 경우 다른 길을 찾습니다.

프로퍼티기능

Shape 장애물 지오메트리의 모양입니다. 오브젝트 모양에 가장 적합한 것을 선택해야 합니다.
    Box  
        Center 변환 포지션에 대한 박스의 상대적인 중심입니다.
        Size 상자의 크기입니다.
    Capsule  
        Center 변환 포지션에 대한 캡슐의 상대적인 중심입니다.
        Radius 캡슐의 반지름입니다.
        Height 캡슐의 높이입니다.
Carve Carve 체크박스를 선택하면 내비메시 장애물이 내비메시에 구멍을 만듭니다.
    Move Threshold Unity는 내비메시 장애물이 Move Threshold를 통해 설정한 거리보다 많이 움직인 경우 움직인다고 간주합니다. 이 프로퍼티는 움직이는 파인 구멍을 업데이트하는 임계 거리를 설정하는 데 사용합니다.
    Time To Stationary 장애물이 정지되었다고 간주할 때까지 기다리는 시간(초)입니다.
    Carve Only Stationary 이 옵션을 활성화하면 장애물이 정지되어 있을 때만 구멍을 팝니다. 자세한 내용은 아래의 내비메시 장애물 이동 논리를 참조하십시오.

세부 정보

내비메시 장애물은 게임 중에 다음 두 가지 방법으로 내비메시 에이전트의 내비게이션에 영향을 미칠 수 있습니다.

방해

Carve 가 활성화되지 않은 경우 내비메시 장애물의 디폴트 동작은 콜라이더와 비슷합니다. 내비메시 에이전트는 내비메시 장애물과 충돌을 피하려고 하고, 내비메시 장애물과 가까우면 충돌합니다. 장애물 회피 동작은 매우 기본적이며 짧은 반경을 가집니다. 따라서 내비메시 에이전트는 내비메시 장애물이 많은 환경에서 장애물을 피해갈 길을 찾지 못할 수 있습니다. 이 모드는 장애물이 계속 움직이는 경우(예: 차량이나 플레이어 캐릭터)에 사용하면 가장 적합합니다.

카빙

Carve 가 활성화된 경우, 장애물은 정지 중일 때 내비메시에 구멍을 팝니다. 장애물은 이동할 때 방해물이 됩니다. 내비메시에 구멍을 파면 패스파인더는 내비메시 에이전트가 장애물이 많은 장소를 피해 돌아가도록 하거나, 현재 경로가 장애물에 막힐 경우 다른 길을 찾을 수 있습니다. 일반적으로 내비게이션을 막지만 플레이어나 기타 폭발 같은 게임 이벤트에 의해 이동될 수 있는 내비메시 장애물(예: 크레이트 또는 배럴)에 대해서는 카빙을 켜는 것이 좋습니다.

 

내비메시 장애물 이동 논리

Unity는 내비메시 장애물이 Carve > Move Threshold 에서 설정한 거리보다 많이 이동할 경우 이동 중인 것으로 간주합니다. 내비메시 장애물이 이동하면 파인 구멍도 이동합니다. 하지만 CPU 오버헤드를 줄이기 위해 필요한 경우에만 구멍을 다시 산출합니다. 이 산출의 결과는 다음 프레임 업데이트에 사용 가능합니다. 재산출 논리에는 다음 두 가지 옵션이 있습니다.

  • 내비메시 장애물이 정지 상태일 때만 카빙
  • 내비메시 장애물이 이동된 경우에 카빙

내비메시 장애물이 정지 상태일 때만 카빙

디폴트 동작입니다. 이 옵션을 활성화하려면 Nav Mesh Obstacle 컴포넌트의 Carve Only Stationary 체크박스를 선택해야 합니다. 이 모드에서 내비메시 장애물이 움직이면 파인 구멍이 제거됩니다. 내비메시 장애물의 이동이 멈추고 Carving Time To Stationary 에서 설정한 시간보다 오랫동안 정지해 있으면 정지 상태로 간주되고 파인 구멍이 다시 업데이트됩니다. 내비메시 장애물이 이동 중인 동안에는 내비메시 에이전트가 충돌 방지를 사용하여 장애물을 피하지만 장애물을 돌아갈 경로를 계획하지는 않습니다.

Carve Only Stationary 는 일반적으로 성능의 관점에서 최선의 선택이고 내비메시 장애물과 연관된 게임 오브젝트가 물리를 통해 제어되는 경우에 적합합니다.

내비메시 장애물이 이동된 경우에 카빙

이 모드를 활성화하려면 Nav Mesh Obstacle 컴포넌트의 Carve Only Stationary 체크박스를 선택 해제해야 합니다. 선택 해제하면 장애물이 Carving Move Threshold 에서 설정된 거리보다 많이 이동한 경우 파인 구멍이 업데이트됩니다. 이 모드는 예를 들어 보병대가 피하려고 하는 탱크처럼 크고 천천히 이동하는 장애물에 유용합니다.

참고: 내비메시 쿼리 메서드를 사용하는 경우 내비메시 장애물을 변경한 후 이 변경 사항이 내비메시에 영향을 미칠 때까지 1프레임이 지연됨을 감안해야 합니다.

 

오프 메시 링크

OffMeshLink 컴포넌트는 걸을 수 있는 표면만으로는 표현할 수 없는 네비게이션 단축키를 구체화할 수 있도록 해 줍니다. 예를 들어, 도랑이나 울타리를 뛰어넘는 경우나 문을 열어야 걸어 지나갈 수 있을 경우, 이런 동작을 오프 메시 링크로 묘사할 수 있습니다.

프로퍼티

프로퍼티기능

Start 오프 메시 링크의 시작 지점을 나타내는 오브젝트입니다.
End 오프 메시 링크의 시작 지점을 나타내는 오브젝트입니다.
Cost Override 값이 양수이면 패스 요청을 처리하는 데 드는 패스 비용 산출에 이 값을 사용합니다. 그 외의 경우 디폴트 비용이 사용됩니다(게임 오브젝트가 속한 영역의 코스트). 비용 오버라이드 값이 3.0으로 설정되면, 디폴트 내비메시 영역에서 이동하는 경우에 비해 오프 메시 링크를 통해 같은 거리를 이동할 경우 비용이 세 배가 됩니다. 비용 오버라이드가 유용한 경우는, 에이전트가 걷기를 보통 선호하지만 걸어가면 거리가 더 길어지는 상황 때문에 오프 메시 링크를 이용하도록 만들고자 할 때입니다.
Bi-Directional 옵션을 활성화하면, 오프 메시 링크에서 양방향으로 이동할 수 있습니다. 비활성화하면 시작 에서  방향으로만 이동할 수 있습니다.
Activated 링크를 경로 탐색에 사용할 것인지 여부를 설정합니다(거짓인 경우 무시됨).
Auto Update Positions 옵션을 활성화하면, 오프 메시 링크의 끝 지점이 이동할 때 내비메시에 재연결됩니다. 비활성화하면 링크는 끝 지점이 이동하더라도 시작 지점에 계속 머무릅니다.
Navigation Area 링크의 네비게이션 영역 타입을 나타냅니다. 이 영역 타입을 통해 일반 횡단 비용을 유사한 영역 타입에 적용할 수 있으며, 특정 캐릭터가 해당 에이전트의 영역 마스크에 기반한 오프 메시 링크에 접근하는 것을 막을 수 있습니다.

세부 정보

에이전트가 오프 메시 링크를 따라 이동하지 않는다면 양 끝 지점이 제대로 연결되어 있는지 확인해야 합니다. 올바르게 연결된 끝 지점은 액세스 포인트 주위에 원형으로 표시가 나타납니다.

또 다른 원인으로 내비메시 에이전트의 에어리어 마스크 가 오프 메시 링크 영역을 포함하지 않았을 수도 있습니다.

 

NavMeshAgent에 목표로 이동 지시

NavMeshAgent.destination 프로퍼티를 설정하는 것으로 에이전트에 이동시키고자 하는 포인트까지의 경로를 산출시킬 수 있습니다. 산출이 완료되는 즉시 에이전트는 자동으로 경로를 따라 이동하여 목표에 도달합니다. 다음의 코드는 게임 오브젝트를 이용하여 목표 포인트를 설정하는 간단한 클래스를 사용하며 Start 함수의 destination 프로퍼티에 할당됩니다. 스크립트는 이미 NavMeshAgent 컴포넌트를 에디터로부터 추가하고 설정했다고 간주한다는 점에 유의해야 합니다.

    // MoveDestination.cs
        using UnityEngine;
    
        public class MoveDestination : MonoBehaviour {
       
           public Transform goal;
       
           void Start () {
              NavMeshAgent agent = GetComponent<NavMeshAgent>();
              agent.destination = goal.position; 
           }
        }
    // MoveDestination.js
        var goal: Transform;

        function Start() {
          var agent: NavMeshAgent = GetComponent.<NavMeshAgent>();
          agent.destination = goal.position; 
        }

마우스로 클릭한 포지션으로 에이전트 이동

이 스크립트에서는 오브젝트의 표면을 마우스로 클릭하여 내비메시의 목표 포인트로 선택합니다. 클릭 포지션은 레이저 빔을 오브젝트에 쏴서 어디에 광선이 닿는지 확인하는 것처럼 레이캐스트 로 결정됩니다. 이 기법에 대한 자세한 설명은 카메라에서 나오는 레이 페이지를 참조하십시오. GetComponent 함수는 실행 속도가 매우 느리므로 스크립트는 결과를 업데이트 시에 반복적으로 호출하지 많고 Start 함수 실행 중에 결과를 변수 안에 저장합니다.

    // MoveToClickPoint.cs
        using UnityEngine;
        using UnityEngine.AI;
    
        public class MoveToClickPoint : MonoBehaviour {
            NavMeshAgent agent;
        
            void Start() {
                agent = GetComponent<NavMeshAgent>();
            }
        
            void Update() {
                if (Input.GetMouseButtonDown(0)) {
                    RaycastHit hit;
                
                    if (Physics.Raycast(Camera.main.ScreenPointToRay(Input.mousePosition), out hit, 100)) {
                        agent.destination = hit.point;
                    }
                }
            }
        }
    //MoveToClickPoint.js
        var agent: NavMeshAgent;
    
        function Start() {
            agent = GetComponent.<NavMeshAgent>();
        }

        function Update() {
            if (Input.GetMouseButtonDown(0)) {
                var hit: RaycastHit;
        
                if (Physics.Raycast(Camera.main.ScreenPointToRay(Input.mousePosition), hit, 100)) {
                    agent.destination = hit.point;
                }
            }
        }

포인트 세트 사이에 에이전트 패트롤 생성

많은 게임이 자동으로 영역을 패트롤하는 NPC를 활용하고 있습니다. 이 동작을 실행하는 데 내비게이션 시스템을 사용할 수 있지만 일반적인 경로 탐색보다 더 많은 부분에서 관여해야 합니다. 일반적인 경로 탐색은 두 점 사이에 단순히 가장 가까운 경로를 지정하는 것으로 제한되고 예상되는 패트롤 루트를 만들어 냅니다. NPC가 경로를 따라 움직이고 방문하는 시퀀스를 특정할 때 “유용한” 키 포인트 세트를 지정하여 더 그럴싸한 패트롤 패턴을 만들어 낼 수 있습니다. 예를 들면 미로에서 갈림길과 코너에 키 패트롤 포인트를 지정하고 에이전트가 모든 복도를 체크하도록 할 수 있습니다. 사무실 건물에서는 키 포인트가 개별 사무실이나 다른 방이 될 수 있습니다.

키 패트롤 포인트가 마크된 미로

이상적인 패트롤 포인트의 시퀀스는 NPC가 어떻게 행동하길 원하는지에 따라 다릅니다. 예를 들어 로봇이 체계적인 순서로 포인트를 방문하지만 인간 경비는 더 랜덤한 패턴으로 플레이어를 잡으려 들지 모릅니다. 로봇의 단순한 동작은 아래의 코드로 구현될 수 있습니다.

패트롤 포인트는 변환의 공용 배열을 사용해서 스크립트에 제공됩니다. 이 배열은 인스펙터에서 게임 오브젝트를 사용하여 포인트의 포지션을 마크하기 위해 할당될 수 있습니다. GotoNextPoint 함수는 에이전트를 위한 목적지 포인트를 지정하며(에이전트를 움직이게 만듦) 다음 호출에 사용할 새로운 목적지를 선택합니다. 이에 따라 코드가 배열에서 발생하는 순서대로 포인트를 돌아다니게 됩니다. 이는 쉽게 수정할 수 있는데, 예를 들어 Random.Range를 사용하여 랜덤하게 배열 인덱스를 고를 수 있습니다.

Update 함수에서 remainingDistance 프로퍼티를 사용하여 에이전트가 얼마나 목적지에서 떨어져 있는지를 스크립트가 체크합니다. 거리가 짧으면 GotoNextPoint 를 호출하여 다음 패트롤을 시작하게 합니다.

    // Patrol.cs
        using UnityEngine;
        using UnityEngine.AI;
        using System.Collections;


        public class Patrol : MonoBehaviour {

            public Transform[] points;
            private int destPoint = 0;
            private NavMeshAgent agent;


            void Start () {
                agent = GetComponent<NavMeshAgent>();

                // Disabling auto-braking allows for continuous movement
                // between points (ie, the agent doesn't slow down as it
                // approaches a destination point).
                agent.autoBraking = false;

                GotoNextPoint();
            }


            void GotoNextPoint() {
                // Returns if no points have been set up
                if (points.Length == 0)
                    return;

                // Set the agent to go to the currently selected destination.
                agent.destination = points[destPoint].position;

                // Choose the next point in the array as the destination,
                // cycling to the start if necessary.
                destPoint = (destPoint + 1) % points.Length;
            }


            void Update () {
                // Choose the next destination point when the agent gets
                // close to the current one.
                if (!agent.pathPending && agent.remainingDistance < 0.5f)
                    GotoNextPoint();
            }
        }
    // Patrol.js
        var points: Transform[];
        var destPoint: int = 0;
        var agent: NavMeshAgent;


        function Start() {
            agent = GetComponent.<NavMeshAgent>();

            // Disabling auto-braking allows for continuous movement
            // between points (ie, the agent doesn't slow down as it
            // approaches a destination point).
            agent.autoBraking = false;

            GotoNextPoint();
        }


        function GotoNextPoint() {
            // Returns if no points have been set up
            if (points.Length == 0)
                return;
            
            // Set the agent to go to the currently selected destination.
            agent.destination = points[destPoint].position;

            // Choose the next point in the array as the destination,
            // cycling to the start if necessary.
            destPoint = (destPoint + 1) % points.Length;
        }


        function Update() {
            // Choose the next destination point when the agent gets
            // close to the current one.
            if (!agent.pathPending && agent.remainingDistance < 0.5f)
                GotoNextPoint();
        }

애니메이션과 내비게이션 연결

이 문서는 내비게이션 시스템을 사용하여 내비게이팅 휴머노이드 캐릭터가 움직이도록 설정하는 방법을 설명하기 위해 작성되었습니다.

여기서는 Unity 빌트인 애니메이션 시스템, 내비게이션 시스템, 커스텀 스크립팅을 활용해서 휴머노이드 캐릭터를 움직이게 할 것입니다.

Unity 기본 사용법과 메카님 애니메이션 시스템을 완전히 이해하고 있다는 전제 하에 시작됩니다.

예제 프로젝트를 이용할 수 있으므로 처음부터 스크립트를 추가하거나 애니메이션 및 애니메이션 컨트롤러를 설정할 필요는 없습니다.

애니메이션 컨트롤러 생성

광범위한 움직임을 커버할 수 있는, 즉각적인 대응이 가능하고 다양한 용도로 활용할 수 있는 애니메이션 컨트롤러를 갖추기 위해서는 다양한 방향으로 움직이는 애니메이션 세트가 필요합니다. 세트는 스트레이프 세트(strafe-set)라고도 불립니다.

움직이는 애니메이션 외에도 서 있는 캐릭터를 위한 애니메이션이 필요합니다.

2D 블렌드 트리에 스트레이프 세트를 정렬하는 것으로 블렌드 타입을 선택합니다. 2D Simple Directional을 이용하며 Compute Positions > Velocity XZ를 통해 애니메이션을 적용합니다.

블렌딩 조절을 위해 velx vely 두 개의 플로트 파라미터를 추가하고 블렌드 트리에 할당합니다.

여기서 7개의 달리기 애니메이션을 배치합니다. 각 애니메이션은 다른 속도로 달리고 있습니다. 앞(+ 좌/우)과 뒤(+ 좌/우)에 더해서 제자리에서 달리는 애니메이션 클립을 사용합니다. 후자는 아래의 2D 블렌드 맵의 중앙에 하이라이트되어 있습니다. 제자리 달리기 애니메이션이 필요한 이유는 두 가지가 있습니다. 첫 번째 이유는 다른 애니메이션과 혼합되는 경우에도 달리는 스타일을 유지할 수 있기 때문이며, 두 번째 이유는 혼합되는 동안 발이 미끄러지는 현상을 방지하기 때문입니다.

애니메이션 노드에 대기 애니메이션 클립을 추가합니다(Idle). 이제 두 개의 트랜지션에 각각 해당하는 두 개의 분리된 애니메이션 상태가 준비되었습니다.

움직임과 대기 상태의 전환을 조절하려면 부울(boolean) 제어 파라미터 move를 추가합니다. 그리고 트랜지션에서 종료 시간 있음(Has Exit Time) 프로퍼티를 비활성화합니다. 이를 통해 애니메이션 중 어느 때라도 트랜지션이 트리거될 수 있습니다. 즉각적으로 대응하는 트랜지션을 위해 트랜지션 시간은 대략 0.10초로 설정합니다.

새롭게 생성한 애니메이션 컨트롤러를 움직일 캐릭터에 위치시킵니다.

계층 구조 창에서 플레이 버튼을 누르고 캐릭터를 선택합니다. 이제 수동으로 애니메이터 창의 애니메이션 값을 조절해서 움직임 상태와 속도를 변경할 수 있습니다.

다음으로 애니메이션 파라미터를 조절할 수 있는 다른 방법을 생성합니다.

내비게이션 컨트롤

NavMeshAgent 컴포넌트를 캐릭터에 적용하고 반지름, 높이를 캐릭터와 일치하도록 조정합니다. 추가적으로 속도 프로퍼티를 애니메이션 블렌드 트리의 최대 속도와 일치하도록 변경합니다.

캐릭터를 배치한 씬의 내비메시를 생성합니다.

다음으로 캐릭터에게 어디로 이동할지 지정합니다. 지정하는 방법은 애플리케이션에 따라 크게 다릅니다. 이번에는 사용자가 화면에서 클릭하는 포인트로 캐릭터가 움직이게 해서 동작을 지정합니다.

// ClickToMove.cs
using UnityEngine;
using UnityEngine.AI;

[RequireComponent (typeof (NavMeshAgent))]
public class ClickToMove : MonoBehaviour {
    RaycastHit hitInfo = new RaycastHit();
    NavMeshAgent agent;

    void Start () {
        agent = GetComponent<NavMeshAgent> ();
    }
    void Update () {
        if(Input.GetMouseButtonDown(0)) {
            Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
            if (Physics.Raycast(ray.origin, ray.direction, out hitInfo))
                agent.destination = hitInfo.point;
        }
    }
}

플레이 버튼을 누르고 나서 씬 안을 클릭하면 캐릭터가 움직입니다. 그러나 애니메이션이 움직임과 일치하지 않습니다. 에이전트의 상태와 속도를 애니메이션 컨트롤러와 연동시켜야 합니다.

에이전트의 속도와 상태 정보를 애니메이션 컨트롤러로 전달하기 위해 추가 스크립트를 더합니다.

// LocomotionSimpleAgent.cs
using UnityEngine;
using UnityEngine.AI;

[RequireComponent (typeof (NavMeshAgent))]
[RequireComponent (typeof (Animator))]
public class LocomotionSimpleAgent : MonoBehaviour {
    Animator anim;
    NavMeshAgent agent;
    Vector2 smoothDeltaPosition = Vector2.zero;
    Vector2 velocity = Vector2.zero;

    void Start ()
    {
        anim = GetComponent<Animator> ();
        agent = GetComponent<NavMeshAgent> ();
        // Don’t update position automatically
        agent.updatePosition = false;
    }
    
    void Update ()
    {
        Vector3 worldDeltaPosition = agent.nextPosition - transform.position;

        // Map 'worldDeltaPosition' to local space
        float dx = Vector3.Dot (transform.right, worldDeltaPosition);
        float dy = Vector3.Dot (transform.forward, worldDeltaPosition);
        Vector2 deltaPosition = new Vector2 (dx, dy);

        // Low-pass filter the deltaMove
        float smooth = Mathf.Min(1.0f, Time.deltaTime/0.15f);
        smoothDeltaPosition = Vector2.Lerp (smoothDeltaPosition, deltaPosition, smooth);

        // Update velocity if time advances
        if (Time.deltaTime > 1e-5f)
            velocity = smoothDeltaPosition / Time.deltaTime;

        bool shouldMove = velocity.magnitude > 0.5f && agent.remainingDistance > agent.radius;

        // Update animation parameters
        anim.SetBool("move", shouldMove);
        anim.SetFloat ("velx", velocity.x);
        anim.SetFloat ("vely", velocity.y);

        GetComponent<LookAt>().lookAtTargetPosition = agent.steeringTarget + transform.forward;
    }

    void OnAnimatorMove ()
    {
        // Update position to agent position
        transform.position = agent.nextPosition;
    }
}

스크립트에는 설명이 필요합니다. Animator NavMeshAgent 컴포넌트가 추가된 캐릭터에 적용되며 위에서 설명한 클릭한 포인트로 움직이는 스크립트가 적용되어 있습니다.

먼저 스크립트가 에이전트에게 자동으로 캐릭터 포지션을 업데이트하지 말라고 전달합니다. 포지션 업데이트는 스크립트의 마지막에서 다룹니다. 오리엔테이션은 에이전트에 의해 업데이트 됩니다.

애니메이션 블렌드는 에이전트 속도를 읽는 것으로 조절됩니다. 상대적인 속도로 변환되고(캐릭터 오리엔테이션에 기초) 부드럽게 조정됩니다. 변환된 수평 속도 컴포넌트는 Animator로 이어지며 이에 더해서 대기 상태와 이동 사이의 전환은 속도에 의해 조절됩니다(예를 들어, 속도 규모).

OnAnimatorMove() 콜백에서 캐릭터의 포지션을 NavMeshAgent와 일치하도록 업데이트합니다.

씬을 다시 플레이하면 애니메이션이 최대한 움직임과 일치하는 것이 확인됩니다.

내비게이팅 캐릭터의 품질 개선

애니메이션 및 내비게이션 캐릭터의 품질을 개선하기 위해 여러 옵션을 살펴보겠습니다.

바라보기

관심있는 영역으로 캐릭터를 돌려서 바라볼 수 있게 하면 관심과 기대를 전달하는 데 유용합니다. 이제 애니메이션 시스템 바라보기 API를 사용하겠습니다. 이것은 또 다른 스크립트를 호출합니다.

// LookAt.cs
using UnityEngine;
using System.Collections;

[RequireComponent (typeof (Animator))]
public class LookAt : MonoBehaviour {
    public Transform head = null;
    public Vector3 lookAtTargetPosition;
    public float lookAtCoolTime = 0.2f;
    public float lookAtHeatTime = 0.2f;
    public bool looking = true;

    private Vector3 lookAtPosition;
    private Animator animator;
    private float lookAtWeight = 0.0f;

    void Start ()
    {
        if (!head)
        {
            Debug.LogError("No head transform - LookAt disabled");
            enabled = false;
            return;
        }
        animator = GetComponent<Animator> ();
        lookAtTargetPosition = head.position + transform.forward;
        lookAtPosition = lookAtTargetPosition;
    }

    void OnAnimatorIK ()
    {
        lookAtTargetPosition.y = head.position.y;
        float lookAtTargetWeight = looking ? 1.0f : 0.0f;

        Vector3 curDir = lookAtPosition - head.position;
        Vector3 futDir = lookAtTargetPosition - head.position;

        curDir = Vector3.RotateTowards(curDir, futDir, 6.28f*Time.deltaTime, float.PositiveInfinity);
        lookAtPosition = head.position + curDir;

        float blendTime = lookAtTargetWeight > lookAtWeight ? lookAtHeatTime : lookAtCoolTime;
        lookAtWeight = Mathf.MoveTowards (lookAtWeight, lookAtTargetWeight, Time.deltaTime/blendTime);
        animator.SetLookAtWeight (lookAtWeight, 0.2f, 0.5f, 0.7f, 0.5f);
        animator.SetLookAtPosition (lookAtPosition);
    }
}

캐릭터에 스크립트를 추가하고 헤드 프로퍼티를 캐릭터의 트랜스폼 계층 구조에 할당합니다. LookAt 스크립트는 내비게이션 조절과 관련 없습니다. 그러므로 어디를 보는지 조절하려면 LocomotionSimpleAgent.cs 스크립트로 돌아가서 두 행을 추가하는 것으로 바라보기를 조절합니다. Update()의 끝을 추가하려면 다음을 더합니다.

        LookAt lookAt = GetComponent<LookAt> ();
                if (lookAt)
                    lookAt.lookAtTargetPosition = agent.steeringTarget + transform.forward;

위의 행은 LookAt 스크립트가 경로 상에 모서리가 있는 경우에는 다음 모서리에, 없는 경우에는 경로 끝에 관심 지점을 설정하도록 합니다.**

시도해야 합니다.

내비게이션을 사용해 애니메이션으로 구동되는 캐릭터

이제까지 캐릭터는 에이전트가 명시한 포지션에 따라 완전히 제어되었습니다. 이렇게 하면 다른 캐릭터나 장애물이 제어하는 캐릭터의 포지션에 겹치지 않게 되지만, 애니메이션이 원하는 속도를 지원하지 못하는 경우에는 발이 미끄러지는 현상이 발생할 수 있습니다. 여기서는 캐릭터의 제한을 조금 완화하여, 회피 품질을 희생하여 애니메이션 품질을 개선해볼 것입니다.

LocomotionSimpleAgent.cs 스크립트에서 OnAnimatorMove() 콜백을 다음 행으로 교체합니다.

    void OnAnimatorMove ()
        {
            // Update position based on animation movement using navigation surface height
            Vector3 position = anim.rootPosition;
            position.y = agent.nextPosition.y;
            transform.position = position;
        }

교체를 시도할 때 캐릭터가 에이전트 포지션에서 멀어지는 것을 볼 수 있습니다(녹색 와이어프레임 실린더). 캐릭터가 멀어지는 것을 제한해야 합니다. 이렇게 하려면 에이전트를 캐릭터를 향해 당기거나, 캐릭터를 에이전트의 위치를 향해 당기면 됩니다. LocomotionSimpleAgent.cs 스크립트의 Update() 메서드 마지막에 다음을 추가합니다.

        // Pull character towards agent
                if (worldDeltaPosition.magnitude > agent.radius)
                    transform.position = agent.nextPosition - 0.9f*worldDeltaPosition;

아니면 에이전트가 캐릭터를 따라가도록 할 수 있습니다.

        // Pull agent towards character
                if (worldDeltaPosition.magnitude > agent.radius)
                    agent.nextPosition = transform.position + 0.9f*worldDeltaPosition;

각각의 경우에 따라 가장 적합한 방법은 다를 수 있습니다.

결론

지금까지 내비게이션 시스템을 사용하여 움직이고 그에 맞춘 애니메이션을 가지는 캐릭터를 설정해보았습니다. 블렌드 시간이나 바라보기 가중치를 조절해 보십시오. 캐릭터의 품질이 개선될 뿐만 아니라, 설정을 익히는 데에도 도움이 될 것입니다.

 

https://docs.unity3d.com/kr/2021.3/Manual/nav-CouplingAnimationAndNavigation.html

+ Recent posts