EasyCastleUNITY

SpaceShooter 몬스터 피격 및, 몬스터의 공격 본문

유니티 심화

SpaceShooter 몬스터 피격 및, 몬스터의 공격

EasyCastleT 2023. 8. 24. 13:08

몬스터 피격

피격 포인트에서 생성되는 normal
사용한 코드

normal을 기준으로 피격 이펙트(피)를 생성해야 하는데, 

이렇게 뒤집어져 있으면, 피가 뒤로 뿜어져 나오는것 처럼 보이게 된다. 

따라서 -normal을 이용

무엇인가 이상함 -> 총알이 애초에 사선으로 발사 되어서 생기는 문제

 

Resources를 통해 동적으로 할당

https://docs.unity3d.com/ScriptReference/Resources.Load.html

 

Unity - Scripting API: Resources.Load

This method returns the asset at path if it can be found, otherwise it returns null. Note that the path is case insensitive and must not contain a file extension. All asset names and paths in Unity use forward slashes, so using backslashes in the path will

docs.unity3d.com

반드시 Asset이 Resources 라는 폴더에 있어야 동작이 가능한 코드 이다.

(Resources는 예약 폴더이기에 이름이 바뀌면 안됨)

Resoures.Load 주의 할 점: 동적로드 할 때 중복로드 되지 않게 하자

예를들어서 총알을 발사 해야 하는 발사 하는 메서드에서 리소스를 로드후 프리팹 인스턴스화 하면 안된다

 

총알 피격 결과

몬스터 공격

몬스터의 왼쪽 팔

Rigidbody의 isKinematic을 체크하면, 운동역학으로 설정된다.

운동역학이란, 물리 엔진의 시뮬레이션 연산으로 이동 및 회전하는 것이 아니라 

스크립트 또는 애니메이션에 의해 Transform 컴포넌트의 속성값을 변화시켜 이동 및 회전하는 것을 말한다. 

Kinematic 움직임

따라서 따로 설정하지 않아도 애니메이션에 따라, 콜라이더가 움직이며 충돌감지를 실행하여 플레이어를 공격한다. 

플레이어의 리지드 바디

Freeze Rotation을 통해 이동할 때 넘어지지 않도록 설정,

이걸 설정해서, 폭발에 휘말려도 날아가지 않는다.

 

 

 

플레이어의 리지드바디

 

 

 

 

 

캐릭터 컨트롤러 컴포넌트

캐릭터 컨트롤러 컴포넌트는,

Capsule Collider와 Rigidbody 컴포넌트의 역할을 하며

벽에 부딫혔을 때,

떨림 현상(Jittring)이나 외부의 힘으로 밀리는 현상이 발생하지 않는다. 

 

 

 

 

 

 

PlayerController

using System;
using System.Collections;
using UnityEngine;
using static PlayerControl;

public class PlayerController : MonoBehaviour
{

    public enum eAnimState
    {
        Idle, RunB, RunF, RunL, RunR
    }

    [SerializeField]
    private float moveSpeed = 2.0f;
    [SerializeField]
    private float turnSpeed = 80f;

    //카메라 정보
    private FollowCam followCam;
    private float distance;
    private float height;

    private Animation anim;

    //----------------------------------------
    //초기 생명 값
    private readonly float initHp = 100.0f;
    //현재 생명 값
    [SerializeField] private float hp;
    //----------------------------------------
    private IEnumerator Start()
    {
        this.hp = this.initHp;
        this.followCam = GameObject.FindAnyObjectByType<FollowCam>();
        this.anim = this.GetComponent<Animation>();
        //실행하자마자 바로 돌아가는 것 방지 
        this.turnSpeed = 0.0f;
        yield return new WaitForSeconds(0.3f);
        this.turnSpeed = 80.0f;
    }

    private void Update()
    {
        this.distance = this.followCam.Distance;
        this.height = this.followCam.Height;    
        float h = Input.GetAxis("Horizontal");
        float v = Input.GetAxis("Vertical");
        float r = Input.GetAxis("Mouse X"); //왼쪽으로 마우스 움직이면, -, 오른쪽으로 마우스 움직이면 + 
        Vector3 moveDir = (Vector3.forward * v) + (Vector3.right * h);
        //Debug.Log(moveDir); 
        DrawArrow.ForDebug(this.transform.position, moveDir.normalized, 0f,Color.red);
        this.transform.Translate(moveDir.normalized * this.moveSpeed * Time.deltaTime);
        //마우스 왼쪽으로 움직이면, 왼쪽으로 회전, 마우스 오른쪽으로 움직이면, 오른쪽으로 회전
        Vector3 angle = Vector3.up * turnSpeed * Time.deltaTime * r;
        this.transform.Rotate(angle); //set이 아니라 원래 rotation에서 더하는 방식으로 됨

        this.PlayAnimation(moveDir);
    }

    private void OnDrawGizmos()
    {
        Gizmos.color = Color.white;
        GizmosExtensions.DrawWireArc(this.transform.position, this.transform.forward, 360, 1);
        DrawArrow.ForDebug(this.transform.position,this.transform.forward,0f,Color.green); //플레이어 앞에 길이 1의 화살표 그림
        DrawArrow.ForDebug(this.transform.position, -1 * this.transform.forward * distance, 0f, Color.yellow); //플레이어 뒤에 길이 3의 화살표 그림 
        Vector3 pos = this.transform.position + (-1 * this.transform.forward *distance);
        DrawArrow.ForDebug(pos, Vector3.up * height, 0f, Color.green);
    }

    private void PlayAnimation(Vector3 dir)
    {
        if (dir.x > 0)
        {
            //right
            this.anim.CrossFade(eAnimState.RunR.ToString(), 0.25f);
        }
        else if (dir.x < 0)
        {
            //left
            this.anim.CrossFade(eAnimState.RunL.ToString(), 0.25f);
        }
        else if (dir.z > 0)
        {
            //forward
            this.anim.CrossFade(eAnimState.RunF.ToString(), 0.25f);

        }
        else if (dir.z < 0)
        {
            //back
            this.anim.CrossFade(eAnimState.RunB.ToString(), 0.25f);
        }
        else
        {
            //Idle
            this.anim.CrossFade(eAnimState.Idle.ToString(), 0.25f);
        }
    }

    private void OnTriggerEnter(Collider other)
    {
        if(this.hp > 0.0f && other.CompareTag("Punch"))
        {
            this.hp -= 10.0f;
            Debug.LogFormat("<color=yellow>Player Hp: ({0} / {1})</color>", this.hp, this.initHp);
            //Debug.Log($"Player Hp = {this.hp / initHp}");

            //Player의 생명이 0이하면 사망처리
            if (this.hp <= 0)
            {
                this.PlayerDie();
            }
        }
    }

    private void PlayerDie()
    {
        Debug.Log("PlayerDie!!!");
    }
}

MonsterController

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

using UnityEngine.AI; //네비게이션 기능을 사용하기 위해 추가해야하는 namespace

public class MonsterController : MonoBehaviour
{
    //몬스터의 상태 정보
    public enum eState
    {
        IDLE,TRACE,ATTACK,DIE
    }
    //몬스터의 현재 상태
    [SerializeField] private eState state = eState.IDLE;
    //추적 사정거리
    [SerializeField] private float traceDist = 10.0f;
    //공격 사정거리
    [SerializeField] private float attackDist = 2.0f;
    //몬스터의 사망여부
    [SerializeField] private bool isDie = false;
    private Transform playerTrans;
    private NavMeshAgent agent;
    private Animator anim;

    //Animator 파라미터의 해시값 추출
    private readonly int hashTrace = Animator.StringToHash("IsTrace");
    private readonly int hashAttack = Animator.StringToHash("IsAttack");
    private readonly int hashHit = Animator.StringToHash("Hit");

    //혈흔 효과 프리팹
    private GameObject bloodEffectPrefab;
    // Start is called before the first frame update
    void Start()
    {
        //추적대상인 Player의 Transform을 할당
        this.playerTrans = GameObject.FindWithTag("Player").GetComponent<Transform>();
        //NavMeshAgent 컴포넌트 할당
        this.agent=this.GetComponent<NavMeshAgent>();
        //추적 대상의 위치를 설정하면 바로 추적 시작
        //agent.destination = this.playerTrans.position;
        //agent.SetDestination(this.playerTrans.position); //두개가 같은 기능
        this.anim = this.GetComponent<Animator>();
        //새로운 할당 방법 Resources, 동적으로 asset을 load
        this.bloodEffectPrefab = Resources.Load<GameObject>("BloodSprayEffect");
        //몬스터의 상태를 체크하는 코루틴 함수 호출
        this.StartCoroutine(CoCheckMonsterState());
        //상태에 따라 몬스터의 행동을 수행하는 코루틴 함수 호출
        this.StartCoroutine(CoMonsterAction());
    }
    //일정한 간격으로 몬스터의 행동 상태를 체크
    private IEnumerator CoCheckMonsterState()
    {
        while (!isDie) //몬스터가 안 죽었다면 반복
        {
            //0.3초 동안 대기하고 다음 프레임으로 넘어감
            yield return new WaitForSeconds(0.3f);
            //플레이어와 몬스터 캐릭터 사이의 거리를 측정
            float distance = Vector3.Distance(this.playerTrans.position, this.transform.position);

            //공격 사거리 범위로 들어왔는지 확인
            if(distance <= attackDist)
            {
                this.state = eState.ATTACK;
            }
            //추적 사거리 범위로 들어왔는지 확인
            else if(distance <= traceDist)
            {
                this.state = eState.TRACE;
            }
            else
            {
                this.state = eState.IDLE;
            }
        }
    }

    //몬스터의 상태에 따라 동작을 수행
    private IEnumerator CoMonsterAction()
    {
        while (!isDie)
        {
            switch (state)
            {
                //IDLE 상태
                case eState.IDLE:
                    //추적중지
                    agent.isStopped = true;
                    //Animator의 IsTrace 변수를 false로 설정
                    this.anim.SetBool(this.hashTrace,false);
                    break;
                //추적 상태
                case eState.TRACE:
                    //추적 대상의 좌표를 이동 시작
                    this.agent.SetDestination(this.playerTrans.position);
                    this.agent.isStopped = false;
                    //Animator의 IsTrace 변수를 true로 설정
                    this.anim.SetBool(this.hashTrace, true);
                    //Animator의 IsAttack 변수를 false로 설정
                    this.anim.SetBool(this.hashAttack,false);
                    break;
                //공격 상태
                case eState.ATTACK:
                    //Animator의 IsAttack 변수를 true로 설정
                    this.anim.SetBool(hashAttack,true);
                    break;
                //사망
                case eState.DIE:
                    break;
            }
            yield return new WaitForSeconds(0.3f);
        }
    }

    private void OnCollisionEnter(Collision collision)
    {
        if (collision.collider.CompareTag("Bullet"))
        {
            //충돌된 총알을 삭제
            Destroy(collision.gameObject);
            //피격 애니메이션을 실행
            this.anim.SetTrigger(this.hashHit);
            ContactPoint cp = collision.GetContact(0);
            //충돌지점
            Vector3 hitPoint = collision.GetContact(0).point;
            Debug.LogError("!"); //error pause 용도 
            //법선벡터
            DrawArrow.ForDebug(hitPoint, -cp.normal, 5f,Color.green, ArrowType.Solid);
            //회전 값 생성
            Vector3 dir = -cp.normal;
            Quaternion rot = Quaternion.LookRotation(dir);
            //혈흔 효과를 생성하는 함수 호출
            ShowBloodEffect(hitPoint, rot); 
        }
    }
    //혈흔 효과
    private void ShowBloodEffect(Vector3 pos, Quaternion rot)
    {
        //혈흔 생성
        GameObject blood = Instantiate<GameObject>(bloodEffectPrefab, pos, rot, this.transform); //해당 transform의 자식 오브젝트로 복제함
        Destroy(blood, 1.0f);
    }
    private void OnDrawGizmos()
    {
        //추적 사정거리 표시
        if(state == eState.TRACE)
        {
            Gizmos.color = Color.blue;
            Gizmos.DrawWireSphere(this.transform.position,traceDist);
        }
        //공격 사정거리 표시
        if(state == eState.ATTACK)
        {
            Gizmos.color = Color.red;
            Gizmos.DrawWireSphere(this.transform.position, attackDist);
        }
    }
}

 

플레이어를 몬스터가 공격 시연 영상

이러한 방법은 구현하기 쉽다는 장점이 있지만, 그 이후에 고려해야 될 점이 많다는 단점도 가지고 있다. 

먼저 몬스터가 공격이 가능한 공격 사거리를 잘 고려를 해야한다. 

제대로 고려하지 않는경우, 공격을 하기도 전에 접근한 것 만으로,

플레이어의 콜라이더와 충돌을 일으켜 공격을 당하지도 않았는데 플레이어는 공격을 당하는 상태가 될 가능성도 있다. 

 

또한 플레이어가 이동을 하다가 몬스터와 부딫치는 경우에도 공격을 당한 것은 아니지만,

충돌 감지가 발생하여 공격을 당한다고 인식할 수 도 있다. 

 

또한 몬스터가 피격을 인식하는 콜라이더와, 몬스터 팔에 부착되어 있는 콜라이더는 필연적으로 겹칠 수 밖에 없다. 

그래서 설정이 제대로 되어 있지 않으면,

몬스터 자체에 충돌처리가 지속적으로 발생하여, 몬스터의 동작이 이상해질 수도 있다.

 

따라서 이러한 방법은 여러가지를 고려를 하고 만드는 것이 좋다. 

 

위에서 언급한 문제중 하나인 몬스터 팔과 몬스터 몸체의 콜라이더 충돌은 레이어를 활용하여 해결할 수 있다. 

몸체에는 Moster_Body라는 레이어를 

팔에는 Monster_Punch라는 레이어를 추가하고 설정하였다. 

이러한 레이어를 가지게 된 몸체와 팔들은 Edit -> Project Settings -> Physics에서 충돌을 계산 안하도록 설정할 수 있다.

Layer Collision Matrix (충돌 매트릭스 옵션) 

프로젝트 세팅 창, body와 punch는 서로 물리적인 충돌이 처리되지 않는다.

대리자활용 event, 대리자를 event로 만드는 방법 

이벤트에 대한 간단한 설명

https://learn.microsoft.com/ko-kr/dotnet/csharp/language-reference/keywords/event

 

event - C# 참조

event(C# 참조) 아티클 04/08/2023 기여자 16명 피드백 이 문서의 내용 --> event 키워드는 게시자 클래스에서 이벤트를 선언하는 데 사용됩니다. 예제 다음 예제에서는 EventHandler를 기본 대리자 형식으로

learn.microsoft.com

대리자를 결합하는 방법 (멀티캐스트 대리자)

https://learn.microsoft.com/ko-kr/dotnet/csharp/programming-guide/delegates/how-to-combine-delegates-multicast-delegates

 

대리자를 결합하는 방법(멀티캐스트 대리자) - C# 프로그래밍 가이드

대리자를 결합하여 멀티캐스트 대리자를 만드는 방법을 알아봅니다. 코드 예제를 살펴보고 사용 가능한 추가 리소스를 확인합니다.

learn.microsoft.com

이벤트하고 연결되었다고 하는 것을 '이벤트와 구독 되었다' 라고 말하며, + 연산자를 통해 이루어진다. 

 

이 프로젝트에서는 플레이어가 죽었다는 이벤트를 몬스터가 구독을 한다. 

이벤트 선언
이벤트 호출
몬스터가 플레이어 이벤트를 구독

이러한 번개 모양은 모두 이벤트를 의미한다. 

몬스터가 플레이어 이벤트 구독 취소

몬스터 죽음

몬스터의 애니메이터
몬스터 사망 시연 영상

PlayerController

using System;
using System.Collections;
using UnityEngine;
using static PlayerControl;

public class PlayerController : MonoBehaviour
{

    public enum eAnimState
    {
        Idle, RunB, RunF, RunL, RunR
    }

    [SerializeField]
    private float moveSpeed = 2.0f;
    [SerializeField]
    private float turnSpeed = 80f;

    //카메라 정보
    private FollowCam followCam;
    private float distance;
    private float height;
    //애니메이터
    private Animation anim;

    //델리게이트 선언
    public delegate void PlayerDieHandler(); //타입 정의
    //event 키워드
    public static event PlayerDieHandler OnPlayerDie; 

    //----------------------------------------
    //초기 생명 값
    private readonly float initHp = 100.0f;
    //현재 생명 값
    [SerializeField] private float hp;
    //----------------------------------------
    private IEnumerator Start()
    {
        this.hp = this.initHp;
        this.followCam = GameObject.FindAnyObjectByType<FollowCam>();
        this.anim = this.GetComponent<Animation>();
        //실행하자마자 바로 돌아가는 것 방지 
        this.turnSpeed = 0.0f;
        yield return new WaitForSeconds(0.3f);
        this.turnSpeed = 80.0f;
    }

    private void Update()
    {
        this.distance = this.followCam.Distance;
        this.height = this.followCam.Height;    
        float h = Input.GetAxis("Horizontal");
        float v = Input.GetAxis("Vertical");
        float r = Input.GetAxis("Mouse X"); //왼쪽으로 마우스 움직이면, -, 오른쪽으로 마우스 움직이면 + 
        Vector3 moveDir = (Vector3.forward * v) + (Vector3.right * h);
        //Debug.Log(moveDir); 
        DrawArrow.ForDebug(this.transform.position, moveDir.normalized, 0f,Color.red);
        this.transform.Translate(moveDir.normalized * this.moveSpeed * Time.deltaTime);
        //마우스 왼쪽으로 움직이면, 왼쪽으로 회전, 마우스 오른쪽으로 움직이면, 오른쪽으로 회전
        Vector3 angle = Vector3.up * turnSpeed * Time.deltaTime * r;
        this.transform.Rotate(angle); //set이 아니라 원래 rotation에서 더하는 방식으로 됨

        this.PlayAnimation(moveDir);
    }

    private void OnDrawGizmos()
    {
        Gizmos.color = Color.white;
        GizmosExtensions.DrawWireArc(this.transform.position, this.transform.forward, 360, 1);
        DrawArrow.ForDebug(this.transform.position,this.transform.forward,0f,Color.green); //플레이어 앞에 길이 1의 화살표 그림
        DrawArrow.ForDebug(this.transform.position, -1 * this.transform.forward * distance, 0f, Color.yellow); //플레이어 뒤에 길이 3의 화살표 그림 
        Vector3 pos = this.transform.position + (-1 * this.transform.forward *distance);
        DrawArrow.ForDebug(pos, Vector3.up * height, 0f, Color.green);
    }

    private void PlayAnimation(Vector3 dir)
    {
        if (dir.x > 0)
        {
            //right
            this.anim.CrossFade(eAnimState.RunR.ToString(), 0.25f);
        }
        else if (dir.x < 0)
        {
            //left
            this.anim.CrossFade(eAnimState.RunL.ToString(), 0.25f);
        }
        else if (dir.z > 0)
        {
            //forward
            this.anim.CrossFade(eAnimState.RunF.ToString(), 0.25f);

        }
        else if (dir.z < 0)
        {
            //back
            this.anim.CrossFade(eAnimState.RunB.ToString(), 0.25f);
        }
        else
        {
            //Idle
            this.anim.CrossFade(eAnimState.Idle.ToString(), 0.25f);
        }
    }

    private void OnTriggerEnter(Collider other)
    {
        if(this.hp > 0.0f && other.CompareTag("Punch"))
        {
            this.hp -= 10.0f;
            Debug.LogFormat("<color=yellow>Player Hp: ({0} / {1})</color>", this.hp, this.initHp);
            //Debug.Log($"Player Hp = {this.hp / initHp}");

            //Player의 생명이 0이하면 사망처리
            if (this.hp <= 0)
            {
                this.PlayerDie();
            }
        }
    }

    private void PlayerDie()
    {
        Debug.Log("PlayerDie!!!");

        ////Monster 태그를 가진 모든 게임오브젝트를 찾아옴
        //GameObject[] arrMonsterGo = GameObject.FindGameObjectsWithTag("Monster");
        ////모든 몬스터의 OnPlayerDie 함수를 순차적으로 호출, 이름을 통해 Monster의 OnPlayerDie 함수를 호출
        //foreach (GameObject go in arrMonsterGo)
        //{
        //    go.SendMessage("OnPlayerDie", SendMessageOptions.DontRequireReceiver); //잘 사용되지 않는 메서드, 
        //}
        //이벤트를 구독 중인 모든 애들에게 알림
        OnPlayerDie(); //알림 (notification), 대리자 호출, 이벤트 호출
    }
}

MonsterController

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

using UnityEngine.AI; //네비게이션 기능을 사용하기 위해 추가해야하는 namespace

public class MonsterController : MonoBehaviour
{
    //몬스터의 상태 정보
    public enum eState
    {
        IDLE,TRACE,ATTACK,DIE
    }
    //몬스터의 현재 상태
    [SerializeField] private eState state = eState.IDLE;
    //추적 사정거리
    [SerializeField] private float traceDist = 10.0f;
    //공격 사정거리
    [SerializeField] private float attackDist = 2.0f;
    //몬스터의 사망여부
    [SerializeField] private bool isDie = false;
    private Transform playerTrans;
    private NavMeshAgent agent;
    private Animator anim;

    //Animator 파라미터의 해시값 추출
    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 bloodEffectPrefab;
    //--------------------------------------------------
    private int hp = 100;
    //--------------------------------------------------
    private void OnEnable() //Start보다 먼저 호출됨 ,unity 파이프라인 보기, 스크립트가 활성화 될 때마다 호출되는 함수
    {
        //구독
        PlayerController.OnPlayerDie += OnPlayerDie; //람다로도 가능
    }
    //까먹고 구독취소를 안하면?
    //이벤트가 쌓임 OnPlayerDie가 여러번 호출 될 수 있음
    private void OnDisable() //스크립트가 비활성화 될 때마다 호출되는 함수
    {
        //구독 취소
        PlayerController.OnPlayerDie -= OnPlayerDie;
    }
    // Start is called before the first frame update
    void Start()
    {
        //추적대상인 Player의 Transform을 할당
        this.playerTrans = GameObject.FindWithTag("Player").GetComponent<Transform>();
        //NavMeshAgent 컴포넌트 할당
        this.agent=this.GetComponent<NavMeshAgent>();
        //추적 대상의 위치를 설정하면 바로 추적 시작
        //agent.destination = this.playerTrans.position;
        //agent.SetDestination(this.playerTrans.position); //두개가 같은 기능
        this.anim = this.GetComponent<Animator>();
        //새로운 할당 방법 Resources, 동적으로 asset을 load
        this.bloodEffectPrefab = Resources.Load<GameObject>("BloodSprayEffect");
        //몬스터의 상태를 체크하는 코루틴 함수 호출
        this.StartCoroutine(CoCheckMonsterState());
        //상태에 따라 몬스터의 행동을 수행하는 코루틴 함수 호출
        this.StartCoroutine(CoMonsterAction());
    }
    //일정한 간격으로 몬스터의 행동 상태를 체크
    private IEnumerator CoCheckMonsterState()
    {
        while (!isDie) //몬스터가 안 죽었다면 반복
        {
            //0.3초 동안 대기하고 다음 프레임으로 넘어감
            yield return new WaitForSeconds(0.3f);

            //몬스터의 상태가 DIE 일때, 코루틴을 종료
            if (state == eState.DIE) yield break;
            //플레이어와 몬스터 캐릭터 사이의 거리를 측정
            float distance = Vector3.Distance(this.playerTrans.position, this.transform.position);

            //공격 사거리 범위로 들어왔는지 확인
            if(distance <= attackDist)
            {
                this.state = eState.ATTACK;
            }
            //추적 사거리 범위로 들어왔는지 확인
            else if(distance <= traceDist)
            {
                this.state = eState.TRACE;
            }
            else
            {
                this.state = eState.IDLE;
            }
        }
    }

    //몬스터의 상태에 따라 동작을 수행
    private IEnumerator CoMonsterAction()
    {
        while (!isDie)
        {
            switch (state)
            {
                //IDLE 상태
                case eState.IDLE:
                    //추적중지
                    agent.isStopped = true;
                    //Animator의 IsTrace 변수를 false로 설정
                    this.anim.SetBool(this.hashTrace,false);
                    break;
                //추적 상태
                case eState.TRACE:
                    //추적 대상의 좌표를 이동 시작
                    this.agent.SetDestination(this.playerTrans.position);
                    this.agent.isStopped = false;
                    //Animator의 IsTrace 변수를 true로 설정
                    this.anim.SetBool(this.hashTrace, true);
                    //Animator의 IsAttack 변수를 false로 설정
                    this.anim.SetBool(this.hashAttack,false);
                    break;
                //공격 상태
                case eState.ATTACK:
                    //Animator의 IsAttack 변수를 true로 설정
                    this.anim.SetBool(hashAttack,true);
                    break;
                //사망
                case eState.DIE:
                    isDie = true;
                    //추적 정지
                    this.agent.isStopped = true;
                    //사망 애니메이션 실행
                    this.anim.SetTrigger(hashDie);
                    //몬스터의 콜라이더 비활성화
                    this.GetComponent<CapsuleCollider>().enabled = false;
                    break;
            }
            yield return new WaitForSeconds(0.3f);
        }
    }

    private void OnCollisionEnter(Collision collision)
    {
        if (collision.collider.CompareTag("Bullet"))
        {
            //충돌된 총알을 삭제
            Destroy(collision.gameObject);
            //피격 애니메이션을 실행
            this.anim.SetTrigger(this.hashHit);
            ContactPoint cp = collision.GetContact(0);
            //충돌지점
            Vector3 hitPoint = collision.GetContact(0).point;
            //Debug.LogError("!"); //error pause 용도 
            //법선벡터
            DrawArrow.ForDebug(hitPoint, -cp.normal, 5f,Color.green, ArrowType.Solid);
            //회전 값 생성
            Vector3 dir = -cp.normal;
            Quaternion rot = Quaternion.LookRotation(dir);
            //혈흔 효과를 생성하는 함수 호출
            ShowBloodEffect(hitPoint, rot);

            //몬스터의 hp 차감
            this.hp -= 10;
            if(this.hp <= 0)
            {
                state = eState.DIE;
                Debug.Log("<color=red>Monster Die!!!</color>");
            }

        }
    }
    //혈흔 효과
    private void ShowBloodEffect(Vector3 pos, Quaternion rot)
    {
        //혈흔 생성
        GameObject blood = Instantiate<GameObject>(bloodEffectPrefab, pos, rot, this.transform); //해당 transform의 자식 오브젝트로 복제함
        Destroy(blood, 1.0f);
    }

    private void OnPlayerDie()
    {
        this.StopAllCoroutines(); //몬스터의 상태를 체크하는 코루틴 함수를 모두 정지시킴
        //추적을 정지하고 춤추는 애니메이션을 수행
        this.agent.isStopped = true; //멈추고, 
        this.anim.SetFloat(hashSpeed, Random.Range(0.8f, 1.2f)); //애니메이션 재생속도 조절
        this.anim.SetTrigger(hashPlayerDie);

    }

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

'유니티 심화' 카테고리의 다른 글

Space Shooter 간단한 UI 적용 (플레이어 Hp Bar)  (0) 2023.08.25
HeroShooter 씬 전환 시에 변화  (0) 2023.08.24
Zombero를 레퍼런스한 프로젝트  (0) 2023.08.22
가장 가까운 무기 장착  (0) 2023.08.21
총알반사  (0) 2023.08.21