Building Simple Audio Visualization with Unity ECS Part III — ECS

Ernes Budiman
Kolektif Gamedev
Published in
5 min readAug 6, 2018

This is the last part from the three part series of creating a simple audio visualizer to learn ECS. The first part we talked about how to make things work with MonoBehavior way. The second part we change the MonoBehavior into a Jobified Unity Job System. Today we’re going to walk to our end goal, transforming our Audio Visualization into ECS form.

The promise of the new ECS system is the performance gain. Usually before moving from one coding principles to another, we have to list all the problems that we could get when changing into the new paradigm. Is the performance gain worth the hassle? Is it worth the time investing things to change the codebase into this new ECS things? But in games, almost always we must pursue the performance gain, because the experience of playing a games smoothly would always beat codework.

The ECS guarantees linear data layout when iterating entities in chunks, this is the critical performance gains that is promised by ECS, before delving deeply into ECS we need to clear out few things.

ECS have three core concept, Entity, Component and the System. Entity is basically an ID, you can add component or remove component into it. The component itself is classes which contains data with no behavior, it is used purely for data. Then come the system which told to do a specific things. Sounds confused? If you’re creating a system to scale all cube entities then all you’ve to do is query all entities in the world that have “cube” id, then for each entity, we’re going to scale the object like we used to do.

There are a couple of changes that we need to do in our code, but first we must identify which one is entity, our component data, and the systems that are going to work on them.

Our entity in this case is the cube, component data is the origin, then for the systems we need a system to spawn all the cube, process the audio data, then change the scale of the cubes, at least three system.

Our MonoBehavior is now gone, changed into a static Init which sole purpose is to initialize our world with the entityManager, get the Settings, and initialize our systems. CubePrototype is used to spawn our cubes that is now added to Unity hierarchy view. CubePrototype have MeshInstanceRenderer that defines it’s look and Unity built-in Game Object Entity Script, this is used so that Unity ECS know that this is an Game Object Entity and could be processed on ECS system.

Cube is used as an id, every entity that is created with this Cube component data is considered a Cube. For the Origin component data, it is used to store the cube origin value before we offset it.

The process of spawning and arranging the cube spirally has now moved into SpawnCubeSystem. Also we disabled the built-in TransformSystem on Unity, we will talk about this later.

Here are the SpawnCubeSystem

Our Init method is used to create the entityArchetype and spawning cubes entity from the archetype. As you can see that we create the archetype with the Cube component, Origin, TransformMatrix, Position, LocalRotation and Scale. This means that our cube entity that we are going to spawn has all those component data.
A CubesGroup is a struct that is used inside SpawnCubeSystem, what it does is it’s collecting all Entity which has Cube, Position, and Origin. Then we laid out the cubes positions spirally on the OnUpdate() method. We do this one time only so we need to make sure that even though the OnUpdate is called each time the system update, it won’t change the position again.

If you want to change the ComponentData value, currently the code in the current Unity ECS needs to copy that into another variable first, change it, and then assigning it back. You’ll often see this kind of code in the ECS system.

Position position = group.positions[groupIndex];
position.Value = new float3(pos.x, 0, pos.z);
group.positions[groupIndex] = position;

Unity said it would change it in the future so it’ll be less cumbersome than now. Anyway after we set each positions and origins, we now need create the system to update the scale of the cubes. Below you could see the SpectrumSystem snippet

Different from the SpawnCubeSystem that is derived from ComponentSystem, our SpectrumSystem derived from the JobComponentSystem since we need to schedule the SpectrumJob here. As you notice that the content of the SpectrumSystem is not a big difference with the previous Job version. It only added the SpectrumGroup to read the component data, everything else is the same.

Now our ECS system is now ready except for one thing, the current unity version that I use don’t have a scale component, so in order to make the transform works, we need to disable the built-in TransformSystem and make our own version ofCustomTransformSystem that take a scale changes into account. You would remember that we disable the TransformSystem with

World.Active.GetExistingManager<TransformSystem>().Enabled = false;

The contents of the CustomTransformSystem is this

It sole purpose is to add the scale changes of the component into the TransformMatrix ComponentData.

You could see the result video below

https://www.youtube.com/watch?v=S8u0nA8YDNQ

Previously we could only do 4086 sample for two channels, now doing 8192 sample for two channels (16384 data) is no sweat.
Here are the result comparison between MonoBehavior and ECS + Job. I’m testing this on MacBookPro Mid 2015 2.2 GHz Intel Core i7, 16 GB 1600 MHz DDR3.

MBP Mid 2015 2.2 GHz Intel Core i7 
MonoBehavior: 8192 cubes = FPS Roughly 45 - 50ish
ECS + Job : 8192 samples x 2 Channels ~ 16384 cubes = FPS 110++

The ECS shown on the video is displaying two channels. Again, the code for making two channel displayed is omitted to make things shorter, but what you would do for two channels is making sure you create an Entity with a different component data that is used only for differentiating left and right spectrum. Because the way the GetSpectrumSample works that it returns the data linearly, 0 — sampleSize for left channel and 0 — sampleSize for right channel, but the way things processed on the spectrum job is operating things sequentially, ofsetting the index inside the Execute causes some Error on Unity. Or you could try merging the data from left channel and right channel into one contiguous array before it is processed by SpectrumSystem, I haven’t tried this approach yet.

And there you have it, the ECS version of a simple Audio Visualizer. We have walk through the process from making a simple MonoBehavior, changing into a JobSystem, and then moving to a ECS one. Full source code for references on github.

Thank you for reading! hope this shed some light on how to approach the new Unity ECS system.

--

--