Unreal vs Unity

2023/10 19 22:10

It seems that recent news has caused another wave of people leaving Unity for Unreal or at least considering a switch and checking things out “on the other side” to see if it’s worth losing access to all those assets bought over the years. If you’re reading this, chances are you’re one of these people. 🙂

As someone who had to make a similar call a few years ago, I can say that even accounting for the months “lost” on porting and having to learn new tools, it was still worth it. (Obviously the project was not near shipping, YMMV.)

UE4 or UE5?

As of writing, 5.3 is the latest stable version. There is no TECH/LTS split in Unreal. Versions with “preview” in their names are just that, betas, anything else numbered is stable (generally, more stable than a Unity LTS version, and you can apply your own fixes on top of the engine’s source if needed).

The system requirements for UE4 and UE5 are the same, but UE5 defaults to higher fidelity settings out of the box. If you find that a sandbox project is “running slower” than you’re used to, that’s because of this and not due to an innate disadvantage of 5. You can change your project settings to match how a UE4 project would behave (or open a UE4 project) to get similar performance characteristics.

UE5’s high-end rendering features such as Nanite and Lumen have a relatively large setup or resolution-based cost, but they scale extremely well, so performance on a sandbox scene is not really relevant.

With earlier versions, there were a few situations (mostly mobile and VR) where UE4 was still an obvious winner, but as time goes on, its advantage is eroding.

For mobile specifically, UE4’s API and runtime support has stagnated, and you’ll probably need to use a custom engine build of 4.27-plus to get accepted in the two big app stores. On the other hand, UE5 does not support 32-bit targets.

VR has also been making steady progress; if you’re not shipping right away, chances are all the remaining issues will have been fixed by the time you ship. Shipping for Quest is where one might consider sticking with UE4 for the time being.

Unless you want to commit to the 4.27+ branch for your entire project, I would strongly recommend starting on the latest UE5 version right away to avoid generating extra porting work.

Development

Unity has a very programmer-centric development style compared to Unreal, with Bolt being a relatively recent addition to the engine. Even designers often deal with C# code and everything revolves around MonoBehaviours one way or another.

Unreal is not just more artist-focused, but the entire architecture is different in a way that I don’t think the official documentation explains well enough. Definitely read that page, but then please come back here and let me tell you about the things that took me some time to realize. 🙂

Game code

In Unity, you write scripts and attach them to GameObjects that don’t really do anything by themselves, all functionality comes from components. There is also a very clean line drawn between “your code” and “engine code”.

In Unreal, your C++ code is on equal footing with the engine, you’re extending the architecture itself with your game-specific classes. Start unlearning to make everything a component and organize your code in classes that make sense in your game world. Don’t try and cross-reference components across different actors, that’s rare to do in Unreal. You can directly have logic in your actors unlike in Unity, use them directly.

Because your C++ is going directly into the engine, the way you compile code is also different. Normally you leave Unity running, change some C#, reload the script runtime, and test your changes. Unreal should be treated more like as if you were writing a command-line program: you’re working on the engine itself instead of being limited to a sandbox. The sandbox is BP and it provides a similar rapid iteration workflow.

There are QoL tools that attempt to do some form of hot reloading, one of which is broken, and the other one is unstable. Feel free to use Live Coding, but you’ll need some time to get used to working with this style of development–you’ll get some number of successful reloads (anywhere from 0 to many) before you need to ultimately close the Unreal Editor, compile your code “cleanly”, and start again. This does not mean a full rebuild, but the only 100% reliable compilation method is building your C++ code with the editor closedthen starting it anew.

Getting started

If you don’t already know C++, these are good resources for learning it:
https://stackoverflow.com/questions/388242/the-definitive-c-book-guide-and-list
https://www.learncpp.com

If you do, I wrote an article to bring you up to speed.

This (underwhelmingly-titled) course from Epic will introduce you to the various USOMETHING macros that you’re expected to use:
https://dev.epicgames.com/community/learning/courses/KJ/converting-blueprint-to-c/

In your first month or two, I’d also recommend keeping https://tackytortoise.github.io/2022/06/24/common-slacker-issues.html on speed dial.

Using ReSharper or Rider is also highly recommended, getting autocomplete for the magic macros is very nice as a beginner. Visual Assist used to be another solid choice for Unreal development.

C++ “vs” Blueprints

Most people with a Unity mindset think of this as a choice that has to be made, and default to C++ as the obvious choice. That’s a mistake, the correct choice is both.

BP is not just the node graph programming language, it’s a collection of systems built on the reflection system that also deals with saving values on your actors (“prefabs”), upgrading components in derived classes, serializing data (“ScriptableObjects”), etc.

I found it useful to default to making actor classes in pairs–immediately create a BP subclass for your C++ class and only ever use the BP one. Empty classes are virtually free and you’ll have them ready to go when you need to add something in either language. The situation becomes a little more complicated with subsystems. If you find yourself thinking of using constructor helpers (don’t!) and there’s no obvious BP escape route, look into UDeveloperSettings.

Other than this, BP is great for anything that involves logic running across multiple frames (their name for async stuff is “latent”, nodes with a ? icon). I’m shamelessly promoting developing a plugin that adds coroutines to UE5 (see the top of this page), but BP remains the only officially-supported way that doesn’t involve callback hell. Timeline nodes are very convenient for interpolation and quick one-off animations. You can easily run them by calling a custom BlueprintImplementableEvent from C++.

I won’t attempt to suggest a “correct” balance of BP and C++ because it depends on team skills and preferences. Being C++ heavy, BP heavy, or a mix closer to half-and-half all have their individual benefits and drawbacks. 100% of either is a Very Bad Idea™.

Overall engine “feel”

Let’s face it, C# is amazing and a serious benefit of Unity compared to Unreal. However, you have to use Unity for that, which is becoming more and more of an issue. This is the part where Unreal shines and makes it worth tolerating C++ and BP.

lot of things that you would buy Unity assets for or develop yourself are just… there already. Very often you need but to click a checkbox. Especially when you’re new, it’s worth spending ≈10 minutes trying to find an engine implementation of whatever you’re trying to do, because it will usually be there. (And, you have full source code access as standard!)

There is no messing around with packages and their incompatibilities. There is no SRP fragmentation hell (there is an alternative forward-rendering pipeline which is compatible with the deferred one if you don’t access the GBuffer). A character controller (both manual and AI) comes out of the box. It’s a built-in class, which is great because virtually every marketplace asset will support it. There is multiplayer support. There are obviously the shiny new features such as Nanite and Lumen. There is an entire generic gameplay ability system that comes with skill levels, cooldowns, status effects, etc. The list goes on.

Epic Games actually makes and ships games, and you can’t help but notice all the small QoL things that permeate the engine and are completely missing from Unity.

Drawbacks

Unreal is not perfect and there are some things that are worse and require additional discipline or forethought compared to them Just Working™ in Unity.

A lot of what would be YAML files in Unity are binary files that are a nightmare to version control. Things are mainly referenced by paths instead of GUIDs that lead to hacks like “redirector” assets or configuration entries to keep references alive when you move or rename something. You’ll need to avoid merges with a locking workflow using something like Git LFS or P4. One File Per Actor provides some relief for .umaps, use it (4.26+)!

Unity uses quaternions throughout with helper functions such as Euler to convert from designer-friendly representations, making it relatively straightforward to avoid gimbal lock. Unreal has FRotator as an entirely separate type, and you’ll need to go out of your way to make sure everything uses the FQuat overloads. BP doesn’t even have quaternions exposed by default.

Epic ships a lot of Not-Invented-Here replacements for standard stuff that are incompatible, buggy, or both, for example TUniquePtr instead of std::unique_ptrTFunction instead of std::functionTVariant instead of std::variant, etc. There is no universal best pick for these (see here), some of them are genuinely better and good to have, and Epic themselves have started abandoning others like TAtomic.

Speaking of bugs, although this is unfortunately equivalent to Unity, there are some features that are more or less broken. UChildActorComponents sound like a nice replacement for prefabs, but don’t use them (at all).

Strings come in three flavors with their separate macros for literals. FName needs "" (most often), FString needs TEXT("")FText needs LOCTEXT/NSLOCTEXT/INVTEXT, but some other combinations also work (and waste CPU). Epic code is not consistent when it comes to using these correctly.

Constructors make UObjects differently compared to when they’re made at runtime. This is a small minefield that you have to correctly navigate every time: components need Visible UPROPERTY specifiers, not Edit (even for editing), and when creating them at runtime you need to call both RegisterComponent and AddInstanceComponent. Why these are not one function call is a mystery, and even engine code randomly forgets calling the latter.

The convention for engine units is 1u=1cm, and the coordinate system uses +X for forward. This means most digital content creation tools need special settings to export assets correctly (and often you’ll see hacks like characters being rotated 90° in a blueprint because the meshes were imported from Maya).

Some parts of BP are best to be avoided completely, structs and enums are infamous examples.