Exported properties

Like methods, properties can be exported. The #[property] attribute above a field declaration makes the field available to Godot, with its name and type.

In the previous example, we could replace the count_enemies() method with a property enemy_count.

#![allow(unused)]
fn main() {
#[derive(NativeClass)]
#[inherit(Node)]
pub struct GodotApi {
    #[property]
    enemy_count: i32,
}
}

The GDScript code would be changed as follows.

print("enemies: ", api.enemy_count)

That's it.

Export options

The #[property] attribute can accept a several options to refine the export behavior.

You can specify default property value with the following argument:

#![allow(unused)]
fn main() {
#[property(default = 10)]
enemy_count: i32,
}

If you need to hide this property in Godot editor, use no_editor option:

#![allow(unused)]
fn main() {
#[property(no_editor)]
enemy_count: i32,
}

Property get/set

Properties can register set and get methods to be called from Godot.

Default get/set functions can be registered as per the following example:


#[derive(NativeClass, Default)]
#[inherit(Node)]
struct GodotApi {
    // property registration
    // Note: This is actually equivalent to #[property]
    #[property(get, set)]
    prop: i32,
}

If you need custom setters and getters, you can set them in the property attribute such as in the following example:

#![allow(unused)]
fn main() {
#[derive(NativeClass)]
#[inherit(Node)]
struct HelloWorld {
    // property registration
    #[property(get = "Self::get", set = "Self::set")]
    prop: i32,
}

impl HelloWorld {
    fn new(_base: &Node) -> Self {
        HelloWorld { prop: 0i32 }
    }
}

#[methods]
impl HelloWorld {
    fn get(&self, _base: TRef<Node>) -> i32 {
        godot_print!("get() -> {}", &self.prop);
        self.prop
    }

    fn set(&mut self, _base: TRef<Node>, value: i32) {
        godot_print!("set({})", &value);
        self.prop = value;
    }
}
}

Note: get vs get_ref

There are two ways to return the property.

  • get will return a value of T which must result in the value being cloned.
  • get_ref must point to a function that returns &T, this is useful when working with large data that would be very expensive to copy unnecessarily.

Modifying the previous example accordingly results in the following:

#![allow(unused)]
fn main() {
#[derive(NativeClass)]
#[inherit(Node)]
struct GodotApi {
    // property registration
    #[property(get_ref = "Self::get", set = "Self::set")]
    prop: String,
}

impl GodotApi {
    fn new(_base: &Node) -> Self {
        GodotApi { prop: String::new() }
    }
}

#[methods]
impl GodotApi {
    fn get(&self, _base: TRef<Node>) -> &String {
        godot_print!("get() -> {}", &self.prop);
        &self.prop
    }

    fn set(&mut self, _base: TRef<Node>, value: String) {
        godot_print!("set({})", &value);
        self.prop = value;
    }
}
}

Manual property registration

For cases not covered by the #[property] attribute, it may be necessary to manually register the properties instead.

This is often the case where custom hint behavior is desired for primitive types, such as an Integer value including an IntEnum hint.

To do so, you can use the ClassBuilder -- such as in the following examples -- to manually register each property and customize how they interface in the editor.

#![allow(unused)]

fn main() {
#[derive(NativeClass)]
#[inherit(Node)]
#[register_with(Self::register_properties)]
#[no_constructor]
pub struct MyNode {
    number: i32,
    number_enum: i32,
    float_range: f64,
    my_filepath: String,
}

#[methods]
impl MyNode {
    fn register_properties(builder: &ClassBuilder<MyNode>) {
        use gdnative::export::hint::*;
        // Add a number with a getter and setter. 
        // (This is the equivalent of adding the `#[property]` attribute for `number`)
        builder
            .property::<i32>("number")
            .with_getter(Self::number_getter)
            .with_setter(Self::number_setter)
            .done();

        // Register the number as an Enum
        builder
            .property::<i32>("number_enum")
            .with_getter(move |my_node: &MyNode, _base: TRef<Node>| my_node.number_enum)
            .with_setter(move |my_node: &mut MyNode, _base: TRef<Node>, new_value| my_node.number_enum = new_value)
            .with_default(1)
            .with_hint(IntHint::Enum(EnumHint::new(vec!["a".to_owned(), "b".to_owned(), "c".to_owned(), "d".to_owned()])))
            .done();

        // Register a floating point value with a range from 0.0 to 100.0 with a step of 0.1
        builder
            .property::<f64>("float_range")
            .with_getter(move |my_node: &MyNode, _base: TRef<Node>| my_node.float_range)
            .with_setter(move |my_node: &mut MyNode, _base: TRef<Node>, new_value| my_node.float_range = new_value)
            .with_default(1.0)
            .with_hint(FloatHint::Range(RangeHint::new(0.0, 100.0).with_step(0.1)))
            .done();

        // Manually register a string as a file path for .txt and .dat files.
        builder
            .property::<String>("my_filepath")
            .with_ref_getter(move |my_node: &MyNode, _base: TRef<Node>| &my_node.my_filepath)
            .with_setter(move |my_node: &mut MyNode, _base: TRef<Node>, new_value: String| my_node.my_filepath = new_value)
            .with_default("".to_owned())
            .with_hint(StringHint::File(EnumHint::new(vec!["*.txt".to_owned(), "*.dat".to_owned()])))
            .done();
    }
    fn number_getter(&self, _base: TRef<Node>) -> i32 {
        self.number
    }

    fn number_setter(&mut self, _base: TRef<Node>, new_value: i32) {
        self.number = new_value
    }
}
}

Property<T> and when to use it

Sometimes it can be useful to expose a value as a property instead of as a function. Properties of this type serve as a marker that can be registered with Godot and viewed in the editor without containing any data in Rust.

This can be useful for data (similar to the first sample) where the count serves more as a property of enemies rather than as its own distinct data, such as the following:

struct Enemy {
    // Enemy Data
}
#[derive(NativeClass)]
struct GodotApi {
    enemies: Vec<Enemy>,
    // Note: As the property is a "marker" property, this will never be used in code.
    #[allow(dead_code)]
    #[property(get = "Self::get_size")]
    enemy_count: Property<u32>,
}

#[methods]
impl GodotApi {
    //...

    fn get_size(&self, _base: TRef<Reference>) -> u32 {
        self.enemies.len() as u32
    }
}