본문 바로가기

Unity

[Unity3D]FSM을 활용한 AI개발(5)

1. 서론

이 글에서는 좀비가 어떤 상태 값을 가져야 하는지 알아보도록 하겠다.

거두절미하고 바로 알아보자. 사실할 말이 없다.

2. 본론

그림1. 좀비의 상태들

좀비는 우리가 배열로 정의했던 것만큼의 클래스(상태)들이 필요하다. 걷고, 뛰고, 공격하고, 죽고.

가장 먼저 우리는 IDLE상태값부터 보도록 하자.

 

보기 전에 첫 번째로, 상속 부분을 확인해야 한다.

그림2. 추상클래스 상속

추상 클래스 FSM <T>를 상속받은 Zombie_Idle는 전에 강제했던 Begin(), Run(), Exit()를 구현하지 않았기 때문에 이런 컴파일 결과를 내어준다. 너무 친절하니 좋다. 우리는 전구를 클릭해 자동생성을 하면 된다.

그림3. 따란~

가주 간편하다. 알아서 abstract로 강제한 추상클래스를 override 된 함수로 전부 다 구현해 주니 말이다. 이게 상속의 장점이자 장점이자 장점이다. 두번 세 번 쓰도록 하자. 적당히  때에 맞게 쓰자...

 

https://docs.microsoft.com/ko-kr/dotnet/api/system.notimplementedexception?view=netframework-4.8

 

NotImplementedException Class (System)

요청한 메서드 또는 연산이 구현되지 않을 때 throw되는 예외입니다.The exception that is thrown when a requested method or operation is not implemented.

docs.microsoft.com

내가 직접 Throw객체에 대해 설명 할 수도 있겠지만 msdn에 너무 잘 설명이 되어있어 대체한다.

절대 귀찮아서 그런것이 아니다.

 

우리는 메소드 안 구현되지 않았다는 예외를 지우고 안에 채워 넣으면 된다.

 

//Zombie_Idle.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

namespace MyFSM
{
    public class Zombie_Idle : FSM<ZombieFSM>
    {
    	//주인
        private ZombieFSM m_Owner;
        
        //생성자 초기화
        public Zombie_Idle(ZombieFSM _owner)
        {
            m_Owner = _owner;   
        }
        
        //시작
        public override void Begin()
        {
            Debug.Log("Idle Begin");
            m_Owner.m_eCurState = ZOMBIE_STATE.Idle;
        }

		//동작
        public override void Run()
        {
            if (FindRange())
            {
                m_Owner.ChangeFSM(ZOMBIE_STATE.Walk);
            }
            else
            {
                m_Owner.m_TransTarget = null;
            }
        }

		//종료
        public override void Exit()
        {
            Debug.Log("Idle Exit");
            m_Owner.m_ePrevState = ZOMBIE_STATE.Idle;
        }

		//찾는 함수
        private bool FindRange()
        {
            Collider[] hitColliders = Physics.OverlapSphere(m_Owner.transform.position, m_Owner.m_fFindRange, (1 << LayerMask.NameToLayer("Civilian")) | (1 << LayerMask.NameToLayer("Soldier")));
            
            if (hitColliders.Length != 0)
            {
                for (int i = 0; i < hitColliders.Length; ++i)
                {
                    if (hitColliders[i].gameObject.layer == LayerMask.NameToLayer("Soldier"))
                    {
                        m_Owner.m_TransTarget = hitColliders[i].transform;
                    }
                }

                if (m_Owner.m_TransTarget == null)
                    m_Owner.m_TransTarget = hitColliders[0].transform;
               
                return true;
            }
            return false;
        }
    }
}

 

초기 생성자 값에 우리는 this를 넣어줬으므로, T자리에는 Zombie_FSM이 올 것을 알고 있기 때문에, Zombie클래스로 받아 준다. 아이들 상태에 왔기 때문에 상태 값을 시작 부분에 Idle로 Zombie_FSM에 현재 상태 값을 변경해주고 동작시켜준다.

마지막에 Exit가 호출될 때 이전 상태 값에 Idle상태 값을 변경해주면 Idle상태 값의 할 일이 끝난다.

 

동작 부분 Run함수를 보면, FindRange 했는지 안 했는지 판별한다.

범위 내에 있으면 오너 변수에 타깃을 넣어주고, 없으면 계속 반복하는 형식이다.

FindRange 함수 동작 부분에 Physics.OverlapSphere 피직스 객체의 오버랩 스피어 메서드를 통해 우리는 범위 계산을 쉽게 할 수 있다. 이 방법 이외에도 사방으로 Ray를 쏴서 검출하는 방법도 있으니, 본인의 상황에 맞게 쓰길 권한다.

 

https://docs.unity3d.com/kr/530/ScriptReference/Physics.OverlapSphere.html

 

Unity - 스크립팅 API: Physics.OverlapSphere

Success! Thank you for helping us improve the quality of Unity Documentation. Although we cannot accept all submissions, we do read each suggested change from our users and will make updates where applicable. 닫기

docs.unity3d.com

피직스와 관련된 유니티 문서이다.

내가 짚고 넘어갈 부분은 LayerMask부분이다. 딱히 마스크를 씌어주지 않으면 모든 콜라이더에 관련되어 탐색하니 꼭 레이어 마스크를 씌어주도록 하자. 비트 연산으로 레이어를 접목하기 때문에 간편하게 등록 가능하다.

비트 연산은 추 후 다루도록 하겠다.

 

그림4. LayerMask

레이어 등록방법은 쉽다. 오브젝트의 레이어 항목을 클릭해서 Add Layer로 쉽게 추가가 가능하다.

추가로 Edit -> Physics 세팅에서 충돌 레이어를 설정할 수 있으니 참고하길 바란다.

그림5. Phsics 셋팅

이렇게 하면 좀 더 효과적인 물리 충돌 연산이 가능하다.

 

3. 결론

한 글에 State들을 다 담으려 했지만... 글이 길어지는 것 같아 다음 글에 다른 상태 값을 분석해 보도록 하겠다.

 

질문은 댓글로 적어주시면 최대한 빠르게 답글 달아 드리겠습니다.

또한 핵심 코드는 맨 마지막 글에 업로드 해 놓을 것이니 참고 부탁드리겠습니다.