Building multiple platforms with one click

EDIT: this is now up on GitHub.

I finally got tired of manually changing platforms after creating builds, so I went and created a customised BuildPipeline. This creates a 64-bit Linux build, then switches to 64-bit Windows, and ends with 64-bit Mac OSX, all with one click of a button. I wish I had known how to do this earlier!

The basis of this is manually calling BuildPipeline.BuildPlayer. This requires a few parameters:

  • An array of strings with the names of the levels you want to include
  • The name and location of the executable (i.e. what the players are going to click on)
  • The target platform (in my case, 64-bit standalones for Linux, Windows and Mac).
  • The BuildOptions. This is the tricky one: according to the docs, it is possible to combine multiple values of this, but I haven’t figured out how to do this yet. It might be because I’m on 5.3.6, instead of 5.5.

And that’s it. Here’s what the code looks like:

using UnityEditor;
using UnityEngine;
using System;

public class MyBuildPipeline {

// constants for defaults
const string WINDOWS_FILENAME = "_Windows";
const string LINUX_FILENAME = "_Linux";
const string MAC_FILENAME = "_Mac";
const string SCENE_FOLDER = "Assets/_Scenes/";
const string BUILD_PATH = "Player Files/";

// TODO: find how to combine these
static BuildOptions devOptions = BuildOptions.AllowDebugging;

[MenuItem("Tools/Aceade/Build")]
public static void Build()
{
Debug.Log("Creating new builds");
var levels = new string[6]{
SCENE_FOLDER + "Preloading.Unity", SCENE_FOLDER + "Main Menu.unity", SCENE_FOLDER + "Sound Test.unity",
SCENE_FOLDER + "City Inbound.unity", SCENE_FOLDER + "Inner City.unity", SCENE_FOLDER + "Museum Model.unity"
};

// calculate the build date
DateTime date = DateTime.Now;
string buildDate = string.Format("_{0}.{1}.{2}",date.Day, date.Month, date.Year);
Debug.LogFormat("Creating new builds on {0}", buildDate);

MakeLinuxBuild(levels, buildDate, BUILD_PATH);
MakeWindowsBuild(levels, buildDate, BUILD_PATH);
MakeMacBuild(levels, buildDate, BUILD_PATH);
}

///

/// Makes a Linux build.
///

/// Levels to build.
/// Date string.
/// Path.
static void MakeLinuxBuild(string[] levelsToBuild, string dateString, string path)
{
Debug.Log("Creating Linux build");
string fileName = LINUX_FILENAME + dateString + ".x86_64";
BuildTarget target = BuildTarget.StandaloneLinux64;
BuildPipeline.BuildPlayer(levelsToBuild, path + fileName, target, devOptions);
}

///

/// Makes the Windows build.
///

/// Levels to build.
/// Date string.
/// Path.
static void MakeWindowsBuild(string[] levelsToBuild, string dateString, string path)
{
Debug.Log("Creating Windows build");
string fileName = WINDOWS_FILENAME + dateString + ".exe";
BuildTarget target = BuildTarget.StandaloneWindows64;
BuildPipeline.BuildPlayer(levelsToBuild, path + fileName, target, devOptions);
}

///

/// Makes a build for Mac devices.
///

/// Levels to build.
/// Date string.
/// Path.
static void MakeMacBuild(string[] levelsToBuild, string dateString, string path)
{
Debug.Log("Creating Mac OSX build");
string fileName = MAC_FILENAME + dateString + ".app";
BuildTarget target = BuildTarget.StandaloneOSXIntel64;
BuildPipeline.BuildPlayer(levelsToBuild, path + fileName, target, devOptions);
}
}

I’m considering adding this to GitHub, once I’ve figured out the BuildOptions.

UnityList: Where to find open-source Unity projects

This is not my work, but it’s something I want to spread the word about. Somebody has created a site to list all the Unity-related projects on GitHub.

It is in beta, so there’s still a few things missing. Firstly, it doesn’t allow you to filter by licence yet, but that’s coming. Secondly, it doesn’t seem to preserve the formatting in the documentation/README. However, those are the only issues I have with it, and it’s an awesome tool. So, another place to look for snippets that might be useful when I get around to doing a new project.

NullHunter: An Editor script that flags potential Null References

When giving Spamocalypse a test recently, I ran into an issue where I hadn’t initialised a variable, resulting in my console being filled with NullReferenceExceptions. This annoyed me enough that I went and wrote an Editor extension that looks for null public variables in a particular MonoBehaviour.

It’s based around reflection, which is the ability of a computer programme to examine itself. This allows you to do things like create mock objects for unit testing, examine the fields of a class, or even change a private variable or method to public! This is something I’ve never done before, but it only took me a few hours of reading the MSDN docs and a quick check on the Unity forums when I ran into an ArgumentException before I was able to make a basic version.

You can find it on GitHub. I’ve decided to release it under an MIT licence; so you just need to attribute it to me if you use it yourself, but there are no restrictions on how you use it.

Update: I’ve created <a href=”https://www.youtube.com/watch?v=8FrS6jHotsg”>a video demo</a>.

 

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.

Spamocalypse: Respawn mechanism and NPC models

Another update to Spamocalypse. This time, I’ve added a respawn mechanism for the spammers. They will respawn after a 30 second delay, which will remain constant across all difficulties.

My experience of stealth games has mainly been shaped by Thief and Thief 2. In those games, killing people is discouraged – in fact, on expert difficulty killing people is forbidden. On the lower difficulties, it is generally allowed (though Thief 2 has a few missions where it has to be kept to a minimum for the sake of the plot), but it is discouraged by the fact that you have to hide bodies, and that does include removing their blood stains. I don’t have those in Spamocalypse, so I’ve added the respawn mechanism as an alternative to that.

What happens is that when an enemy is killed, the Level Manager will wait thirty seconds and respawn them in the same spot. If this happens too often, they will be come suspicious and start searching around them. The criteria for “too often” will depend on the NPC type – spambots require about 20 deaths to realise that something is wrong, regular spammers will cop on at about 5 deaths, and Moderators will realise this after 3 deaths. In the meantime, other NPCs who find their corpse will trigger an alert.

I also have some NPC models! These haven’t gone into a build yet, as I’ve only just finished the Moderator. However, they are textured and, after following this tutorial, they are animated.

Spambot/Dumbbot
The Spambot/Dumbbot is the stupidest weapon available to the Word of Turscar. It trundles around blaring out Turscarite propaganda (i.e. spamming loudly) and launching cans of spam at anyone who gets in the way. They were designed to be mass-produced and deployed en masse for assaults on target Sites, and have not been programmed to account for Sockpuppet during their sales attempts.

Not very bright, but it doesn't need to be. This is what I imagine spambots look like.
Not very bright, but it doesn’t need to be. This is what I imagine spambots look like.

Regular
These two show a regular spammer, which is really just a zombie that vomits spam. Excessive exposure to spam may result in conversion. They can use whatever humanity they have left to identify a Sockpuppet, and will search nearby when they recognise one.

Spammer1
If they’re idle, it’s because they need to take a break. Hence the preference for bots during attacks.
Spammer1 Attacking
Achoo! It’s supposed to be vomiting spam rather than sneezing, but I haven’t got that far yet.

Moderator
The Order of the Hammer of Moderation is an order that is pledged to protect Sites from Turscarites, Trolls and other degenerates of the Nett. Any Moderator (or Hammerite) that is caught by the Turscarites can expect a long, slow and painful process of conversion. When the process is finished, they are no longer human, and serve the Word of Turscar with the same fanaticism with which they once opposed it. They still retain their abilities, including that of tracing a Sockpuppet’s launch position.

I did originally plan to give them a muzzle-loading air rifle for ranged attacks, or possibly a wheel-lock pistol like in Dishonored, but I decided that would take too much time. Instead, I just went with the hammer.

I art bored, brethren.
Guard duty art tedious, brethren.
To arms, brethren!
To arms, brethren!

So, my artwork isn’t great at the moment. However, the regular spammer and the moderator are the first two humanoid characters I’ve ever animated, so I think they can be excused. 3D modelling of characters was the last major development hurdle for Spamocalypse…at least, until the bugs and Gremlins turn up.

Unity Linux Editor

I’ve been using the Linux Editor for Unity3D for a few weeks now, and I’ve run into some odd bugs while doing so.

The first one is that mouse rotation has a very limited range – it’s basically limited to the screen size, which limits it to about a 90 degree field of rotation. This occurs in the Linux builds as well. There is a workaround, which is to recentre the mouse (i.e. when paused), but that’s pretty tedious.

I started working on a workaround for that, which is to use the arrow keys to rotate. As far as I remember, System Shock 2, Thief and Thief 2 allowed this as well, so that’s another reason for doing so. It works perfectly fine in the build, but not quite in the editor. What appears to happen is that the up and down keys cause the game tab to lose focus. And I’m not the only person who’s run into that.

Finally, I had a massive drop in framerate for no apparent reason. Even an empty scene got a massive hit of less than 40-50 FPS. However, this wasn’t an editor problem, but a graphics driver problem: my graphics card was using version 1:7.3.0-1ubuntu3.1 of the recommended X.Org X Server driver, but when I changed it to use the fglrx-updates driver and restarted, it worked absolutely fine. If you’re using an AMD/ATI graphics card and having a similar problem, give that a try.

There are also some minor issues such as the contents of a UI Text disappearing after losing and regaining focus (see the below images), but that is purely cosmetic, and the text reappears after you do anything like select the text in the hierarchy, or tweak something in the inspector if you have it already selected.

Finally, Application.persistentDataPath returns null in both editor and build, which is causing an issue with saving data. However, Application.dataPath and Application.persistentDataPath do work, so I have a workaround for this.

So far, however, it generally works.

Pathfinding updates and Spamocalypse Objective system

Somebody found a few bugs in Spamocalypse, which turned out to be an issue with my pathfinding code. I had accidentally overwritten the navmesh when I generated one for a new level, which was caused by the meshes not being loaded beforehand. I’ve added a fix for this, which is to create a static constructor to the BuildMeshEditor class in my repository. That will call the GameManger.LoadNavMeshes method.

The way static constructors work in C# is that they are always called first, and apply to all instances of the class. The constructor for the BuildMeshEditor class is called as soon as you select the cube that defines its boundaries, so the meshes are loaded in the background. If you then build a mesh for a particular level, it will be added to the list of meshes, or update/overwrite the specific one for that level. This is what it should have been doing.

In the meantime, I finally have an objective system. That’s coming in a second post.

Editor Todo List on GitHub

I have a problem keeping track of where I am in my projects, so I created a task list for the Unity Editor. It’s now up on GitHub under the MIT Licence, so anyone can use or edit it.

It basically works as follows: a Task is very obviously something you need to do. It has a status of open or closed, a name and a description. When a task is marked as finished, it is assigned a close date. Multiple Tasks are grouped as a Category, which is simply a list of tasks and a name.

There is also a GUI panel for the Unity Editor, which allows you to see this and save/load tasks from the hard disk. That’s the only part likely to need updating, unless I get a request for more features.

To use it, just visit the GitHub like above, and then either clone it using git, or just download it as a zip. In either case, put it under Assets/Aceade/Todo List.