背景
Rust是一种系统级编程语言,其设计目标是提供内存安全、并发性和高性能的解决方案。以下是使用Rust开发游戏的一些优势:
- 内存安全性:Rust通过强制执行所有权和生命周期的概念,确保了内存安全性。这可以避免许多常见的编程错误,如缓冲区溢出、空指针解引用等,从而提高游戏的稳定性和安全性。
- 并发性:Rust的设计使得并发编程变得容易和高效。游戏通常需要处理大量的并发事件,如玩家的输入、游戏逻辑的更新和渲染等。Rust的并发性使得游戏开发人员能够更加容易地处理这些并发事件,从而提高游戏的性能和响应速度。
- 高性能:Rust的性能非常优秀,它被设计为能够实现接近C语言的性能,同时还能提供更高的内存安全性和并发性。这使得Rust在开发高性能的游戏应用时非常有优势。
跨平台:Rust可以编译成可以在多种操作系统和硬件平台上运行的二进制代码。这使得游戏开发人员能够更加容易地将游戏部署到多个平台上,从而扩大游戏的受众范围。 - 生态圈:Rust拥有一个不断壮大的生态圈,越来越多的开发者开始关注和使用Rust。Rust拥有丰富的库和工具,使得游戏开发人员能够更加容易地开发各种游戏应用。
总之,Rust是一种非常有前途的编程语言,它的内存安全性、并发性和高性能使得它在开发游戏应用时非常有优势。
由于Rust是一个较新的编程语言,传统的游戏引擎很少使用。
Unity游戏引擎的引擎层是用C++编写,并使用C#作为游戏的编程语言。C++没有Rust的内存安全性,而C#运行时需要持续运行后台的GC线程,不如Rust的运行性能高效。
Unreal游戏引擎的引擎层和游戏都使用C++编写,也没有Rust的内存安全性。
Bevy游戏引擎是由Rust编写的,但是目前没有官方的编辑器,开发游戏相对较麻烦。
因此我写了一个由Rust编写的Steel游戏引擎。
Steel介绍
Steel游戏引擎是我为了学习Rust语言和Vulkan渲染而写的。Steel的目标是开源,简单,模块化,高性能,跨平台和自带编辑器:
- 开源:Steel游戏引擎是完全开源的,如果有需要可以方便的修改引擎层代码。源码链接:https://github.com/SSSxCCC/steel
- 简单:游戏引擎是十分复杂的,为了使用最简单的方法写一个游戏引擎,Steel只实现必须的游戏核心模块。并且Steel使用Rust中广泛使用的开源库,游戏开发常用的ECS架构和ui等都有现成的开源库可以直接使用,因此Steel可以专注于实现编辑器功能和渲染效果。
- 模块化:一般游戏模块,例如物理引擎,可以以插件的形式添加。你可以轻松的添加别人写的任意插件,你也可以写一个插件提供给别人使用。
- 高性能:高性能主要分为CPU侧的高性能和GPU侧的高性能。Steel使用Rust以及ECS架构,可以轻松写出高性能高并发的代码,从而实现CPU侧的高性能。Steel使用Vulkan这个先进的图形API,从而实现GPU侧的高性能。
- 跨平台:Rust本身是跨平台的,并且Vulkan也是一个跨平台的API,因此用Rust和Vulkan写的代码可以轻松运行到几乎所有的平台。Steel目前可以一键编译运行到Windows和Android系统中,未来还会支持更多的平台。
- 自带编辑器:Steel游戏引擎从一开始就有编辑器,编辑器和游戏核心模块一起开发升级,为游戏开发带来极大的便利。
为了实现这些目标,接下来详细分析Steel引擎技术。
ECS架构
一般游戏基本架构可以分为GameObject架构和ECS架构。GameObject架构是面向对象编程,而ECS架构是面向数据编程。
ECS(Entity Component System)架构是一种软件设计模式,用于组织和管理游戏或应用程序中的实体(Entity)、组件(Component)和系统(System)。在ECS架构中,实体代表游戏或应用程序中的对象或角色,组件代表实体的属性或特征,而系统负责处理和更新组件。
相对于GameObject架构,ECS架构具有以下优势:
- 更好的数据管理:在GameObject架构中,游戏对象的属性和行为通常混合在一起,这使得数据管理变得复杂和难以维护。在ECS架构中,实体和组件的数据存储在一起,使得数据管理变得更加简单和高效。
- 更高的性能:在GameObject架构中,游戏对象的属性和行为通常由脚本控制,这会导致性能问题,且难以并行化。在ECS架构中,系统只处理特定的组件,因此可以更高效地进行优化和并行处理。特别是在现代CPU中,核心数越来越多,ECS架构可以轻松并行化,充分利用所有的核心,相对于GameObject架构具有巨大的优势。
- 更容易的扩展:在GameObject架构中,添加新属性或特征通常需要修改游戏对象的脚本或添加新的脚本。在ECS架构中,只需添加新的组件即可,无需修改实体或系统。
- 更高的代码复用性:在GameObject架构中,相同的行为通常需要在多个游戏对象的脚本中实现。在ECS架构中,相同的组件可以被多个实体使用,相同的系统可以处理多个组件。
- 更简单的游戏开发:在ECS架构中,开发人员可以更轻松地管理游戏对象和属性,从而更快地开发游戏。相对于GameObject架构,ECS架构可以更简单地处理复杂的游戏逻辑和系统。
总之,ECS架构是一种软件设计模式,用于组织和管理游戏或应用程序中的实体、组件和系统。相对于GameObject架构,ECS架构具有更好的数据管理、更高的性能、更容易的扩展、更高的代码复用性和更简单的游戏开发等优势。
因此Steel游戏引擎使用ECS架构实现。Rust有很多开源的ECS框架,其中shipyard是功能非常完整,API非常的简洁的ECS实现。Steel引擎选用shipyard作为ECS架构的实现。
渲染架构
Vulkan是一种现代的图形API,旨在提供高效、低开销的图形渲染,适用于游戏、虚拟现实和增强现实等应用。以下是Vulkan的一些优势:
- 低开销:Vulkan的设计目标是提供低开销的图形渲染,通过减少CPU和GPU之间的通信开销来提高性能。Vulkan提供了一种更直接的方式来控制GPU,使得应用程序可以更有效地利用硬件资源。
- 高级控制:Vulkan提供了更多的控制选项,使得开发人员可以更精确地控制图形渲染过程。这包括对渲染状态、着色器和纹理等的更细粒度的控制。
- 跨平台:Vulkan可以在多个平台上运行,包括Windows、Linux、MacOS、Android和iOS等。这使得开发人员可以更容易地将应用程序部署到多个平台上。
- 多线程:Vulkan支持多线程渲染,可以更容易地实现高性能应用程序。这使得开发人员可以更有效地利用多核CPU和GPU来提高渲染性能。
- 可扩展性:Vulkan提供了一些可扩展性,使得开发人员可以更容易地扩展应用程序。Vulkan支持多个设备和多个渲染目标,可以更容易地实现复杂的图形效果。
总之,Vulkan是一种现代的图形API,旨在提供高效、低开销的图形渲染,适用于游戏、虚拟现实和增强现实等应用。Vulkan具有低开销、高级控制、跨平台、多线程和可扩展性等优势。
然而Vulkan的API是非常繁琐的,需要写大量的模板代码,增加维护成本,提升程序复杂度。幸好Rust有一个包装了Vulkan的开源库vulkano。vulkano是一个Vulkan的safe包装,去掉了对Vulkan的unsafe调用,极大简化了Vulkan的代码,并且内存释放方式与Rust保持一致,也就是Vulkan对象离开作用域自动释放内存,防止了Vulkan内存泄漏。
vulkano的这些优点可以极大提升Vulkan程序的开发效率,因此Steel引擎选用vulkano作为渲染API。
编辑器架构
一般游戏编辑器提供可视化搭建场景,可视化数据设置,可视化资源管理和动态加载游戏代码等功能,是一个复杂的图形应用程序。
GUI框架
egui是一个Rust编写的即时模式(immediate mode)GUI库,这意味着它根据应用程序的当前状态在每一帧更新用户界面。相比之下,保留模式(retained mode)GUI库维护用户界面组件的树状结构,并仅在必要时更新它们。在即时模式GUI库中,用户界面在每一帧中构建和更新,这使它们更加灵活和响应迅速,但实现和维护也更加复杂。另一方面,保留模式GUI库更易于实现和维护,但响应速度较慢且灵活性较差。egui的即时模式使其更轻量和高效,因为它不需要维护用户界面组件的复杂树状结构。相反,它根据应用程序的当前状态更新用户界面,这使它响应更快且更易于使用。
游戏程序需要每帧都绘制并显示新的图像,并且有快速响应的需求,所以即时模式GUI库是游戏程序的最佳选择。因此Steel引擎选择egui作为GUI框架。Steel引擎编辑器使用egui编写界面,并且Steel引擎的游戏同样也可以使用egui编写界面。
动态加载代码
Rust是一个静态语言,编写的代码需要编译成动态链接库,就可以动态加载了。
首先为了编译Rust为动态链接库,可以在Cargo.toml中设置create-type为cdylib即可:
[lib]
crate-type = [ "cdylib" ]
接下来还需要实现运行时编译。Rust的Cargo就是一个强大的编译器,我们可以使用Rust的std::process::Command执行cargo build命令即可在编辑器运行时发起编译:
std::process::Command::new("cargo")
.arg("build")
.arg("-p")
.arg("steel-dynlib")
.spawn()?
.wait()?;
最后需要实现运行时加载动态链接库。Rust的libloading可以轻松实现运行时加载一个动态链接库:
let library: Library = unsafe { Library::new(&lib_path)? };
let create_app_fn: Symbol<fn() -> Box<dyn App>> = unsafe { library.get(b"create")? };
let mut app = create_app_fn();
...
Steel编辑器加载了游戏的代码之后,调用create函数生成一个装有游戏数据的Box<dyn App>对象,即可实现游戏代码的动态编译加载了!
总结
Steel是一个Rust编写的开源,简单,模块化,高性能,跨平台和自带编辑器的游戏引擎。
Github链接:https://github.com/SSSxCCC/steel
目前的开发还处于早期阶段,欢迎大家来star/fork!