36일차) [Unity] PoolManager 확장 및 몬스터 관리

엄기태·2026년 2월 23일

Unity

목록 보기
10/13

1. 구현 목표

  • PoolManager 고도화: 총알뿐만 아니라 몬스터와 이펙트(MuzzleFlash)까지 관리하는 로직 추가
  • 발사 이벤트 구현: 총알 발사 시 발사 이펙트(MuzzleFlash) 생성 및 자동 회수 로직 구현
  • 몬스터 스폰 시스템: 스테이지별 몬스터 스탯 보정 및 오브젝트 풀링 기반 스폰 적용

2. 주요 소스코드

📋 PoolManager.cs

큐(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);
	}
}

🔫 PlayerCombat.cs (수정사항)

총알 발사 시 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;
    }
}

3. 문제 해결 및 과정

✅ 에셋 충돌 및 버전 문제

총알 발사 이펙트를 위해 에셋을 임포트했으나, 불필요한 리소스들로 인해 오류가 발생하고 백업 데이터가 꼬이는 현상이 있었다. 이를 해결하기 위해 필요한 이펙트만 선별하여 가져오고, 유니티 버전에 맞는 최신 에셋을 다시 찾아 적용함으로써 해결하였다.

✅ 몬스터 재사용(Pooling) 시 초기화 문제

오브젝트 풀링을 사용하다 보니, 2스테이지에서 몬스터의 isAlive나 hp가 이전 상태(0) 그대로 유지되어 생성되지 않는 문제가 있었다. SetMonsterStat 함수 내에서 콜라이더, HP, 상태값들을 초기화하는 로직을 추가하여 해결하였다.

✅ NavMeshAgent 위치 동기화 오류

transform.position으로 위치를 설정했을 때, 몬스터가 이전 스테이지 위치에 고정되거나 위치 값이 꼬이는 현상이 발생하였다. 이는 NavMeshAgent가 활성화된 상태에서 좌표 충돌이 일어난 것으로 판단하여, agent.Warp() 함수를 사용하여 강제로 위치를 지정함으로써 해결하였다.


4. 개선 계획 및 남은 과제

현재 몬스터 처치 시 포탈이 열리지 않는 버그가 남아 있다. 원인은 짧은 시간에 여러 발의 총알을 맞을 경우 DeadRoutine이 중복 실행되어 몬스터 카운트가 음수(-1)가 되는 문제로 파악되었다. 이를 방지하기 위해 사망 플래그를 더 엄격하게 체크하거나, 중복 실행 방지 로직을 강화할 계획이다.


🔗 결과물 영상


profile
코딩 학습 공간

0개의 댓글