요약
게임을 제작할 때는 오브젝트의 관리가 중요하다. 2D 게임은 오브젝트 풀링이 오버 엔지니어링에 해당할 수도 있다고 생각하였지만, 오브젝트 풀링만이 아니더라도 팩토리 패턴은 필요해 보였기에 오브젝트 풀링과 팩토리 패턴을 사용한 코드를 통해 오브젝트를 관리하였다.
오브젝트 풀링
오브젝트 풀링은 게임에서 기본적으로 사용하는 패턴이어서 유니티에서는 기본적으로 이를 지원한다.
처음, 이 사실을 알게 된 것은 유니티 공식 디자인패턴 문서에서 확인하였다.
공식 문서에는 유니티 오브젝트 풀링 문서에 사용 방법을 알려주었고 이를 통해 코드를 작성하였다.
팩토리 패턴
팩토리 패턴은 GoF 디자인 패턴중 생성 패턴에 해당하며 아주 단순히 설명하면 필요한 오브젝트를 정형화하여 복잡한 코드 없이 오브젝트를 생성하는 것이다. 특히 코드에서 복잡하지 않게 오브젝트를 생성하는 방법을 확인하는 것이 이번 코드에 주요 관점이다.
PoolManager.cs
public enum Prefabs {...}
public List<List<MyObject>> MyObjectLists { get; private set; } = new List<List<MyObject>>();
private List<ObjectPool<MyObject>> pools = new List<ObjectPool<MyObject>>();
private List<GameObject> myObjectPrefabs = new List<GameObject>();
private Prefabs prefabs;
MyObjectLists: 활성화된 오브젝트들만 존재한다. ( MyObjectLists[(int)prefabs][num]와 같은 방법으로 접근 가능 하다.)
pools: 활성화/비활성화된 오브젝트들이 존재한다.
myObjectPrefabs: 오브젝트들의 프리팹을 저장한다.
private void NewPoolAdd(string prefabsName, int _maxSize)
{
myObjectPrefabs.Add(Resources.Load(prefabsName, typeof(GameObject)) as GameObject);
pools.Add(new ObjectPool<MyObject>
(
CreateMyObject,
OnGetMyObject,
OnReleaseMyObject,
OnDestroyMyObject,
maxSize: _maxSize
));
MyObjectLists.Add(new List<MyObject>());
}
private MyObject CreateMyObject()
{
MyObject myObject = Instantiate(myObjectPrefabs[(int)prefabs]).GetComponent<MyObject>();
myObject.Prefabs = prefabs;
myObject.SetManagedPool(pools[(int)prefabs]);
return myObject;
}
private void OnGetMyObject(MyObject myObject)
{
MyObjectLists[(int)prefabs].Add(myObject);
myObject.gameObject.SetActive(true);
}
private void OnReleaseMyObject(MyObject myObject)
{
// 돌아온 오브젝트가 무엇인지 판단
for (int i = 0; i < MyObjectLists[(int)myObject.Prefabs].Count; i++)
{
// 돌아온 오브젝트가 무엇인지 확인되면 myObjectLists에서 제외시킴
if (myObject.Equals(MyObjectLists[(int)myObject.Prefabs][i]))
{
myObject.gameObject.SetActive(false);
MyObjectLists[(int)myObject.Prefabs].RemoveAt(i);
return;
}
}
}
private void OnDestroyMyObject(MyObject myObject)
{
Destroy(myObject.gameObject);
}
오브젝트 풀을 생성하고 재생성/생성/회수/파괴의 역할을 하는 함수를 넣어준다. 이에 대한 정확한 내용은 유니티 오브젝트 풀링 문서에서 확인하면 좋다.
유니티 오브젝트 풀링 문서와 다른 점이 있다고 하면 MyObjectLists를 통해 통합적으로 오브젝트를 제어하는 기능을 넣기 위해 오브젝트가 회수되면 OnReleaseMyObject함수에서 myObject.Equals를 통해 메모리 주소가 같은 오브젝트를 찾아 삭제한다는 점이다.
public void AllDistroyMyObject(Prefabs _prefabs)
{
// RemoveAt으로 인해 0번째에 값들이 계속 존재함
while (MyObjectLists[(int)_prefabs].Count > 0)
{
MyObjectLists[(int)_prefabs][0].Destroy();
}
}
MyObjectLists를 통해 항상 반복문을 사용하지 않아도 특정 오브젝트를 전부 회수할 수 있다.
public ObjectPool<MyObject> SelectPool(Prefabs _prefabs)
{
prefabs = _prefabs;
return pools[(int)_prefabs];
}
오브젝트를 생성하기 위해서는 오브젝트 풀에 접근할 필요가 있다. 이를 위해 해당 함수는 프리팹 enum에 있는 오브젝트를 작성하면 접근한 동일한 오브젝트를 반납한다. 이 코드가 정확하게 동작하기 위해서는 정확히 enum과 NewPoolAdd()을 순서대로 작성할 필요가 있다. 조금은 번거롭지만, 팩토리 패턴에서 if 문을 여러 개 사용하는 방법을 대체할 만하다.
MyObject.cs
// MyObject는 무조건 컴포넌트로 사용해야함
// MyObjectPool에 아래와 같이 선언하였기 때문
// MyObject myObject = Instantiate(myObjectlPrefab).GetComponent<MyObject>();
public class MyObject : MonoBehaviour
{
private IObjectPool<MyObject> _ManagedPool;
public PoolManager.Prefabs Prefabs { get; set; }
public void SetManagedPool(IObjectPool<MyObject> pool)
{
_ManagedPool = pool;
}
public void Destroy()
{
_ManagedPool.Release(this);
}
}
poolManager에 관리를 받는 오브젝트는 모두 MyObject를 컴포넌트로 가져야 한다.
이유는 단순히 내가 어떤 오브젝트고, 파괴될 땐 어디의 오브젝트 폴에 돌아가는지 알기 위해서다.
결론
해당 방법으로 Instance.poolManager.SelectPool(PoolManager.Prefabs.Object).Get()와 같이 간단하게 오브젝트를 생성할 수 있게 되었다. 이 방법이 가장 좋다고 할 수 없지만 이번 프로젝트에서 얻은 가장 값진 것이라 할 수 있다. 실제로 해당 방법을 찾기 전에는 스스로 오브젝트 풀링을 만들었다. 하지만 공식으로 지원하는 것을 확인 후 리팩토링하였고, 오브젝트가 스스로 관리하는 방향으로 갔다가 코드가 길어지는 것을 확인하여 PoolManager에서 관리하는 것으로 바뀌는 것 등 여러 방법을 통해 최적의 방법을 찾았다.
'Unity > 로봇 체스 개발 일지' 카테고리의 다른 글
| [Unity] 배열 Map2D를 통한 오브젝트 위치 추적 : GameManager (0) | 2025.01.09 |
|---|---|
| [Unity] 로봇 움직임 구현(1) : A* 알고리즘 (0) | 2025.01.09 |
| [Unity] 게임 개발을 위한 URP와 HDRP의 고려 사항 (0) | 2024.10.09 |
| [Unity] Collision을 통한 맵에 생성된 오브젝트 확인 (0) | 2024.10.09 |
| [Unity] 플레이어 카메라(Cinemachine) (0) | 2024.10.08 |