设为首页收藏本站|繁體中文

Project1

 找回密码
 注册会员
搜索
查看: 6576|回复: 31
打印 上一主题 下一主题

[讨论] 设计一个简单的游戏引擎

[复制链接]

Lv5.捕梦者 (版主)

梦石
1
星屑
23994
在线时间
3339 小时
注册时间
2011-7-8
帖子
3926

开拓者

跳转到指定楼层
1
发表于 2021-12-8 00:25:59 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式

加入我们,或者,欢迎回来。

您需要 登录 才可以下载或查看,没有帐号?注册会员

x
本帖最后由 guoxiaomi 于 2021-12-8 00:29 编辑

以下是我对游戏引擎的一些理解。

当我想到游戏引擎的各个组分时,很快脑子里就浮现出下面的画面:


一个引擎应该要有处理逻辑、画面、音乐等等部分,然后还要有数据池用来存放数据。为了提高并行度,可能还有任务池,让一些任务能在并行的执行。

提到并行,自然就会想到多线程了。比如,把逻辑、画面、音乐分别放到不同的线程里处理。这些线程会各自管理自己的独有数据,但是也可以被其他的线程所操作:

如果有的功能耗时很长需要异步处理,就需要一个任务队列。这样线程A可以给线程B“布置”任务,布置结束后就继续执行自己的。任务会加入到线程B的任务队列,会按照先来后到的顺序依次执行。

进一步抽象,一个线程需要的东西包括:1) 数据库 2) 任务队列 3) 任务的执行方式。再额外加上一个 4) 暂停标记,用来暂停、恢复线程的运行(有时候布置的任务需要等待其他的线程完成了才能继续):


这个时候,游戏引擎的主干框架就完成了。每个线程,无论是处理逻辑、画面、音乐还是网络,无非就是上面的1-3条有所不同,只需要设计好特定的数据类型、任务类型和执行方式,填入到这个框架里即可。比如:
线程功能数据库可执行的任务任务执行方式
逻辑游戏数据获取网络资源数据每1/60s:更新逻辑,处理队列任务,处理用户输入,呼叫渲染线程绘制画面
绘制图片资源加载资源、释放资源、绘制画面立刻执行队列里的任务
音乐音乐资源加载资源、释放资源、播放音乐立刻执行队列里的任务
网络网络数据访问网站、下载内容收到逻辑线程的下载任务通知,下载完毕后发送通知给逻辑线程


还有一点要注意的是,线程间的同步问题。当绘制线程在绘制画面时,可能要读取玩家在画面上的坐标,而这个数据其实是由逻辑线程保管的。所以可以这样处理:
1. 逻辑线程通知绘制线程可以开始干活
2. 逻辑线程暂停自己
3. 绘制线程访问逻辑线程的数据,绘制画面
4. 绘制线程恢复逻辑线程的运行

这就是前面提到的暂停标记的作用,它可以用来保证只有一个线程会访问数据。

这样的设计就足够简单了吧?接下给出这个主干框架的一个C++实现: asxp.zip (313.76 KB, 下载次数: 28)
内含main.exe是gcc编译的,需要C++20(感受模板元编程的威力吧)。这个C++实现的主干框架,最大的特色在于拥有所谓的零开销抽象,并且没有内存泄漏的问题(new操作符没出现)。

在设计好特定的数据类型、任务类型和执行方式后,只需要组合这些类型,告知每一个线程需要管理哪些数据、处理哪些任务以及具体的执行者,程序就完成了,只要40行代码:

main.h + main.cpp

评分

参与人数 2+2 收起 理由
b565185756 + 1 岂可修!我居然不是一楼!!!.
pporder + 1 塞糖

查看全部评分

熟悉rgss和ruby,xp区版主~
正在填坑:《膜拜组传奇》讲述膜拜组和学霸们的故事。
已上steam:与TXBD合作的Reformers《变革者》
* 战斗调用公共事件 *
* RGSOS 网络脚本 *

Lv5.捕梦者

梦石
0
星屑
35186
在线时间
4169 小时
注册时间
2007-12-15
帖子
10067
2
发表于 2021-12-8 10:08:28 | 只看该作者
本帖最后由 89444640 于 2021-12-8 10:09 编辑

说起多线程和并行,我就想起一个问题,为什么更高级的制作软件他不卡,而rm并行处理,地图里只要多于一个必定会卡,而且地图越大卡的越明显,这还是走格子游戏,实际上处理已经降低了很多,而且地图一般也不会画满500*500,既然给了500*500的地图那就得做到这个大小情况下保证运行效率呀!如果是rm这个处理方式,按真实坐标判点断根本就没法运行了,这还得说xp刷新率只有40帧一般游戏都是60帧
比如一个很常见的NPC满大街跑,无论怎么绕都是躲避障碍物移动到某点然后消失,就是寻路,地图只要大那么一点,同时执行寻路的npc多那么一点,就卡的根本动不了,
还有一些动作游戏中常见的,躲避敌人的视线潜入据点,地图中肯定会有多个npc在巡逻,很多动作游戏该有的基础的东西,XP根本就没法实现╮(╯▽╰)╭

点评

理解万岁!  发表于 2021-12-8 15:06
回复 支持 1 反对 0

使用道具 举报

Lv4.逐梦者

梦石
0
星屑
8064
在线时间
1862 小时
注册时间
2017-10-23
帖子
355
3
发表于 2021-12-8 10:53:09 | 只看该作者
作为引擎使用者,我觉得最重要的是效率吧。编辑器界面要简洁,能快速点击功能。
而不是技术力 更不是光效 3D 网游。

也就是分成很多个专用引擎,把制作的类型固定住,但是又让制作者能轻微换皮魔改的空间。

建议楼主先做一些小游戏的制作引擎, 比如 连连看,拼涩图,小说gal。
然后是横版闯关   

点评

我不准备做编辑器,而是做一个新runtime,类似rgd  发表于 2021-12-8 14:03
回复 支持 反对

使用道具 举报

Lv4.逐梦者

梦石
0
星屑
7921
在线时间
1049 小时
注册时间
2012-4-3
帖子
1271

开拓者

4
发表于 2021-12-8 18:52:07 手机端发表。 | 只看该作者
新runtime有XP的份吗?如果有务必要期待一番,前面出过的runtime对XP的支持并不是很好。

点评

会以XP为目标尽量兼容,因为XP的功能比较少,更好实现  发表于 2021-12-8 19:11
回复 支持 反对

使用道具 举报

Lv5.捕梦者 (版主)

梦石
1
星屑
23994
在线时间
3339 小时
注册时间
2011-7-8
帖子
3926

开拓者

5
 楼主| 发表于 2021-12-8 19:14:17 | 只看该作者
本帖最后由 guoxiaomi 于 2021-12-8 19:21 编辑
Im剑侠客 发表于 2021-12-8 18:52
新runtime有XP的份吗?如果有务必要期待一番,前面出过的runtime对XP的支持并不是很好。 ...


其实在 https://rpg.blue/thread-486547-4-1.html 的34楼时,已经把XP做的差不多了,就是有点卡(比原版还卡),不过卡顿的原因已经找到了。

我没有继续做的原因是程序设计太丑陋,所以去补了课(设计模式、C++元编程),用新学的知识重构了底层框架。接下来把原来做的功能挨个加入这个新的底层框架即可。

点评

太强了,太强了!  发表于 2023-4-23 23:58
慢慢做吧不急,等到版本1.0的时候再来瞅瞅。  发表于 2021-12-8 21:44

评分

参与人数 1+1 收起 理由
89444640 + 1 塞糖

查看全部评分

熟悉rgss和ruby,xp区版主~
正在填坑:《膜拜组传奇》讲述膜拜组和学霸们的故事。
已上steam:与TXBD合作的Reformers《变革者》
* 战斗调用公共事件 *
* RGSOS 网络脚本 *
回复 支持 反对

使用道具 举报

Lv5.捕梦者 (版主)

梦石
1
星屑
23994
在线时间
3339 小时
注册时间
2011-7-8
帖子
3926

开拓者

6
 楼主| 发表于 2022-4-8 14:35:29 | 只看该作者
本帖最后由 guoxiaomi 于 2022-4-8 14:47 编辑

清明节前后有几天空闲时间,写好了引擎的核心框架: rgm_v0.1.2.zip (5.15 MB, 下载次数: 9) ,决定加入RG系列,命名为RGM,意为Modern RPG Maker。26个字母快不够用了

双击 main.exe 就会执行 main.rb 中的代码。核心代码在 src/core 里(模板元编程高能预警),思路跟主楼的区别不是很大,但是把SDL2和ruby都安排进来了。

这个核心框架有两个独立线程:逻辑线程(ruby)和渲染线程(SDL2),它们之间通过传递指令来交互,而后续的开发的主要工作就是堆上各种指令,这也算是一种模块化吧。

比如,下面的 test.hpp 中定义了2个指令
1. fill_red 会把整个画面涂成红色
2. call_fill_red 定义了内部类 wrapper,并绑定其静态函数 call_fill_red 到 ruby 中,成为 Test 模块的 fill_red 方法。调用 Test.fill_red 时会向渲染线程发送指令 fill_red。
3. 指令可以定义类型对象 data,指定其需要的数据类别。可以定义run方法代表具体的操作,以及静态方法before和after代表在线程开始和结束时的额外任务。这些都是可选的,比如 call_fill_red 就只有 before 方法。

  1. #include "core/core.hpp"

  2. struct fill_red
  3. {
  4.     using data = rgm::data<std::unique_ptr<cen::window>, std::unique_ptr<cen::renderer>>;

  5.     void run(rgm::worker auto* worker)
  6.     {
  7.         cen::window& window = *worker->template get<std::unique_ptr<cen::window>>();
  8.         cen::renderer& renderer = *worker->template get<std::unique_ptr<cen::renderer>>();

  9.         printf("fill %d x %d.\n", window.width(), window.height());
  10.         renderer.clear_with(cen::colors::red);
  11.         renderer.present();
  12.     }
  13. };

  14. struct bind_fill_red
  15. {
  16.     static void before(rgm::worker auto* worker)
  17.     {
  18.         static const decltype(worker) w{ worker };

  19.         struct wrapper
  20.         {
  21.             static VALUE call_fill_red(VALUE module)
  22.             {
  23.                 printf("call fill red.\n");
  24.                 fill_red a{};
  25.                 w->send(std::move(a));
  26.                 return Qnil;
  27.             }
  28.         };

  29.         VALUE rb_mTest = rb_define_module("Test");
  30.         rb_define_module_function(rb_mTest, "fill_red", wrapper::call_fill_red, 0);
  31.     }
  32. };


  33. namespace rgm::core
  34. {
  35. using list1_t = commandlist<bind_fill_red>;
  36. using list2_t = commandlist<fill_red>;

  37. using worker1_t = worker<commandlist<init_ruby, list1_t>, executor_ruby>;
  38. using worker2_t = worker<commandlist<init_sdl2, list2_t>, executor_sdl2>;

  39. using engine_t = scheduler<worker1_t, worker2_t>;

  40. template <>
  41. struct traits::magic_cast<scheduler<>*>
  42. {
  43.     using type = engine_t*;
  44. };
  45. }

  46. int main(int argc, char* argv[])
  47. {
  48.     printf("start rgm.\n");
  49.     cen::library centurion;
  50.     rgm::core::engine_t engine;
  51.     engine.run();
  52.     printf("exit rgm.\n");
  53.     return 0;
  54. }
复制代码


个人觉得,核心框架应该是最难写的部分了,后面的难点大概是渲染流程(Graphics.update)和shader的应用(Bitmap.hue_change和Tone)。
熟悉rgss和ruby,xp区版主~
正在填坑:《膜拜组传奇》讲述膜拜组和学霸们的故事。
已上steam:与TXBD合作的Reformers《变革者》
* 战斗调用公共事件 *
* RGSOS 网络脚本 *
回复 支持 反对

使用道具 举报

Lv5.捕梦者 (版主)

梦石
1
星屑
23994
在线时间
3339 小时
注册时间
2011-7-8
帖子
3926

开拓者

7
 楼主| 发表于 2022-4-26 00:56:01 | 只看该作者
本帖最后由 guoxiaomi 于 2022-4-26 19:39 编辑

V0.2版本

已达成该版本的主要目标:完成渲染流程。到这一步,游戏引擎的架构已经很清晰了:多线程执行,隔离线程数据,线程间使用管道交换信息。
画面的绘制是异步的,表现在:
1. ruby线程里对Bitmap的创建、销毁和绘制等操作,都是发送相应指令到render线程异步执行。
- 这意味着对Bitmap的操作几乎是瞬间返回
- 由于指令有严格的顺序,可以保证在下一次画面绘制之前,所有的Bitmap的操作都已完成
- 在创建Bitmap时,会先快速读出图片的高和宽,供后续的ruby脚本调用

2. 画面绘制开始时,ruby线程会按z轴顺序遍历所有的viewport和sprite,向render线程发出相应的绘制指令,然后锁定自己,直到收到render线程的信号。
- 这样可以保证数据的一致性,从而render线程能正常访问属于ruby线程的数据
- 遍历红黑树、将ruby数据打包这些内容都是ruby线程负责,分担了render线程的工作量
- 绘制结束时render线程会先解锁ruby线程,然后等待垂直同步信号。也可以选择强制同步,先等待画面完成绘制后解锁ruby线程。


目前可以做以下事情:
1. 创建Bitmap,读取图片或者创建已知大小的空白图
2. 使用Bitmap#fill方法涂上某种颜色
3. 创建Sprite/Viewport,设置其z轴和位置等属性
4. Sprite支持设置bitmap和src_rect
5. 按照z轴顺序,渲染所有存在的Sprite到画面上
6. 给Viewport和Sprite定义了finalizer,使其在GC时也会释放相应的内存
7. 监听系统消息,点击右上角的 X 可以关闭窗口
8. 在exe中内置脚本 main.rb 和 imagesize.rb
9. 给exe添加了水印和简单的防篡改功能

后续的开发主要是在这个架构的基础上,增加绘制window/plane/tilemap的功能和一些bitmap的操作。当然,引入shader可能会有一些难度。

有兴趣的朋友可以看看src里的代码,脚本入口在script/entry.rb(因为main.rb已经内置不可修改)。

编译的exe文件和v0.2.1版本的代码: rgm_v0.2.1.zip (5.6 MB, 下载次数: 698076)
示意图:

熟悉rgss和ruby,xp区版主~
正在填坑:《膜拜组传奇》讲述膜拜组和学霸们的故事。
已上steam:与TXBD合作的Reformers《变革者》
* 战斗调用公共事件 *
* RGSOS 网络脚本 *
回复 支持 反对

使用道具 举报

Lv5.捕梦者 (版主)

梦石
1
星屑
23994
在线时间
3339 小时
注册时间
2011-7-8
帖子
3926

开拓者

8
 楼主| 发表于 2022-5-8 01:29:29 | 只看该作者
本帖最后由 guoxiaomi 于 2022-5-8 01:43 编辑

Mark一下,居然已经写了5000行的代码~

下了个插件发现好像也没那么多,其中有1400行是RGSS内置数据结构,从F1里抄的。

只有2000行C++那我就放心了,预计能控制在4000行内。
熟悉rgss和ruby,xp区版主~
正在填坑:《膜拜组传奇》讲述膜拜组和学霸们的故事。
已上steam:与TXBD合作的Reformers《变革者》
* 战斗调用公共事件 *
* RGSOS 网络脚本 *
回复 支持 反对

使用道具 举报

Lv3.寻梦者

梦石
0
星屑
1085
在线时间
93 小时
注册时间
2022-5-7
帖子
70
9
发表于 2022-5-11 00:00:04 | 只看该作者
佬!
你永远想象不到大佬背后的努力是多么

评分

参与人数 1+1 收起 理由
guoxiaomi + 1 塞糖

查看全部评分

回复 支持 反对

使用道具 举报

Lv5.捕梦者 (版主)

梦石
1
星屑
23994
在线时间
3339 小时
注册时间
2011-7-8
帖子
3926

开拓者

10
 楼主| 发表于 2022-6-5 01:26:57 | 只看该作者
本帖最后由 guoxiaomi 于 2022-6-5 02:11 编辑

V0.4版本

度盘链接:https://pan.baidu.com/s/16XiBn3dfwEAEXc0XAGud3w?pwd=dgdi
提取码: dgdi

因为给 shader 传参数时调用了 D3DXGetShaderConstantTable,所以必须链接到 d3dx9_43.dll,已经附带在压缩包中。
main.exe 会执行内嵌的 main.rb 的内容,在加载 src/script 中各个脚本后,从 entry.rb 开始执行。
release.exe 执行的内容已经全部内嵌在 exe 中,还顺便打包了默认工程里的 Data 文件夹。

相比之前的v0.2版本,这次新增了很多功能:
1. 实现 color / tone / table 等基础数据类
2. 实现 RGSS 的几个可绘制类,但是目前只有 Sprite 能绘制
3. 实现 Sprite 绘制的全部功能,包括使用 shader 实现 tone
4. 实现 Input 的全部功能,允许绑定不同的按键,提供 Input.debug 供调试
5. 更新异步绘制流程,顺便提升了绘制效率
6. 实现 core::stopwatch,用于测试某一段重复运行的代码效率
7. 更新了 Makefile,并提供打包成 release 版本的功能
- release 版本的内嵌数据是加密的(但愿比默认加密强度高),包括一个 Data 文件夹和原来在 src/script 里的脚本

代码分布情况:

那个 ini 显然是测试工程里自带的 Game.ini 啦~

接下来的绘制相关的功能就没有什么难点了,对照着 RMXP 逐个实现就行。
再次感谢将 SDL2 和 HLSL 结合起来的项目:https://github.com/felipetavares/sdlrenderer-hlsl
熟悉rgss和ruby,xp区版主~
正在填坑:《膜拜组传奇》讲述膜拜组和学霸们的故事。
已上steam:与TXBD合作的Reformers《变革者》
* 战斗调用公共事件 *
* RGSOS 网络脚本 *
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 注册会员

本版积分规则

拿上你的纸笔,建造一个属于你的梦想世界,加入吧。
 注册会员
找回密码

站长信箱:[email protected]|手机版|小黑屋|无图版|Project1游戏制作

GMT+8, 2024-11-21 19:27

Powered by Discuz! X3.1

© 2001-2013 Comsenz Inc.

快速回复 返回顶部 返回列表