Player 사망-Event 처리, 몬스터 사망
2023. 8. 24.

 

특정 레이어간 충돌 감지

-Monster_Body, Monster_Punch 레이어를 추가후 Physics에서 셋팅을 변경시켜주면 collider간 충돌을 방지할 수 있다.

 

본구조의 최적화

 

https://docs.unity3d.com/kr/2021.1/Manual/FBXImporter-Rig.html

 

릭 탭 - Unity 매뉴얼

릭(Rig) 탭의 설정은 Unity에서 임포트된 모델의 메시에 디포머를 매핑하여 메시를 애니메이션화할 수 있게 하는 방법을 정의합니다. 휴머노이드 캐릭터의 경우 이는 아바타를 할당 또는 생성하는

docs.unity3d.com

 

몬스터 공격중지

 

 

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

PlayerController.cs
MonsterController.cs

-멀티캐스트와 대리자 결합을 참고하자

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

 

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

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

learn.microsoft.com

*이벤트는 멀티캐스트 대리자이다.

몬스터 사망 구현

checkstate() 메서드 수정
MonsterAction()메서드 수정

 

oncollisionenter 수정

 

 

몬스터가 죽는 애니메이션에서 피벗 좌표 수정
몬스터 사망 구현

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.AI;
using UnityEngine.UIElements;

public class MonsterController : MonoBehaviour
{
    
    public enum eState
    {//몬스터 상태 정의
        IDLE, TRACE, ATTACK, DIE
    }
    public eState state = eState.IDLE;//몬스터의 현재 상태
    private bool isDie=false;//몬스터의 사망 여부
    private NavMeshAgent agent;
    private Transform playerTrans;
    private Animator anim;
    private readonly int hashTrace = Animator.StringToHash("IsTrace");//Animator 파라미터의 해시값 추출
    private readonly int hashAttack = Animator.StringToHash("IsAttack");
    private readonly int hashGotHit = Animator.StringToHash("GotHit");
    private readonly int hashDance = Animator.StringToHash("Dance");
    private readonly int hashSpeed = Animator.StringToHash("Speed");
    private readonly int hashDie = Animator.StringToHash("Die");
    private GameObject bloodEffectPrefab;//혈흔 효과 프리팹
    [SerializeField]private float traceDistance = 10.0f;//추적 사거리
    [SerializeField]private float attackDistance = 2f;//공격 사거리
    //--Data----------------------------------------------------
    private float initHp = 100f;
    private float currHp;
    // Start is called before the first frame update
    void Start()
    {
        this.currHp = this.initHp;
        this.bloodEffectPrefab = Resources.Load<GameObject>("BloodSprayEffect");
        this.anim = GetComponent<Animator>();
        this.agent = this.GetComponent<NavMeshAgent>();//agent 컴포넌트 가져옴
        GameObject playerGo = GameObject.FindGameObjectWithTag("Player");
        this.playerTrans = playerGo.GetComponent<Transform>();
       // Vector3 playerPosition = playerTrans.position;
       // this.agent.destination = playerPosition;//destination 속성을 사용해 이동할 좌푯값 지정

        StartCoroutine(this.CheckState());//상태 갱신
        StartCoroutine(this.MonsterAction());//상태에따라 몬스터 행동 수행
    }

    // Update is called once per frame
    void Update()
    {
        
    }
    private IEnumerator CheckState()
    {//상태 갱신 코루틴 함수: 일정 간격으로 몬스터의 행동상태 체크
        while (!isDie)
        {
            yield return new WaitForSeconds(0.3f);
            if (this.state == eState.DIE) yield break;
            float distance = Vector3.Distance(this.transform.position, this.playerTrans.position);
            // Debug.Log(distance);
            //Debug.Log(this.state);

          
            if (distance <= this.traceDistance)//추적 사거리에 들어왔다면
            {
                this.state = eState.TRACE;//추적상태로 변경
                if (distance <= this.attackDistance) //공격 사거리에 들어왔다면
                {
                    this.state = eState.IDLE;
                    yield return null;
                    this.state = eState.ATTACK;//공격상태로 변경
                }

            }           
            else//멈춤
            {
                this.state = eState.IDLE;
            }
        }
        
    }

    private IEnumerator MonsterAction()
    {
        while (!isDie)
        {
            switch (this.state)
            {
                case eState.IDLE:
                    this.agent.isStopped = true;//추적 중지
                    //this.anim.SetBool("IsTrace", false);
                    this.anim.SetBool(this.hashTrace, false);
                    this.anim.SetBool(this.hashAttack,false);
                    break;

                case eState.TRACE:
                    this.agent.SetDestination(this.playerTrans.position);
                    this.agent.isStopped = false;                 
                    this.anim.SetBool(this.hashTrace, true);                
                    this.anim.SetBool(this.hashAttack, false);
                    break;

                case eState.ATTACK:    
                    this.anim.SetBool(this.hashAttack, true);
                    this.anim.SetBool(this.hashTrace, false);
                    break;

                case eState.DIE:
                    this.isDie = true;
                    this.agent.isStopped = true;
                    this.anim.SetTrigger(this.hashDie);
                    GetComponent<CapsuleCollider>().enabled = false;//몬스터의 collider 비활성화
                    break;
            }
            yield return new WaitForSeconds(0.3f);
        }
    }

    private void OnCollisionEnter(Collision collision)
    {
        if (collision.collider.CompareTag("Bullet"))
        {
            ContactPoint contactPoint = collision.GetContact(0);//첫 번째 충돌 지점
            //DrawArrow.ForDebug(contactPoint.point, -contactPoint.normal, 5f, Color.white);
            //point:위치, normal:법선, 법선 반대로 그려보기
            Quaternion rotation = Quaternion.LookRotation(-contactPoint.normal);//바라볼 방향
            //충돌 지점의 법선 벡터를 쿼터니언 타입으로 변환
            GameObject go = Instantiate(this.bloodEffectPrefab,contactPoint.point,rotation,this.transform);
            //go.transform.position = contactPoint.point;
            //go.transform.rotation = rotation;//법선 벡터로 회전         
        //    Debug.LogErrorFormat("{0}",contactPoint.point);
            Destroy(collision.gameObject);//remove bullet
            Destroy(go, 0.5f);//remove blood Effect
            this.anim.SetTrigger(this.hashGotHit);

            this.currHp -= 30.0f;
           // Debug.Log(this.state);
            if(this.currHp <= 0)
            {//몬스터 사망
                this.state = eState.DIE;
            }
        }
    }
    private void OnEnable()
    {//이벤트 발생 시 수행할 함수 연결
        PlayerController.OnPlayerDie += this.OnPlayerDie;
    }
    private void OnDisable()
    {//기존에 연결된 함수 해제
        PlayerController.OnPlayerDie -= this.OnPlayerDie;
    }
    void OnPlayerDie()
    {
        StopAllCoroutines();
        this.agent.isStopped = true;
        this.anim.SetFloat(this.hashSpeed, Random.Range(0.8f, 1.2f));
        this.anim.SetTrigger(this.hashDance);
    }
    private void OnDrawGizmos()
    {
        if(this.state == eState.TRACE)
        {
            Gizmos.color = Color.yellow;
            Gizmos.DrawWireSphere(this.transform.position, traceDistance);
        }
        else if(this.state == eState.ATTACK)
        {
            Gizmos.color= Color.green;
            Gizmos.DrawWireSphere(this.transform.position,attackDistance);
        }
    }
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;

public class PlayerController : MonoBehaviour
{
    public Vector3 cameraPosition;

    private Vector3 moveDir;
    private Vector3 aPosition;
    private Animation anim;

    //delegate 선언
    public delegate void PlayerDieHandler();//타입 정의
    //event
    public static event PlayerDieHandler OnPlayerDie;//대리자 타입의 변수 정의
    
    [SerializeField]private float moveSpeed = 1f;
    [SerializeField] private float turnSpeed =80f;
    //data----------------------------------------------
    private readonly float initHp = 100.0f;//초기 생명값
    public float currHp;//현재 체력
    IEnumerator Start()
    {
        this.currHp = this.initHp;//체력 초기화
        this.anim = GetComponent<Animation>();
        anim.Play("Idle");//시작 시 Idle

        this.turnSpeed = 0.0f;
        yield return new WaitForSeconds(0.3f);
        this.turnSpeed = 80.0f;
    }
    private void Update()
    {
      
        //키보드 이동
        float h = Input.GetAxis("Horizontal");
        float v = Input.GetAxis("Vertical");
        float r = Input.GetAxis("Mouse X");//마우스를 왼쪽으로 움직이면 음수, 오른쪽 양수 반환
        moveDir = (Vector3.forward * v) + Vector3.right * h;
        this.transform.Translate(moveDir.normalized * this.moveSpeed * Time.deltaTime);//이동
        //   Debug.LogFormat("{0} , {1}", moveDir, moveDir.normalized);
        //  DrawArrow.ForDebug(this.transform.position, moveDir, 5f, Color.red);
        Vector3 angle = Vector3.up * turnSpeed * Time.deltaTime * r;
       // Vector3 angle2 = Vector3.up * turnSpeed * r;//Time.deltaTime을 빼보았다.
      //  Debug.LogFormat("angle2:{0},r:{1}",angle2,r );
        this.transform.Rotate(angle);//Vector3.up을 축으로 회전
        PlayAnim(h,v);

        
    }
    
    private void OnDrawGizmos()
    {
        DrawArrow.ForGizmo(this.transform.position, this.transform.forward , Color.red);
        Gizmos.color = Color.white;
        GizmosExtensions.DrawWireArc(this.transform.position, this.transform.forward, 360, 1);
        DrawArrow.ForGizmo(this.transform.position, -this.transform.forward * 3, Color.yellow);
        this.aPosition = this.transform.position + (-this.transform.forward * 3);//a지점의 위치
        DrawArrow.ForGizmo(this.aPosition, Vector3.up * 2, Color.green);//카메라의 위치를 의미한다.
        this.cameraPosition = this.aPosition+ (Vector3.up*2);
    }
    private void PlayAnim(float h,float v)//키보드 입력값을 기준으로 애니메이션 play
    {
        if (v >= 0.1f)//x축 : 전,후
        {
            anim.CrossFade("RunF", 0.25f);//전진
        }
        else if (v <= -0.1f)
        {
            anim.CrossFade("RunB", 0.25f);//후진
        }
        else if (h >= 0.1f)//z축 : 좌, 우
        {
            anim.CrossFade("RunR", 0.25f);
        }
        else if (h <= -0.1f)
        {
            anim.CrossFade("RunL", 0.25f);
        }
        else
        {
            anim.CrossFade("Idle", 0.25f);//정지 시 Idle
        }
    }

    private void OnTriggerEnter(Collider coll)
    {
        if(this.currHp >= 0.0f && coll.CompareTag("Punch"))
        {
                Debug.Log(coll.tag);
                this.currHp -= 10.0f;
            
            if(this.currHp < 0.0f)
            {
                PlayerDie();
            }
        }

    }
    private void PlayerDie()
    {
        Debug.Log("Player Die!!");
        //GameObject[] arrMonstersGo = GameObject.FindGameObjectsWithTag("Monster");
        ////monster 태그를 가진 모든 게임 오브젝트를 찾아옴
        //foreach(GameObject monster in arrMonstersGo)
        //{
        //    monster.SendMessage("OnPlayerDie",SendMessageOptions.DontRequireReceiver);
        //}
        OnPlayerDie();//Player 사망 이벤트 발생(Notification)- 대리자 호출
    }
}
myoskin