[유니티 C#] 2주차 - FPS + RPG 게임 제작 - 몬스터와 오브젝트 풀링

한승호·2022년 7월 13일
0

The Last Of City 2주차(5)

몬스터와 오브젝트 풀링

기존에는 대부분의 상황에서 Instantiate()를 사용해 오브젝트를 복사하는 방법을 사용했었다.
그러나 계속되는 생성과 삭제로 메모리를 많이 사용하게 되어 다른 방법이 필요했고, 오브젝트 풀링을 알게 되었다.

오브젝트 풀링

  • 오브젝트 풀링은 오브젝트들을 담을 오브젝트 풀을 만든다.
  • 그 후 오브젝트를 생성해 풀에서 오브젝트들을 관리한다.
  • 오브젝트가 필요하다면, 풀에서 꺼내고, 필요가 없다면 다시 넣어준다.
  • 만약, 오브젝트를 꺼내려는데, 이미 사용중이라 더 이상 꺼낼 수 없다면, 새로 만들어준다.
    ->즉, 오브젝트를 미리 생성하고 필요할 때 마다 꺼내 쓰는 개념인 것이다.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class MonsterSpawner : MonoBehaviour
{
    public static MonsterSpawner instance;
    public Monster monsterPrefab;
    public Player player;
    
    public Queue<Monster> monsterSpawnQueue = new Queue<Monster>();

    BoxCollider rangeCollider;

    private void Awake()
    {
        instance = this;

        rangeCollider = GetComponent<BoxCollider>();

        StartCoroutine(SpawnTime());
    }
    private void CreateMonster(int _count)
    {
        for(int i = 0; i < _count; i++)
        {
            Monster monsterSpawn = Instantiate(monsterPrefab, RandomPosition(),transform.rotation, transform);
            
            monsterSpawnQueue.Enqueue(monsterSpawn);
        }
    }
    IEnumerator SpawnTime()
    {
        yield return new WaitForSeconds(3f);
        CreateMonster(10);
    }
    public void ReturnQueue(Monster _gameObject)
    {
        _gameObject.gameObject.SetActive(false);
        monsterSpawnQueue.Enqueue(_gameObject);
        Invoke("GetQueue", 7f);
    }

    public Monster GetQueue()
    {
        Monster getGameObject = monsterSpawnQueue.Dequeue();
        gameObject.transform.position = RandomPosition();
        getGameObject.gameObject.SetActive(true);
        return getGameObject;
    }

    private Vector3 RandomPosition()
    {
        Vector3 originPosition = transform.position;

        float spawnX = Random.Range((rangeCollider.bounds.size.x /2)*-1, (rangeCollider.bounds.size.x/2));
        float spawnZ = Random.Range((rangeCollider.bounds.size.z /2)*-1, (rangeCollider.bounds.size.z/2));

        Vector3 newPosition = new Vector3(spawnX, 0f, spawnZ);
        Vector3 respawnPosition = originPosition + newPosition;

        return respawnPosition;
    }
}
  • 싱글톤화 시킨 후 Queue로 몬스터를 담아줄 풀을 만들어 준다.
  • CreateMonster(int _count)에서 몬스터 수를 정해주고 그 만큼 생성하고 Queue에 넣어준다.
  • 단, 몬스터는 게임 시작 후 즉시 생성되기 때문에 다른 오브젝트 풀링과 다르게 비활성화해서 넣어주지 않는다.
  • ReturnQueue()는 몬스터가 죽는 경우 해당 몬스터를 비활성화하고 다시 Queue에 넣는다. 그 후 리스폰 시간을 설정해 Invoke 함수를 사용해 GetQueue()를 실행한다.
  • GetQueue()는 몬스터 재생성으로 몬스터가 다시 리스폰되는 경우 위치를 새롭게 설정해 랜덤하게 등장하도록 한다.

몬스터

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

public class Monster : MonoBehaviour
{
    public static Monster instance;
    public static UnityAction<Monster> onSpawn;
    public UnityAction onDie;
    public NavMeshAgent monsterNav;
    public GameObject monsterDropItem;
    public Transform monsterDropItemPosition;

    public string monsterName;
    public float hp;
    public float maxHp;
    public float defence;
    public int monsterExp;

    public float monsterSpeed;
    public float playerDistance;
    public Player target;

    public bool isWalk;
    public bool isAttack;
    public bool isSafeZoneIn;

    private void OnEnable()
    {
        onSpawn(this);
        monsterNav = GetComponent<NavMeshAgent>();
        MonsterNavMeshMove(monsterSpeed);
        hp = (int)maxHp;
    }
    public void Start()
    {
        onDie += Die;
        onDie += DropExp;
    }
    public void Die()
    {
        MonsterSpawner.instance.ReturnQueue(this);
    }
    public void DropExp()
    {
        PlayerState.instance.CurreunExp += monsterExp;
        PlayerState.instance.MonsterHunterCount++;
    }
    public void DropItem()
    {
        GameObject _dropItem = Instantiate(monsterDropItem, monsterDropItemPosition);
        _dropItem.transform.parent = null;
    }
    public void MonsterNavMeshMove(float _monsterSpeed)
    {
        if(isSafeZoneIn == true)
        {
            return;
        }
        playerDistance = (target.transform.position - transform.position).magnitude;
        if (playerDistance < 20 && 2 < playerDistance)
        {
            isWalk = true;
            monsterNav.SetDestination(target.transform.position);
        }
        if (playerDistance <= 2)
        {
            isWalk = false;
            isAttack = true;
            monsterNav.SetDestination(transform.position);

            if(isAttack == true)
            {
                Attack();
            }
        }
    }

    public virtual void Attack()
    {
        Debug.Log("공격");
    }

    #region Monster HP
    public float Hp
    {
        get
        {
            return hp;
        }
        set
        {
            hp = value;

            if (hp <= 0)
            {
                hp = 0;
                onDie();
            }
            if (hp > maxHp)
            {
                hp = maxHp;
            }
        }
    }
    #endregion
}
  • 모든 몬스터가 상속받는 클래스이며, NavMeshAgent를 통해 플레이어를 추적한다.
  • UnityAction으로 이를 상속받는 몬스터들에게서 필요한 것들을 추가 및 삭제해준다.
  • 몬스터가 활성화될 때 마다 플레이어를 새로 추적할 수 있도록 onSpawn을 플레이어에서 호출해 타겟을 플레이어로 초기화한다.
  • onDie는 모든 몬스터는 죽을 때 마다 드랍되는 아이템이나 경험치 등이 다르기 때문에 액션(델리게이트)를 활용해준다.
  • 이런식으로 몬스터 스크립트를 만들어 놓으면, 이를 상속받는 모든 몬스터는 생성될 때 마다 플레이어를 추적하고 공격하며, 죽는 것 까지 간단하게 관리할 수 있다.
profile
모든 실수를 경험해 더 발전하고 싶은 개발자입니다.

0개의 댓글