基于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面板上的关闭按钮添加事件监听,采取的方法是对关闭按钮图片资源进 ...
随机推荐
- SpringBoot实现统一异常处理
目录 前言 实现步骤 定义统一响应对象类 定义业务异常枚举接口和实现 定义业务异常基类 定义全局异常处理切面 测试和验证 总结 前言 近日心血来潮想做一个开源项目,目标是做一款可以适配多端.功能完备的 ...
- JS LeetCode 566. 重塑矩阵题解分析,数组降维的几种方式
壹 ❀ 引 今天是过完年到公司的第二天,年前因为封版,到今天我们小组是第一个发版的组,然后就各种踩坑,到现在还在公司等运维解决jenkins问题,闲着也是闲着,做一道算法题,简单记录下解题思路,本题来 ...
- 【Unity3D】点选物体、框选物体、绘制外边框
1 需求描述 绘制物体外框线条盒子 中介绍了绘制物体外框长方体的方法,本文将介绍物体投影到屏幕上的二维外框绘制方法. 点选物体:点击物体,可以选中物体,按住 Ctrl 追加选中,选中的物体设置为红 ...
- 【Unity3D】刚体组件Rigidbody
1 前言 刚体(Rigidbody)是运动学(Kinematic)中的一个概念,指在运动中和受力作用后,形状和大小不变,而且内部各点的相对位置不变的物体.在 Unity3D 中,刚体组件赋予了游戏 ...
- Js中Math对象
Js中Math对象 Math是一个内置对象,它拥有一些数学常数属性和数学函数方法,Math用于Number类型,其不支持BigInt. 描述 Math不是一个函数对象,也就是说Math不是一个构造器, ...
- mktemp命令
mktemp命令 mktemp命令用于安全地创建一个临时文件或目录,并输出其名称,TEMPLATE在最后一个组件中必须至少包含3个连续的X,如果未指定TEMPLATE,则使用tmp.XXXXXXXXX ...
- SSL/TLS 资料整理
1. HTTPS详解二:SSL / TLS 工作原理和详细握手过程 看到另外几篇介绍不错的文章,再次分享一下 园内大佬写的, 通过一个小故事,理解 HTTPS 工作原理 这篇博文已经把 SSL 的工作 ...
- Xposed 原理
Xposed 使用替换app_process的方式(这是个二进制文件) xposed 的 app_main2.cpp中做了xposed的初始化 /** Initialize Xposed (unles ...
- [Android逆向]Exposed 破解 jwxdxnx02.apk
使用exposed 遇到了一些坑,这里记录一下 源码: package com.example.exposedlesson01; import de.robv.android.xposed.IXpos ...
- SBI信息反馈法
https://baijiahao.baidu.com/s?id=1605128367255769158&wfr=spider&for=pc