UE4: My thoughts a year on

· Read in about 15 min · (3160 Words)

Our Story So Far

Just over a year ago, I started the process of learning Unreal Engine 4. I should probably say re-started, because I’d experimented with it before - in fact when the Epic Store launched, I was surprised to have an unexplained store credit, which turned out to be because I paid for UE4 for a while when it was a monthly subscription, and they refunded some of that after they made it free.

A couple of months later, I decided to use UE4 for all my future game development, and I wrote a blog post about that. I’d intended for this to be the first part of a series, and that I’d talk about more areas of the system in follow up posts shortly afterward.

Where’s Part 2??

Part 2 of that series has been AWOL for some time, as some people have occasionally pointed out 😉 The reason is that I quickly realised that what I had to say in those other areas was, in essence, more of the same.

At that point I could have talked about all those other subjects like materials, networking, rendering tech etc, but ultimately I just had all the same thoughts: that Unreal was a little harder to use, and expected more of you, but ultimately came with more meat on the bones than Unity. What’s more, it had had this stuff for a while, so it felt pretty mature and battle-tested. So, it had its ducks in a row, if you could figure out where the ducks were, and were willing to put the work in to make them go where you wanted.

But still, it was early days. I’d only done a few test projects.

So I decided that it would be far more useful hold off following up that original post until I’d gained more practical experience with the engine. Perhaps then I’d have something genuinely new to say that would be worth me writing, and you reading. Or, I’d just have more experience and would be able to confirm or refute my previous conclusions with the benefit of hindsight.

This is that post.

My Year of Learning & Building

Let’s quickly talk about what’s happened since I posted that initial evaluation.

We’re building a game right now, but it’s a ways off. We’ve made a pretty solid prototype of a key mechanical part of our game, and have learned a lot of the in’s and out’s of building real systems in UE. Despite UE’s solid core, there are still things you’ll want to add to it, and a lot of my time has been figuring out what those gaps are, and making sure that key systems are in place so we don’t have a stop-start situation later on.

I’ve tried to share as much of what I’ve learned as I go, sometimes as blog posts but mostly by releasing my plugin work as open source. Let’s summarise; I’ve written these blog posts:

  1. Using Bullet as a deterministic physics engine
  2. Self-hosting with file locking using Git-LFS and Gitea
  3. Tips on UE4 C++ Interfaces

And I’ve released these Open Source plugins and helpers:

  1. Steve’s Persistent Unreal Data (SPUD): a save game and streaming level persistence solution
  2. Steve’s Unreal Quest System (SUQS): a data-driven quest system
  3. StevesUEHelpers: a plugin with miscellaneous helper tools I use, mostly around UI and input right now
  4. Command Line Scripts: useful Powershell scripts to package and distribute game releases, compile blueprints, and more

Now I look back on it, that’s not bad for a year 😄 After publishing some of these, I started a Patreon on the basis that it would help keep me motivated to continue investing the time to publish my work, rather than do it just for our own game. I guess it worked; SPUD and SUQS were both published in the last few months.

Thanks to my Patrons for helping me justify spending time doing this, even while our unfinished game gives me the side-eye 😉

Now on to my updated view on what it’s like to have moved to Unreal, after a year of heavy use.

The C++ Is Alright

In my original post about moving to UE4, I bemoaned how much less productive I felt going back to C++. I stand by this, and I hope that the fact the Epic have acquired the company that made SkookumScript will eventually lead to a better mid-level solution for game coding, between Blueprints and C++.

I also stand by my conclusion that Blueprints are a lot better as a solution for higher-level code; particularly for fast iteration, and doing asynchronous / deferred things. I still do most of my orchestration and high-level direction in Blueprints just because its easy to noodle around and the “code” is understandable to Marie as well.

However, what has changed is that I’ve become accustomed to C++ again, or at least the subset of it that you need for Unreal. Some of this is because I’ve spent a bunch of time making subsystem plugins (listed above) which really needed to be C++. As a result of just spending more time with it, I’ve optimised my workflow, assisted by Rider for Unreal, and also that I’ve switched to Live Coding. More detail on those later.

Together these things mean that now, I’m writing more C++ than I thought I would when I wrote that previous blog post. My general guide now is that anything that feels systemic, like core world rules, subsystems or other pieces of functionality which are fundamental and fairly stable, is written in C++ first.

Anything which I’m not sure about, I’ll experiment with in Blueprints and consider migrating to C++ if it settles down to something core.

Higher level things in the game that require a lot of fine tweaking and iteration, or are situational/one-offs (e.g. orchestrating events and triggers in game), or tied closely to other content (e.g. animation blueprints), or there’s a bunch of timing / deferred things going on, I’ll do in Blueprints. The iteration time, proximity to assets, clear visual event flow for deferred things, and mixed-media benefits (artists being able to parse and perhaps edit) are just too useful.

Live Coding

Hot Reload and Live Coding are alternative ways to make C++ changes without having to reload the editor.

I’d originally chosen Hot Reload for C++ iteration because, early on, it had dealt with most scenarios pretty well. With Live Coding, many header changes can’t be picked up without reloading the editor so you’re more limited. However, over time Hot Reload just stopped being reliable. Maybe it’s a project size thing, or maybe there are certain features that when you start using them it breaks HR, but I increasingly found it would just randomly fail / crash which was becoming a problem.

So I switched to Live Coding, which has much better iteration times for .cpp source changes, at the expense of having to reload the editor any time you make header changes that need to be exposed to the editor.

To mitigate the restart issue, you can increase the speed of your editor restart by a surprising degree by simply creating a dedicated project shortcut, so that you skip the project list prompt. That can easily halve your startup time, just create a shortcut something like:

"C:\Program Files\Epic Games\UE_4.26\Engine\Binaries\Win64\UE4Editor.exe" "C:\Path\To\Your\Project.uproject" -skipcompile

Then, when you know you’re going to be making larger C++ changes, shut down the editor while you make them, use the regular build button in your IDE, and use that shortcut to restart when you’re ready. It’s still more laborious and manual than Unity C#, but you do get into a rhythm after a while. The Unreal Editor’s startup times with a dedicated project shortcut are really quite good (about 12 seconds for our project on my fairly modest machine), due to the fact that it never has to import anything at startup.

Rider for Unreal

Rider for Unreal is still in Preview, but it’s a great C++ tool, IMHO much more productive than Visual Studio. The inclusion of lots of Unreal-specific features like live templates, info about Blueprint overrides and INI file settings, and lots of quick code generation features really takes the edge off the sometimes laborious hoops that Unreal C++ makes you jump through.

Also, the embedded Git support is brilliant. I have a history of not liking any IDE version control integrations, but this one changed my mind. Not only does it do most things as well as standalone Git GUIs (to which I was so dedicated, I wrote my own once), it brings some new things of its own, such as Changelists. When I saw this I assumed it was Perforce-only, but nope, you can use changelists to effectively have multiple Git indexes, partitioning your changes up however you like. Lovely.

Rider is honestly worth the money many times over, I highly recommend it. The one wrinkle is that the 2021.1 update has had a few minor wobbles / regressions, but I’m confident they’ll be resolved, all the bugs I’ve raised have been fixed for the next patch.

The debugger maybe still isn’t quite as advanced as Visual Studio, but honestly it’s been perfectly good enough for me. And you can still hook up VS just for debugging if you really feel you need to for some cases.

An Aside on GC & Pooling

When using Unity, it’s stressed pretty early that you should think about re-using objects through your own object pools, to avoid the garbage collector kicking in so much and randomly adding overhead.

When I moved to Unreal, I initially started looking around for pooling solutions, thinking it would be the same. I didn’t really find many, and the consensus from people I talked to was “don’t worry about it, unless you’re doing regular massive spawn / destroy clusters”.

Even though Unreal is C++, it does use garbage collection, but it uses its own custom implementation. I can only assume that because it’s aware of what else is going on in the engine, it’s able to make smarter decisions about when to run - this is pure supposition, I haven’t dug into the code. Also, there are places where pooling is automatic, for example particle systems. Altogether I think this explains why most people with normal game workloads don’t seem to worry about spawning and destroying things as needed. It’s nice not to have to think about this additional layer.

Use the Source Luke

Documentation in Unreal is about as variable as it is in Unity. Sometimes it’s really good, and sometimes it’s just…not.

In game development, as with most software, the devil often comes down to the detail. What exactly does this call do under a given set of circumstances? What else does it affect? What order do these things happen in? Documentation inevitably often omits nuances like this.

The advantage with Unreal is that the answer is always available - you “just” have to read the engine source. I’ve come to really appreciate this over the last year, even though “read the source” is the worst possible comeback anyone can ever give to feedback about poor documentation. Yes, documentation should always strive to be better, and access to the source shouldn’t be an excuse.

But, when push comes to shove and the documentation inevitably fails you, the ability to get the full, unequivocal answer yourself without relying on support channels is absolutely invaluable. I should know this of course, since I used to run an open source project where we would point out the benefits of this all the time. But now, being on the other side of it, I viscerally appreciate the benefit of it.

After a year of having to regularly go to the source for clarification, I’d go as far to say that if you don’t have someone on your team who can at least read C++ adequately, you probably shouldn’t be using Unreal. When you start really getting into the nuts and bolts of a real project, you’re going to need it eventually.

The Game Framework

I could have talked about this in my first post, but a big adjustment when you use Unreal can be summed up as “How to learn to stop worrying and love the game framework”.

You see, Unreal has a lot of built-in game functionality, which can be very useful. However, it requires you to work in very specific ways, and fit your game around its framework, particularly if you’re ever considering doing multiplayer.

If you’ve made games any other way, you’ll probably spend an awful lot of your early time in Unreal either fighting the framework, or trying to ignore it. Learning all these pre-built classes seems like a distraction if you already have a structure in mind that you’re used to. But, eventually you’ll have to submit and use the framework as intended. It does make sense, and once you get used to it you get a lot of stuff for free (especially around AI, networking), but it will initially feel like you’re writing your game as a mod to someone else’s. Which you sort of are, since your game is technically a plugin. 😉

Anyway, trust me and go with it. Bookmark things like Tom Looman’s Guide and refer back to it whenever things get fuzzy again, which will be often. I can only assume that after a few games it becomes second nature.

Upgrades: less scary?

I’ve only been through 2 major upgrades of UE4 so far; we started on 4.24 and have taken on 4.25 and 4.26, so this isn’t necessarily a definite view yet, but so far taking on major upgrades to UE4 has been fairly painless.

We had one issue where the lightmapper changed behaviour a little and exposed something we were doing that we probably shouldn’t have, but which we got away with previously. Apart from that, nothing has really broken, just a couple of very minor API changes which took a few minutes to fix. Behaviourally, everything has been very stable and the new versions have just brought the odd experimental thing into mainstream (like Niagara), and added fringe things that are cool but definitely optional. Core changes seem rare, I guess they’re saving big changes for UE5, but honestly that’s how I like it. Stability is very valuable on long-running projects, when the base is solid.

Summary

After a year, I have to say that for the kinds of games we’re working on now (fully 3D instead of 2/2.5D), switching to UE4 still seems like the right choice for us. The reasons are primarily the same as the ones I listed a year ago; but at least now I can reinforce them with the benefit of more experience, and report how the downsides (mainly C++) have mitigated somewhat.

While of course not perfect, and at times not the most friendly, the core of UE4 has the feel of something that’s been used to ship a lot of real-world games. Which I guess, it has. So has Unity, but there the churn was so much greater, at least in the 2019/2020 period - it felt like everything you built needed a time-specific set of duct tape to hold it together, which would be rendered obsolete shortly afterward. Any game you wrote today would probably use a completely different set of components to one that had just shipped, so it was harder to take confidence from that.

With UE4 I rarely have to worry about whether something is fit for purpose, most of it has been around for years. Experimental things are both rare and heavily signposted, and if anything that marker seems to be very conservative; features remain marked experimental (like Niagara) seemingly long after they’re being used to ship things. Yes, the primary test bed for that is Fortnite, which is a single game, but still - the fact that these new systems are being tested on a very large, very real player base before I get anywhere near them is very welcome.

The core is both fully-featured and has been around a good while. The built-in feature set is quite varied; networking, behaviour tree AI, animation aim offsets, timeline nodes for incidental animations inside an object, good character controllers, there’s a lot there; even if sometimes it’s a bit obtuse.

As a result I’ve been able to focus on higher level systems add-ons e.g. (SPUD, SUQS. The exception has been UI and input improvements; it baffles me that seemingly all engines seem to have gaps in these areas for really basic things like controller navigation. 🙄

But, the price you pay for this is more time investment on your part. It’s not unfriendly, but realistically you’re expected to do your homework. Blueprints can be a little misleading - they look easy and friendly, but they do have their own nuances; ask people about how pure nodes can execute multiple times even though they’re only present once in the Blueprint. You need to use them sensibly to avoid unmanageable sprawls. And ultimately, they’re built atop the game framework which you’re expected to understand.

In addition, I really do think you need some C++ skills, at a minimum just to read the engine code when you need to clarify something. Although in theory you can make a game entirely in Blueprints, I think in practice you’ll hit obstacles where something you need isn’t exposed to Blueprints. Maybe it’s doable, but I would not recommend it. But Blueprints are great too, for things that fit their strengths, and for broadening access to game behaviour to non-coder team members.

On the other hand, it’s not as bad going back to C++ as I feared. There’s definitely some element of Stockholm Syndrome here, but the fact is if you work in an environment for a long enough you eventually get used to it, and refine your processes to make the best of it. I no longer acutely feel the reduction in iteration time over C#, even though I know it’s there, albeit mitigated by improved tools, familiarity, and streamlined workflow habits. But, I’d still be lying if I told you I thought it was as productive as more modern languages. And bear in mind that I had the benefit of almost 20 years of C++ before my 10-year hiatus. It’s a bit like riding a bike: a slightly rusty one, with some nasty metal edges you can cut yourself on, but it goes fast and all the cool gears are on show. 😉

So, will UE4 be the right choice for you? I can’t really answer that; sadly there’s no shortcut to figuring out what suits your team, and anything different to what you’re used to will be worse at first. But I can definitely say, after a year of using it in earnest, that it’s worth spending the time to figure that out; if you can spare it.