[Unity Engine] 유니티 엔진 게임 개발 일지 #8

신형석·2023년 5월 10일
0

게임 개발 일지

목록 보기
10/10

몬스터 AI에 대해 살펴본 내용을 조금 정리해보려고 한다.

우선, AI라는 것부터 먼저 알아봐야 할 것 같다.

AI란?

4차 산업 중 하나로 많이 들어봤을지도 모르겠다. AI는 Artificial Intelligence의 줄임말로, 한국말로는 인공지능이라고 한다. 말 그대로, 인공적으로 만들어진 지능이라는 뜻이다.

인간은 뇌를 가지고 있다. 이러한 뇌 또한 뇌 세포 간 전기 신호를 이용하여 계산하고, 이는 컴퓨터가 무엇인가를 계산하는 것과 같다. 하지만, 뇌(인간)는 자의식을 갖고 판단할 수 있지만, 컴퓨터(전자회로)는 자의식을 가지고 판단하지는 않는다.

인공지능은, 인간이 짠 코드를 이용하면 인간처럼 판단하고 행동하는 것처럼 보이게 할 수 있다는 것에서 게임에 꼭 필요한 기술이라고 할 수 있다.

AI를 만들기 위해, 많은 자료를 찾아보았다. 찾아본 자료들을 정리하고 내가 다시 이해하기 위해 적어보도록 하겠다.

1. Graph를 이용한 AI 제작

Graph(그래프)란, 데이터 구조 중 하나로
위 그림처럼 생겼다. 선이 검은색이라 잘 보이지 않지만, 여튼 노드(Node, 위 그림에서의 원)와 노드 사이가 이어져 있는 데이터 구조라고 보면 될 것이다.

그래프를 이용하는 건 개념적으로 아주 간단하다. 각각의 상태(State)를 그래프로 이어서, 특정 상태에서 특정 상태로 넘어가는 것을 구현하면 되는 것이다.

이 데이터 구조와 AI는 무슨 상관이 있을까? 각 노드에 한 몬스터의 여러 가지 행동 상태가 저장되어 있다고 가정해보자.


위 그림과 같이, 노드 하나에는 한 몬스터의 특정 상태가 저장되어 있고, 특정 이벤트가 일어나면 다른 노드로 이동하는 형태를 보이고 있다.

변수 한 개를 설정하여, 이는 "정지", "움직임", "공격"과 같은 상태가 변하는 변수로 설정한다. 그리고, 각 조건을 확인하며 조건에 맞으면 변수 상태를 변경하고, 그 변수 상태와 같은 행동을 하게 하면 되는 것이다.

2. RayCast의 이용

유니티에는 RayCast라는 기능이 존재한다. RayCast란, 특정 물체에서 레이저를 쏘아 보낸 후 그 레이저에 맞은 물체와의 관계, 물체의 종류 등을 알 수 있는 기능을 말한다.

현재 개인 프로젝트에 사용하는 몬스터 AI는 이 기능을 사용하고 있고, 경력이 오래된 개발자는 아니지만 굉장히 강력한 기능인 것 같아서 자세히 설명해보려고 한다.

언제나 이런 기능은 Unity Document부터 확인해야 한다. 이곳을 먼저 확인하고 보는 것이 좋을 것이다.

위에서 설명하였듯이, RayCast는 물체에서 레이저를 쏜 후에 그에 맞은 물체와의 관계 등을 알아낼 수 있는 방법이다. 이를 UI에 어떻게 사용할까?

본인은 이렇게 생각해보았다:

  1. 몬스터는 일정 속도로, 한 방향으로 움직인다.
  2. 한 방향으로 움직이다, 벽을 만나면 어떻게 될까?
  3. 벽을 만나면, 몬스터는 왼쪽이나 오른쪽을 향해 방향을 바꾼 후 다시 직진할 것이다.
  4. 만약 사거리를 만난다면, 몬스터는 왼쪽, 오른쪽으로 방향을 틀거나 직진하는 방법 총 3가지의 경우의 수로 움직일 것이다.
  5. 만약 몬스터의 앞 일정 거리 앞에 캐릭터가 있다면, 속도를 높여서 따라오고, 캐릭터가 다시 사거리에서 벗어나면 원래 속도로 돌아간다.

이런 AI를 사용하는 게임은 팩맨이 대표적일 것이다. 팩맨의 몬스터들도 길을 따라가다가, 벽을 만나면 랜덤한 방향으로 몸을 틀어서 직진을 하는 것을 알 수 있다.

그럼 위에 적은 방법으로 자동으로 방향을 변경하는 물체를 만들어보겠다. 우선, Script를 하나 만들고 이 변수를 선언해준다:

RaycastHit hit;

RaycastHit형 변수 hit를 선언하였다. hit라는 변수는 이때부터, ray와 부딪힌 물체의 정보, 물체와의 거리 등을 저장하는 변수로써 사용할 수 있게 된다.

[SerializeField] float speed = 1f;
Vector3 angle = new Vector3(0f, -90f, 0f);

[SerializeField]를 이용하여, 외부에서 수정할 수 있는 변수 speed를 만든다. 이 변수를 수정하면, 물체가 전진하는 속도를 변화시킬 수 있을 것이다.

angle 변수는 말 그대로 각도 변수이며, 본인은 하드 코딩으로, 즉 상수로 선언하였지만, 여기에 랜덤성을 부여하면 랜덤으로 돌아다니는 것처럼 보일 것이다.

그 후, Update() 메서드에 다음과 같이 추가한다:

transform.Translate(0, 0, speed * Time.deltaTime);

이 코드는, 이 전 게시글에서도 많이 사용하였듯이 Update의 각 프레임마다 작동되면서 물체를 움직이게 하는 코드이다.

Debug.DrawRay(this.transform.position, this.transform.forward * MaxDistance, Color.red);

드디어 새로운 코드가 나왔다. Debug의 함수 중 하나인 DrawRay는, 이곳을 확인하면 조금 더 자세히 알 수 있다. 특정 포지션에서, 특정 길이만큼의 Ray를, 특정 색깔로 그려서 월드에서 확인할 수 있게 하는 코드이다. 이 코드의 특징은, Debug에 소속되어 있어서 그런지 플레이 시에는 보이지 않지만 Scene에서는 확인할 수 있다.

if (Physics.Raycast(this.transform.position, this.transform.forward, out hit, MaxDistance) && hit.collider.tag != "Player"){
    Debug.Log(hit.transform.gameObject);
    transform.Rotate(angle, Space.Self);
}
if (Physics.Raycast(this.transform.position, this.transform.forward, out hit, 5f) && hit.collider.tag == "Player"){
    speed = 3f;
}
else{
    speed = 1f;
}

Raycast의 핵심이 들어가있어서 한번에 들고왔다. 한 줄씩 보도록 하겠다.

Debug.DrawRay는, 우리가 실제로 광선을 볼 수 있게만 해주지 실제로 Ray를 발사하는 코드는 아니다. 그러므로, 실제로 코드를 그려주는 코드가 필요하다. 그것이 Physics.Raycast이다.

위 Unity Document에 따르면, Raycast는 boolean형이다. 즉, True와 False로 답이 나온다는 뜻이다. 그래서, 자료를 찾아보면 If문과 굉장히 많이 사용된다는 사실을 확인할 수 있었다.

Raycast의 각 파라미터는

this.transform.position : ray가 발사되는 위치
this.transform.forward : ray가 발사되는 방향
out hit : ray가 발사된 후 부딪히는 물체와의 관계 등의 정보를 저장하는 변수 설정
MaxDistance : ray가 발사되는 길이

로 정의할 수 있다. 즉, 위 코드는

특정 위치에서, 특정 방향으로 ray를 특정 길이만큼 발사한 후 특정 변수에 저장한다

로 해석할 수 있다.

현재 그럼 데이터는 어디 저장되어 있을까? 바로 위에서 저장한, RaycastHit형 변수 hit에 저장되어 있다. 본인도 적으면서 어떻게 hit 변수에 저장되었을까? 하는 의문을 품었는데, 이는

out hit

이 파라미터에 확인할 수 있다. 이 문법은 C#에 있는 특이한 문법이라고 한다.

보통 우리가 생각하기엔,

hit = 물체와의 관계 어쩌구저쩌구

의 문법으로 이루어져야 할 것 같지만, C#에는 out 하나로 퉁쳐버린다고 한다.

아무튼, 이렇게 해서 hit 변수 안에 특정 정보들을 넣는 작업들을 하였다. 그러면, 이를 활용할 방법도 찾아야 한다.

RaycastHit는 사실 구조체형이다. 이곳에서 조금 더 자세히 볼 수 있을 것이고, 여러 변수들을 저장하고 있는 구조체의 역할을 한다. 그 중 이해하기 좀 쉬운 것들만을 꼽는다면,

collider : ray가 충돌한 물체의 정보
point : ray가 충돌체와 충돌한 point
distance : ray의 시작지점부터 충돌한 지점과의 거리
transform : ray가 충돌한 물체의 transform 정보
rigidbody : ray가 충돌한 물체의 rigidbody 정보

이다. 위 코드에서 사용한 것은, collider 안의 tag 정보이다. 만약 충돌한 물체의 tag 정보가 player라면 속도를 높이고, 그렇지 않으면 방향을 변경하는 코드를 짠 것이다. 그것을 확인할 수 있는 코드가 바로

hit.collider.tag != "Player"

이다. 위 코드에서는, tag가 Player가 아니라면

transform.Rotate(angle, Space.Self);

위에서 설정한 angle 변수의 Vector3 값만큼 Rotate를 하도록 설정되어있고, tag가 Player라면

speed = 3f;

speed 변수의 값을 바꿔주는 작업을 한다.

이곳에서 결과를 확인할 수 있고, 벽 앞에서 회전하고 캐릭터 앞에서는 가속하는 모습을 확인할 수 있다.


AI에 대해 처음부터 장황하게 설명을 한 것 같은데, 막상 적어보니 실제로 구현해본 것이 RayCast에 관한 것만 자세하게 적은 것 같다. 본인이 직접 사용해본 결과, RayCast는 굉장히 좋은 기능인 것 같다.

RayCast를 이용하면, 현재 UI의 어색한 점을 고칠 수 있을 것이라고 생각하였다. 만약 범위 안에 주울 수 있는 물체가 들어온다면, 물체에서부터 플레이어의 머리 위치로 Ray를 날려, 그 Ray에 직각인 각도로 Text를 띄워 어느 각도에서 봐도 줍는 키를 볼 수 있는 기능을 만들 수도 있을 것이다.

다음은 유니티의 Animation 기능에 대해서 설명하겠다.

0개의 댓글

관련 채용 정보