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!


Comments

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.