#gd-native #godot #proc-macro #reduce-boilerplate #godot-export #nodepath

gdnative_export_node_as_path

通过NodePath导出节点引用时的样板代码替换宏

1个不稳定版本

0.1.2 2024年1月18日

#1923 in 游戏开发

26 每月下载次数
2 crates 中使用

自定义许可

23KB
265

gdrust_export_node_path_macro

通过NodePath获取引用时减少样板代码。使用方法包括(下文几段完整的示例)

  • #[derive(NativeClass, Default)] #[inherit(Node)] 替换为 #[extends(Node)]
  • 删除带有 #[property] 标记的 NodePath 字段
  • 在您希望通过 NodePath 获取的字段之后放置 #[export_path]
  • 删除您的 fn new(&mut self, _owner: &Node) 实现
  • 在您的 _ready() 声明中手动调用 self.grab_nodes_by_path(_owner);

#[extends] 属性宏

  • #[derive(NativeClass)] #[inherit(Node)] 替换自身
  • 然后,对于每个标记有 #[export_path]
    • 声明另一个同名但带有 path_ 前缀和 NodePath 类型的字段,以及一个常规的 #[property] 属性。 (Vec<Ref<Node>> 使用 Vec<NodePath> 类型)
    • 示例输入/输出:#[export_path] node: Option<Ref<Node>> / #[property] path_node: NodePath, node: Option<Ref<Node>>,
  • 声明一个包含两个函数的 impl 块
    • fn grab_nodes_by_path(&mut self, _owner: &Node) { //搜索从生成的 NodePath 字段中生成的所有节点/实例,并将每个分配给 self 中的原始字段}
    • fn new(_owner: &Node) -> Self { Self::default() }

支持导出

  • Option<Ref<T>> 其中 T 是继承自 Node 的 Godot 内置类型(例如:Node2DControlProgressBarKinematicBody,...)
  • Vec<Ref<T>> 其中 T 是继承自 Node 的 Godot 内置类型
  • Option<Instance<T>> 其中 T 是您定义的自定义原生脚本,只要该脚本继承自 Godot 内置类型 Node
  • Vec<Instance<T>> 其中 T 是您定义的自定义原生脚本,只要该脚本继承自 Godot 内置类型 Node

PS 1:请注意,Vec<Ref<T>>/Vec<Instance<T>> 使用 Ref<T>/Instance<T> 直接,而不是 Option<Ref<T>>/Option<Instance<T>>

PS 2:这与本 crate 无关,而是与 gdnative-rust 本身相关:在编辑器中,Vec<NodePath> 将显示为 Variant 数组,您必须点击数组中的每个元素,选择 NodePath,然后您就可以拖动节点了。

实现的基础取自 gdrust

用法

替换

#[derive(NativeClass)]
#[inherit(Node)]
struct MyGodotScript {
    #[property] path_exported_node: NodePath,
    exported_node: Option<Ref<Node>>,

    #[property] path_exported_instance: NodePath,
    exported_instance: Option<Instance<NativeScriptTest>>,

    #[property] paths_exported_nodes: Vec<NodePath>,
    vec_nodes: Vec<Ref<Node>>,

    #[property] paths_exported_instances: Vec<NodePath>,
    vec_instances: Vec<Instance<NativeScriptTest>>,
}

impl MyGodotScript {
    fn new(_owner: &Node) -> Self {
        return Self {
            path_exported_node: NodePath::default(),
            exported_node: None,

            path_exported_instance: NodePath::default(),
            exported_instance: None,

            paths_exported_nodes: Vec::new(),
            vec_nodes: Vec::new(),

            paths_exported_instances: Vec::new(),
            vec_instances: Vec::new(),
        };
    }
}

#[methods]
impl MyGodotScript {
    #[method]
    fn _ready(&mut self, #[base] _owner: &Node) {
        self.exported_node = Some(unsafe {_owner.get_node_as::<Node>(self.path_exported_node.new_ref()).unwrap().assume_shared()});
        self.exported_instance = Some(unsafe {_owner.get_node_as_instance::<NativeScriptTest>(self.path_exported_instance.new_ref()).unwrap().claim()});

        for path in self.paths_exported_nodes.iter() {
            self.vec_nodes.push(unsafe { _owner.get_node_as::<Node>(path.new_ref()).unwrap().assume_shared()});
        }

        for path in self.paths_exported_instances.iter() {
            self.vec_instances.push(unsafe { _owner.get_node_as_instance::<NativeScriptTest>(path.new_ref()).unwrap().claim()});
        }
    }
}

#[derive(NativeClass)]
#[inherit(Node)]
pub struct NativeScriptTest { }

impl NativeScriptTest {
    fn new(_owner: &Node) -> Self {
        return Self {};
    }
}

#[extends(Node)] // you can replace Node with any other Godot built-in node type
struct MyGodotScript {
    #[export_path] exported_node: Option<Ref<Node>>,                        // you can replace Node with any other Godot built-in node type
    #[export_path] exported_instance: Option<Instance<NativeScriptTest>>,   // replace NativeScriptTest with your own type
    #[export_path] vec_nodes: Vec<Ref<Node>>,                               // you can replace Node with any other Godot built-in node type
    #[export_path] vec_instances: Vec<Instance<NativeScriptTest>>,          // replace NativeScriptTest with your own type
}

#[methods]
impl MyGodotScript {
    #[method]
    fn _ready(&mut self, #[base] _owner: &Node) { // replace Node with the extended type
        self.grab_nodes_by_path(_owner);  // you must call this manually, it replaces your old _ready() call
    }
}

#[extends(Node)]
pub struct NativeScriptTest { }

它扩展为(手动格式化以增强可读性)

#[derive(gdnative::prelude::NativeClass, Default)]
#[inherit(Node)]
struct MyGodotScript {
    #[property] path_vec_instances: Vec<gdnative::prelude::NodePath>,
    #[property] path_vec_nodes    : Vec<gdnative::prelude::NodePath>,
    #[property] path_exported_instance: gdnative::prelude::NodePath,
    #[property] path_exported_node    : gdnative::prelude::NodePath,
    exported_node: Option<Ref<Node>>,
    exported_instance: Option<Instance<NativeScriptTest>>,
    vec_nodes: Vec<Ref<Node>>,
    vec_instances: Vec<Instance<NativeScriptTest>>,
}

impl MyGodotScript {
    fn new(_owner: &Node) -> Self { Self::default() }

    fn grab_nodes_by_path(&mut self, owner: &Node) {
        self.exported_node = Some(unsafe { owner.get_node_as::<Node>(self.path_exported_node.new_ref()).unwrap().assume_shared() });
        self.exported_instance = Some(unsafe { owner.get_node_as_instance::<NativeScriptTest>(self.path_exported_instance.new_ref()).unwrap().claim() });

        for path in self.path_vec_nodes.iter() {
            self.vec_nodes.push(unsafe { owner.get_node_as::<Node>(path.new_ref()).unwrap().assume_shared() });
        }

        for path in self.path_vec_instances.iter() {
            self.vec_instances.push(unsafe { owner.get_node_as_instance::<NativeScriptTest>(path.new_ref()).unwrap().claim() });
        }
    }
}

杂项/限制

  • 您仍然可以自由使用其他 gdnative 属性,如 #[register_with] #[no_constructor] #[user_data] #[property]。只需确保始终将它们放置在 #[extends] 下方,除了 #[property],它仍然位于字段之后。
  • grab_nodes_by_path(&&mut self, owner: &Node) 如果在验证任何导出路径失败时将引发恐慌。
  • 您不能定义自己的 Self::new()
  • _ownergrab_nodes_by_path(&mut self, owner: &Node) 中使用了硬编码的 &,因此您不能在 _ready(&mut self, #[base] _owner: &Node) 中将 owner 声明为 owner: TRef<Node>

依赖项

~3–4.5MB
~88K SLoC