Object Pooling으로 몬스터 생성
2023. 8. 28.

Object Pooling - 미리 비활성화 생성 후 쓸 때 활성화

 

-몬스터를 불규칙한 위치에 생성

spawnPointGroup을 만들고 다음과 같은 코드를 작성한다.

https://docs.unity3d.com/ScriptReference/GameObject-activeInHierarchy.html

 

-실행하면 자동으로 리스트에 객체가 추가된다.

 

실행 전/후

 

 

GameObjt.activeInHierachy

-

 

https://docs.unity3d.com/ScriptReference/GameObject-activeInHierarchy.html

 

Unity - Scripting API: GameObject.activeInHierarchy

Unlike GameObject.activeSelf, this also checks if any parent GameObjects affect the GameObject’s currently active state. When a parent GameObject is deactivated, its children are usually marked as active even though they are not visible, so they are stil

docs.unity3d.com

 

InvokeRepeating

싱글톤 인스턴스
CreateMonsterPool() 메서드는 몬스터를 비활성화 상태로 미리 생성하는 역할

 

CreateMonster() 메서드는 불규칙한 위치에 몬스터를 생성한다.(미리 만들어둔 오브젝트를 가져옴)

 

-몬스터 풀에서 비활성화된 몬스터를 미리 만들어둔다. 

-불규칙적인 위리츨 리스트로 저장하고, InvokeReapeating으로 CreateMonster() 메서드를 호출해 일정 시간 간격으로 몬스터를 호출한다.

 

-MonsterController의 start함수를 awake로 변경하고, 2개의 코루틴함수를 OnEnable로 옮긴다.

MonsterAction() 수정
Monster_02가 사망시 비활성화 되었다가 다시 생성되는것을 확인

 

플레이어가 죽으면 몬스터가 생성되지 않도록 코드 수정
플레이어 사망시 몬스터가 생성되지 않음

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;
  
    void Awake()
    {
        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 속성을 사용해 이동할 좌푯값 지정

       
    }
   
    // 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 비활성화

                    yield return new WaitForSeconds(3.0f);
                    //몬스터 사망 시, 일정 시간 대기후 오브젝트 풀링으로 환원
                    this.currHp = 100;//사망 후 다시 사용을 위한 hp 초기화
                    this.isDie = false;
                    GetComponent<CapsuleCollider>().enabled = true;//몬스터 Collider 활성화
                    this.gameObject.SetActive(false);//몬스터 비활성화 - 오브젝트 풀에서 다시 추출가능한 상태로 만듦.
                    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;//이벤트 발생 시 수행할 함수 연결
        StartCoroutine(this.CheckState());//상태 갱신
        StartCoroutine(this.MonsterAction());//상태에따라 몬스터 행동 수행
    }
    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;

public class GameManager : MonoBehaviour
{
    [SerializeField] GameObject monsterPrefab;
    public List<Transform> points = new List<Transform>();//몬스터가 출현할 위치를 저장할 List 변수

    public List<GameObject> monsterPools = new List<GameObject>();//몬스터를 미리 생성해 저장
    public int maxMonsters = 10; //오브젝트 풀에 생성할 몬스터 최대 개수

    public static GameManager instance = null;//싱글톤 인스턴스 생성
    private float createTime = 3.0f;
    private bool isGameOver; //게임 종류 여부
    public bool IsGameOver
    {
        get { return this.isGameOver; }
        set
        {
            this.isGameOver = value;
            if (this.isGameOver)
            {
                CancelInvoke("CreateMonster");
            }
        }
    }
    private void Awake()
    {
        if(instance == null)
        {//인스터스가 할당되지 않았을경우
            instance = this;
        }
        else if(instance != this)
        {//인스턴스에 할당된 클래스의 인스턴스가 다르게 새로 생성된 클래스
            Destroy(this.gameObject);
        }

        DontDestroyOnLoad(this.gameObject);//다른 씬으로 넘어가더라도 삭제하지 않고 유지
    }

    // Start is called before the first frame update


    void Start()
    {
        CreateMonsterPool();
        Transform spawnPointGroup = GameObject.Find("SpawnPointGroup").transform;

        //

        foreach(Transform point in spawnPointGroup)
        {
            this.points.Add(point);//parent 제외하고 child의 컴포넌트만 추출
        }
        InvokeRepeating("CreateMonster", 2.0f, this.createTime);//일정한 시간 간격으로 함수 호출
    }

    // Update is called once per frame
    void Update()
    {
        
    }
    private void CreateMonster()
    {
        int idx = Random.Range(0, this.points.Count);//몬스터의 불규칙한 생성 위치 산출
        //풀에서 비활성화된 오브젝트 가져오기
        GameObject monster = this.GetMonsterInPool();
        monster?.transform.SetPositionAndRotation(points[idx].position, points[idx].rotation);
        monster?.SetActive(true);
    }
    public GameObject GetMonsterInPool()
    {
        //오브젝트 풀의 처음부터 끝까지 순회
        foreach(var monster in monsterPools)
        {
            if(monster.activeSelf == false)
            {
                return monster;//비활성화 여부로 사용 가능한 몬스터 판단
            }
        }
        return null;
    }
    private void CreateMonsterPool()
    {
        for(int i= 0; i < this.maxMonsters; i++)
        {
            GameObject monster = Instantiate<GameObject>(monsterPrefab);
            monster.name = $"Monster_{i:00}";//몬스터의 이름 지정
            monster.SetActive(false);//몬스터 비활성화
            monsterPools.Add(monster);//생성한 몬스터를 오브젝트 풀에 추가
        }
    }
}
myoskin