0
\$\begingroup\$

I'm doing a lot of 2D simulations using Unity, and I usually instantiate a lot of squares/circles sprite. When the number of sprites reaches 1k or higher, the FPS usually reduces to 10-20, which makes everything really slow/unusable.

So my question is, how can I draw 2D squares/circles with less impact on frame rate (maybe using some other methods as well)? The only requirements are that the color, position and scale of individual squares/circles must be changeable (for the simulation), and the squares/circles must be filled (with that color).

\$\endgroup\$
3
  • \$\begingroup\$ Is your bottleneck the instantiating or having them at the same time? Do they have to have some physic interaction or would a particle system work just as well? \$\endgroup\$ Commented Apr 7, 2022 at 14:36
  • \$\begingroup\$ @Zibelas Instantiating is not much of a problem, as I usually create them gradually with a coroutine; it's just having too many at the same time wears the fps down. Also, no physics interaction need (colliders, rigidbodies,...) but as stated above, they have to be controllable in some way. \$\endgroup\$ Commented Apr 7, 2022 at 14:45
  • 1
    \$\begingroup\$ Maybe this could be something for you? docs.unity3d.com/Manual/JobSystemOverview.html \$\endgroup\$ Commented Apr 7, 2022 at 14:47

2 Answers 2

1
\$\begingroup\$

I'd second Zibelas's recommendation in the comments to use a particle system.

You can create an array to hold the information about all of your shapes:

const int MAX_SHAPES = 5000; ParticleSystem.Particle[] _shapeBuffer = new ParticleSystem.Particle[MAX_SHAPES]; 

Then iterate over that array to update the positions / rotations / shapes / colours / etc. of the shapes you want to draw.

for (int i = 0; i < currentShapeCount; i++) { var particle = _shapeBuffer[i]; particle.position = UpdateShapePosition(particle, i); particle.startColor = UpdateShapeColor(particle, i); particle.startSize = UpdateShapeSize(particle, i); particle.rotation = UpdateShapeAngle(particle, i); // Keep this particle alive forever. particle.startLifetime = float.MaxValue; particle.remainingLifetime = float.MaxValue; _shapeBuffer[i] = particle; } for (i = currentShapeCount; i < MAX_SHAPES; i++) { var particle = _shapeBuffer[i]; // This kills the particle. ;) particle.remainingLifetime = -1; _shapeBuffer[i] = particle; } 

Then write your changed buffer back to the particle system to be drawn:

particleSystem.SetParticles(_shapeBuffer, MAX_SHAPES, 0); 

If your shapes should move in predictable ways and only a few of them need custom updates, you can delegate that to the particle system by assigning particles a velocity / angular velocity, and using GetParticles to read its updates back into your C#-side buffer when you want to intervene.

You can even use the flipbook feature to draw both your circles and squares with a single buffer, without any shader magic, by using the randomSeed or remainingLifetime parameters to control which flipbook page this particle uses.

The job system is also great for big batch work like this, but it's a bigger commitment with a steeper learning curve and initial development cost than a quick-and-dirty abuse of the particle renderer. 😉

\$\endgroup\$
1
  • \$\begingroup\$ Actually never thought of using Particle System since I thought particles can't be controlled individually. Really interesting! \$\endgroup\$ Commented Apr 8, 2022 at 11:53
2
\$\begingroup\$

The existing answers are constructive. Here are some other suggestions:

First of all, it is necessary to clarify what caused the program slow. There are many reasons for it such as code logic, draw calls, rendering.

  1. Use Profiler to get performance information. Find out which line of code cost most cpu time and rewrite it more efficiently.

  2. Check your Draw call on Game tab -> Stats -> Batches. If the number of draw call is high, Ensure Dynamic Batching is taking effect.

    The condition of batching is:

    1. All sprites are in the same SpriteAtlas.
    2. All sprites share a same Material. You can set different color to material at runtime.
    3. Batching option is open.
  3. Usually the CPU is the performance bottleneck in small project. But if you suspect it is the problem of GPU rendering, go check FrameDebugger.

Before using JobSystem, Make sure your code is thread-safe at the functional level. For example, If the behaviour of an object depends on another, they can not run Parallel.

\$\endgroup\$

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.