unity3d
オブジェクトプーリング
サーチ…
オブジェクトプール
ゲームをするときに、同じタイプのオブジェクトを繰り返し作成して破壊する必要があります。プレハブを作成し、必要に応じてこれをインスタンス化/破棄することで簡単に行うことができますが、これを行うと効率が悪くなり、ゲームが遅くなる可能性があります。
この問題を回避する1つの方法は、オブジェクトプーリングです。基本的には、不必要なインスタンス化や破壊を防ぐためにいつでも再利用するオブジェクトのプール(量に制限があるかどうかにかかわらず)があることを意味します。
以下は、単純なオブジェクトプールの例です
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
:開始時にプールを作成するかどうかを選択できます。そうすることで、プレハブのインスタンスでプールがいっぱいになるので、初めて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より大きい場合は(そうでなければ無期限に作成する)
これは、新しいオブジェクトをインスタンス化し、それらをプールに入れる単純なループです。注意すべき点の1つは、すべてのインスタンスを非アクティブに設定することです。この方法では、まだゲームでは表示されません。
次に、 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
機能はUnityの独自Instantiate
機能のように見えますが、プレハブはすでにクラスメンバーとして用意されています。
Instantiate
機能の最初のステップは、プール内に非アクティブなオブジェクトが存在するかどうかを確認することです。つまり、そのオブジェクトを再利用してリクエスタに返すことができます。プール内に非アクティブなオブジェクトがある場合は、位置と回転を設定し、アクティブに設定します(そうしないと、アクティブにするのを忘れた場合は偶発的に再利用される可能性があります)。
2番目のステップは、プール内に非アクティブな項目がなく、プールが最初の量を超えて増加する場合にのみ発生します。何が起こるかは簡単です:プレハブの別のインスタンスが作成され、プールに追加されます。プールの拡大を許可すると、プール内に適切な量のオブジェクトを配置できます。
3番目の「ステップ」は、プール内に非アクティブな項目がなく、プールが拡張できない場合にのみ発生します。これが起きると、リクエスターは空のGameObjectを受け取ります。これは何も利用できなかったことを意味し、 NullReferenceExceptions
を防ぐために適切に処理する必要があります。
重要!
あなたのアイテムがプールに戻るようにするには、ゲームオブジェクトを破壊してはいけません 。あなたがする必要があるのは、それらを非アクティブに設定することだけで、プールを介して再利用できるようになります。
単純オブジェクトプール
以下は、特定のオブジェクトタイプのレンタルと返却を可能にするオブジェクトプールの例です。オブジェクトプールを作成するには、create関数の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.
}
}
別の単純なオブジェクトプール
別の例:弾丸を撃つ武器。
Weaponは、作成するBulletsのオブジェクトプールとして機能します。
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);
}
}