Last week in the series we took a step back to verify that the C++ plugin’s performance was acceptable. With that confirmed, we’ll continue this week by making our programming lives easier. One pain point so far has been with exposing new Unity APIs to C++. It’s not that it’s difficult to do this, but there’s a lot of boilerplate required. That boilerplate takes time to write and it’s easy to make mistakes copying and pasting existing functions. So this week’s article introduces a code generator that will write the boilerplate for us! We’ll also reorganize the project a little so the code that supports C++ scripting is separated away from our game code. That’ll make it easy to add support for C++ scripting to any Unity project.

Table of Contents

First, let’s add a code generator to generate all the boilerplate required to make C# functions callable by C++. The code generator is simply a Unity editor script that we can run with a menu item or via the command line. It loads a JSON file describing what should be exposed. For example, in previous articles we exposed the default GameObject constructor and the GameObject.transform and Transform.position properties. Do do that, we’d add them and their base classes to a JSON document and say that those types can be found in UnityEngine.dll:

{
    "Assemblies": [
        {
            "Path": "/Applications/Unity/Unity.app/Contents/Managed/UnityEngine.dll",
            "Types": [
                {
                    "Name": "UnityEngine.Object",
                    "Constructors": [],
                    "Methods": [],
                    "Properties": [],
                    "Fields": []
                },
                {
                    "Name": "UnityEngine.GameObject",
                    "Constructors": [
                        {
                            "Types": []
                        }
                    ],
                    "Properties": [ "transform" ],
                    "Fields": []
                },
                {
                    "Name": "UnityEngine.Component",
                    "Constructors": [],
                    "Methods": [],
                    "Properties": [],
                    "Fields": []
                },
                {
                    "Name": "UnityEngine.Transform",
                    "Constructors": [],
                    "Methods": [],
                    "Properties": [ "position" ],
                    "Fields": []
                }
            ]
        }
    ]
}

This document allows us to specify exactly what we want to expose to C++. This keeps bloat down and allows the code generator to run really fast: under a second on my machine. We simply fill in the constructors, methods, properties, and fields we want exposed and run the code generator to fill in all the required C# and C++ code.

The code generator is currently 1700 lines, so I won’t go over all of how it works. At a high level though, it uses C# reflection to load a DLL, builds a lot of strings of source code, and injects them into existing source files between start and end markers. Currently, the code generator supports some basic functionality but lacks support for some C# features:

Supported Not Supported
Constructors Arrays (single- or multi-dimensional)
Properties (get and set) out and ref parameters
Fields Struct types
Methods Generic functions and types
Class types (static and regular) Delegates

With this tool available to us, we can quickly expose new Unity API functionality. For example, if we wanted to expose the Component.transform property we’d simply modify one line of the JSON and run the code generator:

"Properties": [ "transform" ],

We can also expose functionality from non-Unity DLLs. For example, we can expose System.Diagnostics.Stopwatch from the .NET API by adding this JSON block:

{
    "Path": "/Applications/Unity/Unity.app/Contents/Mono/lib/mono/unity/System.dll",
    "Types": [
        {
            "Name": "System.Diagnostics.Stopwatch",
            "Constructors": [
                {
                    "Types": []
                }
            ],
            "Methods": [
                {
                    "Name": "Start",
                    "Types": []
                },
                {
                    "Name": "Reset",
                    "Types": []
                }
            ],
            "Properties": [ "ElapsedMilliseconds" ],
            "Fields": []
        }
    ]
}

Then we can use it just like the Unity types we exposed before:

// Make the Stopwatch and start it
Stopwatch sw;
sw.Start();
 
// ... do slow stuff
 
// Find out how long the slow stuff took
int64_t elapsed = sw.GetElapsedMilliseconds();
 
// Print the result to Unity's Debug console
char buf[128];
sprintf(buf, "Slow stuff took: %lld", elapsed);
Debug::Log(String(buf));

Next up for today, let’s split the existing code up into two parts. First is the code that supports C++ scripting. This part includes all the native plugin loading and unloading, exposing C# functions to C++, and object-oriented type definitions. This is the “bindings” layer that games shouldn’t ever need to touch. It should be easy to copy/paste into a new project and start scripting that project in C++.

The second part is the part where all the game-specific code goes. It just focuses on getting the job done for the game, not the gory details of how to bind C# to C++. It looks like the Stopwatch code above.

So far everything has been in one C# file and one C++ file. Instead, we’ll split up the project this way:

Assets
|- Game.cpp                  // Game-specific code. Can rename this file, add headers, etc.
|- NativeScriptTypes.json    // JSON describing which .NET types the game wants to expose to C++
|- NativeScriptConstants.cs  // Game-specific constants such as plugin names and paths
|- NativeScript/             // C++ scripting system. Drop this into your project.
   |- Editor/
      |- GenerateBindings.cs // Code generator
   |- Bindings.cs            // C# code to expose functionality to C++
   |- ObjectStore.cs         // Object handles system
   |- Bindings.h             // C++ wrapper types for C# (declaration)
   |- Bindings.cpp           // C++ wrapper types for C# (definition)
   |- BootScript.cs          // MonoBehaviour to boot up the C++ plugin
   |- BootScene.unity        // Scene with just BootScript on an empty GameObject

This way the NativeScript directory can simply be dropped into a Unity project or updated. The game never modifies anything in there. It does, however, require the game to provide a few things. First, the game must define a NativeConstants class in the global namespace with a few required fields:

public static class NativeScriptConstants
{
	/// <summary>
	/// Name of the plugin used by [DllImport] when running outside the editor
	/// </summary>
	public const string PluginName = "NativeScript";
 
	/// <summary>
	/// Path to load the plugin from when running inside the editor
	/// </summary>
#if UNITY_EDITOR_OSX
	public const string PluginPath = "/NativeScript.bundle/Contents/MacOS/NativeScript";
#elif UNITY_EDITOR_LINUX
	public const string PluginPath = "/NativeScript.so";
#elif UNITY_EDITOR_WIN
	public const string PluginPath = "/NativeScript.dll";
#endif
 
	/// <summary>
	/// Maximum number of simultaneous managed objects that the C++ plugin uses
	/// </summary>
	public const int MaxManagedObjects = 1024;
 
	/// <summary>
	/// Path within the Unity project to the exposed types JSON file
	/// </summary>
	public const string ExposedTypesJsonPath = "NativeScriptTypes.json";
}

Second, NativeScriptConstants.ExposedTypesJsonPath needs to point to a JSON file describing all the .NET types to expose. This is the same kind of JSON file as we talked about above.

Third, the game’s C++ code must define two functions:

// Called when the plugin is initialized
void PluginMain()
{
}
 
// Called for MonoBehaviour.Update
void PluginUpdate()
{
}

That’s it! With those simple requirements out of the way, any Unity project can start writing scripts in C++.

At this point there are multiple files, a directory structure, and a sizable code generator involved so there’s too much to post right here in the article. So I’ve created a GitHub project which you can check out if you want to see the details or try out some C++ scripting for yourself.

The last major missing piece of this project is a system to easily build the C++ plugin for a variety of platforms: Windows, Mac, iOS, Android, etc. That will be the subject of next week’s article.