godot-rust / gdnative

Rust bindings for Godot 3
https://godot-rust.github.io
MIT License
3.62k stars 210 forks source link

Add example of loading and using resources #156

Closed beatgammit closed 5 years ago

beatgammit commented 5 years ago

I'm working on a terrain generator, and that requires loading and instantiating scenes, and then adding them as children to the current object. I have an example in GDScript that does that, but I can't figure out how to port it to Rust.

I'm essentially trying to replicate this GDScript:

onready var grass = preload("res://grass.tscn")

And here's my attempt in Rust:

    let mut loader = ResourceLoader::godot_singleton();
    let res = ResourceLoader::load(&mut loader, GodotString::from_str("res://grass.tscn"), GodotString::from_str(""), false);

The first line seems to work since I can print out loader with godot_print!("loader: {:?}"), and it gives me something like this:

loader: ResourceLoader { this: 0x4a4a340 }

However, the next line always crashes the game, with the following being printed out periodically:

modules/gdnative/gdnative.cpp:393 - No valid library handle, can't terminate GDNative object

From what I can tell, the last two arguments are optional, so I gave them what appear to be default values.

I'd be happy to create an example that loads resources once I can figure it out. Here's my terrain generation code in GDScript that I'm trying to port to Rust:

for x in WIDTH:
    for y in HEIGHT:
        var val = open_simplex_noise.get_noise_2d(x, y)
        var obj
        if val > 0:
            obj = soil.instance()
        else:
            obj = grass.instance()

        obj.transform.origin = Vector3(x - WIDTH/2, round(val * 10), y - HEIGHT/2)
        add_child(obj)

EDIT:

I found another project that has bindings that seem to work with resource loading, so I think this is a problem with these bindings, but I'm not 100% sure:

https://github.com/Thinkofname/rust-godot-example/blob/master/rust/src/lib.rs

karroffel commented 5 years ago

The error that you are seeing is related to an unloading error inside of Godot, this should be fixed on Godot's master branch (maybe even 3.1.1, I don't know if it was cherry-picked or not).

beatgammit commented 5 years ago

Oh, awesome! I'll have to play with the new Godot then. I'll post back here if I can confirm that it works on the latest Godot, and I'll probably add a PR for an example loading resources.

extraymond commented 5 years ago

Tried this on 3.1.1, and it stills crashes on loader.load. Unable to handle return Option with Option.expect() too.

tom-leys commented 5 years ago

I believe I have fixed this in my PR https://github.com/GodotNativeTools/godot-rust/pull/168 as I ran into exactly the same issue as you.

tom-leys commented 5 years ago

Here is my working code, you'll see it is very rough.

Note the script expects to be in a Spatial node and the scene to have a Spatial (or subclass) root node. It depends on some minor changes I made to specify a transform nicely. https://github.com/tom-leys/godot-rust/commit/30923cb42b6967ac35d9b33526b03fe1218cff72 .

I am no means an expert, I only just got this working myself. I hope it helps you.

You'll see in my picture the 3 "spaceships" (yes, they look like conveyors)

#[derive(gdnative::NativeClass)]
#[inherit(gdnative::Spatial)]
pub struct StarWorldLink {
}

#[gdnative::methods]
impl StarWorldLink {

    fn _init(_owner: gdnative::Spatial) -> Self {
        StarWorldLink {
        }
    }

    #[export]
    fn _ready(&self, mut owner: gdnative::Spatial) {
        godot_print!("hello, StarWorldLink.");

        let ship_scene = ResourceLoader::godot_singleton().load(
            GodotString::from_str("res://ship_root.tscn"),
            GodotString::from_str("PackedScene"), false);

        godot_print!("StarWorldLink Have scene");

        if let Some(ship_scene) = ship_scene.and_then(|s| s.cast::<PackedScene>()) {
            for x in 0..3 {
                let mut instance = ship_scene.instance(0); // 0 - GEN_EDIT_STATE_DISABLED

                if let Some(mut instance) = instance.and_then(|inst| unsafe {
                      inst.cast::<Spatial>()
                  }) {

                    godot_print!("StarWorldLink About to make instances");
                    let transform = Transform::translate(Vector3::new(5.0 + 5.0 * x as f32, 0.0, 0.0));
                    unsafe {
                        instance.set_global_transform(transform);
                        owner.add_child(Some(instance.to_object()), false);
                    }
                    godot_print!("StarWorldLink Instance {} Made", x);
                }
                else {
                    godot_print!("Should be able to instance scene");
                }

            }

        }
        else {
            godot_print!("StarWorldLink could not load ship_link scene");
        }
    }
}

Capture

extraymond commented 5 years ago

Thx man!!! Just tested it and it worked. Finally being able to load scenes through ResourceLoader.

Can you explain why the need to cast from resource -> PackedScene -> Spatial instead of add_child() directly (this crashed the program for me)?

tom-leys commented 5 years ago

I'm really glad that this helped you getting it working too, that's awesome!

The process as I understand it is

  1. ResourceLoader returns a resource (could be a texture, a scene, a script, a ... anything really!)
  2. We check that it loaded something (that is what .and_then does) and then try to turn it into a PackedScene
  3. If that works, we can now make an instance of it, so we do that.
  4. If we got an instance, next we add it the Script's spatial node. I make sure we actually have one here too.

Check out https://docs.godotengine.org/en/3.1/getting_started/step_by_step/resources.html to help you understand step 1.

tom-leys commented 5 years ago

Here is version 2 which creates a new ship every 2 seconds until there are 10.

#[derive(gdnative::NativeClass)]
#[inherit(gdnative::Spatial)]
pub struct StarWorldLink {
    world_id : u32,

    ship_scene : Option<PackedScene>,

    num_ships : u32,
    time_passed : f64,
}

#[gdnative::methods]
impl StarWorldLink {

    fn _init(_owner: gdnative::Spatial) -> Self {
        StarWorldLink {
            world_id: 0,

            ship_scene: None,
            num_ships: 0,
            time_passed: 0.0,
        }
    }

    #[export]
    fn _ready(&mut self, mut owner: gdnative::Spatial) {
        godot_print!("hello, StarWorldLink.");

        let ship_scene = ResourceLoader::godot_singleton().load(
            GodotString::from_str("res://ship_root.tscn"),
            GodotString::from_str("PackedScene"), false);

        if let Some(ship_scene) = ship_scene.and_then(|s| s.cast::<PackedScene>()) {
            godot_print!("StarWorldLink Have scene");
            self.ship_scene = Some(ship_scene);
        }
        else {
            godot_print!("StarWorldLink could not load ship_link scene");
        }
    }

    #[export]
    fn _process(&mut self, mut owner: gdnative::Spatial, delta: f64) {
        self.time_passed += delta;
        if self.num_ships < 10 && self.time_passed > (self.num_ships as f64 * 2.0) {
            if let Some(ship_scene) = &self.ship_scene {
                self.num_ships += 1;

                let mut instance = ship_scene.instance(0); // 0 - GEN_EDIT_STATE_DISABLED

                if let Some(mut instance) = instance.and_then(|inst| unsafe {
                    inst.cast::<Spatial>()
                }) {
                    let transform = Transform::translate(Vector3::new(10.0 + 6.0 * self.num_ships as f32, 0.0, 0.0));
                    unsafe {
                        instance.set_global_transform(transform);
                        owner.add_child(Some(instance.to_object()), false);
                    }
                    godot_print!("StarWorldLink Instance {} Made", self.num_ships);
                } else {
                    godot_print!("Should be able to instance scene");
                }
            }
        }
    }
}
karroffel commented 5 years ago

Glad that it works! :) PR #168 is merged, does this resolve this issue so it can be closed or do you think it should stay open?

tom-leys commented 5 years ago

We should probably create a simple example as originally requested, I'm sure it would be highly useful for the next new person.

karroffel commented 5 years ago

Yes sounds good, I will create a tracker issue for this.

tom-leys commented 5 years ago

Scene Creation example here - https://github.com/GodotNativeTools/godot-rust/pull/170

karroffel commented 5 years ago

170 was merged! Is that example sufficient for now in terms of resource loading or should there be more examples for that?

tom-leys commented 5 years ago

I would expect that #170 makes it well covered but lets see if OP has something to say :D

beatgammit commented 5 years ago

Yeah, that looks sufficient. If I need anything else, I'll fiddle with it and perhaps make a new example.