基于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面板上的关闭按钮添加事件监听,采取的方法是对关闭按钮图片资源进 ...
随机推荐
- 优化利器In-Memory开启和效果
本文主要介绍Oracle In-Memory 选件,Oracle在12.1.0.2就已经推出了In-Memory这个选件,现在通常会建议所有使用19.8及之后版本的用户,有条件都要留给In-memor ...
- 如何修改11g RAC集群名称
背景:有一套11.2.0.4 RAC集群的环境,为了测试DG,直接复制了一套一模一样的环境,修改过IP之后,依然有问题,无法同时启动. 初步判断是因为在同一子网存在两个同名的集群(都是jystdrac ...
- CF1425F Flamingoes of Mystery 题解
题目传送门 前置知识 前缀和 & 差分 解法 令 \(sum_k=\sum\limits_{i=1}^{k} a_k\).考虑分别输入 \(sum_2 \sim sum_n\),故可以由于差分 ...
- NC20240 [SCOI2005]互不侵犯KING
题目链接 题目 题目描述 在N×N的棋盘里面放K个国王,使他们互不攻击,共有多少种摆放方案. 国王能攻击到它上下左右,以及左上 左下右上右下八个方向上附近的各一个格子,共8个格子. 输入描述 只有一行 ...
- Ubuntu下SSH管理及SFTP下载工具Muon(Snowflake)
简介 Muon其实更像是一个基于ssh的服务器管理工具, 界面中有PAC Manager的影子, 集成了文件管理, ssh命令行, 服务器性能监测和工具包等功能. 因为这个工具的编写语言是Java, ...
- C++ 多线程的错误和如何避免(10)
线程中的异常可以使用 std::rethrow_exception 抛给主线程 问题分析:一个线程中抛出的异常是没法被另一个线程捕获的.假如我们在主线程中创建一个子线程,子线程中的函数抛出了异常,主线 ...
- go经典知识及总结
1.无论sync.Mutex还是其衍生品都会提示不能复制,但是能够编译运行 加锁后复制变量,会将锁的状态也复制,所以 mu1 其实是已经加锁状态,再加锁会死锁. 所以此题的答案是 fatal erro ...
- 项目实战:Qt球机控制工具 v1.0.0(球机运动八个方向以及运动速度,设置运动到指定角度,查询当前水平和垂直角度)
需求 1.调试球机控制,方向速度,设置到指定的角度: 2.支持串口,485等基于串口的协议端口配置打开: 3.子线程串口控制和.子线程协议解析: 4.支持球机水平运动速度.垂直运动速度设置: ...
- Android 全面屏体验
一.概述 Android 应用中经常会有一些要求全屏显隐状态栏导航栏的需求.通过全屏沉浸式的处理可以让应用达到更好的显示效果.在 Android 4.1 之前,只能隐藏状态栏, 在 Android4. ...
- 【Azure 应用服务】Azure Function 不能被触发
问题描述 Azure Function 不能被Postman 触发,错误信息如下: Error: write EPROTO 4020778632:error:100000f7:SSL routines ...