Mastering C# and Unity3D

C++ Scripting: Part 3 – Object-Oriented Bindings

Last week’s article continued the series by eliminating the need to reboot the editor for every change to the C++ plugin. The idea is to make a more productive environment for us, the programmers, to work in. This week we’ll continue that theme by mimicking the object-oriented Unity API in C++. So instead of int transformHandle = GameObjectGetTransform(goHandle) we’ll write a more familiar Transform transform = go.GetTransform(). Also, we’ll build a simple system to automatically clean up object handles so we don’t have to do that manually.

Even better is that the size of any Object, GameObject, or Transform class is just the size of the int32_t Handle it contains: 4 bytes. So it’s super cheap to pass these types around. It’s the same size as a pointer on 32-bit systems and half a pointer on 64-bit systems, so passing them by value is actually more efficient.

At this point we have our object-oriented C++ API mimicking the Unity API. As we can see, it was really easy to implement efficiently and has given a lot of gains in familiarity and type safety. Now that we have it, we can tie up some loose ends.

We’ve been using object handles this whole time, but never bothering to clean them up. Eventually we’d run out of object handles and the app would crash. We could manually clean them up, and in fact that’s a good place to start. All we need is a function for C++ to call when it’s done with a handle:

Manually releasing object handles is error-prone and unintuitive for those of us that are used to the garbage collector in C#. So in keeping with the conversion to object-oriented style for the GameObject and Transform types, we’ll also implement automatic handle releasing so those two ReleaseObject(x.Handle) lines become unnecessary.

This sort of thing is actually quite a core feature of C++. It’s known as RAII: Resource Acquisition Is Initialization. The idea is that if we create an instance of a GameObject then we have acquired a resource in the form of an object handle. When we destroy that GameObject instance then the underlying object handle resource is also destroyed. As with the standard library’s shared_ptr type, this can be easily extended to do reference counting so we only release the object handle when the last GameObject is destroyed. In practice, this works very similarly to a garbage collector when cleans up objects only when the last reference to them is removed.

To implement this, we’ll make use of the new maxManagedObjects parameter to Init. Just like on the C# side where handles are an index into an array of object, we’ll set up an array of reference counts in C++:

At this point we no longer need to call ReleaseObject(go.Handle) in the above example. However, we haven’t implemented this for Transform so we still need to release that handle. Implementing this same code over and over for every System::Object type means a lot of code duplication. Even worse, C++ doesn’t just have constructors and destructors but also “copy” and “move” constructors and assignment operators. Thankfully, we can work around this using a macro so we just need to do this to insert all the various constructors, destructors, and assignment operators that C++ requires:

Now we never need to call ReleaseObject and even our types like GameObject don’t need to be concerned with it. It’s tucked away in a macro that deals with the reference counting for us. So we can safely remove the ReleaseObject calls and we’ll still release the object handles when we’re done with them, regardless of how we pass them around through our code.

For only about 500 lines of code we now have a programming environment with the following features:

All game code written in C++

C++ changes don’t require restarting the editor

C++ Unity API looks mostly the same

C++ Unity API behaves mostly the same

We’re approaching a system we could actually use to make a game. Still, it’s missing some important pieces to make it truly easy to work with. One is that it takes more work than we’d like to expose more of the Unity API to C++. We’ll tackle that issue in next week’s article later in the series.