유니티

유니티 오브젝트풀링(object pooling)

파란색까마귀 2022. 2. 15. 16:46

2022. 2. 3. 19:37

https://blog.naver.com/nagne2011/222638060370

 

유니티 오브젝트풀링(object pooling)

#유니티 #오브젝트풀링 #오브젝트풀링 #슈팅게임 #서바이벌게임 오브젝트이 생성(Instantiate)과 삭제(De...

blog.naver.com

 

 

#유니티 #오브젝트풀링 #오브젝트풀링 #슈팅게임 #서바이벌게임

 

오브젝트이 생성(Instantiate)과 삭제(Destroy)가 무거운 유니티 특성상,

그리고 최적화 하기에 가장 쉬운부분인 잦은 생성삭제의 관리를 위해서 오브젝트 풀링 하나 만들어두면 두고두고 잘 쓸수있다

 

특히 슈팅게임 같이 잦은 오브젝트 생성이 강제되는 경우에 큰 도움이 되고

그 외에도 스크롤이나 기타 오브젝트를 관리할때도 생성과 삭제가 빠르게 반복된다면 오브젝트 풀링으로 관리하는게 좋다

 

기본 원리는 무거운 생성과 삭제 대신 상대적으로 가벼운 오브젝트 활성/비활성을 사용하며

그 오브젝트 관리를 Queue스택 자료구조를 응용하는 방식이다

 

대표사진 삭제

그림으로 보면 더욱 간단하다

'풀'안에 객체를 미리 (생성 후) 등록해두고, 필요한만큼 꺼내서 사용하고

사용이 끝나면 다시 풀 안에 넣어주는 순서다

이때, 미리 생성해둔걸 다 쓰게된다면 어쩔수없이 추가로 '생성'을 해줘야되지만

한번 생성하면 다시 재활용할수있으니 기존 생성/삭제 방식보다는 훨씬 효율적이다

 

코드로 살펴보기전에

'객체'를 미리 만들어줘야한다

대표사진 삭제

사진 설명을 입력하세요.

간단히 오브젝트와 Bullet스크립트를 생성해서 붙여준다

따로 충돌처리도 사용할꺼면 Collider도 넣어주고 Spirte도 추가해주자

 

추가로 오브젝트 풀링 특성상, 생성/삭제때 동작하는 몇몇 기본함수는 사용할 수 없다

(awake, start 등등)

대신 OnEnable, OnDisable같은 활성/비활성 관련 기본함수를 사용해야하며

개인적으로는 따로 '초기화 함수'를 만들어서 '비활성화 할때' 초기화 함수를 통해 초기화를 진행하는걸 추천한다

public class Bullet : MonoBehaviour
{
	private int m_hp = 2;

	public void Initial()
	{
		m_hp = 2;
	}

	void Update()
	{
		transform.Translate(Time.deltaTime, Time.deltaTime, 0);
	}

	public void DestroyBullet()
	{
		//BulletManager.ReturnObject(this);
	}

	void OnTriggerEnter2D(Collider2D other)
	{
		if (other.gameObject.CompareTag("Enemy"))
		{
			m_hp--;
			if (m_hp <= 0)
				DestroyBullet();
		}
	}
}
 

코드는 간결하게 구성했다

투사체 특성상 '체력'을 둬서 충돌횟수를 관리하게 만들었다

update함수를 통해서 적당한 방향으로 이동하게 만들었는데, 이건 기획에 따라 방향과 속력등을 수정해야한다

 

중요한건 위에서 말한 '초기화 함수'랑

오브젝트풀에 다시 들어갈때 동작하는 '파괴 함수'가 필수적이다

각 함수가 기존 생성/파괴를 담당한다고 보면된다

(파괴함수의 주석처리한 부분이 '오브젝트 풀에 다시 들어가는 동작'이라고 보면된다)

 

충돌처리를 위해 OnTriggerEnter2D함수도 넣어줬고,

간단한 조건문을 사용해서 Tag이름이 'Enemy'인 오브젝트에 두번 부딪혔을때 '파괴'되도록 조건을 걸어줬다

 

이제 해당 오브젝트를 따로 프리펩으로 만들어주고

오브젝트 풀을 관리할 스크립트를 만들어준다

 

public class BulletManager : MonoBehaviour
{
	public static BulletManager Instance;

	[SerializeField]
	private GameObject m_bulletPrefab;

	[SerializeField]
	private Transform m_bulletsParent;
	[SerializeField]
	private Transform m_usedBullet;

	Queue<Bullet> m_poolingObjectQueue = new Queue<Bullet>();
 
 

 

초기 선언은 위와 같이 만들어준다

(외부에서 사용하기 편하게 하기위해서 싱글톤으로 생성해놨다 역시 기획등에 따라 맞춰서 구성 하자)

대표사진 삭제

사진 설명을 입력하세요.

사실 오브젝트 부모설정은 상관이 없는데, 제대로 동작하는지 확인하기위해 구분하는 용도로 만들어놨다

아까 만들어둔 Bullet프리펩을 등록하고, 풀링 안쪽/바깥쪽을 구분하는 오브젝트도 만들었다

 

그리고 각 오브젝트의 입출력을 제어할 Queue자료구도조 생성해준다

 

먼저 어쨋든 객체가 아예 없는것부터 시작하거나, 미리 일정갯수를 만들어두고 시작해야하기 때문에

'생성' 코드는 만들어줘야한다

Instantiate함수를 사용해서 프리펩을 '생성'하고, Queue자료에 넣어주는거로 마무리를 하자

(부모오브젝트 설정도 해주자)

	private Bullet CreateNewBullet()
	{
		var obj = Instantiate(m_bulletPrefab.GetComponent<Bullet>());
		obj.gameObject.SetActive(false);
		obj.transform.SetParent(Instance.m_usedBullet);
		m_poolingObjectQueue.Enqueue(obj);

		return obj;
	}
 
private void Awake()
	{
		Instance = this;

		Initalize(10);
	}

	private void Initalize(int count)
	{
		for (int i = 0; i < count; i++)
		{
			CreateNewBullet();
		}
	}
 

그리고 싱글톤을 위한 Instance설정과

'미리 10개의 객체를 생성'하도록 해준다

게이머 입장에서도 초기에 10개 만들어서 재사용하는게, 게임 도중 하나하나 만들고 삭제하는것 보다 낫다

 

이제 오브젝트풀에서 객체를 '꺼내는' 함수를 보자

public static Bullet GetObject()
	{
		if (Instance.m_poolingObjectQueue.Count <= 0)
		{
			Instance.CreateNewBullet();
		}

		var obj = Instance.m_poolingObjectQueue.Dequeue();
		obj.gameObject.SetActive(true);
		obj.transform.SetParent(Instance.m_bulletsParent);
		return obj;

	}
 

먼저 현재 Queue자료(풀)에 남아있는 객체가 있는지 확인한다

현재 풀 안에 객체가 없다면 미리 만들어둔 객체가 이미 전부 활성화중이라는 뜻이므로 어쩔수없이 새로 오브젝트를 만들어줘야한다

이부분이 최대한 호출이 적게 되야 오브젝트풀링을 사용하는 의미가되지만, 게임 상황에따라 객체가 모자라는 상황은 충분히 나올수있기때문이다

 

새로생성했거나, 풀 안에 객체가 남아있다면

Dequque()를 통해 객체를 빼주자

Queue 구조상 객체를 넣고빼는 순서가 순차적으로 진행되기때문에 오브젝트풀을 최대한 효율적이게 사용할수있다

사진 삭제

사진 설명을 입력하세요.

이제 빼낸 객체를 '활성화' 해주고, 부모오브젝트를 옮겨서 구분해주고

호출한곳에 return해준다

 

마지막으로 '삭제' 함수도 추가해준다

	public static void ReturnObject(Enemy obj)
	{
		obj.gameObject.SetActive(false);
		obj.transform.SetParent(Instance.m_usedEnemy);
		obj.Initial();
		Instance.m_poolingObjectQueue.Enqueue(obj);
	}
 

앞서 말했듯 객체의 사용이 끝났다면, '삭제'하는 대신 다시 풀에 넣어주고 '비활성화' 해주는게 오브젝트풀링 방식이다

해당 오브젝트를 '비활성화'하고 부모오브젝트를 옮겨서 구분해준다

여기서는 아까 만들어뒀던 객체의 '초기화' 함수도 활용해준다

(초기화 함수는 '생성'시에 넣어도 되고, '삭제'시에 넣어도 된다)

마지막으로 Queue자료에 오브젝트를 다시 넣어주고 마무리한다

 

 

이거로 오브젝트풀링 준비는 모두 끝났다

var bullet = BulletManager.GetObject();
 

사용방법은 기존 '생성/삭제'하던 방식대로 사용하고

대신 관리스크립트에서 만든 '생성/삭제' 함수를 사용해주면된다

 

위 코드를 사용해서 bullet이라는 오브젝트를 꺼내와서

기타 행동을 부여하고

 

	public void DestroyBullet()
	{
		BulletManager.ReturnObject(this);
	}
 

각각의 객체, 혹은 외부에서 해당 객체를 '삭제'해서 풀링으로 되돌려놓는다

 

해당 오브젝트 풀링을 응용한 예시를 보자

 

영상에서의 각 오브젝트(핑크색 enemy, 파란색 bullet, 초록색 orb)는 모두 오브젝트풀링 방식으로

재활용하고 있어서 생성/삭제시 부하를 최소한으로 하고있다

 

 

728x90