- Home /
Performance issues when creating many tiny objects
I have a script that allows me to create an explosion effect on a sprite. It iterates through each pixel of the sprite (24x24 on average) and creates a 1x1 pixel game object for each pixel with the color of the original pixel. It then gives each pixel a random velocity. This creates the effect that the sprite is exploding into it's individual pixels and looks pretty cool.
My issue is that when using this on larger sprites or on more than 5 sprites at once I run into some performance issues and lag spikes. I can't really use an object pool for this since the pixels have to be specific to the object's current sprite during it's animation.
How could I optimize the code below?
using UnityEngine;
using System.Collections;
public class ExplodeEffect : MonoBehaviour
{
public GameObject pixelPrefab;
public void Explode(Vector3 velocity, Sprite sprite)
{
for (int i = 0; i <= sprite.bounds.size.x * 10f; i++)
{
for (int j = 0; j <= sprite.bounds.size.y * 10f; j++)
{
Vector2 positionOffest = new Vector2(sprite.bounds.extents.x - sprite.bounds.center.x,
sprite.bounds.extents.y - sprite.bounds.center.y - 0.05f);
Vector3 pixelPosition = transform.TransformPoint((i / 10f) - positionOffest.x,
(j / 10f) - positionOffest.y, 0);
Color pixelColor = sprite.texture.GetPixel((int)sprite.rect.x + i,
(int)sprite.rect.y + j);
if (pixelColor.a != 0f)
{
GameObject pixelInstance = Instantiate(pixelPrefab, pixelPosition, Quaternion.identity) as GameObject;
pixelInstance.GetComponent<SpriteRenderer>().color = pixelColor;
pixelInstance.rigidbody2D.AddForce(new Vector2(((velocity.x * 10f) + Random.Range(-250, 250)) * 2.5f,
((velocity.y * 10f) + Random.Range(-250, 300)) * 2.5f));
}
}
}
}
}
Update: Thanks to @Bunny83 I got a working particle system implementation. Below is my modified version of his code.
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
public class SpriteExplosion : MonoBehaviour
{
public float pixelsPerUnit = 10f;
public float lifetime = 4f;
public string sortingLayer = "Foreground";
public int sortingOrder = 1;
public void Explode(Vector3 velocity, Sprite sprite)
{
StartCoroutine(DoExplode(velocity, sprite));
}
private IEnumerator DoExplode(Vector3 velocity, Sprite sprite)
{
ParticleSystem partSystem = GetComponent<ParticleSystem>();
List<ParticleSystem.Particle> particles = new List<ParticleSystem.Particle>();
ParticleSystem.Particle currentParticle = new ParticleSystem.Particle();
partSystem.renderer.sortingLayerName = sortingLayer;
partSystem.renderer.sortingOrder = sortingOrder;
currentParticle.size = 1f / pixelsPerUnit;
for (int i = 0; i < sprite.bounds.size.x * 10f; i++)
{
for (int j = 0; j < sprite.bounds.size.y * 10f; j++)
{
Vector2 positionOffset = new Vector2(sprite.bounds.extents.x - sprite.bounds.center.x - 0.05f,
sprite.bounds.extents.y - sprite.bounds.center.y - 0.05f);
Vector3 particlePosition = transform.TransformPoint((i / 10f) - positionOffset.x,
(j / 10f) - positionOffset.y, 0);
Color particleColor = sprite.texture.GetPixel((int)sprite.rect.x + i,
(int)sprite.rect.y + j);
if (particleColor.a != 0f)
{
currentParticle.position = particlePosition;
currentParticle.rotation = 0f;
currentParticle.color = particleColor;
currentParticle.startLifetime = currentParticle.lifetime = lifetime;
currentParticle.velocity = new Vector2(velocity.x + Random.Range(-5f, 5f),
velocity.y + Random.Range(-5f, 6f));
particles.Add(currentParticle);
}
}
}
partSystem.SetParticles(particles.ToArray(), particles.ToArray().Length);
Destroy(gameObject, lifetime);
yield return new WaitForSeconds(1f);
}
}
Just think about what you are doing. 5 sprites 24x24 size: 5x24x24 = 2880. You have 2880 pairs of Instantiate+GetComponent calls (just with 5 average size sprites). That's a lot for a function that is supposed to execute in a single frame. Besides, Unity still has to deal with the rest of objects in scene. You'll get a performance spike for sure.
Besides the fact that you actually can use pooling (maybe you don't see it now but you will see it, believe me), if you want to stick to your instantiate approach maybe you should consider the use of a coroutine to distribute the prefab instantiation over a few frames.
As Andres has pointed out, you can use pooling. Just take one object from the pool (which will be the same as if you had an instiated prefab) and change its values as you do right now.
It will improve your performance because Instantiate is a really heavy function in performance terms.
You could try to make one GameObject which manages all the pixel colors, positions and velocitys. Then you draw each pixel onto one big texture.
And as stated above you should consider pooling.
@Andres How big should I make the object pool? I won't know how many object's I'll need until they are needed. For now I'll give your co-routine idea a shot.
Particle system is the answer, as indicated by @Bunny83. A particle system is optimised specifically for this type of work.
But to complete the discussion on object pooling there are several options to make a pool work without knowing the total size. In no particular order:
Calculate a maximum theoretical size of the pool
Allow the pool to grow
Build a pool size to match your performance budget. Drop objects once your budget is spent.
Answer by Bunny83 · Oct 10, 2014 at 08:46 AM
I would recommend to use a ParticleSystem instead. You just need to setup the grid with particles and move / scale them to match the pixels positions / sizes / colors.
Watch out! There are two different "Particle" structs. The one in the UnityEngine namespace belongs to the old legacy Particle system. The new ParticleSystem has it's own struct as nested class "ParticleSystem.Particle". Particle systems are designed to handle several thousands of particles.
edit
Ok here is my little sample webplayer as usual :) It also has a link at the bottom to the unitypackage for that example.
Originally i've created this example with Unity beta, however i just discovered that you can't publish beta webbuilds as the Unity webplayer doesn't have the beta player available automatically. So i migrated to the latest Unity version. Unfortunately Unity beta has a quite useful change on the Sprite class which allows you to read the "pixelsPerUnit" value. I've commented that line out and replaced it with a hardcoded value for each sprite. The pixelsPerUnit are important to determine the pixel / particle size in worldspace.
The sample scene contains some sprites with different resolutions and settings. Keep in mind that non-uniform scaled sprites (so with different x and y scale values) can't be proberly represented with a billboard particle since those are always square. I simply use the area-average at the moment. It would be possible to create a custom mesh on-the-fly and use that mesh as particle, however i guess that's not worth the trouble.
Finally i'd like to mention that the angularVelocity of the particles simply don't work in a lot cases. This has been an issue for ages and it still exists. Setting a rotation manually works, however the angularVelacity somehow get reset automatically to 0.
All in all it does work quite well. Even with nearly 80k particles (that's about 320k vertices) it still runs at 60fps (at least on my PC :))
Keep in mind that a single ParticleSystem can't have more than 64k vertices, so the max resolution of your sprite isn't that high. I have used a 32x32 and two 128x128 textures. However i actually let the ParticleSystem discard pixels with an alpha value of 0 by setting the lifetime to a negative value. That way those particles are removed even before they are rendered for the first time. This can of course be optimised to truly discard those particles and use a smaller array. This however requires you to either preprocess the whole image once to count the actual pixels or to use a dynamic List instead of an array. Both include additional overhead.
I originally tried this approach however I couldn't figure out if I was supposed to have a ParticleSystem already on the object or if it was all already done from code. If you could provide a more complete solution that would be much appreciated.
@Bunny83 Just now saw your update! I'll try it out and let you know the results.
I'll be taking that with me. Look forward to pixel by pixel explosions in my game soon.
Works great for me.
Your answer
Follow this Question
Related Questions
Distribute terrain in zones 3 Answers
Multiple Cars not working 1 Answer
Activating GameObject(s) is slow 2 Answers
Is there a way to optimize Loading.PersistentManager? 2 Answers