Demotivation
Jon Blow - Code quickly
Jon Blow - Run quickly
Derek Yu - Finish your game
Derek Yu - Don't make jaggy sprites
Previous threads gave a wide overview of various tools and game engines but I want to try a more focused approach. Nearly all of us know what we want to use, so I think it's more helpful to share useful techniques for using those tools. I haven't released a commercial game, but I've released several game-like 3D applications for serious business purposes (e.g. Roller Coaster Tycoon but it's an assembly line that someone will build in real life). These were in Unity and I can share a few simple techniques that have proven most productive to me. I hope others share war stories with their tools and how they overcame roadblocks - I'll add them here. But first, a couple techniques that apply to everybody.
Version Control
Oh no, your computer broke and you lost everything! One approach is to archive your project regularly. This is fine for solo projects with regular uploads to online storage, but there are professional tools that do essentially the same thing but with more convenience for combining work between multiple contributors - repositories. Examples include Mercurial, SVN, TFS, PlasticSCM, and Perforce, but if you don't already use version control, you should use Git. This is a controversial recommendation because Git isn't the easiest to learn, and more troubling it's not even the best tool for game development - Git doesn't handle large binary files well - e.g. textures and videos. But you're indie, and probably have small asset loads. And what you gain from Git is ubiquity - there are popular sites where you can host your huge project for free, and you definitely know somebody who can send you the right commands to fix problems you will run into.
Github and
Bitbucket are the most popular repository hosts, but I recommend the up-and-comer
GitLab. Without comparing features, I can safely say it's the most generous. Unlimited private repos, each with 10 GB of storage. Free project management boards. Free Docker hosting for continuous integration.
Github has a good
tutorial.
Continuous Integration
This is probably really rare in indie dev, but it's a huge win for tracking bug regressions and enabling QA down the line. Every time you commit to your main branch, a server automatically creates builds for all your platforms and associates them with that commit. I don't have a walkthrough for setting this up because it depends on your build stack, but if you google
Docker [Your Game Engine], you might find help. If you're using Unity, you can use their version of this called Cloud Build - it has limitations but if you haven't done anything interesting with the build settings it should work.
-----
Custom Engine -----
Missile's advice for learning software rendering:
1 2
-----
Unity -----
Exotic Scripting Languages
Because scripts defined in managed DLLs can be attached to GameObjects, you can use any
CLI language as your primary language.
I played around with F# in Unity and it was a good fit because of a couple design choices:
1 2
But Unity clings to DLLs, so testing code changes is tedious:
Code:
close Unity
rebuild DLL
reopen Unity
play
Lazy initialization
Nobody does this, but because most API calls can only be called on the main thread I think it's essential.
This code will crash.
Code:
class GameLoop : MonoBehaviour
{
readonly GameObject example = new GameObject("Example");
}
Pretty sure the reason is because the initialization happens off the main thread. The common workaround is to split the definition and initialize the field in a startup method.
Code:
class GameLoop : MonoBehaviour
{
GameObject example;
void Awake()
{
example = new GameObject("Example");
}
}
But I now prefer to create a construct for that.
Code:
class GameLoop : MonoBehaviour
{
readonly Lazy<GameObject> example = new Lazy<GameObject>(() => new GameObject("Example"));
}
Code:
public class Lazy<T> where T : class
{
T value;
readonly Action constructor;
public Lazy(Action constructor)
{
this.action = action;
}
public bool HasValue { get { return value != null; } }
public T Value { get { return value ?? (value = constructor()); } }
public void Destroy()
{
if (value is Component && value != null)
{
Destroy(value.gameObject);
}
value = null;
}
}
This way, it won't be initialized until you call .Value for the first time, and you can easily recreate it by calling .Destroy() and then .Value. May not seem like much, but the code that tries this with null checks is a lot more prone to disaster down the line. This is a contrived example, but this type of thing is everywhere.
Code:
class GameLoop : MonoBehaviour
{
readonly GameObject example;
void Awake()
{
example = new GameObject("Example");
}
void Update()
{
if (Input.GetKeyDown(KeyCode.A) && example != null)
{
GameObject.Destroy(example);
}
if (Input.GetKeyDown(KeyCode.S))
{
if (example == null)
{
example = new GameObject("Example");
}
Debug.Log(example.Value.name);
}
}
}
Code:
class GameLoop : MonoBehaviour
{
readonly Lazy<GameObject> example = new Lazy<GameObject>(() => new GameObject("Example"));
void Update()
{
if (Input.GetKeyDown(KeyCode.A))
{
example.Destroy();
}
if (Input.GetKeyDown(KeyCode.S))
{
Debug.Log(example.Value.name);
}
}
}
Threading
Coroutines are almost always used for operations that could happen on a separate thread, putting unnecessary strain on the main core. Even the Unity API does this for downloading, which is bs. Maybe you're decompressing an image you downloaded over http and need to set a material's texture to that at the end - do that on another thread. Just make a thread for the heavy lifting and hook back onto the main thread to call the Unity API.
Code:
class GameLoop : MonoBehaviour
{
void Awake()
{
var thread = new Thread(() =>
{
// Pretend this is doing something meaningful and expensive
int count = 0;
for (var i = 0; i < 1000000; i++)
{
count += i;
}
Globals.ThreadHelper.Add(() =>
{
Debug.Log(count);
});
});
thread.Start();
}
void Update()
{
Globals.ThreadHelper.Execute();
}
}
Code:
public static class Globals
{
public static readonly ThreadHelper ThreadHelper = new ThreadHelper();
}
Code:
public class ThreadHelper
{
readonly Queue<Action> actions = new Queue<Action>();
public void Add(Action action)
{
lock (actions)
{
actions.Enqueue(action);
}
}
public void Execute()
{
if (actions.Count > 0)
{
Action action = null;
lock (actions)
{
action = actions.Dequeue();
}
action();
}
}
}
Allocation
Everybody talks about this, so I'll just summarize. Someday Unity will upgrade to the modern version of C# that improves this problem, although a Unity engineer told me the upgrade was right around the corner back in 2014 and here we are. Garbage collection of heap allocated objects is slow, so use stack allocations where possible - use structs instead of classes for temporary variables. Keep small objects in memory forever I found an FPS counter which created a collection of strings for each possible framerate, "1" through "60" - completely ridiculous, but also the most practical implementation. Small allocations from converting the framerate integer to a string every time would slowly build up until inevitably triggering a hang. The Job Simulator team spawn every object they will ever need and never destroys anything. They picked the perfect game design for this - player is in a big room full of stuff that you never leave.
-----
Unreal -----
They made similar decisions as Unity so the same tactics apply. Core API functions must be called from the main thread and they even use a custom garbage collector. The scripting language is C++ so you won't have to do as much silliness to avoid allocation.
-----
MonoGame -----
You get the latest C# and F# if you use this, modern generational garbage collector included. I ported an allocation-heavy Unity project to MonoGame and got about a 10x speedup.