Godot-Rust: Creating a Scene from another Scene

This article is aiming to provide a guide to instantiate a Godot scene from another scene. A typical use case of this can be shooting bullets. In this case, we instantiate bullets of the Bullet scene from the Player scene. I will be using gdnative 0.9.3 and rustc 1.50.0.

Bullet Scene

First, let’s consider the Bullet scene. In the below example, I am using a KinematicBody2D node as the root node. Let's give it some properties so that when we create a bullet, it will move towards the positive x-direction.

use gdnative::prelude::*;

#[derive(NativeClass)]
#[inherit(KinematicBody2D)]
pub struct Bullet {
acceleration: f32,
max_speed: f32,
direction: Vector2,
viewport: Ref<Viewport, Unique>,
}

Let’s destroy the bullets when they move out of the viewport. For this, we will use the queue_free() method of the type Ref<T, Unique>. This is because, in the later section, we create bullets of this type. This type is a manually-managed object reference type. Therefore we have to manually deallocate the memory.

#[methods]
impl Bullet {
...

#[export]
fn _physics_process(&self, owner: &KinematicBody2D, delta: f32) {
...

Save this scene at “res://Bullet.tscn”. In the next section, we will use this path to load this scene inside the Player scene.

Player Scene

Let’s use a KinematicBody2D as the root node of our Player as well. Then we can attach a GDNative Rust script to that node. Let’s add a property to hold the Bullet scene we are loading.

use gdnative::prelude::*;

#[derive(NativeClass)]
#[inherit(KinematicBody2D)]
pub struct Player {
bullet_scene: Ref<PackedScene, Shared>,
}

Let’s load the Bullet scene from the Player now. The proper design and implementation of resource loading and handling are out of the scope of this article. Therefore we will just load it inside the constructor of the Player.

#[methods]
impl Player {
fn new(_owner: &KinematicBody2D) -> Self {
Player {
bullet_scene: ResourceLoader::godot_singleton()
.load("res://Bullet.tscn", "PackedScene", false)
.unwrap()
.cast::<PackedScene>()
.unwrap(),
}
}
}

load() method returns an Option<Ref<Resource, Shared>>. We unwrap it to get Ref<Resource, Shared>. Then we have to cast the Resource to a PackedScene. This is because PackedScene allows us to instantiate the scene’s node hierarchy using the instance() method. Let’s see how to do this in the next section.

#[export]
fn _process(&self, owner: &KinematicBody2D, _delta: f32) {
let input = Input::godot_singleton();
let shoot = input.get_action_strength("shoot") != 0.0;

In the Input Map of the Godot engine, I have mapped the “shoot” to the Space key. When the Space key is pressed, we want to instantiate the bullet. We cannot call API methods on Ref<T, Shared> types. We have to obtain a safe view for this. This is achieved using the assume_safe() method. It is safe here because we know that the lifetime of self.bullet_scene is valid during the entirety of the lifetime of the local bullet_scene reference. Now we can call the instance() method to instantiate the bullet’s node hierarchy and get the Node object.

GDNative Rust has three Typestates to express thread safety.

  1. Shared
  2. ThreadLocal
  3. Unique

You can read more about them here. Since we are not using multiple threads in this example, We don’t have to worry about these states much. Let’s cast the bullet instance to KinematicBody2D so that we can set the position of the bullet.

#[export]
fn _process(&self, owner: &KinematicBody2D, _delta: f32) {
...

Again, we need to obtain a safe view to call API methods on the bullet. Here we can assume_safe() but since we know that this will not be shared anywhere else, it is safe to assume_unique(). After casting, we can set the position of the bullet relative to the Player. If you run the game now, you will still see that even if we press Space, bullets not appearing. Let’s add these bullet instances to the main scene of the game as children nodes to fix this.

#[export]
fn _process(&self, owner: &KinematicBody2D, _delta: f32) {
...

I hope this article will help someone to understand how to load a scene and instantiate instances of it from a different scene using the Rust programming language in the Godot game engine.

Software Engineer