수색…
개체 풀
가끔 게임을 만들 때 같은 유형의 많은 물체를 반복해서 만들고 파괴해야합니다. 프리 패브 (prefab)를 만들어 필요할 때마다 프리 팹 (instantfab)을 만들고 인스턴스화 (instantiate / destroy)하면됩니다. 그러나 이렇게하면 비효율적이며 게임 속도가 느려질 수 있습니다.
이 문제를 해결할 수있는 한 가지 방법은 개체 풀링입니다. 기본적으로 이것이 의미하는 것은 불필요한 인스턴스화 또는 파괴를 막을 수있을 때마다 재사용 할 객체의 풀 (양에 제한이 있거나없는)을 가지고 있다는 것입니다.
다음은 간단한 개체 풀의 예입니다.
public class ObjectPool : MonoBehaviour
{
public GameObject prefab;
public int amount = 0;
public bool populateOnStart = true;
public bool growOverAmount = true;
private List<GameObject> pool = new List<GameObject>();
void Start()
{
if (populateOnStart && prefab != null && amount > 0)
{
for (int i = 0; i < amount; i++)
{
var instance = Instantiate(Prefab);
instance.SetActive(false);
pool.Add(instance);
}
}
}
public GameObject Instantiate (Vector3 position, Quaternion rotation)
{
foreach (var item in pool)
{
if (!item.activeInHierarchy)
{
item.transform.position = position;
item.transform.rotation = rotation;
item.SetActive( true );
return item;
}
}
if (growOverAmount)
{
var instance = (GameObject)Instantiate(prefab, position, rotation);
pool.Add(instance);
return instance;
}
return null;
}
}
먼저 변수를 살펴 보겠습니다.
public GameObject prefab;
public int amount = 0;
public bool populateOnStart = true;
public bool growOverAmount = true;
private List<GameObject> pool = new List<GameObject>();
-
GameObject prefab
: 이것은 객체 풀이 새로운 객체를 풀에 인스턴스화하는 데 사용할 수있는 프리 패브입니다. -
int amount
: 풀에있을 수있는 항목의 최대 크기입니다. 다른 항목을 인스턴스화하려는 경우 풀이 이미 한계에 도달하면 풀의 다른 항목이 사용됩니다. -
bool populateOnStart
: 시작시 풀 채우기를 선택할 수 있습니다. 그렇게하면 prefab의 인스턴스로 풀이 채워 지므로Instantiate
를 처음 호출 할 때 이미 존재하는 객체를 얻을 수 있습니다 -
bool growOverAmount
:이 값을 true로 설정하면 특정 시간대에 금액보다 많은 요청이있을 때마다 풀이 커질 수 있습니다. 수영장에 넣을 물건의 양을 항상 정확하게 예측할 수있는 것은 아니므로 필요할 때 수영장에 더 많이 넣을 수 있습니다. -
List<GameObject> pool
: 인스턴스화 된 / 파괴 된 모든 개체가 저장되는 풀입니다.
이제 Start
함수를 살펴 봅시다.
void Start()
{
if (populateOnStart && prefab != null && amount > 0)
{
for (int i = 0; i < amount; i++)
{
var instance = Instantiate(Prefab);
instance.SetActive(false);
pool.Add(instance);
}
}
}
시작 함수에서 시작시 목록을 채워야하는지 확인하고, prefab
이 설정되어 있고 양이 0보다 큰 경우 (그렇지 않으면 무한정 생성됩니다).
이것은 새로운 객체를 인스턴스화하고 풀에 넣는 단순한 루프입니다. 주의를 기울여야 할 것은 모든 인스턴스를 비활성으로 설정하는 것입니다. 이 방법으로 그들은 아직 게임에서 보이지 않습니다.
다음으로 Instantiate
함수가 있습니다.이 함수는 대부분의 마법이 일어나는 곳입니다.
public GameObject Instantiate (Vector3 position, Quaternion rotation)
{
foreach (var item in pool)
{
if (!item.activeInHierarchy)
{
item.transform.position = position;
item.transform.rotation = rotation;
item.SetActive(true);
return item;
}
}
if (growOverAmount)
{
var instance = (GameObject)Instantiate(prefab, position, rotation);
pool.Add(instance);
return instance;
}
return null;
}
Instantiate
기능은 유니티의 자신과 같은 Instantiate
조립식 이미 클래스 멤버로 이상 제공되었습니다 제외하고, 기능.
Instantiate
함수의 첫 번째 단계는 현재 풀에 비활성 객체가 있는지 확인하는 것입니다. 이것은 우리가 그 객체를 재사용하여 요청자에게 돌려 줄 수 있다는 것을 의미합니다. 풀에 비활성 객체가있는 경우 위치와 회전을 설정하고 활성화 상태로 설정합니다 (활성화하지 않은 경우 실수로 재사용 할 수 있음).
두 번째 단계는 풀에 비활성 항목이없고 풀이 초기 값 이상으로 커질 수있는 경우에만 발생합니다. 어떤 일이 발생하는지 간단합니다 : prefab의 다른 인스턴스가 생성되어 풀에 추가됩니다. 수영장의 성장을 허용하면 수영장에 적당한 양의 물체를 보유 할 수 있습니다.
풀에는 비활성 항목이없는 경우 세 번째 "단계"에만 발생과 풀은 증가 할 수 없습니다. 이 경우 리퀘 스터는 사용할 수있는 것이 없으며 NullReferenceExceptions
을 방지하기 위해 적절히 처리해야하는 null GameObject를 받게됩니다.
중대한!
아이템이 수영장으로 다시 들어가게하려면 게임 오브젝트를 파괴해서는 안됩니다 . 당신이해야 할 유일한 일은 그것들을 비활성 상태로 설정하고 수영장을 통해 재사용 할 수있게 할 것입니다.
단순 개체 풀
다음은 주어진 객체 유형을 임대하고 반환 할 수있는 객체 풀의 예입니다. 객체 풀을 생성하려면 create function을위한 Func과 객체를 파기하는 Action이 필요하다. 풀이 비어있을 때 객체를 요청하면 새로운 객체가 생성되고 풀에 객체가 있으면 객체가 풀에서 제거되어 반환됩니다.
개체 풀
public class ResourcePool<T> where T : class
{
private readonly List<T> objectPool = new List<T>();
private readonly Action<T> cleanUpAction;
private readonly Func<T> createAction;
public ResourcePool(Action<T> cleanUpAction, Func<T> createAction)
{
this.cleanUpAction = cleanUpAction;
this.createAction = createAction;
}
public void Return(T resource)
{
this.objectPool.Add(resource);
}
private void PurgeSingleResource()
{
var resource = this.Rent();
this.cleanUpAction(resource);
}
public void TrimResourcesBy(int count)
{
count = Math.Min(count, this.objectPool.Count);
for (int i = 0; i < count; i++)
{
this.PurgeSingleResource();
}
}
public T Rent()
{
int count = this.objectPool.Count;
if (count == 0)
{
Debug.Log("Creating new object.");
return this.createAction();
}
else
{
Debug.Log("Retrieving existing object.");
T resource = this.objectPool[count-1];
this.objectPool.RemoveAt(count-1);
return resource;
}
}
}
샘플 사용법
public class Test : MonoBehaviour
{
private ResourcePool<GameObject> objectPool;
[SerializeField]
private GameObject enemyPrefab;
void Start()
{
this.objectPool = new ResourcePool<GameObject>(Destroy,() => Instantiate(this.enemyPrefab) );
}
void Update()
{
// To get existing object or create new from pool
var newEnemy = this.objectPool.Rent();
// To return object to pool
this.objectPool.Return(newEnemy);
// In this example the message 'Creating new object' should only be seen on the frame call
// after that the same object in the pool will be returned.
}
}
또 다른 간단한 개체 풀
또 다른 예 : 총알을 발사하는 무기.
무기는 생성 된 총알에 대한 개체 풀 역할을합니다.
public class Weapon : MonoBehaviour {
// The Bullet prefab that the Weapon will create
public Bullet bulletPrefab;
// This List is our object pool, which starts out empty
private List<Bullet> availableBullets = new List<Bullet>();
// The Transform that will act as the Bullet starting position
public Transform bulletInstantiationPoint;
// To spawn a new Bullet, this method either grabs an available Bullet from the pool,
// otherwise Instantiates a new Bullet
public Bullet CreateBullet () {
Bullet newBullet = null;
// If a Bullet is available in the pool, take the first one and make it active
if (availableBullets.Count > 0) {
newBullet = availableBullets[availableBullets.Count - 1];
// Remove the Bullet from the pool
availableBullets.RemoveAt(availableBullets.Count - 1);
// Set the Bullet's position and make its GameObject active
newBullet.transform.position = bulletInstantiationPoint.position;
newBullet.gameObject.SetActive(true);
}
// If no Bullets are available in the pool, Instantiate a new Bullet
else {
newBullet newObject = Instantiate(bulletPrefab, bulletInstantiationPoint.position, Quaternion.identity);
// Set the Bullet's Weapon so we know which pool to return to later on
newBullet.weapon = this;
}
return newBullet;
}
}
public class Bullet : MonoBehaviour {
public Weapon weapon;
// When Bullet collides with something, rather than Destroying it, we return it to the pool
public void ReturnToPool () {
// Add Bullet to the pool
weapon.availableBullets.Add(this);
// Disable the Bullet's GameObject so it's hidden from view
gameObject.SetActive(false);
}
}