百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术文章 > 正文

思考:如何设计游戏业务框架_游戏设计框架模块

ccwgpt 2025-08-21 01:45 1 浏览 0 评论

虽然现在连主机游戏都纷纷加入了网战部分,不过其身份主要充当状态同步,矛盾点集中在同步即时性上。以大量数值逻辑为主的业务功能侧重点则不同。如果说写代码就是用状态的操作给问题建模,那么编程范式和设计模式种种的目的就是用一种稳健的方式管理状态和划分操作权。游戏的业务部分往往状态繁多,且有很多异步操作。这么多状态没有一个行之有效的管理模式代码就成一锅汤了,这里记录一下笔者最近的思考。

游戏业务的关注点无外乎主动 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 解决,这样会省下相当多的由数据生成并填充界面,或界面操作反映到数据的代码。伪代码如下:

  1. presenter = new Presenter

  2. view = presenter.show("layout.dat")

  3. model = new Model("config.dat", network)

  4. presenter.bind(

  5. model["player/exp"],

  6. view["window/label"]

  7. )

  8. presenter.reg(

  9. model["game/fight"],

  10. view["window/button.clicked"]

  11. )

如果需要根据数据动态创建对象,则需使用 GUI 模板:

  1. presenter.bindWithTemplate(

  2. model["player/props"].where(true),

  3. view["window/panel"],

  4. "prop_item_layout.dat"

  5. )

既然被称为 DSL,就应该做到支持 where,orderby,group,join 等子句,甚至 precedure。Presenter 中会用一点点代码实现通用数据桥接器,负责被绑定到一起的 View 控件和 Model 值的转换和传递。对于注册到 Model 的 View 层动作,Presenter 同样桥接并传递给 Model。Model 中数据并不一定表示某个具象的实体,还可以是抽象的操作,比如一个对象表示“升级某个英雄”这一操作,在 DSL 中这样表示一次具体的升级操作:

  1. presenter.regWithObject(

  2. model["player/hero/upgrade_operation"],

  3. view["window/upgrade/button.clicked"],

  4. view["window/upgrade/invisible/id"]

  5. )

当然,你可以选择像我这个例子中一样使用 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 层的伪代码如下:

  1. class Hero {

  2. Async Level {

  3. get {

  4. if (_locked("level"))

  5. return Async( => _level);


  6. return _level;

  7. }

  8. private set {

  9. _level = value;


  10. raiseChanged("level", _level);

  11. }

  12. }

  13. Async Gold {

  14. get {

  15. if (_locked("gold"))

  16. return Async( => _gold);


  17. return _gold;

  18. }

  19. private set {

  20. _gold = value;


  21. raiseChanged("gold", _gold);

  22. }

  23. }

  24. Async ChipCount {

  25. get {

  26. if (_locked("chip_count"))

  27. return Async( => _chipCount);


  28. return _chipCount;

  29. }

  30. private set {

  31. _chipCount = value;


  32. raiseChanged("chip_count", _chipCount);

  33. }

  34. }

  35. bool Upgradable {

  36. get {

  37. return Gold >= m && ChipCount >= n;

  38. }

  39. }

  40. void upgrade {

  41. if (!lock("upgrade"))

  42. return;

  43. network.requestUpgrade;

  44. }

  45. void upgradeResponsed(Msg data) {

  46. Level = data.level;

  47. Gold = data.gold;

  48. ChipCount = data.chipCount;

  49. unlock("upgrade");

  50. }

  51. void goldChangedNotification(Msg data) {

  52. Gold = data.gold;

  53. }

  54. bool locked(string op) {

  55. if (op == "upgrade")

  56. return _locked("upgrade") || _locked("gold") || _locked("chip_count");

  57. }

  58. bool lock(string op) {

  59. if (locked(op))

  60. return false;

  61. if (op == "upgrade") {

  62. _lock("upgrade"); _lock("gold"); _lock("chip_count"); _lock("level");


  63. return true;

  64. }

  65. }

  66. void unlock(string op) {

  67. if (op == "upgrade") {

  68. _unlock("upgrade"); _unlock("gold"); _unlock("chip_count"); _unlock("level");

  69. }

  70. }

  71. }

在 Presenter 层,get 数据和 callback 的伪代码如下:

  1. def getter(path)

  2. ret = model[path].get

  3. if (ret is Async) then

  4. schedule(ret)

  5. else

  6. return ret

  7. end if

  8. end def

  9. def callback(path, value)

  10. raise(path, value)

  11. end def


更进一步,甚至可以把 Model 层的读取时对锁的互斥去掉,立即返回本机的当前数据,在 Presenter 层也不需要对异步 getter 做延迟处理。有了写入锁,就可以保证对数据有写入的操作间的互斥,而且不阻碍玩家在 View 层忽略暂时被锁住的操作转而跳去做其他事情。没错,就像 SUPERCELL 的游戏做到的那样。除非你的界面上花了大把力气做了升级、升星、升品、道具获得等等特效,玩家非得看完才对得起你的诚意?

事情谈到这离完整的设计还差另一半,即在 Presenter 层用同步的风格写出异步的代码,这是写出清爽代码的重要因素,我能从网上找到大把各种语言的异步写法,得益于异步无刷新 Web 体验的兴趣,JavaScript 的各种技巧可能是最容易搜到的例子。而具体怎样实现依赖于你的 DSL 实现环境,以及个人风格喜好,用同步风格写异步操作的手感比零碎的函数干净多了。

相关推荐

自己动手写Android数据库框架_android开发数据库搭建

http://blog.csdn.net/feiduclear_up/article/details/50557590推荐理由关于Android数据库操作,由于每次都要自己写数据库操作,每次还得去...

谷歌开源大模型评测工具LMEval,打通谷歌、OpenAI、Anthropic

智东西编译|金碧辉编辑|程茜智东西5月28日消息,据科技媒体TheDecoder5月26日报道,当天,谷歌正式发布开源大模型评测框架LMEval,支持对GPT-4o、Claude3.7...

工信部:着力推动大模型算法、框架等基础性原创性的技术突破

工信部新闻发言人今日在发布会上表示,下一步,我们将坚持突出重点领域,大力推动制造业数字化转型,推动人工智能创新应用。主要从以下四个方面着力。一是夯实人工智能技术底座。通过科技创新重大项目,着力推动大模...

乒乓反复纠结“框架不稳定”的三个小误区

很多球友由于对框架的认知不清晰,往往会把“框架不稳定”当成一种心理负担,从而影响学球进度,其典型状态就是训练中有模有样,一旦进入实战,就像被捆住了手脚。通过训练和学习,结合“基本功打卡群”球友们交流发...

前AMD、英特尔显卡架构师Raja再战GPU,号称要全面重构堆栈

IT之家8月5日消息,知名GPU架构师拉贾科杜里(RajaKoduri)此前曾先后在AMD和英特尔的显卡部门担任要职。而在今日,由Raja创立的GPU软件与IP初创企...

三种必须掌握的嵌入式开发程序架构

前言在嵌入式软件开发,包括单片机开发中,软件架构对于开发人员是一个必须认真考虑的问题。软件架构对于系统整体的稳定性和可靠性是非常重要的,一个合适的软件架构不仅结构清晰,并且便于开发。我相...

怪不得别人3秒就知道软考案例怎么做能50+

软考高级统一合格标准必须三科都达到45分,案例分析也一直是考生头疼的一门,但是掌握到得分点,案例能不能50+还不是你们说了算吗?今天就结合架构案例考点,分享实用的备考攻略~一、吃透考点,搭建知识框架从...

UML统一建模常用图有哪些,各自的作用是什么?一篇文章彻底讲透

10万+爆款解析:9大UML图实战案例,小白也能秒懂!为什么需要UML?UML(统一建模语言)是软件开发的“蓝图”,用图形化语言描述系统结构、行为和交互,让复杂需求一目了然。它能:降低沟通成本避...

勒索软件转向云原生架构,直指备份基础设施

勒索软件组织和其他网络犯罪分子正越来越多地将目标对准基于云的备份系统,对久已确立的灾难恢复方法构成了挑战。谷歌安全研究人员在一份关于云安全威胁演变的报告中警告称,随着攻击者不断改进数据窃取、身份泄露和...

ConceptDraw DIAGRAM:释放创意,绘就高效办公新未来

在当今数字化时代,可视化工具已成为提升工作效率和激发创意的关键。ConceptDrawDIAGRAM,作为一款世界顶级的商业绘图软件,凭借其强大的功能和用户友好的界面,正逐渐成为众多专业人士的首选绘...

APP 制作界面设计教程:一步到位_app界面设计模板一套

想让APP界面设计高效落地,无需繁琐流程,掌握“框架搭建—细节填充—体验优化”三步法,即可一步到位完成专业级设计。黄金框架搭建是基础。采用“三三制布局”:将屏幕横向三等分,纵向保留三...

MCP 的工作原理:关键组件_mcp部件

以下是MCP架构的关键组件:MCP主机:像ClaudeDesktop、GitHubCopilot或旅行助手这样的AI智能体,它们希望通过MCP协议访问工具、资源等。MCP主机会...

软件架构_软件架构师工资一般多少

软件架构师自身需要是程序员,并且必须一直坚持做一线程序员。软件架构应该是能力最强的一群程序员,他们通常会在自身承接编程任务的同时,逐渐引导整个团队向一个能够最大化生产力的系统设计方向前进。软件系统的架...

不知不觉将手机字体调大!老花眼是因为“老了吗”?

现在不管是联系、交友,还是购物,都离不开手机。中老年人使用手机的时间也在逐渐加长,刷抖音、看短视频、发朋友圈……看手机的同时,人们也不得不面对“视力危机”——老花眼,习惯眯眼看、凑近看、瞪眼看,不少人...

8000通用汉字学习系列讲座(第046讲)

[表声母字]加(续)[从声汉字]伽茄泇迦枷痂袈笳嘉驾架咖贺瘸(计14字)嘉[正音]标准音读jiā。[辨形]上下结构,十四画。会意形声字,从壴从加,加也表声。注:从壴,字义与鼓乐有关;从加,字义与...

取消回复欢迎 发表评论: