游戏开发小记 2.1 玩具游戏引擎

上一篇在这里 游戏开发小记 01

在半年前的上一篇,我还在用 SDL 做为基础,虽然 SDL 有各种缺点,但是呢,我本来想的就是做一个简单的 Roguelike 游戏,图形什么的根本不重要,因为 Roguelike 的画面一般是这样的:

20u1imo

根本不用什么画面对不对?

但是,但是呢,做着做着,我的强迫症之魂又开始燃烧了!SDL 的问题大概有:

  1. 功能太简单,不好定制。
  2. 采样算法是最基础的 nearest ,就是放大就一堆像素点的那种,如果要做像素风的游戏大概没什么问题,但是我不打算做像素风啊!在我看来现在像素泛滥已经超过怀旧到了烂俗的地步了。
  3. 不支持 Retina 屏幕,画面颗粒感很严重。

于是只能盯上了 OpenGL,在跟着教程用 C 经过简单的学习 OpenGL 之后我觉得自己大概知道 2D 游戏应该怎么做了,于是开始在 Rust 里面写游戏啦。

Rust 里面有很多基于 OpenGL 的图形库,我最后选中了 glium,这是一个 so suck 的 OpenGL 绑定,OpenGL 本身麻烦的 API 被封装的非常现代简洁,作者也极其活跃的在更新。

于是做了个玩具 2D 引擎的雏形(sansa/engine/src · tioover/sansa),现在除了 UI 还刚开始做以外其它都能满足我的需求了。在这里稍微记录一下里面的设计(总共也才千来行代码囧)。

坐标轴

计算机中 2D 通常的坐标轴是左上角为原点,y 轴向下的。OpenGL 的坐标轴是屏幕中心为原点,x 轴向上 y 轴向右的常见坐标系(Z 轴比较反人类就是了),我比较喜欢 OpenGL 的坐标系,这样的话如果把一个东西放到屏幕中央就不用做位移了。而 Tile-base 的 2D 游戏正需要 Tile 从中央铺展开来。

唯一的问题是 OpenGL 的坐标轴是 [-1, 1] 的,而 2D 游戏是以像素为单位的,所以这里要叠加一个正交矩阵作为摄像机变换,值得注意的是矩阵应该把 Dpi 纳入计算,这样才能在各种 Dpi 下有统一的分辨率:

  \left(  \begin{array}{cccc}  \frac{f}{w} & 0 & 0 & 0\\  0 & \frac{f}{h} & 0 & 0\\  0 & 0 & -1 & 0\\  0 & 0 & 0 & 1 \\  \end{array}  \right)

这里的 f 就是 dpi 倍数。

精灵

对简单的 2D 游戏来说,Sprite 是重中之重,一切都是 Sprite 也不为过吧,这里 Sprite 的设计成这样:

其中 Image 是最简单的渲染单元,包含了顶点,定点索引和材质信息。Transform 是另一个单独的结构,这样分离开就不用为了实现 Transform 写大量 getter/setter 了。

pixel_factor 是个简单的颜色滤镜,在着色器里面乘上所有的像素,可以实现比如说淡入淡出之类的效果。

state 是一个动画状态姬,也是从 SDL 时代继承过来的设计。

动画

这是以前 SDL 时代上传的视频,动画系统说破了就是个状态姬,实际上很容易做,不过用有模式匹配的语言写起来就是爽

(其中 Vec 是动态数组,Box 是指针,Rc 是引用计数指针,i64 是 64 位有符号整型。)

这就是动画的基本状态,Static 就是相当于“0”,是每个类似系统里面都有的元素,Queue Paral Loop 都是控制元素。

Queue 中的动画会按照顺序播放,当前一个状态变成 Static 以后就会被删除,处理下一个状态,为空的时候 Queue 将变为 Static

Paral 中的动画将同时播放,每次都会迭代 Paral 中的所有状态,当 Paral 中所有状态为 Static 的时候,Paral 自身变为 Static

Loop 就是循环。第一个 i64 是计数器,看现在是第几次循环。最后一个 State 是实际进行迭代的状态。

当最后一个 State 变成 Static 的时候,复制一份第二个 Static 到自身重新开始,第二个 Static 相当于模板的作用。

还有个 Hold 就是一个定时停止播放,略过不谈。

这些都是控制结构,动画效果实际上是 Func 状态控制的:

其中的 StateFn (状态函数),是一个被引用计数指针管理的闭包函数,对每个函数传入 Sprite 指针,让其可以修改 Sprite 的状态,再传入 Timer 就能通过时间来控制动画效果了。

而返回值(是一个 Maybe Monad = =)如果是 None 的话就保持当前状态不变,如果是 Some (State) 的话就将自身变成其中的状态,比如说动画放完了,就返回 Some (Static) 来表明停止。

在这里贴一下贝塞尔曲线运动的代码作为示例:

下篇预告,文字渲染和 UI。

游戏开发小记 2.1 玩具游戏引擎》上有2条评论

发表评论

电子邮件地址不会被公开。 必填项已用*标注