Stress Testing Unity's DOTS and LWRP - Zombie Game Part 1
Published on 11th May, 2019 by Administrator
So I thought I could make something that uses it!
I love zombie games! I've kind of grow up on killing floor, the walking dead was one of my favorite series (before it became shit), we played a ton of left4dead LAN with my friends from school, I even saw that terrible zombie movie where a girl and a zombie falls in love and it becomes human. Anyway, you get the point.
So what makes a great zombie game?
Zombies! A Ton of them!
There are a few pretty great examples of this in film and other media, like for example World War Z, or the walking dead.
However, I feel like most of the games fall short on the zombie amount, I've never seen a game that has literally seas of zombies but it would be super cool!
Let's fix that!
My game idea:
*Open world-ish survival game with a light story.
*Thousands possibly millions of zombies
*Vehicles that can hit zombies and some basic ragdoll physics
*Has to run on mobile with at least 30 fps (so I can finally use my AppStore dev account?)
At first glance, this seems impossible, but is it?
When optimizing something for mobile a good first question is: Okay what is the most performance expensive part of this?
In this case, it's definitely animation and rendering. I mean animation is cheap! But throw in a million zombies and it gets pretty expensive pretty fast. The other problem is rendering and draw call count, Unity still does not have proper GPU-skinning so you can't GPU-instance animated characters. Your 1 million zombies become 1 million draw calls witch is impossible on mobile and we are not even talking about the environment yet! Even if you could gpu-instance animated characters the animation calculations (blending, moving the bones, weights, etc..) would be tremendously taxing on a mobile CPU and we still need CPU for some basic physics, gameplay, ai and other stuff so good luck getting that 30 fps.
If we take a step back and take a look at how animation works in the most simplified way possible we get something like this:
What if we don't really need all the fancy real-time anim blending, ik, and all that stuff, can we just skip all of the CPU-calculations and render offset vertex positions on the GPU?
We actually can!
A few years ago I saw a few presentations about this, the magic is the following: Bake the animations into textures and play them in a shader ?
These are awesome! I highly recommend watching it!
I was pretty surprised that it already has a unity implementation for a starting point:
https://github.com/chenjd/Render-Crowd-Of-Animated-Characters
With the basics out of the way let's make some zombies!
I used Artalasky's Modern Zombie Free asset for some experimentation.
https://assetstore.unity.com/packages/3d/characters/humanoids/modern-zombie-free-58134
First of all, I made a decimated low-poly version of the model with merged textures with Simplygon.
Remember, we are storing every vertex's position in every frame on a texture. So more vertexes == more baking time and more expensive. Luckily Simplygon did a pretty good job baking the small mesh details into a normal map.
Next, I uploaded it to mixamo and got a few zombie animations. I wanted to have different states for the zombies so I got an idle, walk, run, and crawl animation. This way I can GPU-instance every zombie so I can render a lot of them in just a few draw calls and add per-instance data such as state, blending animation offset, random color etc via Unity's material property blocks.
Here is my first result with 10 000 zombies in just 22! draw calls. This could be even less if you modify the max instance count but it was good enough for me as a test.
But what about blending? I really wanted to blend between animations so i implemented the simplest possible blending into my shader. Basically what it does it samples 4 animation-textures (idle, walk, run, crawl), multiplies each of them with an amount (like walk amount, idle amount, etc..) divides with 4 and returns the result. This result then will be our new vertex-position for the given frame.
This way we can animate the blending with only a couple of floats and an animation curve from a C# script.
You can see an example of how this works below:
Now that rendering and animation are (probably) not a problem anymore the most performance expensive part becomes pathfinding, navigation and AI.
This is where DOTS comes into the picture! The entity component system could be a great way to deal with a lot of zombies AI-wise. My plan is to implement pathfinding and AI in the new and shiny entity component system. If you never heard of unity's DOTS before check it out: https://unity.com/dots
Stay tuned for part 2 where I implement pathfinding and ai based on ECS and make this project more like a game ?
Thanks for reading!
Feel free to contact me on twitter if you have any questions or just follow me to don't miss any future updates :D (@PreyKW)