As mentioned in the previous post, I have been splitting my time between laying the groundwork for Project Orion and learning about some new topics in Game Dev. After brushing up on Unity for a bit with Harmonize, I thought I would try my hand at a different engine, to get some more experience. At first, Unreal Engine was an easy choice for what to try next, being the other big name in the market, but recently I had been hearing a lot of talk around Godot - an open source game engine that has been around for nearly a decade now.
I had previously worked with Godot very briefly during a game jam some five years ago, and found it surprisingly easy to pick up. Since then, it has received a major update in the form of its recently released version 4, which sparked many recommendations from friends and around the internet, so I decided to give it a go - and in this post, I aim to share a little of what I have learned about this engine so far.
Unlike Unity's GameObject-Component model, Godot's architecture is based around Nodes. These are the basic building blocks used in the engine, and they represent both the objects/entities in a game and their functioning. Everything from the camera and lights to the physics-bodies of entities are Nodes, and these nodes are then organized into a tree structure to form Scenes. Each node has a bit a code that defines its behaviour, and developers can extend these classes with custom scripts to implement game logic. Furthermore, new types of nodes can be created, inheriting from any other node, and complex node trees can be saved as scenes that can be reused and extended as well.
In the example above, Building is the root node of a custom scene, and it has a set of child nodes that help define what a "Building" is and what it does. The root node itself is a standard Node3D, which has a Transform and not much more, thus being useful to represent an object with a spatial representation in 3D. StaticBody, MeshInstance and CollisionShape are also all standard, prebuilt Nodes which offer the Building their functionalities - having a physical representation in space, rendering a visible mesh and defining a shape for collision detection. Finally, BuildingInventory and TaskProvider are custom nodes I created to encapsulate game-specific logic that helps define what a building can do - store stuff and create tasks.
This structure, I will admit, was quite foreign to me at first. It was easy to think of child nodes as components in a gameObject-like scene, but there are some explicit choices made for this model that set it apart. There is a large emphasis on OOP principles, as both individual nodes and entire scenes can be inherited from to create specializations - in a way, it would be like being able to create prefabs that inherit other prefabs.
As part of its quite comprehensive documentation, Godot suggests some best practices for this model. First and perhaps most importantly, nodes should be able to function independently - TaskProvider should be able to exist on its own, outside of a Building, and Building should work even without a BuildingInventory. This forces the developer to be mindful of code coupling and separation of responsibilities.
Another tenet of Godot's Scene-Node structure regards communication between nodes. Here, the recommendation is simple: a parent node may call its children directly, but the opposite should not happen. For communicating up the tree, or even among siblings, the use of signals or delegates is suggested.
I am still coming to fully understand the implications of this arrangement, but so far its been looking quite promising in terms of structuring code in a project.
With all this focus being give to Nodes, it can be easy to get carried away and use them as infinite Lego bricks to build any game entirely out of them. So much so in fact, that Godot's maintainers chose to dedicate a page of their Best Practices docs to advice on how to avoid using nodes for everything. Because sure enough there can be such a thing as too many nodes, as I was quick to find out in my little practice project.
Don't get me wrong, nodes are great. They are convenient and the whole environment invites us to play around with putting a bunch of them together to make stuff. However, for certain situations, it can be best to prioritize reusing existing nodes. In my case, that came about when I was implementing buildings in the practice project. Consider the following: a game may have dozens, even hundreds of different buildings - walls, doors, tables, boxes and so on. If we were to create a scene to represent each of these we would have a massive amount of scenes, which would then have to be loaded and indexed for access during runtime - not to mention the complexity of adding new buildings and updating existing ones.
Instead of doing that, Godot offers us the ability to use Resources - another fundamental class in this environment. Resources share some of the conveniences that nodes offer - such as full inheritability and editor-friendly interfaces - and are meant to be used as data containers to complement the functioning of nodes. Going back to the buildings example, this means we can store all the data of the many different types of buildings in instances of a Building Resource:
class_name BuildingResource extends Resource @export var building_id: int @export var blueprint_mesh: Mesh @export var mesh: Mesh @export var collision_shape: Shape3D @export var building_name: String @export var construction_task: TaskResource @export var dimensions: Vector2i
This simple code structure defines a BuildingResource type which can then be instanced as many times as needed to represent each different type of building in the game. Then, during the game, when we need to create a new node to represent a building in the game space, we can just instance a generic Building node and give it an instance of this resource containing the data it needs to set itself up as the correct building.
By marking properties as @export they are made visible in the editor for convenience, allowing us to directly set the values for each instance as we go. However, even that is not necessary beyond the convenience while prototyping: we can instead have a file such as a json or csv containing all the data for all buildings, load it when the game launches and parse through the file to instantiate all BuildingResources from that data. This makes adding or editing buildings quite simple.
In the snippet above we also see another powerful aspect of resources: they can hold references to other resources. Mesh, for example, is a prebuilt resource type in Godot meant for storing data for, well, meshes. TaskResource on the other hand is another custom made resource type I created to represent work tasks, such as constructing this building. While setting values through the editor we can simply drag resources to the properties, whereas when parsing from a file we can access external resources through their ids or paths.
Finally, we can use this composition of resources to define specific behaviours of buildings via other resource types, such as a hypothetical BuildingProperty resource. BuildingResource could have an array of BuildingProperties, filled with different specializations of this class, each defining their own behaviours as appropriate. During gameplay, the Building node would simply have to iterate through this array and invoke any functions that exist - it would not need to know anything about the actual inner workings of these functions. This allows us to keep Building a relatively simple class and isolate logic in each BuildingProperty - no massive classes with dozens of responsibilities, hopefully!
There is a lot more to be said about Godot - after all, I only touched upon the basic paradigm of the engine and a couple of important data types that exist - however this post is already getting lengthy and the point is not to create a massive documentation for Godot - that already exists and it is pretty good.
The important takeaway here it that Godot invites us to organize game projects according to a different structure than that of, for example, Unity. And while I still have a lot to learn about the strengths and weaknesses of both paradigms, I must admit that Godot has impressed me. The scene-node structure is deceptively simple, and yet when used along with the best practices suggested in the docs it does seem to result in a quite well organized, maintainable and reusable code base. My adventures with Godot so far have been quite simple, so I can't speak for the scalability of this system when a project gets complex, but I am rather eager to try it out.
In conclusion, I do plan on getting a lot more practice in with this engine - and I do love myself some open-source software, after all. Whether or not Project Orion will be developed using Godot remains to be seen, but it is certainly looking like a strong candidate. Speaking of which, updates on Project Orion coming soon - see you then!
Did you like this post? Tell us
Leave a comment
Log in with your itch.io account to leave a comment.