Let’s get bloody

Let’s get bloody

posted in: Dev blog | 0

When you think about blood action shoot em ups you may think about a few splats here and there, or something more dynamic, bloody, gore, if you like. That’s what we thought about Trespassers. When the 5.2 Unity3D update was released, we were looking forward the promised particle collision module for 2D particles. It was really promising, and looked great … if you didn’t mind spending CPU on tons of particles. In case you did, you could just downgrade the quality of the collisions, but then, weird things started to happend.

As we wanted blood to fall, gravity was a must. But low quality physics settings mixed with gravity led to particles falling through the colliders. The result was not as satisfying as we expected. We made few tests with the profiler, to look if there was something wrong. Images show both quality settings (Low on the left, High on the right):

Trespassers_BloodLow
(Low) Notice how almost no particle stands on the ground, and many of them go through the wall

Trespassers_BloodHigh
(High) Not the best, but the example does not represent a regular case either

┬áLet’s take a look at the profilers at a moment in the simulation:

Trespassers_BloodHigh_PerfTrespassers_BloodLow_Perf

The first one is for the high quality, second for low quality settings. The interesting point, beyond the high difference in FPS lost, is the method used at each setting. And one thing to consider is that, even at high quality, the profiler does not show the CircleCast at every Update. This sparked the idea of generating our own particle system specific for blood.

Trespassers_BloodCustom

Above you can see how does our custom blood particle system behave with roughly the same amount of particles. They collide, stick to the wall and, once on the floor, they simulate the blood spilling and moving as more blood falls. We did care about performance, although we could still trade some fidelity for speed. For example, updating physics for just some of the particles each frame, instead of all. Let’s look at the times measured.Trespassers_BloodCustom_Perf

You may notice that we are sligthly below the “high quality” ms used, and the simulation is way better. Although it would make for a really lengthy post, we want to share an overview of the insights.

  • Particle emitters interact with a global Particle system, specific for the blood. This way is easier to manage them globally, and not depending on the emitter. This is more a design decision, but makes easier some performance issues too. For example, the maximum particle allowance is alloc’ed when the particle system is created, avoiding GC spikes.
  • Particle emitters just control the emission, not the particle itself. Once the particle is emitted, it is managed by the particle system, even if the emitter disappears.
  • Particle system is responsible for drawing the particles too. This was one of the performance problems we faced, as drawing so “many” particles needed a bit of care. We ended up using a MeshFilter with a quad generated per each particle. Rendering is really fast this way, and although we tried the “direct” OpenGL-alike approach, the MeshFilter gave better results.
  • Particle’s lifecycle was an interesting problem. We wanted to avoid extra GC calls, so once a particle dies, it is pooled. We made use of the same array for particles either alive or dead. The dead particles were put at the end, and all the particles still alive, at the start. The current number of particles alive was updated each frame, and only those below that index were updated. However, we didn’t want to sort the array by lifetime left, as this was really dynamic and prone to changes easily as soon as an emitter threw new particles. So in the end, we started swapping positions in the array. We don’t think it is cache friendly, so here may be room for some improvement.
  • Physics interaction was made via Physics2d.LinecastNonAlloc. The reaction to a collision was the hardest part, as we needed blood to slide along the surfaces, be a bit bouncy, sticky and have dampen too. We factored the bounce inversely by the stickiness factor and the dot of the normal of the impact and the up vector. Although the maths are not accurate, the result is satisfying enough
  • We missed the liquid spilling when more drops falled. Like if it was filling the ground. Sort of. Checking particle-to-particle collision was not even considered (there is middleware for this in the Asset Store), We came with a different approach. Our simplification was that, when a particle collides near other particles, it may interact with it, and the interaction increases with the number of particles. So we sorted the particles by collider hit. But it was problematic, as the same collider could be hit by very different positions, and the results usually lead to weird behaviours. So in the end, we just made a partition of the space, so particles colliding in the same partition, added to a per-partition amount that represents how easily a particle colliding may slide along the surface hit. As gravity is interacting, when the particles are “resting”, they are moving a bit, creating the illusion of spilling. It is not accurate, but enough for a fast paced game.
Custom Blood Particles System
Max 10000 particles. Jaggy borders are caused by the GIF compression.

In the end, although the simulation lets enough free CPU cycles for our game, we miss a 2D multithreaded physics system. This is the kind of problems where it would shine (tons of particles and projectiles), but we will have to wait for it, if it ever comes to Unity3D.

Feel free to leave a comment ;). See ya!

Leave a Reply