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

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

by 첨부엉. 2024. 6. 22.
반응형
위키북스 출판사 이재현 저자님의 '절대강좌! 유니티' 책을 참고하여 필기한 내용입니다.

 

사용자 정의 이벤트 - Delegate

주인공이 사망하면 모든적이 공격을 중지할 수 있게 foreac문을 이용하여 순차적으로 공격 중지 함수를 호출 

적 캐릭터가 많다면 SendMessage는 좋은 방법이 아님

 

순차적인 호출 방식을 이벤트 구동(Event Drivern) 방식으로 변경

: 특정한 조건을 만족하면 자동으로 알려주는 메세지를 의미 , 순차적으로 호출하는 방식보다 효율적임

 

델리게이트

함수를 참조하는 변수를 의미, 함수를 저장할 수 있는 일종의 변수 ( C++의 포인터)

아래의 코드는 예제이므로 작성하지 않아도 된다.

using UnityEngine;

public class DelegateDemo : MonoBehaviour
{
    // 델리게이트 선언
    delegate float SumHadler(float a, float b);
    // 델리게이트 타입의 변수 선언
    SumHadler sumHadler;
    
    // 덧셈 연산을 하는 함수
    float Sum(float a, float b)
    {
        return a + b;
    }

    void Start()
    {
        // 델리게이트 변수에 함수(메서드) 연결(할당)
        sumHadler = Sum;
        // 델리게이트 실행
        float sum = sumHadler(10.0f, 5.0f);
        // 결괏값 출력
        Debug.Log($"Sum = {sum}");
    }
}

 

델리게이트의 선언을 보면 float 타입이고 float 타입의 매개변수 두개를 갖는 함수로 지정하였다.

delegate float SumHadler(float a, float b);

해당 델리게이트 타입의 변수를 선언한다.

SumHadler sumHadler;

Start 함수에서 sumHandler에 Sum 함수를 저장함, 이때 sumHandler를 호출하면 Sum함수도 호출

 void Start()
 {
     // 델리게이트 변수에 함수(메서드) 연결(할당)
     sumHadler = Sum;
     // 델리게이트 실행
     float sum = sumHadler(10.0f, 5.0f);
     // 결괏값 출력
     Debug.Log($"Sum = {sum}");
 }

 

주인공의 사망 이벤트 처리

playerCtrl 스크립트를 아래와 같이 수정한다. 기존에 작성되어 있단 PlayerDie()함수는 주석처리하고 추가한다.

using System.Collections;
using UnityEngine;

public class PlayerCtrl : MonoBehaviour
{
    [SerializeField]
    private Transform tr;
    // 이동 속력 변수
    public float moveSpeed = 10.0f;
    // 회전 속도 변수
    public float turnSpeed = 80.0f;
    // Animation 컴포넌트를 저장할 변수
    private Animation anim;

    // 초기 생명 값
    private readonly float initHP = 100.0f;
    // 현재 생명 값
    public float currHp;

    // 델리게이트 선언 
    public delegate void PlayerDieHandler();
    // 이벤트 선언
    public static event PlayerDieHandler OnPlayerDie;

	...생략
  
    // Player 사망 처리
    void PlayerDie()
    {
        Debug.Log("Player Die !");

        //// MONSTER 태그를 가진 모든 게임오브젝트를 찾아옴
        //GameObject[] monsters = GameObject.FindGameObjectsWithTag("MONSTER");

        //// 모든 몬스터의 OnPlayerDie 함수를 순차적으로 호출
        //foreach(GameObject monster in monsters)
        //{
        //    monster.SendMessage("OnPlayerDie", SendMessageOptions.DontRequireReceiver);

        //}

        // 주인공 사망 이벤트 호출(발생)
        OnPlayerDie();
    }
}

 

이벤트를 정의 하려면 델리게이트를 이용해 이벤트 함수의 원형을 선언해야됨

이벤트 함수는 언제 호출될지 모르기 때문에 정적변수 선언해야됨 Static

// 델리게이트 선언 
public delegate void PlayerDieHandler();
// 이벤트 선언
public static event PlayerDieHandler OnPlayerDie;

 

OnPlayerDie는 이벤트 명이라고 하지만 변수의 일종이며 PlayerDieHandler는 OnPlayerDie 변수의 타입

 

MonsterCtrl 스크립트에 함수 추가한다.

// 스크립트가 활성화될 때마다 호출되는 함수   
private void OnEnable()
{
    // 이벤트 발생 시 수행할 함수 연결
    PlayerCtrl.OnPlayerDie += this.OnPlayerDie;
}

// 스크립트가 비활성화될 때마다 호출되는 함수
private void OnDisable()
{
    // 기존에 연결된 함수 해제
    PlayerCtrl.OnPlayerDie -= this.OnPlayerDie;

 

 

OnEnable() 과 OnDisable() 함수는 스크립트가 활성화되거나 비활성화될 때 수행되는 함수로 이벤트의 결과 해지는 반드시 이 함수에서 처리

 

- 이벤트 연결: (이벤트가 선언된 클래스명).(이벤트명) += (이벤트 발생 시 호출할 함수)

- 이벤트 해지 : (이벤트가 선언된 클래스명).(이벤트명) -= (이벤트 발생 시 호출할 함수)

 

몬스터의 사망 처리

몬스터가 죽는 사망 애니메이션을 완성하기

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 readonly int hashTrace      = Animator.StringToHash("IsTrace");
    private readonly int hashAttack     = Animator.StringToHash("IsAttack");
    private readonly int hashHit        = Animator.StringToHash("Hit");
    private readonly int hashPlayerDie  = Animator.StringToHash("PlayerDie");
    private readonly int hashSpeed      = Animator.StringToHash("Speed");
    private readonly int hashDie        = Animator.StringToHash("Die");


    // 혈흔 효과 프리팹
    private GameObject bloodEffect;

    // 몬스터 생명 변수
    private int hp = 100;

    // 스크립트가 활성화될 때마다 호출되는 함수   
    private void OnEnable()
    {
        ...생략
    }

    // 스크립트가 비활성화될 때마다 호출되는 함수
    private void OnDisable()
    {
        ...생략
    }

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

            // 몬스터의 상태가 DIE일 때 코루틴을 종료
            if (state == State.DIE) yield break;

            // 몬스터와 주인공 캐릭터 사이의 거리 측정
            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;
            }
        }
    }

    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:
                    isDie = true;
                    //추적 정지
                    agent.isStopped = true;
                    //사망 애니메이션 실행
                    anim.SetTrigger(hashDie);
                    // 몬스터의 Collider 컴포넌트 비활성화
                    GetComponent<CapsuleCollider>().enabled = false;
                    break;
            }
            yield return new WaitForSeconds(0.3f);
        }
    }

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

            //총알의 충돌 지점
            Vector3 pos = coll.GetContact(0).point;
            // 총알의 충돌 지점의 법선 벡터
            Quaternion rot = Quaternion.LookRotation(-coll.GetContact(0).normal);
            // 혈흔 효과를 생성하는 함수 호출
            ShowBloodEffect(pos, rot);

            // 몬스터의 hp 차감
            hp -= 10;
            if(hp <= 0)
            {
                state = State.DIE;
            }
        }
    }

    void ShowBloodEffect(Vector3 pos, Quaternion rot)
    {
        ...생략
    }

    void OnPlayerDie()
    {
        ...생략

    }

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

 

몬스터의 생명변수를 선언하고 초깃값은 100으로 설정

// 몬스터 생명 변수
private int hp = 100;

 

 

CheckMonsterState 코루틴 함수에서 몬스터의 상태가 DIE이면 상태를 체크하는 로직을 수행하지 않고 yield break를 사용해 종료

// 몬스터의 상태가 DIE일 때 코루틴을 종료
if (state == State.DIE) yield break;

 

 

Transition은 위와 같이 연결하고 속성도 설정해준다.

Root Transform Position

실행하면 몬스터가 공중에서 죽는 모습을 확인할 수 있다.

Monster 모델의 Animation에서 Root Transform Position(Y)의 Bake Into Pose를 체크한다.

 

반응형