큐(Queue)를 활용하여 총알, 몬스터, 이펙트를 각각 개별적으로 관리하도록 구현하였다.
using System.Collections.Generic;
using UnityEngine;
public class PoolManager : MonoBehaviour
{
public static PoolManager instance { get; private set; }
[SerializeField] private GameObject _bulltetPrefab;
[SerializeField] private GameObject _monsterPrefab;
[SerializeField] private GameObject _muzzleFlashPrefab;
[SerializeField] private int _bulletCount = 20;
[SerializeField] private int _monsterCount = 20;
[SerializeField] private int _muzzleFlashCount = 10;
private Queue<GameObject> _bulletPool = new Queue<GameObject>();
private Queue<GameObject> _monsterPool = new Queue<GameObject>();
private Queue<GameObject> _muzzleFlashPool = new Queue<GameObject>();
private void Awake()
{
if (instance != null && instance != this)
{
Destroy(gameObject);
return;
}
instance = this;
}
void Start()
{
for (int i = 0; i < _bulletCount; i++)
{
GameObject obj = Instantiate(_bulltetPrefab);
obj.SetActive(false);
_bulletPool.Enqueue(obj);
}
for (int i = 0; i < _monsterCount; i++)
{
GameObject obj = Instantiate(_monsterPrefab);
obj.SetActive(false);
_monsterPool.Enqueue(obj);
}
for (int i = 0; i < _muzzleFlashCount; i++)
{
GameObject obj = Instantiate(_muzzleFlashPrefab);
obj.SetActive(false);
_muzzleFlashPool.Enqueue(obj);
}
}
public GameObject GetBullet()
{
if (_bulletPool.Count > 0)
{
GameObject obj = _bulletPool.Dequeue();
obj.SetActive(true);
return obj;
}
GameObject newObj = Instantiate(_bulltetPrefab);
newObj.SetActive(true);
return newObj;
}
public void ReturnBullet(GameObject obj)
{
obj.SetActive(false);
_bulletPool.Enqueue(obj);
}
public GameObject GetMonster()
{
if (_monsterPool.Count > 0)
{
GameObject obj = _monsterPool.Dequeue();
obj.SetActive(true);
return obj;
}
GameObject newObj = Instantiate(_monsterPrefab);
newObj.SetActive(true);
return newObj;
}
public void ReturnMonster(GameObject obj)
{
obj.SetActive(false);
_monsterPool.Enqueue(obj);
}
public GameObject GetMuzzleFlash()
{
if (_muzzleFlashPool.Count > 0)
{
GameObject obj = _muzzleFlashPool.Dequeue();
obj.SetActive(true);
return obj;
}
GameObject newObj = Instantiate(_muzzleFlashPrefab);
newObj.SetActive(true);
return newObj;
}
public void ReturnMuzzleFlash(GameObject obj)
{
obj.SetActive(false);
_muzzleFlashPool.Enqueue(obj);
}
}
총알 발사 시 PoolManager에서 이펙트를 가져오고, 코루틴을 통해 일정 시간 후 반납하도록 처리하였다.
void Update()
{
if (_life <= 0) return;
if (_fireCooldown > 0)
{
_fireCooldown -= Time.deltaTime;
}
if (_fireCooldown > 0) return;
if (Input.GetAxis("Fire1") > 0f)
{
// 총알 생성
GameObject bullet = PoolManager.instance.GetBullet();
bullet.transform.position = _firePosition.position;
bullet.transform.rotation = _firePosition.rotation;
bullet.GetComponent<BulletController>().Fire();
// 총알 발사 이펙트
GameObject muzzleFlash = PoolManager.instance.GetMuzzleFlash();
muzzleFlash.transform.position = _firePosition.position;
muzzleFlash.transform.rotation = _firePosition.rotation;
StartCoroutine(MuzzleFlashTimer(muzzleFlash));
_fireCooldown = _fireInterval;
if (_director.SniperMode)
{
_director.StartCoroutine(_director.SniperNoise());
_fireCooldown = _sniperInterval;
}
}
IEnumerator MuzzleFlashTimer(GameObject obj)
{
yield return new WaitForSeconds(0.3f);
PoolManager.instance.ReturnMuzzleFlash(obj);
yield break;
}
}
총알 발사 이펙트를 위해 에셋을 임포트했으나, 불필요한 리소스들로 인해 오류가 발생하고 백업 데이터가 꼬이는 현상이 있었다. 이를 해결하기 위해 필요한 이펙트만 선별하여 가져오고, 유니티 버전에 맞는 최신 에셋을 다시 찾아 적용함으로써 해결하였다.
오브젝트 풀링을 사용하다 보니, 2스테이지에서 몬스터의 isAlive나 hp가 이전 상태(0) 그대로 유지되어 생성되지 않는 문제가 있었다. SetMonsterStat 함수 내에서 콜라이더, HP, 상태값들을 초기화하는 로직을 추가하여 해결하였다.
transform.position으로 위치를 설정했을 때, 몬스터가 이전 스테이지 위치에 고정되거나 위치 값이 꼬이는 현상이 발생하였다. 이는 NavMeshAgent가 활성화된 상태에서 좌표 충돌이 일어난 것으로 판단하여, agent.Warp() 함수를 사용하여 강제로 위치를 지정함으로써 해결하였다.
현재 몬스터 처치 시 포탈이 열리지 않는 버그가 남아 있다. 원인은 짧은 시간에 여러 발의 총알을 맞을 경우 DeadRoutine이 중복 실행되어 몬스터 카운트가 음수(-1)가 되는 문제로 파악되었다. 이를 방지하기 위해 사망 플래그를 더 엄격하게 체크하거나, 중복 실행 방지 로직을 강화할 계획이다.