思考:如何设计游戏业务框架
yund56 2025-07-06 19:21 3 浏览
虽然现在连主机游戏都纷纷加入了网战部分,不过其身份主要充当状态同步,矛盾点集中在同步即时性上。以大量数值逻辑为主的业务功能侧重点则不同。如果说写代码就是用状态的操作给问题建模,那么编程范式和设计模式种种的目的就是用一种稳健的方式管理状态和划分操作权。游戏的业务部分往往状态繁多,且有很多异步操作。这么多状态没有一个行之有效的管理模式代码就成一锅汤了,这里记录一下笔者最近的思考。
游戏业务的关注点无外乎主动 get 数据并呈现,主动把界面上的输入 set 到数据,数据改变时被动刷新回界面,这三点而已。那么很容易为游戏业务按照经典的 MVP (Model Presenter View) 模式来设计框架。
在笔者的设计中,View 即业务相关 GUI,这一层的实现仅关注如何简洁的构造 GUI 及相关效果,当然,没有哪个效果会和逻辑数据耦合。
Model 包含所有业务相关数据,只对 Presenter 提供 get,set,callback 注册接口。这一层诚如 DB,Presender 并不需要关心数据从哪儿来,当前又存储在哪儿。国内游戏开发圈经历了这么多年引擎用的越来越溜恐怕没人把渲染和逻辑搅在一起了,同理网络读写也应该封装在 Model 层内部。
Presenter 是业务逻辑代码所在,负责向 View 推数据做展示,并注册用户输入处理回调;还会从 Model 层主动 get,set 数据,并接收数据改变回调。
其中 Presenter 和 View 之间的绑定属于体力活,完全可以通过内部或外部 DSL 解决,这样会省下相当多的由数据生成并填充界面,或界面操作反映到数据的代码。伪代码如下:
presenter = new Presenter
view = presenter.show("layout.dat")
model = new Model("config.dat", network)
presenter.bind(
model["player/exp"],
view["window/label"]
)
presenter.reg(
model["game/fight"],
view["window/button.clicked"]
)
如果需要根据数据动态创建对象,则需使用 GUI 模板:
presenter.bindWithTemplate(
model["player/props"].where(true),
view["window/panel"],
"prop_item_layout.dat"
)
既然被称为 DSL,就应该做到支持 where,orderby,group,join 等子句,甚至 precedure。Presenter 中会用一点点代码实现通用数据桥接器,负责被绑定到一起的 View 控件和 Model 值的转换和传递。对于注册到 Model 的 View 层动作,Presenter 同样桥接并传递给 Model。Model 中数据并不一定表示某个具象的实体,还可以是抽象的操作,比如一个对象表示“升级某个英雄”这一操作,在 DSL 中这样表示一次具体的升级操作:
presenter.regWithObject(
model["player/hero/upgrade_operation"],
view["window/upgrade/button.clicked"],
view["window/upgrade/invisible/id"]
)
当然,你可以选择像我这个例子中一样使用 View 层一个纯数据、不可见对象存储当前操作的英雄 ID,也可以在 DSL 内用一个变量保存,DSL 的制定灵活得多,完全没必要拘泥。
使用脚本实现的 DSL 叫做外部 DSL,相对的使用原生语言实现的 DSL 叫做内部 DSL,但无疑主流脚本的一类函数、动态性和内置异步支持会让 DSL 更具声明式特性。
业务逻辑的特点是有很多异步操作,MVP 模式不会把异步的发起者和执行者混到一起,仅专注解决异步的两个核心问题:异步回调,以及异步互斥。这很像是在谈论异步编程模型是不是,没错,游戏业务中的异步问题简化的多,这里不存在并发的情景。我们只是在业务层面使用异步搞定数据修改发起,数据修改执行,数据读(写)互斥这三个问题,这比在底层做大规模并发容易多了。在我的设计中,只有当在 View 层的下一步操作所依赖的 Model 层数据需要等待一个异步结果时才加锁,我们一开始就已经说明,数据流向有三种,get,set 和 callback,读写只会发生在 Model 层,那么不难想到完整的异步读写访问流程如下文所述。
Set 数据时先判断是否有未完成的 set (即是否上锁),如果有则忽略当前 set 操作,注意这时不需要对 View 层锁定操作;否则对数据进行设置并加锁,锁会对关联数据同时加锁,同理,set 前亦需对关联数据判断写入操作是否已被锁定。
Get 数据时判断是否上锁,有则异步等待(通常做法在 View 层转加载圆圈并禁止操作,但不是必需的,下文会做解释),没有锁直接取数据。
数据改变的 callback 由 Model 层的网络消息触发,这一行为有可能是因 set 操作引起的,也有可能是由远端机主动触发的。对于第一种情况,因为 set 时加了锁,所以这里需要解锁对应数据(及关联数据),然后调用数据改变 callback;第二种情况中只回调即可。
还是用英雄升级举例,假定升级英雄会消耗碎片和金币,Model 层的伪代码如下:
class Hero {
Async Level {
get {
if (_locked("level"))
return Async( => _level);
return _level;
}
private set {
_level = value;
raiseChanged("level", _level);
}
}
Async Gold {
get {
if (_locked("gold"))
return Async( => _gold);
return _gold;
}
private set {
_gold = value;
raiseChanged("gold", _gold);
}
}
Async ChipCount {
get {
if (_locked("chip_count"))
return Async( => _chipCount);
return _chipCount;
}
private set {
_chipCount = value;
raiseChanged("chip_count", _chipCount);
}
}
bool Upgradable {
get {
return Gold >= m && ChipCount >= n;
}
}
void upgrade {
if (!lock("upgrade"))
return;
network.requestUpgrade;
}
void upgradeResponsed(Msg data) {
Level = data.level;
Gold = data.gold;
ChipCount = data.chipCount;
unlock("upgrade");
}
void goldChangedNotification(Msg data) {
Gold = data.gold;
}
bool locked(string op) {
if (op == "upgrade")
return _locked("upgrade") || _locked("gold") || _locked("chip_count");
}
bool lock(string op) {
if (locked(op))
return false;
if (op == "upgrade") {
_lock("upgrade"); _lock("gold"); _lock("chip_count"); _lock("level");
return true;
}
}
void unlock(string op) {
if (op == "upgrade") {
_unlock("upgrade"); _unlock("gold"); _unlock("chip_count"); _unlock("level");
}
}
}
在 Presenter 层,get 数据和 callback 的伪代码如下:
def getter(path)
ret = model[path].get
if (ret is Async) then
schedule(ret)
else
return ret
end if
end def
def callback(path, value)
raise(path, value)
end def
更进一步,甚至可以把 Model 层的读取时对锁的互斥去掉,立即返回本机的当前数据,在 Presenter 层也不需要对异步 getter 做延迟处理。有了写入锁,就可以保证对数据有写入的操作间的互斥,而且不阻碍玩家在 View 层忽略暂时被锁住的操作转而跳去做其他事情。没错,就像 SUPERCELL 的游戏做到的那样。除非你的界面上花了大把力气做了升级、升星、升品、道具获得等等特效,玩家非得看完才对得起你的诚意?
事情谈到这离完整的设计还差另一半,即在 Presenter 层用同步的风格写出异步的代码,这是写出清爽代码的重要因素,我能从网上找到大把各种语言的异步写法,得益于异步无刷新 Web 体验的兴趣,JavaScript 的各种技巧可能是最容易搜到的例子。而具体怎样实现依赖于你的 DSL 实现环境,以及个人风格喜好,用同步风格写异步操作的手感比零碎的函数干净多了。
相关推荐
- 仍需打磨:首款Windows 10X模拟器上手
-
今天,微软发布了适用于Windows10X的首款模拟器,以便于开发人员初步了解适用于双屏设备的操作系统调整。微软希望在SurfaceNeo今年年底正式发售之前,让开发人员对应用程序进行优化。因此...
- Windows10 编译OpenCV4.5源码
-
在OpenCV4.5+VisualStudio2017开发环境配置中,介绍了OpenCV4.5的下载和安装,待扩展内容OpenCV源码编译,在本文中做补充。研究源码无疑是学习OpenCV的一...
- 微软7年磨一剑,Windows 10X抢先上手体验
-
2月22日消息,微软在去年10月正式推出了Windows10X系统,该系统除了可用于传统的电脑外,同样适用于双屏设备或者折叠屏设备,拥有更好的触控操作体验。Windows10X在操作系统底层、命令...
- Office重新设计了图标,你觉得如何?
-
微软重新设计了Office的应用图标,在接下来的几个月里,这些图标将从移动端和网页端开始陆续推广至各大平台。距离Office图标的最近一次更新还是在2013年,那时鲍尔默时代的产物,那时微软还在纠结是...
- 微软发布 Win10 Build 21376 内测版:重新设计默认用户界面字体
-
IT之家5月7日消息今年早些时候,微软意外地确认正在为Windows10进行UI改进,并在预览版中发现了相关的非活动代码。微软今天宣布向开发渠道中的内测用户发布Windows1...
- 前端开发需要了解常用7种JavaScript设计模式
-
作者|Deven译者|王强策划|小智转发链接:https://mp.weixin.qq.com/s/Lw4D7bfUSw_kPoJMD6W8gg前言JavaScript中的设计模式指的是...
- 「Qt入门第二篇」基础(二)编写Qt多窗口程序
-
导语程序要实现的功能是:程序开始出现一个对话框,按下按钮后便能进入主窗口,如果直接关闭这个对话框,便不能进入主窗口,整个程序也将退出。当进入主窗口后,我们按下按钮,会弹出一个对话框,无论如何关闭这个对...
- 在吴中 ,哪里有学网页设计的培训班?
-
网页设计介绍Web2.0标准布局之网页长期签约就业班(全日制)课程收费:7680元课程周期:5-6个月(45分钟/课)使用教材:《教师自编教材》考核发证:Adobe《网页设计师》培训内容第一部份:...
- Qt快速入门(工程的创建、UI界面布局、多线程、项目)
-
本文档将介绍QT工程的创建、UI界面布局,并以计数器为例了解QT中多线程的用法,最终完成一个基础的QT项目。1创建QT工程文件在安装好QT之后,能够在其安装组件中找到QtCreator,点击设置项...
- 应用崩溃有救啦!Windows新更新将解决应用崩溃问题
-
【CNMO新闻】对于不少上班族来说,当自己的电脑在运行某个应用程序时,突然出现应用程序崩溃问题,常常会让人十分苦恼。尤其是对于设计师或者编辑,当自己的作品未能及时保存应用崩溃全部消失的时候,简直就是痛...
- Python Qt GUI设计:窗口布局管理方法【强化】(基础篇—6)
-
在PythonQtGUI设计:窗口布局管理方法【基础篇】(基础篇—5)文章中,聊到了如何使用QtDesigner进行窗口布局管理,其实在QtDesigner中可以非常方便进行窗口布局管理设计,...
- 思考:如何设计游戏业务框架
-
虽然现在连主机游戏都纷纷加入了网战部分,不过其身份主要充当状态同步,矛盾点集中在同步即时性上。以大量数值逻辑为主的业务功能侧重点则不同。如果说写代码就是用状态的操作给问题建模,那么编程范式和设计模式种...
- 用.NET设计一个假装黑客的屏幕保护程序
-
本文主要介绍屏幕保护程序的一些相关知识,以及其在安全方面的用途,同时介绍了如何使用.NET开发一款屏幕保护程序,并对核心功能做了介绍,案例代码开源:https://github.com/sangy...
- 光的艺术:灯具创意设计
-
本文转自|艺术与设计微信号|artdesign_org_cn“光”是文明的起源,是思维的开端,同样也是人类睁眼的开始。每个人在出生一刻,便接受了光的照耀和洗礼。远古时候,人们将光奉为神明,用火来...
- Python Qt GUI设计:将UI文件转换Python文件三种妙招(基础篇—2)
-
在开始本文之前提醒各位朋友,Python记得安装PyQt5库文件,Python语言功能很强,但是Python自带的GUI开发库Tkinter功能很弱,难以开发出专业的GUI。好在Python语言的开放...
- 一周热门
- 最近发表
- 标签列表
-
- filter函数js (37)
- filter函数excel用不了 (73)
- 商城开发 (40)
- 影视网站免费源码最新版 (57)
- 影视资源api接口 (46)
- 网站留言板代码大全 (56)
- java版软件下载 (52)
- java教材电子课本下载 (48)
- 0基础编程从什么开始学 (50)
- java是用来干嘛的 (51)
- it入门应该学什么 (55)
- java线上课程 (55)
- 学java的软件叫什么软件 (38)
- 程序开发软件有哪些 (53)
- 软件培训 (59)
- 机器人编程代码大全 (50)
- 少儿编程教程免费 (45)
- 新代系统编程教学 (61)
- 共创世界编程网站 (38)
- 亲测源码 (36)
- 三角函数积分公式表 (35)
- 函数的表示方法 (34)
- 表格乘法的公式怎么设置 (34)
- sumif函数的例子 (34)
- 图片素材 (36)