“Should I make my game in Unreal or Unity?” is a frequent conundrum for gamedevs, and one of the biggest sticking points is Unity’s use of C# versus Unreal’s C++. Freeform Labs' dilemma while developing ElemenTerra was that we preferred Unreal's renderer and editor but Unity’s C# scripting. Our solution was to integrate the open-source Mono-UE plugin, which lets us write C# code for our Unreal game!
C# versus C++ is a complex topic you can read about elsewhere. We’re fluent in both at Freeform Labs, but prefer C# for its faster development speeds:
C# is much quicker to compile; our C# solution builds in 3-5 seconds, while the C++ codebase takes an average of 100.
Because the language builds faster, C# IDEs are quicker and more responsive.
C# error messages are specific and coherent; C++ errors are verbose and often bury the lead.
C# nicely catches its exceptions, whereas most C++ errors cause Unreal to crash.
Overall, being able to work in C# was a huge productivity boost that justified jumping through a few hoops to integrate the plugin.
The Basics of Mono-UE
Let’s start with the core premise: Mono-UE works by hooking C# classes into the same bindings that connect Blueprints and C++.
Those who’ve used Unreal will be familiar with making “Blueprint” children of C++ classes. Blueprint classes are authored with a visual editor that can add new components, scripting nodes, and plenty more. In C++ programmers use macros like UCLASS, UPROPERTY, and UFUNCTION to choose which definitions to expose to Blueprints. The compiler then creates “bindings,” Blueprint-accessible wrappers around C++ code that allow the two ecosystems to communicate.
Mono-UE allows us to take these same bindings and create C# classes instead of Blueprints. The UCLASSes that are extensible by Blueprints can be inherited from in C#, and C# can call and override Blueprint-accessible UFUNCTIONs. This alone encompasses a huge swath of functionality, and lets us write complex gameplay logic in C# code. What’s more, C# classes can themselves be extended by Blueprint children!
Here’s a simple example of a Mono-UE C# Class, with comment blocks highlighting the connection points between C# and C++:
Mono-UE is open-source software maintained by Mikayla Hutchinson and a lovely group of other volunteers to whom we are deeply grateful. The setup process is detailed here. With the plugin still in development and some features missing or unfinished, be aware that you’ll need to pick up a handful of idiosyncratic workarounds as you learn to use it.
Our Mono-UE Workflow
Now that we’ve established the essentials, let’s talk about the day-to-day of working with this plugin! I spent a year of my life in Mono-UE and could go on forever, but for brevity here are just a few usage notes:
C# classes have access to almost all the functions available to Blueprints. This encompasses most of what we need, but sometimes we find C++ functions that don’t have bindings. This is a familiar problem for Blueprint scripters as well, and in both cases the solution is to write Blueprint-exposed C++ wrappers around the desired functions.
Most of our objects are “3-layer cakes” composed of a C++ base, a C# child class, and finally a concrete Blueprint. Generally, gameplay logic goes in C#, engine enhancements go in C++, and artists design audiovisuals in the Blueprint. Thus we spend the majority of our time in the language that’s fastest to write, work in C++ only when necessary, and keep our look-and-feel functionality in a format the artists can edit.
Every once in a while we had to modify the Mono-UE source. Packaging & shipping a standalone executable wasn’t available when we started ElemenTerra, but we fixed the bug and shipped the game! Our modification has now been accepted as a pull request.
“Hot reloading” wasn’t working during ElemenTerra development, so we needed to close and reopen the editor after changing our C# code. This was annoying, but we still found it much faster than recompiling our C++. At the time of writing, there is a not-yet-integrated pull request claiming a partial fix.
With these methods we enjoyed all the benefits of C# described above: faster build times, more responsive IDEs, better error messages, and exception handling that doesn’t crash the engine!
What about performance?
ElemenTerra is a VR game, which required us to hit 90+ fps. One of the main draws of C++ is that it’s a low-level language that can run extremely fast; does using C# then put us at a liability? The answer is not so black-and-white
To start with the facts, we’ve observed that Unreal C++ runs unconditionally faster than Mono-UE C#. Where things get gray is that sometimes C++ is significantly faster, but often the difference is trivial. For generic algorithms and math functions we saw very little difference, but C# was slower when we were doing a lot of thunks. “Thunk” is a silly software term which in this context refers to a call from C# into an Unreal C++ function. These jumps between runtime environments are inherently expensive, and rack up a performance cost when done en masse.
This is a factor that needs to be respected, but is by no means a dealbreaker on Mono-UE. Here are the best-practices we used:
Never optimize before profiling! C# objects that only had a few spawned instances or didn’t call thunks from loops usually didn’t chart on the profiler: no action necessary. This applied to the majority of our C# codebase.
Avoid redundant thunks. Instead of thunking GetActorLocation() 3 times, we can thunk it once and save its output in a variable. Rather than call DestroyActor() on every object in an array, we can write a new DestroyMultipleActors() C++ function and call it once.
Port expensive C# code to C++. When the profiler did show a piece of our C# code was expensive, we moved it down to C++. Often this was done in the simplest way imaginable: copy the whole script, paste it into .h and .cpp files, do a few find-replaces, and fix the errors until it worked. We were surprised by how few complications came from this approach; often the C++ worked on its first run! Our strict encapsulation policies at Freeform Labs made this approach quite painless, as each script had few external dependencies.
With these practices in place, we enjoyed the productivity boost of C# and still reached 90fps.
Is Mono-UE Right for Me?
This article is about why Mono-UE was the right choice for our team, and we have no regrets. If I've piqued your interest, I’ll close with advice for anyone interested in trying Mono-UE themselves.
If you’re a team with C# and C++ experience and want to be writing more of the former, Mono-UE might be right for you! Just be aware that there's an upfront cost to setting it up and that you may run into bugs you’ll need to fix in the plugin’s source.
If you’re a tech-inclined hobbyist looking for something new, give Mono-UE a look! It’s a great opportunity to learn more about how Mono and UE4 work under the hood, and as an open-source project it’s always looking for new contributors.
If you’re a Unity user who wants to try Unreal Engine and only knows C#, you’re in luck as long as you can tolerate the roadbumps that come with using still-in-development software. The first-time setup is tricky and you’ll encounter bugs which you need the community’s help to solve, but the payoff is definitely there.
If you’re new to programming and think C# would be easier to learn than C++, I’d recommend picking up Blueprint Scripting or Unity instead. Both of those tools already have an abundance of online resources for first-time learners, whereas the Mono-UE community is just getting started.
If you’re looking to ship to console or mobile, Mono-UE does not yet support those platforms but may in the future.
If you want to get your company set up with Mono-UE but don’t know where to start, perhaps we can help! You can get in touch here.
Programming Languages and Peace of Mind
As game developers, picking the right tool is important both for the quality of our craft and for the quality of our lives. Personally, I find Mono-UE extremely fun to use: C# is a language where I can get into a great flow state, and not being interrupted by compile times or obtuse errors helps me maintain focus. Whatever you do decide to use, I hope this article has inspired you to reflect on your own development workflow!
If you’re interested in trying Mono-UE yourself, this landing page will direct you to the source code and some community resources. Also feel free to contact us directly if you need a tip or want to talk more. Until next time!