Unity → Unreal
January 31, 2021
In my previous post, I made a conclusion that I would need to pivot from Godot if I was to get the rendering flexibility that I wanted. In this post, I will review how that went and detail another pivot. This one being even more regrettable than the last.
I started by following a series of tutorials on how to create a custom Scriptable Render Pipeline in Unity. After a few days, I finished a complete scriptable render pipeline (forward only) using my own shaders that also support GPU instancing and the Unity batcher. However, I also faced two very bad problems that appear to be limitations in Unity.
First, Unity camera layer filters are applied hierarchically, not on a per-object basis. So, if you have a camera that only renders Layer 2 and it is viewing a subtree with a parent set to Layer 1 and the children set to Layer 2, then the camera will not render the children in the subtree. Technically, culling is supposed to be within my control using a custom rendering pipeline. And yet, the Unity primitives do not give you access to the raw scene objects, nor does it allow you to override this layer culling behavior. And because the renderer requires a CullingResults object, you must perform this layer culling step to draw anything!
Second, Unity only allows one camera to render a canvas subtree. So you cannot apply multi-pass rendering to a canvas (or its children). This totally ruins my entire rendering pipeline plan. So in considering this new information, I see four possible paths to continue development:
- Abandon the multi-pass UI rendering pipeline and pivot to Unreal - this would mean the UI is simpler. It could still contain some basic effects. However, these would primarily consist of animations and simplified post-processing effects.
- Abandon the multi-pass UI rendering pipeline but stick with Unity - this would mean the UI is simpler. However, I would still be able to use the state machine system for UI elements.
- Modify Godot Engine to build the multi-pass UI rendering pipeline - this would mean pivoting back (once again) to Godot. I would face the previous hurdles that I was avoiding before (I would need to build custom versions of the UI that would support animations and customized rendering).
- Abandon the multi-pass UI rendering pipeline and pivot to Godot - this would mean the UI would be deprioritized in favor of the gameplay and the AI logic. I would not focus on visual appearance but on functionality instead. This also would offer the possibility of switching back to option #2 later.
My previous decision-making criteria for pivoting the game engine were the following:
- High speed development - the design → development → testing iteration time is probably the biggest determining factor for overall success.
- Support for making GUI transition animations - not strictly required but highly valued for visual appeal given the "austere" gameplay format
- Support for making custom 2D styling effects - again, not strictly required but I want to be able to apply custom 2D visual effects on GUI elements to provide very clear visual feedback to the player
To reconsider my previous decision, I updated the evaluation criteria that I used with the new information (with the change highlighted in bold).
|GUI Animation System||Bad||Good||Good|
|Custom 2D Effects||None||Good||Bad|
This shows that Unreal Engine and Unity are roughly even with respect to my criteria. I've decided that its worth switching to Unreal Engine with the aim to using it for other personal projects in the future.
I've followed a number of tutorials and online courses for UE4. The UE4 source code is messy but it has a number of interesting features. It supports hot-reloading of dynamic linked libraries (DLLs) for incorporating changes during development –this even applies to the UE4 editor itself. Second, it has an internal representation of most C++ objects in order to support advanced features like Blueprints subclasses, type system reflection, garbage collection, and dynamic event binding. This internal representation is automatically generated by the Unreal Header Tool (UHT) to keep the system internals in sync during development. Lastly, UE4 supports a pretty cool render graph system (RDG) that's used to minimize shader hitching, draw calls, and texture rebinds while also supporting fine-grained culling. Fancy!
But it's not all good news. In some places, Unreal Engine is downright crufty when compared with Unity. It introduces a multithreaded GC (as I mentioned above) but it still relies upon a separate smart pointers system because the GC is not used throughout. For example the Slate UI system relies upon smart pointers but the UMG system (built on top of Slate) uses the GC system. Furthermore, there are many classes that violate the single responsibility principle. For example, the PlayerController class has 324 methods with a header file that is ~2,000 lines of code and an the implementation file that is ~5,500 lines. 😳
In the process of learning UE4, I upgraded my aging PC with a new Intel i9-9900k and Nvidia GeForce GTX 1080. I've also managed to create a minimalist version of my Godot prototype in Unreal but without any input processing. That will be my goal for next week. Stay tuned!