Optimising Unity

For the last three or four weeks, I’ve been trying to fix several performance issues I found while testing. Some of these are graphics issues, which I can’t really fix myself, but the scripting ones are things I can work on. Some of these come from an interesting thread on the Unity forums – which is definitely worth a read. Anyway, here’s the changes.

Removing concave colliders
Concave colliders in Unity are Mesh Colliders that don’t have the “Convex” button checked, and they’re discouraged because they’re quite expensive. In quite a few cases, it is possible to decompose the mesh into primitives, and Unity themselves recommend using compound colliders where possible. I admit, I’ve been guilty of doing this for Spamocalypse – a lot of the scenery is built using Mesh Colliders, though I have been careful to mark these as static to try and offset this, and I’ve been breaking some of them down into separate parts. As it stands, I had to replace the octagonal sound detection meshes with capsule colliders to get the physics cost down when I had at least ten active NPCs in the scene.

Splitting the second level
I ran into a massive problem building the lighting with the second level, mainly due to Unity’s global illumination having a problem with large scenes. There is a way around that, which is to temporarily scale the level down to about 1% of its original size, but I also had a problem with generating the navigation mesh. So, I’ve split part of the second level into a third, which the player will enter via a sewer pipe. This means I’ve had to add more scenery and things to find…which I had planned anyway! So, win-win.

Profiling the build
Normally, you’d test the game in the Editor. The problem is, testing in the Editor includes some extra overhead, so the profiler won’t necessarily be accurate. So, the best thing to do is to profile the final build. The documentation is a great starting point, especially the part about manually profiling segments of code using “Profiler.BeginSample” and “Profiler.EndSample”. Here’s a quick example:


float calculateDistance(Vector3 firstPosition, Vector3 secondPosition)
{
Profiler.BeginSample("getSum");
float distance = Vector3.Distance(firstPosition, secondPosition);
Profiler.EndSample();
return distance;
}

Doing so has uncovered several time sinks in my pathfinding code: the method to translate a position into a node was taking about 5 milliseconds. This is due to my (over-)reliance on Linq to search for nodes: using an Aggregate query on a nav mesh of about 10000 nodes is going to impact performance! So, what I did was change the following:

Node theNode;
if (nodes.ContainsKey(requestPos))
{
theNode = nodes[requestPos];
}
else
{
theNode = nodes.Values.Aggregate((d, n) => Vector3.Distance(d.position, requestPos) <
Vector3.Distance(n.position, requestPos) ? d : n );
}

to round the position to one decimal place before checking if the position exists. If not, it retrieves a limited list of nodes that are less than e.g. 1 metre from the specified position, and then finds whichever fits closest. This alone cut the request time from about 5 ms to 3ms.

Node theNode;

// Round to 1 decimal place
requestPos.x = Mathf.Round(requestPos.x * 10) / 10;
requestPos.y = Mathf.Round(requestPos.y * 10) / 10;
requestPos.z = Mathf.Round(requestPos.z * 10) / 10;

if (nodes.ContainsKey(requestPos))
{
theNode = nodes[requestPos];
}
else
{
// first, prune any nodes that are too far away
// then find whichever is closest to the requested position
List tmpNodes = nodes.Values.Where(d=> Vector3.Distance(d.position, requestPos) Vector3.Distance(d.position, requestPos) <
Vector3.Distance(n.position, requestPos) ? d : n );
}

However, this is still slower than I’d like. I really need to find a better way to calculate light intensity; baking it into the navigation mesh wasn’t as good as I thought!

New asset on GitHub: listing static objects

So, I have a new repo on GitHub. This is an Editor utility script that divides scene objects into static and non-static objects and writes them, along with concave Mesh Colliders, into a text file.

The reason I wrote this is that both of these are excellent ways to boost performance in Unity. Marking an object as static tells the physics engine that this object will never move, so it only needs to check the object’s position once. This lowers the amount of work the physics engine has to do, which frees up the CPU a bit.

Concave Mesh Colliders are another one. Mesh Colliders (and the 2D equivalent PolygonCollider) are based on the object’s mesh, so each vertex in the mesh is accounted for. These are the most accurate colliders, which comes at the expense of performance, but marking them as convex can offset this. This will result in the collider approximating the shape of the mesh, which is far quicker to calculate.

The initial release works out of the box on 5.3.4, but I may add some extras. For starters, the location and name of the file is hard-coded to Assets/staticObjects.txt. Secondly, the list is only valid for a single scene at the moment – if you run it in another scene, it overwrites the file. Neither is particularly difficult, but I’ll get around to it during the week.