游戏中的设计模式一

游戏开发是一个快速迭代的过程,代码复杂度也很高,借助于设计模式,可以帮助我们降低复杂度,降低系统间的耦合,从而高效高质的做出交付。

最近读了这本书:《游戏编程模式》[1],很受启发,所以结合书本知识以及自己的理解,写一写游戏中常用的设计模式。

模式


单例模式

先说要点:这是唯一一个不推荐使用的模式,因为它害处多于好处。

尽管这个模式出现在了 GoF 的书[2]中,但是它弊大于利,并不是一个好的模式。因为它制造了全局变量,而全局变量是有害的。不推荐全局变量的理由也差不多构成了不推荐单例模式的理由。

CppCoreGuidelines[7] 也提到了避免使用单例模式,并指出 Reason 是 “Singletons are basically complicated global objects in disguise”,翻译过来就是:单例本质上就是一些复杂的全局对象。

全局变量会导致这几件事情变得很困难:可测试性、重构、优化、并发。

除了全局变量的原因,在游戏中,还有一个特别的点:游戏对延迟是很敏感的,而单例支持 “延迟初始化”,这反而可能带来卡顿,对游戏是不利的,所以游戏基本上不需要延迟初始化,反而是在一开始就把一些初始化耗时高的模块都先初始化了。

既然不推荐单例,但有些时候也不得不使用全局变量,怎么办?

没办法,该用全局变量的地方还继续用着吧。但是,尽量通过一些办法减少全局变量的数量。比如游戏客户端中,往往不得不定义一个全局变量 world 来表示整个游戏世界的,这个全局变量会被用得到处是,基于这一基本现实,我们可以把一些其他的需要全局访问的变量也放在这个 world 变量里。


状态机模式

状态机出现很频繁,游戏里面的 AI 大多都是用状态机实现的,计算机网络中的 TCP 协议,其实现也是典型的状态机。

以前我觉得状态机平平无奇,没有什么特别的。直到看到《游戏编程模式》[1]里介绍的例子,才惊觉状态机模式真是神奇,化繁为简,使一切变得很有秩序。

如果不使用状态机,要根据输入控制一个英雄的行为,可能会写出这样复杂的,不好维护的代码:

void Heroine::handleInput(Input input)
{if (input == PRESS_B){if (! isJumping_ && ! isDucking_){// Jump...}}else if (input == PRESS_DOWN){if (! isJumping_){isDucking_ = true;setGraphics(IMAGE_DUCK);}else{isJumping_ = false;setGraphics(IMAGE_DIVE);}}else if (input == RELEASE_DOWN){if (isDucking_){// Stand...}}
}

上面的代码,不单复杂难维护,而且还容易出 bug,比如会有很多这类逻辑约束:“主角在跳跃状态的时候不能再跳,但是在俯冲攻击的时候却可以跳跃”,为了实现这类约束,需要加更多的状态变量,更多的判断。

但是如果引入状态机,一切都将变得简单有序。首先,要先写出一个状态机,之后再把它实现出来。状态机有以下几个特征:

  • 你拥有一组状态,并且可以在这组状态之间进行切换
  • 状态机同一时刻只能处于一种状态
  • 状态机会接收一组输入或者事件
  • 每一个状态有一组转换,每一个转换都关联着一个输入并指向另一个状态

准确的说,我们这里需要的是 DFA(有限自动机),如果是 NFA,肯定会超过我们脑子负载的。有限状态机(FSM)可以分为 DFA 和 NFA[4]:

FSM is further distinguished by Deterministic Finite Automata (DFA) and Nondeterministic Finite Automata (NFA). In DFA, for each pair of state and input symbol there is only one transition to a next state whereas, in NFA, there may be several possible next states. Often NFA refers to NFA‐epsilon which allows a transition to a next state without consuming any input symbol. That is, the transition function of NFA is usually defined as T: Q x (ΣU{ε}) → P(Q) where P means power set.Theoretically, DFA and NFA are equivalent as there is an algorithm to transform NFA into DFA.

以上英雄行为的例子画出的状态机如下:

在这里插入图片描述

图1:英雄行为状态机

依据状态机,实现的代码如下:

enum State
{STATE_STANDING,STATE_JUMPING,STATE_DUCKING,STATE_DIVING
};void Heroine::handleInput(Input input)
{switch (state_){case STATE_STANDING:if (input == PRESS_B){state_ = STATE_JUMPING;yVelocity_ = JUMP_VELOCITY;setGraphics(IMAGE_JUMP);}else if (input == PRESS_DOWN){state_ = STATE_DUCKING;setGraphics(IMAGE_DUCK);}break;case STATE_JUMPING:if (input == PRESS_DOWN){state_ = STATE_DIVING;setGraphics(IMAGE_DIVE);}break;case STATE_DUCKING:if (input == RELEASE_DOWN){state_ = STATE_STANDING;setGraphics(IMAGE_STAND);}break;}
}

看起来仍然是普普通通的代码,但是却让一切井井有条。这里面最重要的是我们明确了英雄的状态,确定英雄只能处于某种确定的状态,这让逻辑变得有序。


黑板模式

想不到这也是一种模式吧,unity 里的行为树,就使用了 blackboard 来记录数据。

它本质上就是一个提供数据共享的 key value store,实现了解耦。但也是有缺点[5],比如:

  • 读写比较随意,容易造成数据损坏,或子系统竞争。
  • 可能会产生非法的数据。
  • 出问题的时候,如果是多个子系统共用,会比较难调试。

游戏开发中,行为树通常结合黑板来实现,黑板实现了行为树的节点间“通信”,就是共享数据而已。

黑板模式在《设计模式: 可复用面向对象软件的基础》[2] 和《游戏编程模式》[1] 都没有介绍,但在《面向模式的软件架构卷1模式系统》[6] 有详细介绍,具体可以看一下。


观察者模式

GOF 对它意图的定义是: “定义对象间的一种一对多的依赖关系,当一个对象的状态发生状态时,所有依赖于它的对象都得到通知并被自动更新”[2]。

在游戏中太常见了,对于解耦有特别大的帮助。比如成就系统,如果不使用观察者模式,那么几乎所有的子系统都要直接调用成就系统,这样一来对于业务的侵入性太强了。

通常的实现是这样的:


// 事件
class Event {EventType t;
};// 观察者基类
class Observer {
public:void onNotify(Event e);
};// 被观察者基类
class Subject {
public:void addObserver(Observer* o);void removeObserver(Observer* o);
protected:void notify(EventType et);
};

观察者模式的基本实现:
1、观察者继承 Observer 类,被观察者继承 Subject 类。
2、Subject 类内部会维护一个观察者列表,在事情发生的时候 notify,会直接遍历观察者列表,调用它们的 onNotify 函数。
3、通常来说,是一种同步的实现,即被观察者是直接调用观察者的函数的。

需要注意的是,观察者模式跟发布订阅模式是有区别的,虽然它们的思路相似,但也有明显的不同:
1、观察者模式中观察者跟被观察者是互相知道彼此存在的;而发布订阅模式中订阅者跟发布者往往是不知道对方存在的,它们通过一个 broker 来通讯。
2、观察者模式往往是一对多的,而发布订阅可以是一对多,也可以是多对多。
3、观察者模式往往是同步调用,而发布订阅是异步调用。

直接看图比较容易知道它们的区别。

观察者模式:

observer-pattern

图2:观察者模式

发布订阅模式:

publish-subscribe-pattern

图3:发布订阅模式[8]

参考

[1] [美]Robert Nystrom. 游戏编程模式[M]. GPP翻译组. 北京: 人民邮电出版社, 2016-09-01: 61, 125.

[2] [美]Erich Gramma, Richard Helm, Ralph Johnson, John Vlissides. 设计模式: 可复用面向对象软件的基础[M]. 李英军, 马晓星, 蔡敏, 刘建中, 等. 北京: 机械工业出版社, 2010(1):194.

[3] kevinan. 暴雪Tim Ford:《守望先锋》架构设计与网络同步. Available at https://www.sohu.com/a/148848770_466876, 2017-6.

[4] N.R. Satish. Finite State Machine. Available at https://patterns.eecs.berkeley.edu/?page_id=470.

[5] KillerAery. 游戏设计模式:黑板模式. Available at https://www.cnblogs.com/KillerAery/p/10054558.html, 2019-01-17.

[6] [德]Frank Buschmann, Regine Meunier, Hans Rohnert, et al. 面向模式的软件架构卷1模式系统. 袁国忠. 北京: 人民邮件出版社, 2013.11: 46.

[7] Bjarne Stroustrup, Herb Sutter. CppCoreGuidelines. Available at https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#Ri-singleton.

[8] Microsoft. Publisher-Subscriber pattern. Available at https://learn.microsoft.com/en-us/azure/architecture/patterns/publisher-subscriber.

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/12106.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

win10系统解除微软账户和本地账户绑定

折腾了好久,终于找到一种方法可以退出微软账号了,不过这种方法我测试是成功的,有人留言自己不成功,具体解决方法只能看这些留言了 win10当中没有注销按钮,win x 弹出的菜单里面有关闭或注销,可以选择注销…

最短木板长度 - 贪心思维

系列文章目录 文章目录 系列文章目录前言一、题目描述二、输入描述三、输出描述四、java代码五、测试用例 前言 本人最近再练习算法,所以会发布自己的解题思路,希望大家多指教 一、题目描述 小明有 n 块木板,第 i ( 1 ≤ i ≤ n ) 块木板长…

NASA数据即——Aqua AIRS 第 3 级光谱出射长波辐射 (OLR) 月报 (AIRSIL3MSOLR)

Aqua AIRS Level 3 Spectral Outgoing Longwave Radiation (OLR) Monthly (AIRSIL3MSOLR) Aqua AIRS 第 3 级光谱出射长波辐射 (OLR) 月报 (AIRSIL3MSOLR) 简介 这个 L3 光谱出射长波辐射(OLR)是根据密歇根大学黄向磊开发的算法,利用 AIRS…

前端XHR请求数据

axios封装了XHR(XMLHttpRequest) 效果 项目结构 Jakarta EE9&#xff0c;Web项目。 无额外的maven依赖 1、Web页面 index.html <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title>Title</title&…

【JS红宝书学习笔记】第1、2章 初识JS

第1章 什么是JavaScript JavaScript 是一门用来与网页交互的脚本语言&#xff0c;包含以下三个组成部分。 ECMAScript&#xff1a;由 ECMA-262 定义并提供核心功能。文档对象模型&#xff08;DOM&#xff09;&#xff1a;提供与网页内容交互的方法和接口。浏览器对象模型&…

鸿蒙内核源码分析 (内存池管理) | 如何高效切割合并内存块

动态分配 系列篇将动态分配分成上下两篇&#xff0c;本篇为下篇&#xff0c;阅读之前建议翻看上篇。 鸿蒙内核源码分析(TLFS算法) 结合图表从理论视角说清楚 TLFS 算法鸿蒙内核源码分析(内存池管理) 结合源码说清楚鸿蒙内核动态内存池实现过程&#xff0c;个人认为这部分代码…

羊大师分析,羊奶助力共筑健康中国新生活

羊大师分析&#xff0c;羊奶助力共筑健康中国新生活 在健康中国行动的大背景下&#xff0c;我们越来越注重生活方式的健康与营养。羊大师发现&#xff0c;羊奶作为一种营养丰富、易于吸收的天然食品&#xff0c;正逐渐成为我们追求健康生活的得力助手。 羊奶富含优质蛋白质、矿…

vue3自定义指令​(通过指令钩子获得dom和钩子参数)

实现文本框自动获得焦点 Index.vue: <script setup> import { ref, onMounted } from vue import ./index.cssconst vFocus {mounted: (el, binding) > {el.focus()console.log(binding)} }onMounted(() > {}) </script><template><div class&qu…

设计说明-行为型-状态模式-State

状态接口 public interface State {//状态接口void insertQuarter();//投币void ejectQuarter();//退币void turnCrank();//按下“出纸巾”按钮void dispense();//出纸巾 } 有纸巾类 public class HasQuarterState implements State {private TissueMachine tissueMachine;O…

Python中tkinter编程入门4

在Python中tkinter编程入门3-CSDN博客中创建了Button控件&#xff0c;点击该控件就会产生一个点击事件&#xff0c;在创建Button控件时指定该点击事件的处理程序后&#xff0c;按键控件就会对用户的点击事件产生响应。 1 定义事件处理器 定义事件处理器就是一个自定义的函数。…

前端连续发送同一个请求时,终止上一次请求

场景&#xff1a;几个tab页之间快速的切换&#xff08;tab页只是参数不同&#xff0c;下边的数据渲染给同一个data&#xff09;就会导致如果我在1,2,3&#xff0c;tab页按照顺序快速点击&#xff0c;发送三个请求&#xff0c;我想要展示的是3但是如果1或者2请求响应的时间比3长…

Python tensor向量维度转换,不同维度的向量转化为相同的维度,经过全连接层MLP的维度转换,代码实战

问题&#xff1a;在机器学习特征工程中&#xff0c;假如每类特征需要转化为相同的维度进行拼接&#xff0c;那该怎么办呢&#xff1f;接一个全连接层MLP就可以了。 例子&#xff1a;将&#xff08;128,64&#xff09; 维度的向量转化为&#xff08;128,32&#xff09;维。 impo…

Stm32串口搭配DMA实现自定义printf、scanf

前言:本文仅供学习参考使用&#xff0c;主要目的是让大家快速使用串口调试&#xff0c;文章所提及的GCC适用于Clion&#xff0c;Vscode等第三方编辑器的用户。作者有时间会继续更新^_^ 一、GCC环境 1、标准库 (1)、使用方法 在主函数while(1)初始化中&#xff0c;添加Seria…

柯桥法语学习-5大法语听写网站:全力助攻你的dictée!

提到法语dicte&#xff0c;绝对可是法语学生们的老大难&#xff0c;简直就是心痛得不能自已啊&#xff01;所以今天&#xff0c;法语君整理了5个听写网站助攻大家的dicte哦&#xff01; Projet Voltaire 01 一个很容易让你对dicte上瘾的APP 写邮件、实习报告或者动机信时&…

Nat Plants | 植物抽核单细胞!多组学探究大豆根瘤成熟过程

发表时间&#xff1a;2023-04 发表期刊&#xff1a;Nature Plants 影响因子&#xff1a;17.352 DOI&#xff1a;10.1038/s41477-023-01387-z 研究背景 根瘤菌是亲和互作寄主植物&#xff0c;感染宿主并在根部形成共生器官根瘤&#xff0c;具有固氮…

jmeter中HttpClient4发送失败,java方法请求成功

jmeter中HttpClient4请求失败 上传文件时&#xff1a;Httpclient4: 请求体 请求头 响应结果 ,后端服务都总是提示存在非法标签。 jmeter中使用java请求成功 修改使用java方式&#xff0c;访问正常&#xff1b; 根据分析可能因为HC4对一些特殊字符会进行转义&#xff0c;转义后…

idea配置MySQL提示

点击sql语句&#xff0c;然后再选择show context actions 然后再选择Inject language or reference 然后再选择MySQL 然后我们会发现sql语句变颜色了 如果表是红色 那么需要我们连接mysql的对于的数据库

做私域不止是积累流量,生态也很重要!

如今&#xff0c;私域流水占比已经逼近整个零售市场的30%&#xff0c;达到4万亿规模&#xff0c;百度、阿里、腾讯等头部玩家也都在加速布局&#xff0c;私域运营&#xff0c;已不再是一个单一的商业模式或者运营手段&#xff0c;而是逐渐构成一种可持续的行业生态。 一、什么…

全球量子计算已开始商业化!应用最多的行业你一定想不到

内容来源&#xff1a;量子前哨&#xff08;ID&#xff1a;Qforepost&#xff09; 文丨卉可 排版丨沛贤 深度好文&#xff1a;2600字丨5 分钟阅读 01 量子计算是一场高风险的游戏 近日&#xff0c;PsiQuantum从澳大利亚联邦政府和地方政府获得了10亿澳元资金&#xff0c;这…

大屏UI:建筑可视化应用越来越广泛,根本挡不住。

建筑可视化在可视化大屏中有许多应用场景&#xff0c;以下是其中一些常见的应用场景&#xff1a; 建筑项目展示&#xff1a;可以使用建筑可视化技术展示正在进行或已完成的建筑项目。通过可视化大屏&#xff0c;可以展示建筑的外观、内部布局、材料选择等信息&#xff0c;帮助…