一、引言
自从童年时代深陷 Warcraft III 的 MOD 魔力之中,我就一直对游戏脚本语言怀有特殊的情感。回想那时,使用暴雪开发的 JASS 语言开发魔兽争霸3的游戏关卡,尽管从今天的角度看 JASS 是极其简陋的,主要特点为静态类型 + 无 GC 功能,但它在那个尚未形成行业标准的年代,代表了对游戏开发语言的一种大胆尝试。
为什么要使用脚本语言开发游戏?
游戏脚本语言的引入主要是为了提高开发测试的便捷性。如果直接使用 C++ 这样的底层语言,每更改一行代码,都可能需要耗费大量时间等待复杂工具链的编译与打包。而通过使用脚本语言,可以对实现游戏玩法的程序进行热加载执行,显著提升游戏的开发效率。
随着时间的推移,如 Lua 和 JavaScript 这样的动态类型脚本语言已成为游戏开发中的常客。然而,随着编程语言的发展,我们有机会重新定义游戏脚本语言的新标准——既复古又革新,这就是 Rust + WASM 的组合。
二、Rust + WASM + Dora SSR:重新定义游戏脚本开发
通过结合 Rust 和 WASM,我们可以在不牺牲性能的前提下,直接在例如 Android 或 iOS 设备上进行游戏热更新和测试,且无需依赖传统的应用开发工具链。此外,借助 Dora SSR 开源游戏引擎的 Web IDE 接口,使用 Rust 编写的游戏代码可以一次编译后,在多种游戏设备上进行测试和运行。
为何选择 Rust?
Rust 提供了无与伦比的内存安全保证,而且无需垃圾收集器(GC)的介入,这使得它非常适合游戏开发,尤其是在性能敏感的场景下。结合 WASM,Rust 不仅能够提供高性能的执行效率,还能保持跨平台的一致性和安全性。
快速开始指南
在开始开发之前,我们需要安装 Dora SSR 游戏引擎。该引擎支持多种平台,包括 Windows、Linux、macOS、iOS 和 Android。具体的安装步骤和要求,请参见官方快速开始指南:Dora SSR 快速开始。
第一步:创建新项目
在 Dora SSR 引擎的二进制程序启动以后,在浏览器中打开 Dora SSR 的 Web IDE,右键点击左侧游戏资源树,选择「新建」并创建名为「Hello」的新文件夹。
第二步:编写游戏代码
然后在命令行中创建一个新的 Rust 项目:
rustup target add wasm32-wasi
cargo new hello-dora --name init
cd hello-dora
cargo add dora_ssr
在 src/main.rs
中编写代码:
use dora_ssr::*;
fn main () {
let mut sprite = match Sprite::with_file("Image/logo.png") {
Some(sprite) => sprite,
None => return,
};
let mut sprite_clone = sprite.clone();
sprite.schedule(once(move |mut co| async move {
for i in (1..=3).rev() {
p!("{}", i);
sleep!(co, 1.0);
}
p!("Hello World");
sprite_clone.perform_def(ActionDef::sequence(&vec![
ActionDef::scale(0.1, 1.0, 0.5, EaseType::Linear),
ActionDef::scale(0.5, 0.5, 1.0, EaseType::OutBack),
]));
}));
}
构建生成 WASM 文件:
cargo build --release --target wasm32-wasi
第三步:上传并运行游戏
在 Dora SSR Web IDE 中,右键点击新创建的文件夹「Hello」,选择「上传」并上传编译好的 WASM 文件 init.wasm
。
或者使用辅助脚本 upload.py 在 Rust 项目文件夹内上传 WASM 文件,命令如下,其中的 IP 参数为 Dora SSR 启动后显示的 Web IDE 地址,后一个参数为要上传目录的相对路径:
python3 upload.py "192.168.3.1" "Hello"
第四步:发布游戏
在编辑器左侧游戏资源树中,右键点击刚创建的项目文件夹,选择「下载」。
等待浏览器弹出已打包项目文件的下载提示。
三、怎么实现的
在 Dora SSR 中实现 Rust 语言开发支持和 WASM 运行时嵌入的过程是一次新的技术探索和尝试,主要包括三个关键步骤:
1. 接口定义语言(IDL)的设计
要在 C++ 编写的游戏引擎上嵌入 WASM 运行时并支持 Rust 语言,首先需要设计一种接口定义语言(IDL),以便于不同编程语言之间的通信和数据交换。IDL 的设计以 C++ 的接口为基础,增加了适应 Rust 语言特性的标签,如 object
, readonly
, optional
等,来进行跨语言的接口映射。以下是一段 Dora SSR 种设计的 WASM IDL 的接口示例:
object class EntityGroup @ Group
{
readonly common int count;
optional readonly common Entity* first;
optional Entity* find(function<bool(Entity* e)> func) const;
static EntityGroup* create(VecStr components);
};
考虑到 C++ 的面向对象特性与 Rust 的设计哲学存在差异,我们在 Rust 中部分模拟了 C++ 中面向对象的行为,这需要在 Rust 中额外编写一些机制以对应 C++ 中的类和方法。这种处理方式虽然增加了一些开发工作,但保持了接口的整洁和系统的可维护性。
2. 生成胶水代码的程序
第二步是编写一个程序,通过 IDL 生成 C++、WASM 和 Rust 之间互相调用的胶水代码。为了实现这一点,我们选择使用 Dora SSR 项目自创的 Yuescript 语言。Yuescript 是基于 Lua 的一门动态编程语言,它结合了 Lua 语言生态中的 lpeg 语法解析库来处理 IDL 的解析和胶水代码的生成。使用 Yuescript 的好处是它继承了 Lua 的灵活性和轻量级,同时提供了更丰富的语法和功能,适合处理复杂的代码生成任务。以下是使用 PEG 文法编写的 IDL 解析器的代码节选。
Param = P {
"Param"
Param: V"Func" * White * Name / mark"callback" + Type * White * Name / mark"variable"
Func: Ct P"function<" * White * Type * White * Ct P"(" * White * (V"Param" * (White * P"," * White * V"Param")^0 * White)^-1 * P")" * White * P">"
}
Method = Docs * Ct(White * MethodLabel) * White * Type * White * (C(P"operator==") + Name) * White * (P"@" * White * Name + Cc false) * White * Ct(P"(" * White * (Param * (White * P"," * White * Param)^0 * White)^-1 * P")") * White * C(P"const")^-1 * White * P";" / mark"method"
3. 嵌入 WASM 运行时和代码整合
最后一步是在游戏引擎中嵌入 WASM 运行时以及所生成的 C++ 胶水代码,完成代码的整合。对于 WASM 运行时,我们选择使用 WASM3,这是一个高性能、轻量级的 WebAssembly 解释器,它支持多种 CPU 架构,能够简化编译链的复杂性,并提高跨平台的兼容性。通过这种方式,Dora SSR 能够支持在各种架构的硬件设备上运行 Rust 开发的游戏,极大地提高了游戏项目的可访问性和灵活性。
在整合过程中,我们发布了供 Rust 开发者使用的 crate 包,包含所有必要的接口和工具,以便开发者未来可以轻松地基于 Dora SSR 游戏引擎开发和再发布使用 Rust 语言编写的其它游戏模块。
四、结语
选择 Dora SSR + Rust 作为游戏开发工具不仅是追求技术的前沿,也是对游戏开发流程的一次新的探索。在这里诚邀每一位热爱游戏开发的朋友加入我们的社区,一同探索这一激动人心的技术旅程。欢迎访问我们的 GitHub 仓库 和 项目主页 来了解更多信息,并参与到我们的开发中来。一起开创游戏开发的新纪元吧!
我们的Q群在这里,欢迎来玩吧:512620381