오브젝트풀 패턴 2번째이다
이번엔 여러 가지 오브젝트를 관리하는 코드를 보자 오브젝트 풀 패턴에서 잘 모르면
저번 게시글을 한번 보고 오자
이번에는 해외사이트 돌아다니다가 코드 가져오긴 했는데 몇 년 전에 본 거라서
출처는 찾을 수 없었다.
먼저 코드부터 보자 이 오브젝트 풀 패턴을 사용하려면 조건이 조금 있다.
잘 보면서 따라오면 사용 가능하다.
using System.Collections.Generic;
using UnityEngine;
using System;
public class ObjectPooler : MonoBehaviour
{
static ObjectPooler inst;
void Awake() => inst = this;
[Serializable]
public class Pool
{
public string tag;
public GameObject prefab;
public int size;
}
[SerializeField] Pool[] pools;
List<GameObject> spawnObjects;
Dictionary<string, Queue<GameObject>> poolDictionary;
public static GameObject SpawnFromPool(string tag, Vector3 position) =>
inst._SpawnFromPool(tag, position, Quaternion.identity);
public static GameObject SpawnFromPool(string tag, Vector3 position, Quaternion rotation) =>
inst._SpawnFromPool(tag, position, rotation);
public static T SpawnFromPool<T>(string tag, Vector3 position) where T : Component
{
GameObject obj = inst._SpawnFromPool(tag, position, Quaternion.identity);
if (obj.TryGetComponent(out T component))
return component;
else
{
obj.SetActive(false);
throw new Exception($"Component not found");
}
}
public static T SpawnFromPool<T>(string tag, Vector3 position, Quaternion rotation) where T : Component
{
GameObject obj = inst._SpawnFromPool(tag, position, rotation);
if (obj.TryGetComponent(out T component))
return component;
else
{
obj.SetActive(false);
throw new Exception($"Component not found");
}
}
public static List<GameObject> GetAllPools(string tag)
{
if (!inst.poolDictionary.ContainsKey(tag))
throw new Exception($"Pool with tag {tag} doesn't exist.");
return inst.spawnObjects.FindAll(x => x.name == tag);
}
public static List<T> GetAllPools<T>(string tag) where T : Component
{
List<GameObject> objects = GetAllPools(tag);
if (!objects[0].TryGetComponent(out T component))
throw new Exception("Component not found");
return objects.ConvertAll(x => x.GetComponent<T>());
}
public static void ReturnToPool(GameObject obj)
{
if (!inst.poolDictionary.ContainsKey(obj.name))
throw new Exception($"Pool with tag {obj.name} doesn't exist.");
inst.poolDictionary[obj.name].Enqueue(obj);
}
GameObject _SpawnFromPool(string tag, Vector3 position, Quaternion rotation)
{
if (!poolDictionary.ContainsKey(tag))
throw new Exception($"Pool with tag {tag} doesn't exist.");
Queue<GameObject> poolQueue = poolDictionary[tag];
if (poolQueue.Count <= 0)
{
Pool pool = Array.Find(pools, x => x.tag == tag);
var obj = CreateNewObject(pool.tag, pool.prefab);
ArrangePool(obj);
}
GameObject objectToSpawn = poolQueue.Dequeue();
objectToSpawn.transform.position = position;
objectToSpawn.transform.rotation = rotation;
objectToSpawn.SetActive(true);
return objectToSpawn;
}
void Start()
{
spawnObjects = new List<GameObject>();
poolDictionary = new Dictionary<string, Queue<GameObject>>();
// 미리 생성
foreach (Pool pool in pools)
{
poolDictionary.Add(pool.tag, new Queue<GameObject>());
for (int i = 0; i < pool.size; i++)
{
var obj = CreateNewObject(pool.tag, pool.prefab);
ArrangePool(obj);
}
// OnDisable에 ReturnToPool 구현여부와 중복구현 검사
if (poolDictionary[pool.tag].Count <= 0)
Debug.LogError($"{pool.tag} Set ypur object code => ObjectPooler.ReturnToPool(gameObject);");
else if (poolDictionary[pool.tag].Count != pool.size)
Debug.LogError($"{pool.tag} Tag has the same name in ReturnToPool");
}
}
GameObject CreateNewObject(string tag, GameObject prefab)
{
var obj = Instantiate(prefab, transform);
obj.name = tag;
obj.SetActive(false);
return obj;
}
void ArrangePool(GameObject obj)
{
bool isFind = false;
for (int i = 0; i < transform.childCount; i++)
{
if (i == transform.childCount - 1)
{
obj.transform.SetSiblingIndex(i);
spawnObjects.Insert(i, obj);
break;
}
else if (transform.GetChild(i).name == obj.name)
isFind = true;
else if (isFind)
{
obj.transform.SetSiblingIndex(i);
spawnObjects.Insert(i, obj);
break;
}
}
}
}
새로 ObjectPooler.cs를 만들어 준다.
여기서 스폰하는 것
public static GameObject SpawnFromPool(string tag, Vector3 position)
public static GameObject SpawnFromPool(string tag, Vector3 position, Quaternion rotation)
으로 쓰고 리턴하는 것
public static void ReturnToPool(GameObject obj)
으로 사용한다.
이러고 여기에 사용될 bullet 2가지를 만들어 준다.
Bullet.cs Bullet2.cs 2가지를 만든다.
using UnityEngine;
public class Bullet : MonoBehaviour
{
private Vector3 direction;
public void Shoot(Vector3 direction)
{
this.direction = direction;
Invoke("DestroyBullet", 5f);
}
public void DestroyBullet()
{
gameObject.SetActive(false);
}
private void OnDisable()
{
ObjectPooler.ReturnToPool(gameObject);
}
void Update()
{
transform.Translate(direction);
}
}
using UnityEngine;
public class Bullet2 : MonoBehaviour
{
private Vector3 direction;
public void Shoot(Vector3 direction)
{
this.direction = direction;
Invoke("DestroyBullet", 5f);
}
public void DestroyBullet()
{
gameObject.SetActive(false);
}
private void OnDisable()
{
ObjectPooler.ReturnToPool(gameObject);
}
void Update()
{
transform.Translate(direction);
}
}
여기서 보면 오브젝트 풀에 리턴할 때
오브젝트를 SetActive()으로 끄고
리턴하는 함수는 OnDisable() 안에 넣어둬야 한다.
그러면 이제 소환할 플레이어를 만들어보자.
using UnityEngine;
public class TestPlayer : MonoBehaviour
{
void Update()
{
if (Input.GetMouseButtonDown(0))
{
var bullet = ObjectPooler.SpawnFromPool("bullet", Vector3.zero);
var direction = new Vector3(transform.position.x, transform.position.y, transform.position.z) - transform.forward;
bullet.transform.position = direction.normalized;
bullet.GetComponent<Bullet>().Shoot(direction.normalized);
}
if (Input.GetMouseButtonDown(1))
{
var bullet = ObjectPooler.SpawnFromPool("bullet2", Vector3.zero);
var direction = new Vector3(transform.position.x, transform.position.y, transform.position.z) - transform.forward;
bullet.transform.position = direction.normalized;
bullet.GetComponent<Bullet2>().Shoot(direction.normalized);
}
}
}
SpawnFromPool()으로 가져온다.
스폰할 때 이제 여러 오브젝트를 들고 오기 때문에 bullet과 bullet2를 소환해 보자.
코드 작성 후에 유니티로 돌아가자.
먼저 코드 2개를 넣은 bullet 프리펩을 2개를 만들자.
bullet2 도 같이 세팅해 주자.
유니티에 오브젝트 하나 만들고 거기에 세팅하자.
거기엔 ObjectPooler 넣고 Pools 안에 사용할 프리펩 넣자
Tag는 코드에서 불러올 이름이고 Size는 만들어질 개수이다.
그 후 오브젝트 하나 만들고 TestPlayer 넣고 실행해서 우클릭 좌클릭 하면서 확인해 보자.
여러 프리펩을 오브젝트 풀로 관리하는 모습니다.