Gorgeous Godot games in Rust

Tom Leys
Tom Leys
Sep 21 · 9 min read

So you wanted to use the very best engine, while also using the very best and most performant language?

Well, I have the guide for you — but this adventure is not for the faint of heart. You’re going to run into a lot of issues with the integration, with Godot and also challenges with Rust.

On the other side however, you’ll be coding rock solid and performant game code. Code that you can share across multiple platforms (Windows, Mac & Linux .. even WebAssembly). Also, by using Godot you’ve got a great 2D and 3D engine which you can rebuild to your needs from source under the very permissive MIT license.

In this tutorial, I’ll be showing you how to get started. Together we will download Godot, set up the GDNative bindings and I will show you how to get an example working. I’ll work you through the example code. Based on your questions I will create more tutorials to cover the tough points.

If you would like to learn more about how I use Godot and Rust in The Recall Singularity, jump over to my previous article. Or read a Tale of Conquest to learn what I’m making.

Here is a summary of what we are going to do:

  1. Ensure you are running 64 bit Godot (or change later steps)
  2. Get the GDNative bindings (clone them locally)
  3. Look in examples\hello_world (the tutorial explains the files)
  4. Check Clang is installed on your path and rust is using a 64 bit target
  5. Cargo build in the example folder
  6. Check the Godot target matches the output location
  7. Launch Godot, import the example project.
  8. Launch the project, observe “Hello world”
Picture of Godot’s home page, Download button highlighted.
Picture of Godot’s home page, Download button highlighted.
https://godotengine.org/

Let’s install Godot

Installing Godot is as easy as visiting https://godotengine.org/ and clicking on download. Choose the Standard 64 Bit version, though I won’t judge if you want the Mono support too. I am using this specific version:

https://downloads.tuxfamily.org/godotengine/3.1.1/Godot_v3.1.1-stable_win64.exe.zip

Godot download page with Windows 64 Bit highlighted
Godot download page with Windows 64 Bit highlighted

You will find that there is a single binary file that you just downloaded. My version is called Godot_v3.1.1-stable_win64.exe — Congratulations, you just “installed” Godot. We don’t need to run this just yet.

Install Rust

If you don’t have rust installed, you should follow the instructions here. On windows the current instructions say to download and run “rustup-init.exe”.

If you already had rust installed, I suggest you use “rustup update” to make sure everything about your install is up to date.

> rustup update

Get the GDNative — Rust Bindings

Now, we need to grab the bindings that we are going to use to communicate between Godot and Rust. Checking this code out of Git is not strictly necessary, but I like to do it because in a moment we are going to use examples from inside the repository.

We are going to build a Rust crate which has a “target” that is a Dynamically Linked Library (DLL). When Godot is running, our game project will tell it to load this DLL. The Godot — Rust bindings will help us tell Godot what our DLL can do (which classes / “Scripts” are inside it) and will help us to make calls to Godot to tell IT what to do with our game.

Okay, lets get those bindings. You can find them online on GitHub, lets clone those. If you don’t already have Git installed you might want to do that now. I also use Git Extensions which is a seriously good free GUI for git.

I checked out commit 491904858462875a which is “Merge pull request #203”

Choose Either:

> git clone https://github.com/GodotNativeTools/godot-rust.git

Or perhaps you prefer using Git Extensions, which gives us a nice right click to clone menu

Let’s have a look what we get

Once checked out, please navigate into the repository. You will find a examples / hello_world folder

> cd C:\code\godot-rust\godot-rust\examples\hello_world

Cargo.toml — Tells rust that this is a compile-able crate. Defines what dependencies our crate has, including gdnative

hello_world_library.gdnlib — Tells Godot where to find the compiled output from Rust (or some other language like C++). Instructions for this are here.

Main.tscn — A Godot Scene. Defines where different things are for Godot to view. You will be editing scenes like this in Godot a lot.

project.godot — Tells Godot that this is a folder that you can load

Lets build and run the example.

Okay so we go into the example directory and build the example. Lets see if it works….

Oh No!

On my computer this has failed because I have chosen the wrong “build target” for Godot (I was using GNU) but it is likely you will encounter this issue because you haven’t installed Lib Clang. If you need to install Lib Clang, please do so.

If you have any issues with this, or some other issue, please let me know. It’s been a while since I got this all working and I might have forgotten a step here.

Lets first check our build target for rust (I put stable here, but you might want to use nightly- for awesome features, I know I usually do) :

> rustup default stable-x86_64-pc-windows-msvc

Note: Since we are using 64 Bit Godot above, we need a x86_64 target here or godot will JUST CRASH when you load the DLL. If you are trying to write a 32 bit game, use -i686- not -x86_64-.

Yay! It works!

Where did my Output Go?

It turns out that the examples/hello_world/ project is not standalone within the larger godot-rust repository.

Rust has a concept called Workspaces. The godot-rust git repository has one set up in the root folder, see C:\code\godot-rust\godot-rust\Cargo.toml and you will realise that our example does not stand alone.

Because of the workspace, any output from compiling something (for instance this example) goes into the root / target directory. You’ll find your precious example output at

C:\code\godot-rust\godot-rust\target\debug\hello_world.dll

To find this DLL, godot needs this line in hello_world_library.gdnlib

[Line 5] Windows.64=”res://../../target/debug/hello_world.dll”

(I had to add this, I’ll put a PR in to fix this in godot-rust for you)

Lets load Godot

Okay, jump up a couple of folders and launch Godot. After the splash screen you should see a project manager. I want you to click the import button here.

Tell Godot to import the project in our examples folder. It will notice the project file and add it to the list.

C:\code\godot-rust\godot-rust\examples\hello_world

This is what it will look like if you import the project. Then try to do it again so you can make a tutorial.

A double click on this new project in the list will lead you into Godot.

I would like to draw your attention to the little scroll on the top left there. In this example, the node has a “script” which is being provided from Rust. If you are totally new to Godot you will want to follow the “Your first game” tutorial before you try to do much editing here.

Once you have used Godot for a while you’ll know that scripts are usually GDScript, but in this case it is GDNative.

Lets review the GDNative setup

  1. Clicking on Node reveals the script options.
  2. Next, click on the dark blue region to bring up GDNative info.

Please notice that we have a class name “HelloWorld

We also refer to the hello_world_library.gdnlib which we created earlier.

Lets run our “Game”

Press F5, or click the Play button and you’ll get the very underwhelming project. But it says hello, world!

Can you see it there in both the text console and the Godot Gui?

Lets look at the source code

Here is CLion, in “High Contrast” theme.

The Godot-rust bindings use a lot of macros to make your life easier. Lets go through this file.

#[macro_use]
extern crate gdnative;

First import gdnative. If you look at cargo.toml you’ll note it is defined to reside in a nearby folder. Later I’ll show you how to move from an example into a full project.

#[derive(gdnative::NativeClass)]
#[inherit(gdnative::Node)]
#[user_data(gdnative::user_data::ArcData<HelloWorld>)]
struct HelloWorld;

The struct HelloWorld; is a declaration in Rust of a structure which stores no data at all. Above it are 3 decorators

  1. #[derive(gdnative::NativeClass)] — Adds routines needed by Godot to commuicate with this class
  2. #[inherit(gdnative::Node)] — Tell Godot which class you subclass from. I believe it also lets you call base class routines.
  3. … I actually don’t know what #[user_data(gdnative::user_data::ArcData<HelloWorld>)] does.
#[gdnative::methods]
impl HelloWorld {
fn _init(_owner: gdnative::Node) -> Self {
HelloWorld
}

#[export]
fn _ready(&self, _owner: gdnative::Node) {
godot_print!("hello, world.")
}
}

Each object you write in Rust which is called by godot requires a _init method which constructs and returns a structure instance. Here it doesn’t do much.

The one function we export to Godot is the _ready function — a special function which is called by Godot after a node is added to a scene (or the game starts in this case)… and you’ll see the print instruction.

fn init(handle: gdnative::init::InitHandle) {
handle.add_class::<HelloWorld>();
}

godot_gdnative_init!();
godot_nativescript_init!(init);
godot_gdnative_terminate!();

When Godot loads our library it needs to find 3 different entry point functions. The Godot-rust bindings take care of this with 3 macros. You need to define a function init and you will need to add a line here which registers every class you add as your project grows. Read the docs or the source for more detail on how these work.

What next is up to you!

I plan to write another more advanced tutorial later about creating your first games project that sits separate from this example.

For a hint, try moving this example (or perhaps the scene_create example) into its own folder. Then change

gdnative = { path = "../../gdnative" }

Into :

gdnative = { path = "C:\code\godot-rust\godot-rust\gdnative" }

API Documentation

To do anything really useful in Rust, you will need to call back into functions inside Godot. Some of the API is threadsafe, some not. Everything is safe to call if you are “inside” a function called from Godot.

There is API documentation for the various parts of godot-rust online which you can consult to learn what you can do.

For instance, you will find the documentation for the “Input” singleton here and can see that you need to call Input.godot_singleton() which returns an instance that you can then use to call further Input functions on.

So to call it would be something like

let UI_up = GodotString::from_str("UI_up");
if Input::godot_singleton().is_action_pressed(UI_up) {
// Your code here
}

So far I handle all input in GDScript and then call into rust to do heavy lifting. Most API calls I am currently making move nodes around in the Godot Scene tree from Rust.

Please read

If you want to “be inspired by” my code structure, I have explained it at A basic Godot & Rust Structure. Or you could learn more about how I use 3D assets.

Fellow reader Ardawan Izadi has provided a useful Godot Setup shell script (for mac / linux) which (when run in an empty folder) should set up everything for you. Please back up your work before you run it.

Feel free to follow me on twitter if you would like to read further tutorials like this and read lots of updates about my upcoming game using Godot and Rust to create Space factories. Or feel free to come and hang out in the discord server.

Tom Leys

Written by

Tom Leys

Making a space game

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade