GDExtension - Godot接入
gdextension crate 负责 Rust ↔ Godot 的边界适配。
职责
- 注册 Godot 类 (RustyCore)
- Rust 类型 ↔ Godot 类型转换
- 暴露 Rust API 给 GDScript
- 处理跨语言调用
核心类型
RustyCore
#![allow(unused)] fn main() { use godot::prelude::*; #[derive(GodotClass)] #[class(base=Node)] pub struct RustyCore { runtime: Runtime, #[base] base: Base<Node>, } }
暴露的 API
初始化
#![allow(unused)] fn main() { #[godot_api] impl RustyCore { #[func] pub fn initialize(&mut self, config: Dictionary) { let rust_config = RuntimeConfig { assets_root: PathBuf::from( config.get("assets_root") .unwrap() .to::<GString>() .to_string() ), content_package_path: PathBuf::from( config.get("content_package_path") .unwrap() .to::<GString>() .to_string() ), }; self.runtime.initialize(rust_config).unwrap(); } } }
启动游戏
#![allow(unused)] fn main() { #[godot_api] impl RustyCore { #[func] pub fn start_singleplayer(&mut self) { self.runtime.start_singleplayer().unwrap(); } #[func] pub fn start_host(&mut self, port: i32) { self.runtime.start_host(port as u16).unwrap(); } #[func] pub fn start_client(&mut self, server_addr: GString) { self.runtime.start_client(server_addr.to_string()).unwrap(); } } }
更新和获取数据
#![allow(unused)] fn main() { #[godot_api] impl RustyCore { #[func] pub fn update(&mut self, delta: f64) { self.runtime.update(delta).unwrap(); } #[func] pub fn get_frontend_snapshot(&self) -> Dictionary { let snapshot = self.runtime.get_frontend_snapshot(); snapshot_to_dictionary(snapshot) } } }
发送命令
#![allow(unused)] fn main() { #[godot_api] impl RustyCore { #[func] pub fn send_command(&mut self, command: Dictionary) { let input = dictionary_to_player_input(command); self.runtime.send_input(input).unwrap(); } } }
类型转换
FrontendSnapshot → Dictionary
#![allow(unused)] fn main() { fn snapshot_to_dictionary(snapshot: FrontendSnapshot) -> Dictionary { let mut dict = Dictionary::new(); // 转换单位列表 let mut units_array = Array::new(); for unit in snapshot.units { let mut unit_dict = Dictionary::new(); unit_dict.set("id", unit.id); unit_dict.set("position", Vector2::new(unit.position.0, unit.position.1)); unit_dict.set("health", unit.health.0); unit_dict.set("max_health", unit.health.1); unit_dict.set("unit_type", GString::from(unit.unit_type)); units_array.push(unit_dict); } dict.set("units", units_array); // 转换资源 let mut resources_dict = Dictionary::new(); for (player_id, res) in snapshot.resources { resources_dict.set(player_id, res.credits); } dict.set("resources", resources_dict); dict } }
Dictionary → PlayerInput
#![allow(unused)] fn main() { fn dictionary_to_player_input(dict: Dictionary) -> PlayerInput { let cmd_type = dict.get("type").unwrap().to::<GString>().to_string(); match cmd_type.as_str() { "move" => { let target = dict.get("target").unwrap().to::<Vector2>(); PlayerInput::Move { target: Position { x: target.x, y: target.y, } } } "attack" => { let entity_id = dict.get("entity_id").unwrap().to::<u64>(); PlayerInput::Attack { entity_id } } _ => panic!("Unknown command type"), } } }
GDScript 使用示例
extends Node
var rusty_core: RustyCore
func _ready():
rusty_core = RustyCore.new()
add_child(rusty_core)
# 初始化
rusty_core.initialize({
"assets_root": "res://assets",
"content_package_path": "res://assets/official"
})
# 启动单人游戏
rusty_core.start_singleplayer()
func _process(delta):
# 更新游戏逻辑
rusty_core.update(delta)
# 获取数据
var snapshot = rusty_core.get_frontend_snapshot()
render_snapshot(snapshot)
func _input(event):
if event is InputEventMouseButton and event.pressed:
# 发送移动命令
rusty_core.send_command({
"type": "move",
"target": get_global_mouse_position()
})
构建和部署
使用 builder 工具自动构建:
cargo run -p builder
产物位置:
launcher/godot/addons/rusty_core/bin/
windows/libgdextension.dll
linux/libgdextension.so
macos/libgdextension.dylib
注意事项
线程安全
Godot 在主线程调用 Rust,确保:
- 不在 Rust 中持有 Godot 对象的可变引用
- 使用
Gd<T>而非&T
错误处理
GDScript 没有 Result,错误需要转换:
#![allow(unused)] fn main() { #[func] pub fn start_singleplayer(&mut self) -> bool { match self.runtime.start_singleplayer() { Ok(_) => true, Err(e) => { godot_error!("Failed to start: {}", e); false } } } }