Edit 1: Fixed an error in the description of GameObject layers.
Edit 2: Clarified use of “entity” and “component” terms vs. ECS depiction. Part 1 elaborates on differences.
The time has finally arrived.
The last two articles presented an overview of what one can expect from OOP design and popular game frameworks. This article explores Unity, Unreal Engine 4, and Godot Engine. Specifically, how their frameworks each use the concepts discussed in those articles. If you want to understand how Godot’s Nodes relate to the other APIs, this is the article for you.
If you want the TL;DR; version, each subsection, Unity/Unreal/Godot and Conclusion, has a bullet-point summary of the article’s information.
While it was mentioned in the last two articles, I’ll re-iterate:
This article’s uses of the terms “entity” or “component” are not references to the aspects of an ECS paradigm. Rather, a traditionally OOP Objects’ features are split up into essential elements called “components”. Anything in the world that has components is an “entity”.
In Unity, the “level” is a Scene. Each Scene contains a list of GameObject entities which serve as the root of their own tree of GameObjects.
Each GameObject may have a list of Components attached to it. They also have one Tag and one Layer. Tags and Layers can be defined globally (some are engine-defined). The Tags assist with identification while layers assist in operations (categorized custom rendering/collision/UI sorting, etc.). Each GameObject also has a Transform (so even abstract things have a place in the world).
Unity provides many Components for all manner of gameplay. MonoBehaviour, one of these, is blank aside from a C# script location. C# scripts extending MonoBehaviour are then new, user-defined types of Components. MonoBehaviours are therefore able to delegate to their assigned C# script.
Simulated Multiple Inheritance
Unity’s framework supplies a very traditional Entity-Component framework, super-powered with user-customization and utilities for inheritance simulation.
What is meant by inheritance simulation? Well, Unity employs some tricks with its Component API. A user can look up GameObjects of a certain type. But, GameObjects do not have a user-defined type, and the only definable types are components. So while one is actually testing whether the GameObject has-a Component, Unity simulates it as an is-a relationship.
Unity further solidifies this illusion by mirroring the GameObject’s getComponent method on each Component. Each Component is able to access the others as if it is the GameObject. The Components don’t really own any other components though. Devs added this access for convenience to reduce verbosity.
Components can also enforce dependencies for other Components onto the owning GameObject via the RequireComponent attribute. This means that Components can carry their dependency burdens onto the GameObject.
This all implies a sort of multiple inheritance by composition. The components do not manage their own dependencies, but rather exploit the access to the GameObject to fulfill their requirements. A GameObject is considered to be several different types based on what Components it holds.
Users can mitigate this issue by relying on interfaces (C# version). For Unity specifically, by using interface-based getters for one’s MonoBehaviour properties. This then allows a single MonoBehaviour to loosely declare relationships with other Components. That MonoBehaviour becomes the predominate “type” indicator for the GameObject. The MonoBehaviour has loose “ownership” of the interfaces because users can swap the implementing Components out freely.
The Engine’s serialization solution is the Prefab. Each Prefab saves a single tree of GameObjects. You can instance the Prefab many times and changing the original will update the instances. Prefabs do not support inheritance with each other.
You can also update one instance and apply those changes to the Prefab itself. That then automatically propagates the edited content to all other instances.
Note that in Unity, Prefabs are optional. When one defines a new type, they do it by defining a Component. They can create a GameObject and add the Component without needing a Prefab.
The Prefabs are not officially registered types in Unity. They are resources for recreating a particular arrangement of GameObjects, Components, and property values.
Unity provides two layers of abstraction: the organization of Components on a GameObject and the organization of GameObject trees. Many utilities already exist for the former. The Prefab exists for the latter.
But the Prefab is limited in that it cannot internally form relationships with other Prefabs. This means that a Scene’s list of GameObjects cannot be sub-organized into smaller units. If users build a Prefab, it can only become related to another Prefab in the Scene context.
The 2018.3 version of Unity will support nested Prefabs. With this, the Prefabs will be able to store other Prefabs. This actually stands to improve the situation drastically.
Rather than relying on inter-component relationships to define types, people will be able to use Prefabs. Because Prefabs provide a looser coupling via tree structures, users can simplify their code by reducing the number of related Components on any given GameObject.
Unity provides a lot of simple networking available to users. Like others, it has a low-level API for direct communication and an HLAPI for server/client frameworks.
The main aspect of the HLAPI is having a NetworkManager-owning GameObject in the world that regulates connectivity and other NetworkIdentity-owning GameObjects to be replicated. Different types of NetworkComponents are provided that automatically sync complex, dynamic data between each machine.
The NetworkBehaviour Component then provides several utilities:
- Synchronizing variables.
- Testing whether this “version” of the object is the server or the client.
- Testing whether this object even belongs to the local client.
- Sending notifications to the server or to other clients
All in all, there’s a lot of powerful functionality here if you just want to dive in and get started.
If a user wants to work with data in their Unity project, they need customizable data. One of the most effective ways to do so is with a ScriptableObject.
To create them, users need to dip their feet into the Unity Editor’s tools for modifying the editor itself. They first create an editor script that will create the ScriptableObject they want. Once compiled, the scripts create new menu items that will create the asset.
The earlier C# script defines the properties of the ScriptableObject to create. Once made, those properties are then editable from the Inspector. With these assets, users can create concrete, customized datasets.
If users require more editing power, they can write their own editors. Unity provides a scripting API for generating quick and dirty UI elements called Immediate Mode GUI (IMGUI).
Many Unity plugins make use of these tools. They do not rely on the GameObject-based GUI used for game production though. Instead, it creates a simple set of C# attributes and Editor-related Objects for adding content.
- Zero or more GameObjects compose a Scene (as a list).
- One or more GameObjects compose a GameObject externally (as a tree, including the root).
- Zero or more Components compose a GameObject internally (as a list).
- GameObjects have a Transform and possibly a tag and/or multiple layers.
- Components may inherit from other Components.
- User scripts extend a particular Component: MonoBehaviour or NetworkBehaviour.
- Components may form dependencies between each other on a single GameObject.
- Unity strives to simulate multiple inheritance through its composition techniques.
- Prefabs package up a tree of GameObjects and their Components.
- Editing Prefabs can be executed both directions, rippling to all instances.
- Prefabs do not inherit from each other.
- Prefabs will soon support inter-Prefab ownership.
- Prefabs are optional. GameObjects with components can be “inlined” without formalizing them as a Prefab type.
- Unity provides many utilities for automatically setting up networked gameplay, including…
- a Network Manager to coordinate connections.
- ready-to-use components.
- a blank template component with an HLAPI for server/client infrastructure.
- Unity provides customizable datasets through ScriptableObjects.
- Users can define custom editors for ScriptableObjects and other tools using a quick and easy IMGUI scripting API.
Unreal Engine 4
In Unreal, the “level” is a Level. Each Level contains a list of Actor entities which serve as the root of their own tree of Actors.
Each Actor has an internal tree of ActorComponents attached to it. Each Actor has a Transform, so they each have a place in the world.
Actors and ActorComponents are not the end though. Unreal has a fully fleshed out class system that the editor relies on. The engine provides several types, and the scripting system involves defining new types. Types can be declared with either a C++ script or a Blueprint, a visual scripting language.
Every class is a serializable, instanceable object. Users can edit an Actor C++ class or Blueprint independently and those changes will apply to every instance of that Actor in all Levels. The same goes for ActorComponents instanced within Actors.
Unreal sees everything as a class. The engine detects the creation of new classes or edits to existing ones and updates accordingly.
Unreal also has a variety of tag features. Actors and ActorComponents support multiple Tags. The engine provides an API for requesting all Actors or ActorComponents with a specific tag (one function for each).
The Editor also allows users to define global-scope GameplayTags, which are not the same thing. These can be queried and compared to any GameplayTag(s) property on an Actor or ActorComponent. Because they are properties, they don’t fulfill the same function as the previously mentioned tags.
Unreal is designed around particular ways of structuring one’s code. It helps to enforce good practices by segregating programming tasks into different classes.
- Actors are objects in the world.
- Actors have an internal tree hierarchy of ActorComponents.
- ActorComponents don’t exactly “own” other ActorComponents.
- An Actor may organize ActorComponents to have child ActorComponents, but that structure is only visible to the Actor.
- Hence, ActorComponents cannot be organized into self-contained hierarchies.
- ActorComponent-derived SceneComponents can have positions in the world too. Example: a Camera that is attached to a player and is part of the player’s identity.
- SceneComponent-derived PrimitiveComponents have a graphical aspect to them.
- Because self-contained ActorComponent hierarchies are not possible, UE4.14 added ChildActorComponents. These allow users to represent a child Actor as a SceneComponent in a parent Actor.
- Actor-derived Pawns are Actors that receive instructions from a Controller.
- Controllers can be…
- PlayerControllers that respond to input.
- AIControllers that hook into an AI data structure of some kind.
- Pawn-derived Characters are a simple template for building mobile, animated, humanoid Pawns.
- A GameMode singleton determines the rules of play and the initialization of the game:
- What’s the starting playable character?
- What’s the starting level?
- A GameInstance singleton exists for Level-to-Level data preservation.
The Actor “entity” class absolutely is a type in Unreal, and it has several specializations and relationships to other types. The same goes for its components which have several variations themselves. Quite different from Unity’s generic GameObject which pseudo-adopts the types of its components.
As one can see, this class layout is much more suggestive about how to structure a project. There is a class for every purpose.
The suggestive design has advantages and disadvantages though. On the bright side, it encourages users to write cleaner code. Each class’s abundance of designed functionality improves iteration time and gets projects running quickly. The user’s code is bottled up into each class naturally and dependencies to other classes are clearly defined.
On the downside, it renders other structures more difficult to adapt to the defined dependencies. If the user doesn’t know in advance how they should design their project, then refactoring will be needed.
The most common refactoring issue was needing to change an Actor into a SceneComponent or vice versa. That issue is much easier to resolve though, now that users can simulate Actors as SceneComponents via ChildActorComponents. If users create Actors for most things, they can still nest it within another Actor as a ChildActorComponent.
Note that the root of an Actor’s component tree is a SceneComponent. Users can set another SceneComponent-derived type as the root if desired. So long as it derives from SceneComponent, the Actor will still have a place in the world. In this sense, one might think of a Level as a list of SceneComponent-derived entities with a subtree of components that are wrapped by an Actor’s class code.
One of Unreal’s greatest advantages is in how it allows users to rely on a GUI to organize and edit each class. These GUIs, the Blueprints, can inherit from C++ classes, so users can safely work with both.
Each Blueprint can expose properties, including classes themselves, alongside methods, macros, and events. The workflow for events is identical to that of a void-returning method, and the macros are extremely flexible. Most of the C++ workflow can be reproduced in Blueprints. Indeed, Blueprints can even be compiled down to C++ for optimization.
In terms of Unity, this would be akin to having the full Unity Editor available for constructing the design of engine elements. The MonoBehaviours, Prefabs, GameObjects, and the relationships between them could interact with more than just a code editor and the Inspector.
Of course, the only reason Unreal needs such compartmentalization is that it has so many different high-level classes. However, it is to Unreal’s credit that the Blueprint element of its framework is intuitive, visually readable, and powerful.
A lot can be done with very little work because one glance at any Blueprint gives users a descriptive, high-level view of the class. Users can organize different aspects of a game’s design into the appropriate Unreal classes. Blueprint’s strong autocompletion features can even hint at what users can do and which class they need to access to do it.
As such, users can work very effectively with Blueprints. They can keep well-organized code despite its distributed nature, so long as the user knows what they are doing. This is more difficult to do in Unity, at least until Nested Prefab support is added.
One of the small downsides of everything being a class is that prototyping a makeshift type still involves needing to create a formal class (C++ or BP). Not a big deal, but creating a “throwaway” class still involves creating files that later need to be deleted. Unity will pretty easily let you just make a GameObject, throw components onto it, and then delete it, no filesystem cleanup involved.
Unreal’s networking system is one of the most convenient and mature aspects of the engine. Since Epic Games designed it for online multiplayer FPS games to begin with, it has several integrations with Blueprints that facilitate users’ networking HLAPI use.
- The Editor can launch however many game instances the user desires, with or without a dedicated server instance.
- Users can flag individual properties to replicate in various ways automatically.
- No special classes are required.
- Actors support replication out-of-the-box.
- ActorComponents also support replication if their owning Actor’s replication is turned on.
- Methods, Events, and by extension Macros all support various remote procedure modes (client-only, server-only, synchronize with all clients, etc.).
- The contents of any of these can also test the role of the current context to define custom behavior, e.g. has authority or not, affiliated with the local client or a proxy, etc.
- Users can define explicit validation functions for remotely called methods. These ensure that parameters are valid, i.e. not hacks, before progressing with remote execution.
Considering the depth and variety of features, it’s hard to top Unreal’s out-of-the-box networking smoothness.
Data in Unreal is editable from two main assets: DataTables and CurveTables. Users can make each of them directly from the editor.
Each DataTable is a Map of StringName (i.e. hashed string) keys paired with a Struct instance. A single Struct type is associated with each DataTable, as its properties define the columns.
Structs are declared like classes with C++ or BP, but they don’t have anything except properties. The engine provides a simplified GUI editor for them as well.
CurveTables are quite similar in that they represent spreadsheets with StringName keys. But, their columns are numerically indexed and all cell values are floating point, e.g.
3.14. This is because every row of the CurveTable represents a Curve on the same graph.
With these, Unreal makes it easy to import, export, and edit spreadsheets with customized data. To modify how the data is edited, a user has two options.
The first would be to write a wrapper class to handle it, but it would only work at runtime. In this case, your class would take in inputs and execute changes to the data asset after validating or evaluating those inputs. Likewise, data requests are made simpler since the wrapper class abstracts away access to the spreadsheet. This is similar to what one might do for a typical database.
The second would be to incorporate a Slate plugin to create an Unreal Editor extension. Slate is UE4’s version of Unity’s IMGUI. It relies on its own declarative language embedded within the C++ source code. Slate can be used to create custom editors for the details panel in the Unreal Editor, among other things.
Unreal Engine 4 Summary
- Zero or more Actors compose a Level (as a list).
- One or more Actors compose an Actor externally (as a tree, including the root).
- One or more ActorComponents compose an Actor internally (as a tree, including the root). The root defaults to and must derive from SceneComponent.
- Actors have a Transform because SceneComponents have a Transform.
- Actors and ActorComponents can have tags. Once a user has found an instance, they can run queries on GameplayTag(s) properties.
- Most everything is an inheritable class.
- There is a diverse ecosystem of classes for many purposes. Each one suggests a best practice.
- It is easy to build content when following Unreal’s development plan.
- Deviating from the plan can result in refactoring or manual work. Will usually want to design with Unreal’s API in mind from the beginning.
- Actors can be nested within each other through ChildActorComponents. This provides Unity’s Nested Prefab feature immediately.
- Blueprints provide a wonderful overview of classes. Iteration with them is quite easy.
- Blueprints grant isolated views into a project’s structure.
- Unreal’s class system requires a C++ or Blueprint file. Inlined, disposable types for quick prototyping aren’t possible.
- Unreal is built from the ground up to support networking.
- Several utilities for common tasks.
- Can configure any property, method, or event’s replication from C++ or BP.
- Replication supported for Actors. If an Actor is replicated, its ActorComponents may be specially replicated too.
- Data can be edited as an in-editor spreadsheet. Spreadsheets are always a StringName key mapped to…
- a customizable Struct for DataTables.
- a series of float values representing a curve for CurveTables.
- Creating tools and custom editors is done with Slate, an IMGUI-like system that builds editor changes using a scripting API.
In Godot, the “level” is a scene. Each scene contains a single tree of nodes. Nodes can inherit from other nodes, and as part of a tree, nodes can own other nodes. Users can nest scenes within each other and inherit one scene from another.
Nodes do not necessarily have a location within the world. They do not have a Transform by default, unlike GameObjects and Actors. The most common types of Nodes are Node (empty), Spatial (3D transform), Node2D (2D transform), and Control (2D transform on GUI layer).
Each Node can have a single script which extends its type. The script itself just has to meet a Script interface. Godot 3.0 allows the following script types:
- GDScript, a C++-Python mashup with a high level of Godot Engine integration.
- VisualScript, a visual programming language akin to Blueprint Visual Scripting (although more limited and complex to understand in comparison). Also has high integration with Godot Engine. Users can easily define their own VisualScript nodes using any scripting language, including other VisualScripts (linked example uses GDScript).
- Mono C#, as is present in Unity’s scripting. Exporting a project with C# is still a work-in-progress at the time of Godot 3.0.2. Someone has also been building F# examples, though that isn’t officially maintained (Setup, Implementation).
- NativeScript, a script that executes dynamic library code. To do so, it uses a C API for the scripting engine called GDNative. Languages with generated C bindings can then also be compiled into dynamic libraries and executed. C++ is the only officially maintained binding. Bindings currently exist for…
- PluginScript, a script that also relies on GDNative like NativeScript, but which also teaches the Godot Editor how to handle the language directly automatically recompiling the compiled dynamic library as needed.
Scripts are usually created as files. However, GDScript and VisualScript files can be inlined and saved directly as sub-resources within scene files. This makes prototyping types much easier as there is no filesystem cleanup needed when dumping a disposable type.
Entities don’t really exist, nor do components. The notion of an Entity and a Component is instead replaced by a Node.
Nodes can be members of an arbitrary number of Groups. This is Godot’s tag system. Groups are globally recognized.
Godot games involve running a SceneTree which manages the game’s global node tree.
How do Nodes and Scenes Relate?
So, how exactly does a Node work? How do they fit into scenes in Godot? Well first, let’s break down what a roughly equivalent structure would look like in the other engines. Keep in mind, these are rough estimates. Nodes in Godot are actually quite cheap to produce, so the comparisons aren’t clear-cut.
If Unity Were Godot
In Unity, imagine having a Scene with a single GameObject. That GameObject may have an arbitrary number of child GameObjects. Every single GameObject has exactly one component. The most basic one would by default just be a MonoBehaviour with no C# script attached.
Each GameObject has a single type and supplies a single batch of properties and methods. Each GameObject can operate on its own without any dependencies on the other GameObjects. Any arbitrary subtree can be packaged up into a Prefab, only these Prefabs can inherit from each other and be nested within one another. Then consider that your Scene itself can essentially be one giant Prefab. What if the whole Unity Editor was effectively a Prefab editor?
What if “running a game” meant creating and executing a Prefab? Who needs Scenes when all you have are Prefabs? You would then also be able to “run your game” at any level, because Prefabs exist at all scales of your game’s design.
If Unreal Were Godot
In Unreal, if you recall, this concept was already hinted at: have a Level with a single Actor that manages the entire subtree of Actors. The Level itself becomes a giant tree of SceneComponents that interact with each other. An Actor wrapper encapsulates interactions with each SceneComponent. Each Actor, i.e. relative root SceneComponent, then manages its own internal subtree of ActorComponents.
The Blueprints provide an independent, visual editor for reviewing and developing each class. The class could be an Actor-wrapper or an individual ActorComponent. This provides a full editor for just a class’s functionality, not any particular instance.
At this point, with only a single Actor in each Level, the Unreal Editor with its Viewport, Toolbar, Details Panel, etc. is just a tool for editing a Blueprint’s components and exposed properties in the world.
There is a separate space for “actors” (instances in the world) and “scripts/classes” (the data and behavior of each instance’s class).
What if “running a game” meant creating and executing an Actor Blueprint? Who needs Levels when all you have are Blueprints? You would then also be able to “run your game” at any level because Actor Blueprints exist at all scales of your game’s design.
Godot is Godot. Everything is a Node.
In Godot, each scene has only one Node at its root. Nodes fulfill a singular function but may encapsulate their own data and logic.
Nesting one scene within another is the same as instantiating a new type of Node that micromanages its own internal subtree of Nodes. Editing a scene is then identical to editing a Prefab or Blueprint independently of its context (a declarative extension of the Node’s type).
Defining a new script also allows one to create and add child Nodes, along with micromanaging them. Editing a script is then identical to editing a single-MonoBehaviour GameObject or Blueprint independently (an imperative extension of the Node’s type). A scene’s root node’s script is functionally similar to an Actor wrapper class around an internal subtree of components.
As a result, everything in Godot is a Node. Scripts are imperatively specialized nodes. Scenes are declaratively specialized nodes. A scene’s root node and other internal nodes can all have scripts, letting users mix and match programming styles (Here’s GDQuest’s description). Games are SceneTrees with a root Viewport node. The Godot Editor is one big fancy EditorNode. Everything is a Node, and there’s nothing magical about them.
This point is made even more clear by the fact that a scene cannot be saved or executed unless a root node has been defined. This is because the scene needs to know what type of node the scene is. Contrast that with Unity’s Scenes and Unreal’s Levels which are arbitrary containers of entities that users can safely save or execute in-editor even while empty.
Many of Godot’s C++ nodes are even imperatively defined subtrees of nodes. The image below shows the appearance of a simple scene with a Tree node on the left, as seen from the Editor. On the right, the scene’s runtime view reveals the diversity of nodes that comprise the Tree. The Tree node abstracts away a great many other nodes. The subtree is procedurally generated at runtime as users interact with the Tree’s API.
Scripts and scenes allow users to define new types of nodes with their own localized subtrees in the same way.
Nodes have a notion of “ownership”. Users can see and click on nodes that have no owner in the main screen; however, those nodes don’t exist in the scene dock at design-time. Only “owned” nodes appear in the scene dock.
The Tree node above creates nodes and adds them as children, but doesn’t make itself their owner. As such, they appear to be a part of the Tree node. Any scene file’s root node is automatically made the owner of its sub-nodes so as to make them visible.
“Running a game” means running a SceneTree. SceneTrees automatically instance the user-designated “main” scene. Users can also execute the SceneTree with the currently edited scene though. Because running a game entails running a scene, you can run your game at any level, because scenes exist at all scales of your game’s design.
Loosening Things Up
The big change here is that Unity’s GameObject/MonoBehaviour and Unreal’s Actor/ActorComponent frameworks each rely on composition. They tightly couple the relationship between them. The components cannot survive without their container.
The components also do not handle their own sub-relationships directly. They might declare what other components they need. However, the responsibility of forging those relationships is foisted upon the container logic. This prevents components from handling their own internal substructures without resorting to hacks. If it’s possible to begin with.
In Godot, nodes rely instead on aggregation. Any node can be detached from the SceneTree and continue to exist. It will be frozen in time, blind to the world. Plugging it back in allows it to detect process updates, node movements, and other SceneTree-related notifications.
When users change what scene they are running, only the main scene’s nodes change. The main scene is still a child node of the root Viewport node, which remains unchanged. A user can therefore instantly create a persistent node by just attaching it to the SceneTree’s root node.
Even the scripts attached to nodes don’t have to be attached to operate. A standalone Script functions just like a standalone class. Constants, subclasses, and static methods are all accessible from the loaded script. Attaching the script just causes it to generate a script instance with properties, signals, and local methods.
Nodes operate largely independent of other nodes, so “component” problems are rare. If a node does have a dependency, the editor will clearly warn you.
The scripting API relies on duck-typing inherently. Certain implementations (like C++, C#) might not internally do that, but attempting to call methods on other loaded scripts will operate with duck-typing. As such, interfaces are fast and loose.
Godot’s framework can maintain loose coupling no matter how complex a project becomes. Users can execute and test every scene, and therefore every node/script, independently and at any level.
Need to test a subtree in a scene? No problem. Extract the subtree into its own scene (“saving a branch as a scene”).
Want to copy one scene’s subtree into the current one? No problem. Just merge it in (“merging from a scene”).
There is no aspect of Godot’s class framework that limits users. Users don’t waste time fighting the engine’s framework to build their own. Instead, they use Godot’s framework to build the perfect set of nodes for their game.
Okay, So Everything is a Node?
Well, Ok. Not everything is actually a Node. After all, the SceneTree isn’t one. What do they have in common? Well, they are both Objects. What parts of a Node are an Object?
Well, every Object can generically store properties, methods, constants, and signals (Godot’s event system). They also each store a script. Everything an Object supports, a script can also define (including signals).
The scripts actually implement prototypal inheritance. If a request isn’t met by a script, it delegates the request to the C++ Object. Some scripts (like GDScript and C#) support inheritance between other scripts of the same type. With scripts, users can imperatively define new types. One can even create a SceneTree script, point the Godot executable at it on the command line, and run it as a game!
Everything within an Object is subject to Godot’s animation system too, so the AnimationPlayer node tends to be far more powerful than it first appears. It can control property changes, method calls, signal emissions, signal responses, etc. One can even combine AnimationPlayer nodes to animate the others’ animations.
An Object’s memory is manually allocated and deleted. To simplify memory management, Godot also has a reference-counted Reference type. Those are then extended into the automatically serializable Resource type to which Script belongs. Even scenes get serialized using a Resource called PackedScene.
Resources are most like the ScriptableObjects of Unity. Both provide a class wrapper around a set of data and can be (de-)serialized smoothly. Godot has many pre-made resources for different things (images, audio configurations, animations, etc.).
Creating a Resource is as easy as telling the Inspector to make one and saving it. Users can make custom resources by creating a blank one and attaching a script that derives Resource and defines new exposed properties.
If one needs to create a custom editor for a Resource, Godot’s tools make that simple as well. Any node can be instructed to execute at design-time (“tool” mode). Godot’s editor is itself a Godot game. So, users can seamlessly integrate into the editor the same Control GUI nodes that they use for game development. With Controls, they can build custom docks, panels, toolbars, and main screen viewers very quickly and visually.
If a user wanted to save a tree of nodes, they could also do that by saving a scene using the PackedScene Resource. Another option would be using JSON utilities or a custom Resource to save more precise properties for later reproduction.
Godot 2.1 came with very rudimentary networking features such as an HttpRequest node and direct, low-level Peer-to-Peer communication.
Godot 3.0 adds a networking HLAPI (intro and lobby system tutorial, simple FPS demo). GDScript is well integrated with it, enabling users to specifically declare the replication mode of methods. In addition, every node has access to the HLAPI utility methods, either through its own API or through the SceneTree’s.
- Every Node has access to HLAPI utility methods:
- Can set properties or call methods.
- Users can send each setter/call method to a specific client, to the server, or as a multicast to all.
- Users can send each setter/call method with TCP or unreliably with UDP.
- Every Node can get a reference to the SceneTree for its features.
- The SceneTree provides methods for network connections overall.
- Connecting to other players
- Testing a player’s connection
- Checking which network ID is the authority
- Checking the network ID of the current context.
There are many improvements people are discussing to potentially add to the HLAPI. One desired feature is Unreal’s ability to launch multiple server/client instances when running a scene. Another is the ability to simulate networking problems like latency, packet loss, bandwidth restrictions, etc. on a single machine.
Godot 3.1 will have WebSocket support, implemented by the same guy who made the HLAPI. That will allow people to send custom data over networks more effectively.
All that said, many of the features need refinement (as they were just introduced in the latest major release). Most of the HLAPI is only accessible from the scripting side of the engine, not the editor. As such, you can’t simply flag a node or property’s replication mode from the editor’s Inspector like you can with Unreal.
So, while Godot’s functionality is largely there, its usability still needs some work.
Godot Engine Summary
- There is one SceneTree.
- Scenes contain a single tree of nodes.
- The SceneTree manages the root Viewport node plus 1 main scene and zero or more other child nodes.
- Except for the root Viewport node, every Node has a parent and zero or more child Nodes.
- Nodes manage self-contained data and logic. Nodes can manage other nodes:
- imperatively micro-manage via script (potentially invisible to the editor).
- declaratively micro-manage via scene (fully visible in the editor).
- Scripts and Scenes ultimately just define new types of Nodes.
- Scripts can be inlined into scenes if necessary. No need to clutter the file system while prototyping small things.
- Developers work with the engine to develop the unique API their game needs. They don’t build an API around what the engine makes them use.
- Just as Nodes can inherit from other Nodes and own other nodes, so too can scenes inherit from and own each other.
- Scripts implement prototypal inheritance through ownership. What the script doesn’t do, it passes off to its owning C++ class.
- Godot has a full class hierarchy as well, but rather than defining several high-level objects like Unreal, it defines several low-level objects.
- Object defines properties, constants, methods, and signals (the event system).
- The main low-level Objects include reference-counted References, serializable Resources, Scripts, and PackedScenes.
- Nodes are Objects that support Groups (tag system), delete their own subtrees, and can see the SceneTree.
- Godot provides JSON utilities and scripted Resources for custom data management.
- Custom tools and editors are simple to make.
- Any script can be executed directly in the editor, because…
- The Godot Editor is a Godot game. Control GUI scenes designed in the Editor can easily integrate into the editor. Therefore…
- Users can build games and tools using the node system interchangeably.
- Godot has low-level and high-level networking features, supporting both P2P and server/client frameworks, just like the other engines.
- GDScript has a simplified interface with the HLAPI for better usability.
- The HLAPI does not yet have integration with the Editor. It is purely a scripting API.
So, that was a lot of information, way more than any of the other articles. If you stuck with it though, you should now have a decent understanding of how Godot’s framework compares to Unity’s and Unreal’s. I believe that it naturally allows for more flexibility in users’ design choices.
However, the main goal here was just to help people understand what Godot’s nodes and scenes are all about. The concepts are so different as to be very confusing to newcomers.
Unity, Unreal, and Godot share several properties with minute differences. Those small differences have a large impact on the usability of their design though:
- Each engine provides a description of the world: Scene(s), Level(s), SceneTree.
- That description entails a data structure of objects: list, list, tree.
- The objects each manage their own tree hierarchies of similar objects: true, true, true.
- In some cases, the most basic form of these objects must include a transform: true, true, false.
- In some cases, the engine supports inheritance between these objects: false, true, true.
- Each engine provides a means of assigning ownership between these objects: composition, composition, aggregation.
- In some cases, these objects are containers with components that burden the container with dependencies: true, true, false.
- In some cases, the engine provides an extended class hierarchy for users to use (which is neither good nor bad):
- False. Components only.
- True. High-level API with suggestive design for quick, but inflexible prototyping.
- True. Low-level API that gives you only exactly what you need and stays out of the way.
- In some cases, the engine provides a simple means of prototyping disposable classes: GameObjects w/o Prefabs, false, inlined scripts.
- Each engine provides access to its class API with one or more scripting languages. C#, C++/Blueprint, GDScript/VisualScript/C#/C++/D/Nim/Rust (WIP)/Python.
- Each engine provides a means of organizing reproducible, serializable, connected arrangements of object hierarchies: Prefabs, Blueprints, Scenes.
- In some cases, the engine supports inheritance between these serializable hierarchies: false, true, true.
- Each engine provides a means of assigning ownership between these serializable hierarchies: “Coming Soon” with Nested Prefabs, ChildActorComponents, Scenes.
- In every case, the engine provides a way of overriding exposed properties in instances of serializable hierarchies, within the world: true, true, true.
- In some cases, the engine provides a way of overriding exposed properties in locally owned serializable hierarchies, within other serializable hierarchies: undefined, false, true.
- In some cases, the engine provides independent editors for those pre-made object hierarchies:
- False. Unity Editor only, Prefabs edited in the same environment as other GameObjects.
- True. Blueprints with a separate Blueprint Editor (modify only, not run).
- True. Scenes with the singular Godot Editor (modify and run).
- Each engine provides a means of defining data:
- ScriptableObjects. Must use editor tools and a script to create. Editable from Inspector.
- DataTables and CurveTables. Structs and *Tables can all be created and edited with different in-editor GUIs. Compatibility with spreadsheet tech is a nice plus.
- Resources. Many premade resources available from Inspector. Can use a script for custom data. Editable from Inspector. No tools required.
- Each engine provides a means of defining custom tools and editors.
- IMGUI. Use a special scripting API with attributes to declare in-code what changes to load into the Editor. Quick and dirty.
- Slate. Use a special scripting API with a declarative syntax to declare in-code what changes to load into the Editor. Dirty, with some configuration requirements needed.
- Tool scripts. Use existing gamedev UI knowledge and the full Godot Editor to visually design expressive tools. Use any supported scripting language. Godot Editor is a Godot game with an identical GUI API.
- In-scene changes can be done immediately.
- Editor changes require the use of the EditorPlugin node which has access to the Godot Editor. EditorPlugins require some user configuration to be created (for now).
- Each engine provides a rich, high-level networking API combined with low-level networking access to meet users’ needs: true, true, true.
- In some cases, the engine provides access to networking HLAPI changes from the editor, and not just the scripting API: true, true, false.
So, judging from the bulleted list, it seems like Godot has the most flexible and light design of the bunch. But it’s important to note that this is a skewed list. It is examining the class framework: how the framework compares to best programming practices and what functionality the framework offers its users.
Much more goes into a game engine than just what this article presents above:
- The breadth and depth of out-of-the-box features.
- Available platforms.
- Cost. And not just money, but also development time and maintenance once a product is released.
- Community size and support.
- How reliable the developers are.
- Whether you as a developer can fix or workaround problems you encounter, without depending on the engine’s developers.
- The breadth and depth of introductory and/or intermediate learning materials for different topics within the engine’s use.
- Whether the scripting API is easy to learn and understand. If you’ve gotten through the easier, tutorial level of learning, it’ll need to be easy to follow so that you can continue to build content efficiently.
- Is there readable source code accessible to your team?
- Many, many more.
This series doesn’t go into any of that, so it should by no means be a definitive statement as to why you should use a particular engine.
For full disclosure, I have several Godot Engine enhancements I am working on, including…
- Editor recognition of Scripts and Scenes as named types with Node-like support immediately upon creation.
- Improving usability of EditorPlugin creation and installation (want to automate the configuration needed for EditorPlugins).
- Improving usability of custom nodes defined by shareable EditorPlugins.
This article is bound to have some information I messed up somewhere. While I have used each engine before, it’s been some time since I’ve gotten in deep with Unity and Unreal. Things I didn’t already know for sure, I tried my best to look up and confirm (actually learned about UE4’s ChildActorComponents that way, so that was cool).
If you find anything that is stated in error or which is presented while missing crucial details, please let me know. I update articles with new information and corrections as they are reported to me. I sincerely hope the article helped you to better understand Godot’s relationship to Unity and Unreal Engine 4. Cheers!