I have been looking into the Godot engine more and more recently. Two important features of any engine are how the networking code works and whether you have access to low level programming outside of any scripting languages. I decided to dive into Godot to see how well it handles these two features, and determine if I think it handles them well enough to make a full fledged game with them.
I started out using the GDQuest multiplayer demo example which uses the high level networking API in Godot for a simple multiplayer game. The GDQuest tutorials are very well done so I recommend you check those out if you want to learn more about working with Godot.
You can find the full source code for my GDNative C++ project here.
Godot allows you full access to the engine’s features through a system called GDNative. GDNative is a way to write code in whatever language you wish then hook that code back up to the engine through a set of bindings. In particular, the C++ bindings are fairly well developed and have a fairly active developer base keeping them up to date with the latest engine changes.
To start, you need to pull down the godot-cpp repo to obtain the necessary C++ bindings. A full explanation on how to obtain these bindings and the commands used to obtain the godot_headers submodule is available in the godot-cpp README file.
For more information regarding godot-cpp, go here to check it out.
I ran into a little trouble when trying to get the C++ bindings to work with the version of Godot I was using. The instructions on the godot-cpp repo aren’t very clear when it comes to versioning so it took me a little while to figure out what I needed to do.
When using the 3.1-stable build of the Godot engine, I found the C++ bindings worked best when I used the 3.1 branch of godot-cpp. Using the 3.1 branch as well as the corresponding git commit for the godot_headers repo that is associated with the godot-cpp 3.1 branch has been working reliably for me. If you are using other versions of the various repos and the Godot engine itself, your mileage may vary depending on how everything lines up. If you are running into either compilation or runtime errors then chances are that you have a mismatch with the versioning somewhere.
Setting Up Godot Bindings
There are two concepts you need to be aware of when creating GDNative scripts.
The first concept is of a GDNative Library. The library represents a collection of GDNative scripts that you can use in Godot. In the engine it has a few configurable properties such as whether to make it a singleton and whether it is re-loadable at run-time. It also contains references that point to the actual code libraries built for each platform that the game needs to run on.
When creating a library to use in Godot, your code must contain the entry and exit points for the library. The Godot engine uses these hooks to be able to create an instance of your library. Your code also needs to have the hook for initializing the classes found inside of the library. Example code for the Godot hooks looks like this:
The second concept is NativeScript itself. NativeScript is the actual code/script that you are writing that you want to be able to call from the Godot engine. Inside the Godot engine this script is represented by the class name you are using as well as the GDNative Library that contains the compiled source for that class.
A NativeScript class needs to have a few extras added to it in order for Godot to be able to use it. First, the definition of the class needs to contain the GODOT_CLASS macro. This is used to tell Godot the name of the class you are registering to use in GDNative as well as the class it is inheriting from.
The class also needs to contain a _register_methods function as well as a _init function. Godot uses these to setup the class for use with GDNative and provide hooks to be able to use the engine features for class methods and properties.
An example class declaration looks like this:
Creating a GDNative Script
Now that we have the basic necessities ready to go, it’s time to dive in and actually start creating a script to use in our project.
Continuing with the Rifle class used in the previous examples, let’s expand it to contain the actual logic for the rifle node.
The rifle class requires a few new methods to make it behave properly. These are _process, _on_Timer_timout, and _shoot. The _process method is actually a built-in Godot method that is called once per frame to handle game logic. The _on_Timer_timeout method is used as a callback that is triggered whenever the Timer node attached to the rifle emits the timeout signal. Lastly, the _shoot method is what the rifle class will use to spawn a bullet and send it flying off in the correct direction.
The first thing to do is register all these new methods!
Now that all the methods have been registered, Godot is able to connect the appropriate hooks to be able to call down into our GDNative Library.
Inside of the _process method the rifle needs to be able to “shoot” whenever it is allowed to and the player has pressed the “shoot” input. Since this game is using networking to achieve multiplayer, there is a little more involved with getting this to work properly.
First the code needs to check to make sure that the node calling the code is the network master. The network master is typically assigned to be the authorative server. But for players, the player is assigned to be the network master of their node and sub-nodes. So in this case, since the rifle is a sub-node of the player, only the player that owns this rifle is allowed to perform the code contained in the _process method.
Next the code obtains the Input singleton class to check if the “shoot” input was pressed. If the input was pressed and the rifle is allowed to shoot again, then the _shoot method is called. To make sure the _shoot method is replicated on all clients over the network, instead of just calling _shoot(), we actually call rpc(“_shoot”). This tells the engine to call the _shoot method over the network. In the _register_methods method we gave the _shoot method a rpc mode of sync. The sync mode means that whenever this method is called, make sure it gets called on all other clients as well as the client it was called from. This makes sure that the _shoot method is properly replicated across the network so that all players can see when another player has shot their rifle.
Now that we have the rifle class able to shoot we need to implement the actual _shoot method. This method is fairly simple but caused a lot of confusion when trying to make it work using GDNative.
The method creates a new instance of the Bullet scene, then it adds that instance as a child of the rifle node. Next the bullet’s position is set to that of the rifle. Then the bullet’s direction is set based on the direction the player is facing.
What caused issues for me was trying to set the Bullet’s direction. The direction is a property of the Bullet class that was added from within the Bullet C++ class. The Bullet class registers that property so that the Godot engine is able to both get its value as well as set it. For whatever reason, I was unable to change the direction value by accessing the member variable of the class directly. Instead I found only one method that actually worked to set the Bullet direction variable so that Godot would be happy. That was to call the Bullet node’s set method and pass in the property name and property value.
This post has gotten a little long winded. I wanted to dive into the networking setup for the client/server used in the project. Maybe I’ll save that for another post. If you are interested in finding out more about networking using Godot I would recommend you check out the following links.
Until next time.