본문 바로가기
게임개발/게임엔진 응용 프로그래밍

절대강좌 유니티 (3)몬스터 만들기

by pudding81 2024. 3. 14.

 

 

 

몬스터 컨트롤러

- AI 에이전트에 따라 움직임

- 상태에 따라 애니메이션 동작하기

- 총알에 맞았을때 HP감소

- 사망했을때 블러드 효과 발생시키기

- 플레이어가 사망했을때 이벤트 동작 실행

 

 

  Tag설정  /  Layer 설정 

 

Monster   :   Monster  /  MonsterBody

Monster Lwrist .Rwrist  : Punch / MonsterPunch

 

Edit ->>  Project Settings >>충돌 설정 해제하기 

 

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

public class MonsterCtrl : MonoBehaviour
{
    public enum State
    {
        IDLE, TRACE, ATTACK, DIE //ctrl +shift+ U 대문자로 변경
    }


    public State state = State.IDLE;
    public float traceDist = 10f; //추적사정거리
    public float attackDist = 2f;
    public bool isDie = false;
    private GameObject bloodEffect;
    private int hp=100;

    private Transform playerTrans;
    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 void OnEnable()
    {
        //이벤트 리스너를 등록 한다 
        PlayerCtrl.OnPlayerDie += this.OnPlayerDie;
    }


    void Start()
    {
        agent = GetComponent<NavMeshAgent>();
        //플레이어를 찾는다 (태그로)
        playerTrans = GameObject.FindWithTag("Player").transform;
        //이동한다 
        agent.SetDestination(playerTrans.position);


        anim = GetComponent<Animator>();
        //프리팹을 로드한다 
        this.bloodEffect = Resources.Load<GameObject>("BloodSprayEffect");
        //몬스터의 상태를 체크 하는 코루틴
        StartCoroutine(CheckMonsterState());

        //상태에 따라서 몬스터의 행동을 수행 하는 코루틴 
        StartCoroutine(MonsterAction());



    }


    IEnumerator MonsterAction()
    {
        while (!this.isDie) //죽지 않았을 경우만
        {
            switch (this.state)
            {
                case State.IDLE:
                    agent.isStopped = true; //추적을 중지
                    this.anim.SetBool(hashTrace, false);
                    break;

                case State.TRACE:
                    //추적대상 좌표로 이동한다
                    agent.SetDestination(this.playerTrans.position);
                    this.anim.SetBool(hashTrace, true);
                    this.anim.SetBool(hashAttack, false);


                    agent.isStopped = false;  //추적을 재게  
                    break; 
                
                case State.ATTACK:
                    this.anim.SetBool(hashAttack, true);

                    break;
                case State.DIE:
                    this.isDie = true;
                    agent.isStopped = true;
                    anim.SetTrigger(hashDie);
                    GetComponent<CapsuleCollider>().enabled = false;
                    break;

            }
            yield return new WaitForSeconds(0.3f);  //0.3초 주기로 행동을 함 
        }
    }


    IEnumerator CheckMonsterState()
    {
        while (!this.isDie) //죽지 않았을 경우
        {
            yield return new WaitForSeconds(0.3f);

            if(state ==State.DIE)
            {
                yield break; // 코루틴 즉시 종료
            }

            //거리 측정
            float distance = Vector3.Distance(transform.position, playerTrans.position);

            if(distance<=attackDist) //공격 사거리에 들어왔다면
            {
                state = State.ATTACK;

            }

            else if(distance<=traceDist) //시야에 들어왔다면 
            {
                state = State.TRACE;
            }

            else //공격 사거리에도 안들어오고 시야에도 없다 
            {
                state=State.IDLE;

            }
        }
    }


    
    private void OnCollisionEnter(Collision collision)
    {
        //총알에 충돌했다면 
        if (collision.collider.CompareTag("Bullet") ){
            Destroy(collision.gameObject); //총알 제거 
            anim.SetTrigger(hashHit);

            //충돌 지점을 가져옴 
            ContactPoint cp = collision.GetContact(0);
            Vector3 pos = cp.point;
            //충돌지점의 법선벡터
           Quaternion rot= Quaternion.LookRotation(-cp.normal);

            //이펙트 생성
            this.ShowBloodEffect(pos,rot);

            hp -= 10;
            if (hp <= 0)
            {
                state = State.DIE;
            }

        }
    }

    private void ShowBloodEffect(Vector3 pos, Quaternion rot)
    {
        GameObject blood = Instantiate(this.bloodEffect,pos,rot,this.transform);
        Destroy(blood,1f );

    }

    private void OnDrawGizmos()
    {
        if(state==State.TRACE)
        {
            Gizmos.color = Color.blue;
            Gizmos.DrawWireSphere(this.transform.position, traceDist);

        }

        if (state == State.ATTACK)
        {
            Gizmos.color = Color.yellow;
            Gizmos.DrawWireSphere(this.transform.position, attackDist);

        }





    }


    public void OnPlayerDie()
    {
        this.StopAllCoroutines();
        agent.isStopped = true;
        anim.SetTrigger(hashPlayerDie);
        anim.SetFloat(hashSpeed, UnityEngine.Random.Range(0.8f, 1.2f)); ;

    }

    private void OnDisable()
    {
        PlayerCtrl.OnPlayerDie -= this.OnPlayerDie;
    }

}

 

 

플레이어 수정 

- 플레이어가 사망했을때

- 대리자 호출

 

 

 

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

public class PlayerCtrl : MonoBehaviour
{
    public float movespeed=1f;
    public float turnspeed=1f;
    private Animation anim;

    private readonly float inithp = 100f;
    public float currenthp;
    public delegate void PlayerDieHandler();
    public static  event PlayerDieHandler OnPlayerDie;

    private void Awake()
    {
        //첫번째 호출
    }

    private void OnEnable()
    { 
        //두번째 호출
        
    }
    IEnumerator Start()
    {
        //코루틴으로 호출 가능
       // IEnumerator Start()
        anim = GetComponent<Animation>();
        anim.Play("Idle");

        this.turnspeed = 0;
        yield return new WaitForSeconds(0.3f);
        this.turnspeed = 10f;
        currenthp = inithp;


    }

    private void FixedUpdate()
    {
        //일정간격으로 호출

    }

    void Update()
    {
        //transform.Translate(Vector3.forward * speed * Time.deltaTime); //로컬좌표 기준으로,정면으로 이동
        //transform.Translate(Vector3.forward * 0.01f,Space.World); //월드좌표 기준으로,회전한 상태로 이동   
       
        float h = Input.GetAxis("Horizontal");
        float v = Input.GetAxis("Vertical");
        Vector3 dir = new Vector3(h,0, v);
        transform.Translate(dir.normalized * movespeed * Time.deltaTime);

        float r = Input.GetAxis("Mouse X"); //회전방향      
        transform.Rotate(Vector3.up * turnspeed * r);

        PlayerAnim( h,  v);

    }

    private void LateUpdate()
    {
        
    }

    private void PlayerAnim(float h, float v) 
    {
        if (v >= 0.1f)
        {
            this.anim.CrossFade("RunF", 0.25f);
        }
        else if(v<= -0.1f)
        {
            this.anim.CrossFade("RunB", 0.25f);

        }

       else if (h >= 0.1f)
        {
            this.anim.CrossFade("RunR", 0.25f);
        }
        else if (h <= -0.1f)
        {
            this.anim.CrossFade("RunL", 0.25f);

        }
        else
        {
            this.anim.CrossFade("Idle", 0.25f);

        }


    }



    private void OnTriggerEnter(Collider other)
    {
     if(currenthp>0 && other.CompareTag("Punch"))
        {
            this.currenthp -= 10f;
            Debug.LogFormat("HP : {0}/{1}" ,currenthp,inithp);
        }


        if (currenthp <= 0)
        {
            PlayerDie();
            this.anim.CrossFade("Die", 0.25f);

        }
    }

    private void PlayerDie()
    {
            Debug.Log("플레이어 다이");
        /* GameObject[] monsters = GameObject.FindGameObjectsWithTag("Monster");

         foreach (GameObject monster in monsters)
         {
             monster.SendMessage("OnPlayerDie",SendMessageOptions.DontRequireReceiver);

         }*/

        PlayerCtrl.OnPlayerDie(); //대리자 호출 
    }
}