본문 바로가기
Unity 강의/Unity Course(2) - 절대강좌! 유니티

[Unity Course 2] 06. 적 캐릭터 제작 3

by 첨부엉. 2024. 6. 21.
반응형
위키북스 출판사 이재현 저자님의 '절대강좌! 유니티' 책을 참고하여 필기한 내용입니다.
using System.Collections;
using UnityEngine;
using UnityEngine.AI; // 내비게이션 기능을 사용하기 위해 추가해야 하는 네임 스페이스
public class MonsterCtrl : MonoBehaviour
{

    // 몬스터 상태의 정보   
    public enum State
    {
        IDLE,
        TRACE,
        ATTACK,
        DIE
    }

    // 몬스터 현재 상태
    public State state = State.IDLE;
    // 추적 사정거리
    public float traceDist = 10.0f;
    // 공격 사정거리
    public float attackDist = 2.0f;
    // 몬스터의 사망 여부
    public bool isDie = false;

    // 컴포넌트의 캐시를 처리할 변수
    private Transform monsterTr;
    private Transform playerTr;
    private NavMeshAgent agent;


    private void Start()
    {
        // 몬스터의 Transform 할당
        monsterTr = GetComponent<Transform>();
        // 추적 대상인 Player의 Trasnform 할당
        playerTr = GameObject.FindWithTag("PLAYER").GetComponent<Transform>();

        // NavMEshAgent 컴포넌트 할당
        agent = GetComponent<NavMeshAgent>();

        // 추적 대상의 위치를 설정하면 바로 추척
        // agent.destination = playerTr.position;

        StartCoroutine(CheckMonsterState());
    }
    
    // 일정한 간격을 몬스터의 행동 상태를 체크
    IEnumerator CheckMonsterState()
    {
        while(!isDie)
        {
            // 0.3초 동안 대기하는 동안 제어권을 메세지 루프에 양보
            yield return new WaitForSeconds(0.3f);

            // 몬스터와 주인공 캐릭터 사이의 거리 측정
            float distance = Vector3.Distance(playerTr.position, monsterTr.position);

            // 공격 사정거리 범위로 들어왔는지 확인
            if (distance <= attackDist)
            {
                state = State.ATTACK;
            }

            //추적 사정거리 범위로 들어왔는지 확인
            else if (distance <= traceDist)
            {
                state = State.TRACE;
            }
            else{
                state = State.IDLE;
            }
        }
    }

    private void OnDrawGizmos()
    {
        // 추적 사정거리 표시
        if(state == State.TRACE){
            Gizmos.color = Color.blue;
            Gizmos.DrawWireSphere(transform.position, traceDist);
        }
        // 공격 사정거리 표시
        if (state == State.ATTACK){
            Gizmos.color = Color.red;
            Gizmos.DrawWireSphere(transform.position, attackDist);
        }
    }
}

 

Enum 열거형 변수로 설정하면 인스펙터 뷰에 콤보 박스 형태로 표시됨 int, String 타입의 변수보다 가독성이 좋음

// 몬스터 상태의 정보 
public enum State
{
    IDLE,
    TRACE,
    ATTACK,
    DIE
}

 

Update 함수에서 코드를 작성해도 제대로 작동되지만 적정한 주기로 상태를 검사하는 방법이 더 효율적

 

Vector3.Distance함수를 사용하여 거리를 측정한다.

// 몬스터와 주인공 캐릭터 사이의 거리 측정
float distance = Vector3.Distance(playerTr.position, monsterTr.position);

 

주인공 캐릭터가 접근하여 몬스터의 상태가 변경되는 것을 시각적으로 표현

private void OnDrawGizmos()
{
    // 추적 사정거리 표시
    if(state == State.TRACE){
        Gizmos.color = Color.blue;
        Gizmos.DrawWireSphere(transform.position, traceDist);
    }
    // 공격 사정거리 표시
    if (state == State.ATTACK){
        Gizmos.color = Color.red;
        Gizmos.DrawWireSphere(transform.position, attackDist);
    }
}

 

 

플레이어와의 사이의 거리에 따라 몬스터 상태가 변하는 것을 확인 할 수 있다.

 

MonsterAction 코루틴 함수 추가하고 Start 함수에서 해당 코루틴 시

using System.Collections;
using UnityEngine;
using UnityEngine.AI; // 내비게이션 기능을 사용하기 위해 추가해야 하는 네임 스페이스
public class MonsterCtrl : MonoBehaviour
{

    // 몬스터 상태의 정보   
    public enum State
    {
        IDLE,
        TRACE,
        ATTACK,
        DIE
    }

    // 몬스터 현재 상태
    public State state = State.IDLE;
    // 추적 사정거리
    public float traceDist = 10.0f;
    // 공격 사정거리
    public float attackDist = 2.0f;
    // 몬스터의 사망 여부
    public bool isDie = false;

    // 컴포넌트의 캐시를 처리할 변수
    private Transform monsterTr;
    private Transform playerTr;
    private NavMeshAgent agent;


    private void Start()
    {
        // 몬스터의 Transform 할당
        monsterTr = GetComponent<Transform>();
        // 추적 대상인 Player의 Trasnform 할당
        playerTr = GameObject.FindWithTag("PLAYER").GetComponent<Transform>();

        // NavMEshAgent 컴포넌트 할당
        agent = GetComponent<NavMeshAgent>();

        // 추적 대상의 위치를 설정하면 바로 추척
        // agent.destination = playerTr.position;

        // 몬스터의 상태를 체크하는 코루틴 함수 호출
        StartCoroutine(CheckMonsterState());
        // 상태에 따라 몬스터의 행동을 수행하는 코루틴 함수 호출
        StartCoroutine(MonstorAction());
    }
    
    // 일정한 간격을 몬스터의 행동 상태를 체크
    IEnumerator CheckMonsterState()
    {
        ... 생략
    }

    IEnumerator MonstorAction()
    {
        while(!isDie)
        {
            switch(state){

                // Idle 상태
                case State.IDLE:
                    // 추적 중지
                    agent.isStopped = true;
                    break;

                // 추적 상태
                case State.TRACE:
                    // 추적 대상의 좌표로 이동 시작
                    agent.SetDestination(playerTr.position);
                    agent.isStopped = false;
                    break;

                // 공격 상태
                case State.ATTACK:
                    break;

                // 사망
                case State.DIE:
                    break;
            }
            yield return new WaitForSeconds(0.3f);
        }
    }

    private void OnDrawGizmos()
    {
       ...생략
    }
}

 

추적 중지 여부를 나타내는 것으로 true이면 정지 상태를 의미

false 는 NavMeshAgent를 재시작하는 역할을 함

agent.isStopped = false;
agent.isStopped = true;

 

추적 사정거리 이내에 위치하면 추적하고

멀어지면 추적하지 않는 모습을 확인할 수 있음

 

애니메이션 동기화

using System.Collections;
using UnityEngine;
using UnityEngine.AI; // 내비게이션 기능을 사용하기 위해 추가해야 하는 네임 스페이스
public class MonsterCtrl : MonoBehaviour
{

    // 몬스터 상태의 정보   
    public enum State
    {
        IDLE,
        TRACE,
        ATTACK,
        DIE
    }

    // 몬스터 현재 상태
    public State state = State.IDLE;
    // 추적 사정거리
    public float traceDist = 10.0f;
    // 공격 사정거리
    public float attackDist = 2.0f;
    // 몬스터의 사망 여부
    public bool isDie = false;

    // 컴포넌트의 캐시를 처리할 변수
    private Transform monsterTr;
    private Transform playerTr;
    private NavMeshAgent agent;
    private Animator anim;


    private void Start()
    {
        // 몬스터의 Transform 할당
        monsterTr = GetComponent<Transform>();
        // 추적 대상인 Player의 Trasnform 할당
        playerTr = GameObject.FindWithTag("PLAYER").GetComponent<Transform>();

        // NavMEshAgent 컴포넌트 할당
        agent = GetComponent<NavMeshAgent>();

        // Animator 컴포넌트 할당
        anim = GetComponent<Animator>();

        // 몬스터의 상태를 체크하는 코루틴 함수 호출
        StartCoroutine(CheckMonsterState());
        // 상태에 따라 몬스터의 행동을 수행하는 코루틴 함수 호출
        StartCoroutine(MonstorAction());
    }
    
    // 일정한 간격을 몬스터의 행동 상태를 체크
    IEnumerator CheckMonsterState()
    {
        ...생략
    }

    IEnumerator MonstorAction()
    {
        while(!isDie)
        {
            switch(state){

                // Idle 상태
                case State.IDLE:
                    // 추적 중지
                    agent.isStopped = true;
                    // Animator의 IsTrace 변수를 false로 설정
                    anim.SetBool("IsTrace", false);
                    break;

                // 추적 상태
                case State.TRACE:
                    // 추적 대상의 좌표로 이동 시작
                    agent.SetDestination(playerTr.position);
                    agent.isStopped = false;
                    // Animator의 IsTrace 변수를 true로 설정
                    anim.SetBool("IsTrace", true);
                    break;

                // 공격 상태
                case State.ATTACK:
                    break;

                // 사망
                case State.DIE:
                    break;
            }
            yield return new WaitForSeconds(0.3f);
        }
    }

    private void OnDrawGizmos()
    {
        ...생략
    }
}

 

Set 함수  Get 함수
Animator.SetBool Animator.GetBool
Animator.SetFloat Animator.GetFloat
Animator.SetInteger Animator.GetInteger
Animator.SetTrigger Animator.GetTrigger

 

몬스터 공격 루틴

몬스터의 Attack 애니메이션을 추가하고 

Make Transition을 양방향으로 추가한 후 각 Transition을 아래와 같이 설정

using System.Collections;
using UnityEngine;
using UnityEngine.AI; // 내비게이션 기능을 사용하기 위해 추가해야 하는 네임 스페이스
public class MonsterCtrl : MonoBehaviour
{

    ...생략

    private readonly int hashTrace  = Animator.StringToHash("IsTrace");
    private readonly int hashAttack = Animator.StringToHash("IsAttack");


    private void Start()
    {
        ...생략
    }
    
    // 일정한 간격을 몬스터의 행동 상태를 체크
    IEnumerator CheckMonsterState()
    {
        ...생략
    }

    IEnumerator MonstorAction()
    {
        while(!isDie)
        {
            switch(state){

                // Idle 상태
                case State.IDLE:
                    // 추적 중지
                    agent.isStopped = true;
                    // Animator의 IsTrace 변수를 false로 설정
                    anim.SetBool(hashTrace, false);
                    break;

                // 추적 상태
                case State.TRACE:
                    // 추적 대상의 좌표로 이동 시작
                    agent.SetDestination(playerTr.position);
                    agent.isStopped = false;
                    // Animator의 IsTrace 변수를 true로 설정
                    anim.SetBool(hashTrace, true);
                    // Animator의 IsAttack 변수를 false로 설정
                    anim.SetBool(hashAttack, false);
                    break;

                // 공격 상태
                case State.ATTACK:
                    // Animator의 IsAttack 변수를 true로 설정
                    anim.SetBool(hashAttack, true);
                    break;

                // 사망
                case State.DIE:
                    break;
            }
            yield return new WaitForSeconds(0.3f);
        }
    }

    private void OnDrawGizmos()
    {
        ...생략
    }
}

 

애니메이터 뷰에서 정의한 파라미터에 접근하는 방식을 변경

앞서 전달했던 파라미터는 문자열

anim.SetBool("IsTrace",false);

 

해시값을 미리 추출해 속도면에서 빨라짐

 private readonly int hashTrace  = Animator.StringToHash("IsTrace");
 private readonly int hashAttack = Animator.StringToHash("IsAttack");

 

몬스터 피격 리액션

주인공이 캐릭터가 발사한 총에 맞았을 때 타격을 입는 리액션을 구현

몬스터 오브젝트에 Capsule Collider 컴포넌트를 추가하고 위와 같이 속성을 변경한다

goHit애니메이션을 추가하고 Hit라는 이름의 Trigger 파라미터를 추가

 

각 Transition 은 아래와 같이 설

using System.Collections;
using UnityEngine;
using UnityEngine.AI; // 내비게이션 기능을 사용하기 위해 추가해야 하는 네임 스페이스
public class MonsterCtrl : MonoBehaviour
{
	...생략

    private readonly int hashTrace  = Animator.StringToHash("IsTrace");
    private readonly int hashAttack = Animator.StringToHash("IsAttack");
    private readonly int hashHit    = Animator.StringToHash("Hit");



    private void Start()
    {
        ...생략
    }
    
    // 일정한 간격을 몬스터의 행동 상태를 체크
    IEnumerator CheckMonsterState()
    {
        ...생략
    }

    IEnumerator MonstorAction()
    {
        ...생략
    }

    private void OnCollisionEnter(Collision coll)
    {
        if(coll.collider.CompareTag("BULLET"))
        {
            // 충돌한 총알을 삭제
            Destroy(coll.gameObject);
            // 피격 애니메이션 실행
            anim.SetTrigger(hashHit);
        }
    }

    private void OnDrawGizmos()
    {
        ..생략
    }
}

 

 

반응형