基于Rust的Tile-Based游戏开发杂记(01)导入
什么是Tile-Based游戏?
Tile-based游戏是一种使用tile(译为:瓦片,瓷砖)作为基本构建单位来设计游戏关卡、地图或其他视觉元素的游戏类型。在这样的游戏中,游戏世界的背景、地形、环境等都是由一系列预先定义好的小图片(即tiles)拼接而成的网格状结构。每个tile通常代表一个固定的尺寸区域,可以是一块地表、一片水域、一堵墙、一棵树或者是任何构成游戏环境的一部分。
设计者通过组合不同的tile,能够快速创造出多样化的游戏地图,这在许多类型的游戏如角色扮演游戏(RPG)、策略游戏、冒险游戏、解谜游戏以及一些独立游戏和roguelike游戏中尤为常见。Tile-Based游戏和ASCII游戏有一定的关联,比如经典的CDDA大灾变,无论是是使用ascii字符进行渲染,还是使用图片进行渲染,对应坐标位置上的元素总是一致的:

那么从技术层面出发,想要开发出类似这样的游戏,我们需要准备哪些东西呢?
游戏框架选择
一个基本的游戏循环逻辑如下:
- 游戏运行后我们需要使用一块画布来承载游戏应用,它能够接收用户的各种输入,包括不限于键盘、鼠标等输入事件,以便我们在后续的游戏逻辑更新环节响应外部输入;
- 需要一个模块来承载游戏逻辑,通过接收到外部输入,来更新游戏状态;
- 需要一个渲染模块来扮演图形输出的能力,无论是渲染ascii还是各种2D、3D图形,都需要通过这个模块来完成。
伪代码如下:
0. 游戏初始化(通常是系统层面初始化,而不是游戏逻辑初始化,窗体准备)
1. 资源加载,游戏准备
2. 游戏循环处理
while(游戏进行中) {
2-1. 处理各种输入
2-2. 更新游戏各种状态数据
2-3. 图形渲染
}
3. 游戏退出,资源释放
在Rust生态中已经有很多较为成熟且流行的库来帮助封装对系统底层的调用,让我们更加关心核心的游戏循环处理。比如,winit库封装了不同操作系统平台关于窗体创建的底层实现,暴露出safe的Rust层面API,我们可以直接使用。当然,这个库仅仅提供原生窗体以及相关事件响应封装,并不提供内容渲染能力,换句话说,当我希望在游戏中能够渲染一些文字,或者一些图片,或者一些动画,这些需要对于winit本身来说是无法做到的,需要配合一些另外渲染库通过底层的图形API来完成渲染并绘制到winit创建的窗体上。
当然,已有一些游戏框架、游戏引擎层面的库供我们使用,比较常见有:Bevy、ggez、Piston等等。它们都提供了基本窗体控制、事件循环处理以及对图形API的更上层封装,对于开发者来说,使用这些库以后,代码绘制图形也无需过多的关心底层的图形绘制细节以及各种事件处理。
笔者在对上述游戏开发框架、引擎库进行调研并经过多次实践以后,决定使用ggez这个2D游戏框架来完成本系列文章的游戏开发。
这个库提供了如下的功能:
- 文件系统抽象,允许您从文件夹或 zip 文件加载资源
- 基于
wgpu库图形API构建的硬件加速2D渲染 - 通过
rodio库加载和播放.ogg、.wav和.flac文件 - 使用
glyph_brush库进行TTF字体渲染 - 通过回调轻松处理键盘和鼠标事件的界面
- 用于定义引擎和游戏设置的配置文件,简单的定时和FPS测量功能
- 集成了
mint库作为数学库 - 一些更高级的图形选项:着色器、实例化绘图和渲染目标
除开上述一些能力外,ggez还有一个更加吸引笔者的地方是,相比于Bevy、Piston等库来说,它更加轻量级,更加适合入门游戏开发者,其仓库代码量比起Bevy和Piston来说也更少,如果想要更深入的了解ggez这个库,代码也更易阅读。
最后需要注意的是,ggez只能说一款仅支持2D游戏开发的框架而不算游戏引擎,并没有集成物理引擎、GUI、AI、ECS框架以及网络通讯等能力,但是对于开发Tile-Based游戏来说,ggez本身已经足够了。
实践ggez
通过官方文档,我们可以快速的搭建并运行一个窗体,同时在窗体中渲染一些内容:
use ggez::{Context, ContextBuilder, GameResult};
use ggez::graphics::{self, Color, DrawParam, Quad};
use ggez::event::{self, EventHandler};
use ggez::mint::Point2;
fn main() {
let (ctx, event_loop) =
ContextBuilder::new("my_game", "Cool Game Author")
.build()
.expect("error");
let my_game = MyGame {
x: 0,
to_right: true,
};
event::run(ctx, event_loop, my_game);
}
struct MyGame {
x: i32,
to_right: bool,
}
impl EventHandler for MyGame {
fn update(&mut self, _ctx: &mut Context) -> GameResult {
// 更新方块状态
// ...
Ok(())
}
fn draw(&mut self, ctx: &mut Context) -> GameResult {
let mut canvas = graphics::Canvas::from_frame(ctx, Color::WHITE);
// 方块绘图
// ...
canvas.finish(ctx) // 提交绘图更新
}
}
按照官方文档,我们编写上述示例代码,运行以后会看到如下效果:

具体代码仓库会在文章末尾给出
GUI库选择
介绍
游戏中并非一定要有GUI,比如一些小游戏可能一个回车就开始了游戏,或是只需要通过一些绘图API“画”一个按钮,而不需要一些复杂GUI控件,但考虑到后期,我们的Tile-Based游戏支持处理更加复杂的用户输入,我们不可能完全通过代码“画”一些按钮、表格,或者其他的GUI控件,所以考虑还是引入GUI库来支持渲染一些常用的GUI控件(按钮、文本框、滑动条等)。
游戏开发中的GUI库不同于一些普通桌面应用,因为通常来讲游戏是按照GameLoop一帧一帧不断渲染的,这种GUI渲染模式通常叫做立即模式(immediate mode),即每一帧都在“画”一个控件。更详细的介绍,可以参考下面这些文章:
此外,立即模式实现动画过渡效果非常容易,因为立即模式是每一帧都绘制一遍,我们可以通过每一帧时间间隔和上一帧的状态来计算出当前帧的状态,进而实现一种平滑过渡的效果。
在Rust中,同样有很多库可以帮助我们完成GUI的渲染,例如:egui、iced等等。其中,egui算是比较主流的立即模式GUI库了,读者可以访问egui的Example网站看到效果:web demo。当然,egui本身可以支持很多渲染后端(backend),即egui本身只提供渲染的计算(比如这里要画一个按钮,那里要画一个表格),而具体的渲染工作交给后端来完成,这个后端可以是OpenGL、Vulkan、DirectX以及wgpu等。
在前面,我们选择了ggez游戏开发框架,它本身具备渲染能力(底层使用wgpu),所以我们可以将ggez的wgpu作为后端,将egui与ggez结合,当然,已经有开发者开发好了:ggegui,我们只需使用即可。
实践egui(ggegui)
在原有ggez代码的基础上,我们添加ggegui库,并增加相关的代码后,能够看到如下效果:

由于GIF录制原因导致不清晰,读者可以自行运行Demo代码查看效果
ECS框架选择
介绍
什么是ECS架构?这里有一篇写的很好的文章来介绍ECS架构:游戏开发中的ECS 架构概述。
简单来讲,ECS架构的核心思想是将游戏中的所有事物抽象为一个个独立的实体(Entity),每个实体都拥有自己的组件(Component),组件是游戏状态的最小单元,它可以具备一些数据。而系统System则是对游戏状态的更新处理,它可以根据游戏状态的变化来更新游戏状态,修改这些组件中的数据。值的注意的是,系统从来不会拥有任何的实体或组件,系统仅仅无状态的逻辑处理。
对于我们的Tile-Based游戏来说,就十分适合基于ECS架构来处理游戏循环中的状态更新。所以在后续的开发中,我们会引入一套ECS框架来处理游戏中关于状态更新的部分。
在笔者调研并实践了Bevy_ecs、specs等ECS框架库以后,决定使用specs库。未使用Bevy_ecs是考虑到:
Bevy_ecs尽管用户更多,但是它本身模块不太方便与非Bevy生态结合(PS:Bevy是一整套游戏引擎,包含了游戏引擎本身、ECS框架、GUI库等等);specs这个库是一个独立的ECS框架库,主打轻量级高性能,API清晰独立,且易于与各种逻辑进行结合。我们可以在自己的逻辑代码中更加方便的控制ECS中的逻辑处理。
实践specs
specs的代码逻辑细节就不在本文中进行展示了,烦请读者自行阅读specs的官方文档,以及笔者的仓库代码进行学习。

最后
无论是CDDA大灾变、矮人要塞,还是rogue、nethack等游戏,它们都有着非常丰富的游戏内容,而这些内容除开本身有趣的逻辑外,往往都需要通过一些算法来实现,比如:地图生成、寻路、AI、战斗等等。后续内容笔者将会针对不同的游戏模块内容,介绍相关的开发逻辑、算法。比如经典的a-star寻路算法,基于simplex噪声、perlin噪声的地图生成算法,以及基于ECS的战斗系统等等。另外,笔者也会介绍使用ggez框架过程中学习到的绘图一些经验、技巧,尽情期待!
代码仓库:w4ngzhen/rs-game-dev (github.com)
基于Rust的Tile-Based游戏开发杂记(01)导入的更多相关文章
- 在基于TypeScript的LayaAir HTML5游戏开发中使用AMD
在基于TypeScript的LayaAir HTML5游戏开发中使用AMD AMD AMD是"Asynchronous Module Definition"的缩写,意思就是&quo ...
- 基于HTML5的SLG游戏开发( 三):认识PureMVC
在游戏开发中,对于一般网络游戏,由于需要多人协同开发,每个人负责不同的模块开发,为了减少耦合,需要用来一些MVC框架,减少模块之间的耦合.我们现在使用的mvc框架是pureMVC.pureMVC的官网 ...
- 基于HTML5的SLG游戏开发(序)
2012年前后,HTML5游戏凭借跨平台.易移植.部署简单.节省成本等优点被炒的火热,经过一两年的快速发展,市场出现了一些成功地HTML5游戏产品,像磊友的<修仙三国>,神奇时 ...
- 自动化的基于TypeScript的HTML5游戏开发
自动化的开发流程 在HTML5游戏开发或者说在Web客户端开发中,对项目代码进行修改之后,一般来说,需要手动刷新浏览器来查看代码修改后运行结果.这种手动的方式费时费力,降低了开发效率.另外,如果我们使 ...
- 转: Orz是一个基于Ogre思想的游戏开发架构
Orz是一个基于Ogre思想的游戏开发架构,好的结构可以带来更多的功能.Orz和其他的商业以及非商业游戏开发架构不同.Orz更专著于开发者的感受,简化开发者工作.Orz可以用于集成其他Ogre3D之外 ...
- 基于Cocos2d-x-1.0.1的飞机大战游戏开发实例(中)
接<基于Cocos2d-x-1.0.1的飞机大战游戏开发实例(上)> 三.代码分析 1.界面初始化 bool PlaneWarGame::init() { bool bRet = fals ...
- 转: 基于netty+ protobuf +spring + hibernate + jgroups开发的游戏服务端
from: http://ybak.iteye.com/blog/1853335 基于netty+ protobuf +spring + hibernate + jgroups开发的游戏服务端 游戏服 ...
- 基于Intel x86 Android的RAD游戏开发
zip文件还包含编译的"MonkeyGame-debug".可以在模拟器中运行的二进制文件.在"game.build"文件夹中有一个HTML5 build.在C ...
- 第 1 天|基于 AI 进行游戏开发:5 天创建一个农场游戏!
欢迎使用 AI 进行游戏开发! 在本系列中,我们将使用各种 AI 工具,在 5 天内创建一个功能完备的农场游戏.到本系列结束时,你将了解到如何将多种 AI 工具整合到游戏开发流程中.本系列文章将向你展 ...
- 基于HTML5的SLG游戏开发(一):搭建开发环境(2)
游戏开发过程中经常需要处理各种事件,而HTML5游戏开发中,所有的场景和UI面板都是绘制在Canvas上面,假设需要对某一UI面板上的关闭按钮添加事件监听,采取的方法是对关闭按钮图片资源进 ...
随机推荐
- 7.函数的使用--《Python编程:从入门到实践》
7.1 python 中函数的定义 python 中函数使用 def 定义: def greet_user(): 7.2 传参的传递 普通实参的传毒,可以与 C++ 相同,即按顺序传递. 7. ...
- Linux学习资料锦集
Linux 学习资料链接: (1)Linux常见命令及其用法_STM32李逼的博客-CSDN博客 (2)Linux命令了解_STM32李逼的博客-CSDN博客 3)Linux使用编辑器_STM32李 ...
- 【framework】surfaceflinger启动流程
1 前言 surfaceflinger 的作用是合成来自 WMS 的 Surface 数据,并发送到显示设备. SurfaceFlinger 服务不同于 AMS.WMS.IMP.PMS.DMS ...
- Laravel入坑指南(10)——事件Event
不知不觉,我们已经来到了第10小节.这一小节,我们一起讨论关于"事件"这个话题.众所周知,从二进制到汇编,再到高等级语言,这一路发展下来,代码都是顺序执行的,那么事件是什么?这个事 ...
- Java集合框架学习(三) TreeSet详解
TreeSet介绍 1.TreeSet是SortedSet接口的唯一实现类,TreeSet可以确保集合元素处于排序状态. 2.向TreeSet中加入的应该是同一个类的对象. 3.TreeSet判断两个 ...
- 《系列二》-- 2、bean 的作用域: Scope 有哪些
目录 作用域 Scope 特性概述 常规作用域 web 场景作用域 经典问题 模拟场景 解决办法 方法一 方法二 实现接口 BeanFactoryAware 阅读之前要注意的东西:本文就是主打流水账式 ...
- 关于谷歌浏览器出现“错误代码:net::ERR_UNSAFE_PORT”的解决办法
搭建项目时需要自己配置端口信息,但是有人搭建之后会出现如下情况 但是换用edge等浏览器没有问题,这是因为chorme浏览器有自己的默认非安全端口, 若访问这些端口就会出现这个错误,并且所有采用cho ...
- django学习第三天---django模板渲染,过滤器,反向循环 reversed,自定义标签和过滤器,模板继承
django模板渲染 模板渲染,模板指的就是html文件,渲染指的就是字符串替换,将模板中的特殊符号替换成相关数据 基本语法 {{ 变量 }} {% 逻辑 %} 变量使用 示例 Views.py文件 ...
- 【MongoDB】C# .Net MongoDB常用语法
1.1.驱动安装 使用NuGet包管理器安装MongoDB C#驱动:MongoDB.Driver 1.2. C#连接MongoDB //获取MongoDB连接客户端 MongoClient clie ...
- Hibernate-Validator扩展之自定义注解
一.Hibernate-Validator介绍 Hibernate-Validator框架提供了一系列的注解去校验字段是否符合预期,如@NotNull注解可以校验字段是否为null,如果为null ...