I am creating a 2d Platformer game.
When the pool size for the parallaxer script attached to my prefab is increased, the Unity3D application lags and freezes in larger pool sizes. This event was not present previously in other projects with substantially larger pool sizes. The event seems to persist regardless of game aspect.
Parallaxer Script with Prefab Attached
Pool Size: 10 (where it begins to lag)
Shift Speed: -1
Spawn Rate: 1
Y Spawn Range :
Min Y: 0
Max Y: 2.72
Default Spawn Pos
X: 12.81
Y:-0.03 Z:0
Spawn Immediate: Unchecked
Immediate Spawn Pos
X: 0 Y: 0 Z: 0
Target Aspect Ratio:
X: 10 Y: 16
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Parallaxer : MonoBehaviour {
class PoolObject {
public Transform transform;
public bool inUse;
public PoolObject(Transform t) { transform = t; }
public void Use() { inUse = true; }
public void Dispose() { inUse = false; }
}
[System.Serializable]
public struct YSpawnRange {
public float minY;
public float maxY;
}
public GameObject Prefab;
public int poolSize;
public float shiftSpeed;
public float spawnRate;
public YSpawnRange ySpawnRange;
public Vector3 defaultSpawnPos;
public bool spawnImmediate;
public Vector3 immediateSpawnPos;
public Vector2 targetAspectRatio;
float spawnTimer;
PoolObject[] poolObjects;
float targetAspect;
GameManager game;
void Awake() {
Configure();
}
void Start() {
game = GameManager.Instance;
}
void OnEnable() {
GameManager.OnGameOverConfirmed += OnGameOverConfirmed;
}
void OnDisable() {
GameManager.OnGameOverConfirmed -= OnGameOverConfirmed;
}
void OnGameOverConfirmed() {
for (int i = 0; i < poolObjects.Length; i++) {
poolObjects[i].Dispose();
poolObjects[i].transform.position = Vector3.one * 1000;
}
Configure();
}
void Update() {
if (game.GameOver) return;
Shift();
spawnTimer += Time.deltaTime;
if (spawnTimer > spawnRate) {
Spawn();
spawnTimer = 0;
}
}
void Configure() {
//spawning pool objects
targetAspect = targetAspectRatio.x / targetAspectRatio.y;
poolObjects = new PoolObject[poolSize];
for (int i = 0; i < poolObjects.Length; i++) {
GameObject go = Instantiate(Prefab) as GameObject;
Transform t = go.transform;
t.SetParent(transform);
t.position = Vector3.one * 1000;
poolObjects[i] = new PoolObject(t);
}
if (spawnImmediate) {
SpawnImmediate();
}
}
void Spawn() {
//moving pool objects into place
Transform t = GetPoolObject();
if (t == null) return;
Vector3 pos = Vector3.zero;
pos.y = Random.Range(ySpawnRange.minY, ySpawnRange.maxY);
pos.x = (defaultSpawnPos.x * Camera.main.aspect) / targetAspect;
t.position = pos;
}
void SpawnImmediate() {
Transform t = GetPoolObject();
if (t==null) return;
Vector3 pos = Vector3.zero;
pos.y = Random.Range(ySpawnRange.minY, ySpawnRange.maxY);
pos.x = (immediateSpawnPos.x * Camera.main.aspect) / targetAspect;
t.position = pos;
Spawn();
}
void Shift() {
//loop through pool objects
//moving them
//discarding them as they go off screen
for (int i = 0; i < poolObjects.Length; i++) {
poolObjects[i].transform.position += Vector3.right * shiftSpeed * Time.deltaTime;
CheckDisposeObject(poolObjects[i]);
}
}
void CheckDisposeObject(PoolObject poolObject) {
//place objects off screen
if (poolObject.transform.position.x < (-defaultSpawnPos.x * Camera.main.aspect) / targetAspect) {
poolObject.Dispose();
poolObject.transform.position = Vector3.one * 1000;
}
}
Transform GetPoolObject() {
//retrieving first available pool object
for (int i = 0; i < poolObjects.Length; i++) {
if (!poolObjects[i].inUse) {
poolObjects[i].Use();
return poolObjects[i].transform;
}
}
return null;
}
}
When you change an object's transform.position it can be an expensive process if the object is nested inside of other objects in your game hierarchy. Rather than change the object's transform, consider setting the GameObject to inactive when it is not in use. Additionally, rather than iterate over your list of PoolObjects every time you need to GetPoolObject(), consider creating a Queue of unused objects. This serves 2 purposes:
Example of a queue:
Queue<PoolObject> availableObjects = new Queue<PoolObject>();
class PoolObject
{
public Transform transform;
public bool inUse;
public PoolObject(Transform t) { transform = t; }
public void Use() { inUse = true; }
public void Dispose() { inUse = false; }
}
void CheckDisposeObject(PoolObject poolObject)
{
//place objects off screen
if (poolObject.transform.position.x < (-defaultSpawnPos.x * Camera.main.aspect) / targetAspect)
{
availableObjects.Enqueue(poolObject);
poolObject.transform.gameObject.SetActive(false);
}
}
Transform GetPooledObject()
{
if(availableObjects.Count > 0)
Transform poolObj = availableObjects.Dequeue();
poolObj.gameObject.SetActive(true);
return poolObj;
else
return null;
}