设计模式在游戏开发中的应用
近期在学习设计模式在游戏开发中的应用时,发现它们确实能够提高开发效率,尤其在后期代码调整和维护方面表现出色。
于是写下本文,简要记录学习心得,便于日后回顾时能有所启发。
注意
- 本文只根据书籍和网络资料,总结设计模式在游戏开发中可能的应用场景,不讲解设计模式的原理。
- 在实际应用中,通常会有多种设计模式组合使用的情况。本文以设计模式分类,会出现同一个使用场景在多种设计模式下出现的情况。
- 在实际开发中,有些功能可能会有其他的实现方式,或者有些设计模式有其他的应用场景,本文列举可能有误或不够全面,欢迎各位指正和补充。
创建型 设计模式
1. 单例模式(Singleton)
单例模式在游戏开发中有着广泛的应用。单例模式确保一个类只有一个实例,并提供一个全局访问点来获取该实例。
使用场景 | 说明 |
---|---|
游戏管理器 | 负责处理游戏的核心逻辑,如游戏状态、关卡进度等,可与外观模式组合使用 |
音频管理器 | 大意同上 |
UI管理器 | 同上 |
各种模块管理器 | 同上 |
数据持久化 | 同上 |
网络游戏客户端 | 使用单例模式限制连接数,预防误用产生过多连接,避免服务器端因此失效 |
日志工具 | - |
单例模式虽然方便,但应尽量少用:
“单例模式还违反了开闭原则,因为通过 Instance 方法获取对象是实现类而不是接口类。因此,当设计变更或需求增加时,程序设计师无法将其替换为其他类,只能更改原有实现类内的程序代码,所以无法满足对修改关闭的要求。”
——《设计模式与游戏完美开发》
单例模式可以用于代码的模块化,直接使用单例模式(Singleton)由于没有访问限制,可能会导致代码混乱不易维护。
完整的单例模式应该增加访问限制。如引入 IOC(Inversion of Control 控制反转)容器,通过查询字典实现有访问限制的单例模式的效果。
如果把整理游戏代码框架比作装机,单例比作比较重要的几个模块的连接线,那么引入IOC容器相当于加了个“理线器”(有很多其他的设计模式也具备此功能)。
利用 IOC容器 可以很方便写出符合依赖倒置(D)和单一职责(S)原则的代码。
2. 工厂方法模式(Factory Method)
工厂方法模式提供了一种将对象的创建与使用分离的方式,通过定义一个工厂接口,让子类决定生产哪种具体的对象。
使用场景 | 说明 |
---|---|
角色生成 | 通过使用工厂方法模式,可以定义一个角色工厂接口,然后为每种角色类型(如玩家、敌人、NPC等)实现具体的工厂类,可与建造者模式搭配使用 |
道具生成 | 可以定义一个道具工厂接口,然后为每种道具类型(如武器、防具、消耗品等)实现具体的工厂类 |
3. 抽象工厂模式(Abstract Factory)
抽象工厂模式提供了一种接口,用于创建一系列相关或依赖的对象,而无需指定它们具体的类。系统可以根据当前执行环境,自行决定生产哪一组子类。
使用场景 | 说明 |
---|---|
资源加载 | 通过使用抽象工厂模式,可以为不同类型的资源创建对应的工厂类,从而实现统一的资源加载接口 |
UI创建 | 可以提供不同的界面工厂类,有助于实现UI组件的复用和定制 |
关卡生成 | 可以为不同关卡类型(如森林、沙漠、城市等)创建一个工厂类,其中包含不同的生成内容和元素,当要添加新的关卡类型时可以使用抽象工厂模式 |
道具生成 | 大意同上 |
抽象工厂模式 与 工厂方法模式
抽象工厂模式:
- 抽象工厂类 - *工厂接口 - *工厂实现类 - *生产产品
- 易扩展,不易修改
工厂方法模式:
- 工厂接口 - 工厂实现类 - *生产产品
- 易出现新增产品过多导致工厂爆量
(*表示可以为多个)
4. 建造者模式(Builder)
建造者模式将一个复杂对象的构建流程与其表现分离,使得相同的构建流程可以创建不同的对象表现。(分步组装)
使用场景 | 说明 |
---|---|
角色生成 | 可以分步骤创建角色,并提供不同的组合(如属性、技能、特效、AI行为等),这样可以实现灵活的角色创建 |
关卡生成 | 可以将地图的构建过程分解为多个步骤,每个步骤负责处理特定的地图元素(如地形、建筑物、道具等) |
道具生成 | 可以分步骤(如攻击力、防御力、耐久度等)创建道具和装备,提供灵活的组合方式 |
5. 原型模式(Prototype)
原型模式通过复制现有对象(原型)来创建新对象,而不是通过传统的构造方法。
使用场景 | 说明 |
---|---|
游戏资源加载 | Unity 中的 GameObject.Instance 就是一种原型模式 |
行为型 设计模式
6. 状态模式(State)
状态模式允许对象在其内部状态改变时改变其行为。
使用场景 | 说明 |
---|---|
角色状态管理 | 管理角色状态。像站立、跑动、跳跃、巡逻、攻击等状态,通过使用状态模式,将每个状态的逻辑和行为封装在单独的类中,使得角色状态之间的切换逻辑更加明确 |
游戏关卡或场景切换 | 管理关卡和场景的切换、逻辑行为 |
游戏菜单和UI状态管理 | 如主菜单、设置界面、暂停菜单等。状态模式可以简化这些界面之间的切换逻辑,使得UI系统更加模块化和易于维护 |
状态模式 与 有限状态机(Finite State Machine, FSM)
状态模式可以看作是实现有限状态机的一种面向对象的实现方式。通过使用状态模式,可以更清晰地表示有限状态机中的状态转换和状态行为。
在游戏开发中,状态模式可以用于实现有限状态机,从而管理对象的状态转换和行为。
7. 中介者模式(Mediator)
中介者模式用于降低多个对象之间的耦合,将对象间的交互集中到一个"中介对象"中进行处理。(多个子系统间的交互枢纽)
使用场景 | 说明 |
---|---|
子系统间交互 | 可以作为游戏内各子系统间的交互枢纽 |
UI系统 | 可以使用中介者模式将复杂的UI交互关系从各UI元素中抽离出来,集中到一个中介者对象中进行处理 |
角色间的交互 | 可以将角色之间的对话、交易等交互逻辑从角色对象中分离出来,引入中介者对象进行处理 |
事件系统 | 将玩家被击败、物品被收集等事件的处理逻辑,集中到一个中介者对象中,简化事件的发布和订阅过程(建议使用观察者模式) |
8. 策略模式(Strategy)
策略模式允许在运行时动态地更改对象的行为。通过使用策略模式,可以将一组可互换的算法封装到一组独立的类中,从而简化代码并提高可维护性和可扩展性。
使用场景 | 说明 |
---|---|
角色属性计算 | 可以利用策略模式,将不同的属性和计算方式独立出来,方便修改和使用 |
关卡生成 | 可以将不同的关卡生成算法封装为一组独立的类,并在运行时根据需要动态地切换关卡生成策略 |
游戏难度调整 | 同上 |
策略模式 与 状态模式
“State 是在一群状态中进行切换,状态之间有对应和连接的关系;Strategy 则是由一群没有任何关系的类所组成,不知彼此的存在。
State 受限于状态机的切换规则,在设计初期就会定义所有可能的状态,就算后期追加也需要和现有的状态有所关联,而不是想加入就加入;Strategy 是由封装计算算法而形成的一种设计模式,算法之间不存在任何依赖关系,有新增的算法就可以马上加入或替换。”
——《设计模式与游戏完美开发》
9. 模板方法模式(Template Method)
模板方法模式在一个方法中定义了一个算法的骨架,并将一些重复的步骤从子类提取到父类中。这样,模板方法允许子类在不改变算法结构的情况下重新定义算法的某些步骤。(通用流程)
使用场景 | 说明 |
---|---|
关卡加载 | 可以将关卡加载的基本结构(如预加载资源、初始化场景、加载角色等)定义在一个抽象基类中,并将具体的加载步骤延迟到子类中实现 |
成就系统 | 可以使用模板方法模式定义一个抽象成就类,包含基本的成就检查骨架方法,在子类中实现具体的成就检查逻辑 |
网络游戏登陆 | 通过模板方法模式将登陆流程固定下来,如显示登陆画面、选择登陆方式、输入账号密码、向服务器发送请求等,让登陆功能子类实现具体操作 |
10. 命令模式(Command)
命令模式将请求封装为对象,将客户端的不同请求参数化,并配合队列、记录、复原等方式来执行请求操作。
使用场景 | 说明 |
---|---|
交互逻辑 | 在MVC框架中,可以用于分担 Controller 层的交互逻辑,让很多混乱的交互逻辑代码从 Controller 迁移到 Command 中 |
操作记录 | 通过存储已执行的命令对象,可以轻松实现撤销和重做功能(如移动单位、放置建筑等)。可用于实时策略游戏和编辑器等场景 |
事件系统 | 可以将一系列事件通过命令模式封装起来,使其可以更灵活的调用 |
在凉鞋老师的框架中:
- 事件由 系统层 向 表现层 发送
- 表现层 只能用 Command 改变底层系统层的状态(数据)
- 表现层 可以直接查询数据
11. 责任链模式(Chain of Responsibility)
责任链模式为请求创建了一个接收者对象链。这些接收者对象按顺序处理请求,直到其中一个处理了该请求为止。
使用场景 | 说明 |
---|---|
关卡切换 | 可以设置各关卡切换条件,当条件达成时跳转到对应关卡。在通关判断上,可以配合策略模式,让通关规则具有其他形式变化 |
AI决策 | 将不同的AI行为链接在一起,让AI根据当前情况处理决策请求,从而实现灵活的AI行为控制 |
12. 观察者模式(Observer)
观察者模式定义了一种一对多的依赖关系,当一个对象(主题)的状态发生变化时,所有依赖于它的对象(观察者)都将得到通知并自动更新。
使用场景 | 说明 |
---|---|
事件系统 | 可以实现一个全局的游戏事件系统,当某个事件触发时,所有订阅了该事件的对象都会收到通知并作出相应处理 |
成就系统 | 监测游戏中的各种事件(如角色升级、任务完成、特定敌人击败等),当满足成就条件时,自动解锁相应的成就并通知玩家 |
观察者模式 与 中介者模式
两者结构相同,但使用场景不同。
观察者模式可以理解为了使业务逻辑更加清晰,从中介者模式中分离出一种专门处理事件订阅与分发的设计模式(笔者的粗浅理解)。
13. 备忘录模式(Memento)
备忘录模式在不破坏对象封装的前提下,捕获对象的内部状态,并在该对象之外保存这个状态。这样以后就可将该对象恢复到原先保存的状态。
使用场景 | 说明 |
---|---|
游戏存档 | 可以利用备忘录模式保存玩家的角色状态、关卡进度、游戏设置等信息 |
状态回滚 | 在角色受到负面效果或需要恢复到之前状态时,可以使用备忘录模式回滚角色属性 |
撤销操作 | 在游戏编辑器或游戏中的一些可撤销操作,可以通过备忘录模式记录并恢复对象的状态 |
14. 访问者模式(Visitor)
访问者模式可以定义一个能够在一个对象结构中对于所有元素执行的操作。访问者可以让你定义一个新的操作,而不必要更改到被操作元素的类接口。被访问者需要开放足够的操作方法和信息。
使用场景 | 说明 |
---|---|
统计数据 | 在对游戏中的各种对象(如角色、敌人、道具等)进行统计时,可以使用访问者模式来实现,避免修改原有类定义 |
辅助管理类 | 当需要对各种对象(如角色、敌人、道具等)的“管理容器”类添加功能时,可以通过访问者模式减少对原有代码的更改 |
15. 迭代器模式(Iterator)
在不知道集合内部细节的情况下,提供一个按序方法存取一个对象集合体的每一个单元。(遍历)
使用场景 | 说明 |
---|---|
遍历对象 | 循环语句(如 for 循环)就可以实现迭代器模式 |
16. 解释器模式(Interpreter)
定义一个程序设计语言所需要的语句,并提供解释来解析(执行)该语言。(翻译)
使用场景 | 说明 |
---|---|
表达式计算 | 可以使用解释器模式来解析和计算游戏中的各种表达式 |
结构型 设计模式
17. 外观模式(Facade)
在游戏开发中,外观模式常被用于简化复杂系统的交互。通过为多个子系统提供一个统一的接口,将复杂的内部逻辑抽象为简单的调用。(高级封装)
使用场景 | 说明 |
---|---|
音频管理器 | 创建一个音频管理器类,可以简化音频播放、暂停、停止等操作的调用。只需通过音频管理器接口来控制音频播放,无需关注音频资源的加载和播放器的创建 |
UI管理器 | 将UI系统中的交互逻辑,如按钮点击事件、文本显示等操作,封装在一个UI管理器类中,简化UI组件的访问和修改,同时降低UI系统与其他系统之间的耦合度 |
数据持久化 | 创建一个保存管理器类,可以简化保存和加载过程,同时方便后期扩展更多的存储功能 |
18. 桥接模式(Bridge)
桥接模式用于将抽象与实现分离,使它们可以独立地变化。使抽象类作为不同实现类间的桥梁。
使用场景 | 说明 |
---|---|
角色与装备 | 可以将角色、装备的抽象与具体实现分离,使不同种类的角色可以搭配和使用不同种类的装备 |
输入系统 | 可以将输入设备(如键盘、鼠标、游戏手柄等)的抽象与具体实现分离,从而简化输入设备的管理和扩展,同时保持游戏逻辑的独立性 |
游戏资源管理 | 游戏资源(如纹理、模型、声音等)可能需要支持多种格式和来源(如本地文件、网络下载、内存中的数据等)。可以将资源的抽象与具体实现分离 |
网络系统 | 可以将网络协议(如TCP、UDP、Websockets等)的抽象与具体实现分离,从而简化网络协议的管理和扩展 |
19. 享元模式(Flyweight)
享元模式的主要目标是通过共享相同的对象,来减少内存占用和提高性能。
使用场景 | 说明 |
---|---|
游戏对象属性共享 | 将游戏中大量具有相同属性的游戏物体中的相同部分提取出来,进行统一管理 |
20. 组合模式(Composite)
组合模式将对象组合成树形结构以表示层次结构。组合模式使得客户端可以以一致的方式处理单个对象和对象组合。
使用场景 | 说明 |
---|---|
UI层次结构 | 通过使用组合模式,可以轻松地组织和管理树型UI菜单,实现事件传递和布局调整等功能 |
游戏任务逻辑 | 可以将任务、子任务和条件等逻辑元素组织成树形结构,实现逻辑的判断和执行等功能 |
21. 装饰模式(Decorator)
装饰模式允许在不改变原始对象结构的情况下,动态地向对象添加新功能,从而实现功能的组合和扩展。
使用场景 | 说明 |
---|---|
角色属性修改 | 可以通过装饰模式增加角色的属性(如生命值、攻击力、防御力等) |
游戏界面扩展 | 可以游戏界面添加额外的功能(如特效、动画、音效等),这样实现起来比较灵活,修改也方便 |
数据加密解密 | - |
装饰模式已有目标增加功能非常方便,但是要避免盲目使用导致系统过于混乱,应将可能需要的功能列在早期的开发计划中。
22. 适配器模式(Adapter)
适配器模式将一个类的接口转换成另一个类所期望的接口,使得原本接口不兼容的类可以一起工作,提高组件的复用性和扩展性。(转接口)
使用场景 | 说明 |
---|---|
第三方库集成 | 当需要集成不同的第三方库(如广告、支付、社交等)时,可以使用适配器模式统一接口,方便切换和扩展不同的第三方库 |
输入设备兼容 | 可以统一不同输入设备(如键盘、鼠标、手柄等)的接口,实现多种输入设备的兼容和扩展 |
游戏引擎升级 | 当游戏引擎升级后,一些接口可能发生变化,使用适配器模式可以降低升级带来的影响 |
网络通信协议适配 | 在多人在线游戏中,可能需要支持多种网络通信协议(如TCP、UDP、WebSocket等),可以使用适配器模式统一接口,方便扩展和切换不同的通信协议 |
23. 代理模式(Proxy)
代理模式为其他对象提供一种代理以控制对这个对象的访问。代理模式可以用于延迟加载、安全控制、日志记录等功能。
使用场景 | 说明 |
---|---|
优化测试 | 可以使用代理模式测试游戏优化的效果,以免去修改原始类的接口及实现 |
网络代理 | 在多人在线游戏中,可以使用代理模式处理与服务器的通信,方便进行数据加密、压缩、缓存等操作 |
在学习设计模式的过程中,我发现设计模式其实是面向对象的数据结构。
一个由0和1组成的系统,通过各种各样的数据结构和算法,层层编织出能够模拟现实的游戏世界,这实在是太美妙了!