游戏设计模式:Subclass Sandbox模式,以及功能方法集的设计思考
书中总结出这种 Subclass Sandbox 的设计模式
Game Design Patterns: Subclass Sandbox
这种模式要点有两点:
- 在基类中实现各种功能性方法供子类调用
- 定义沙盒接口供子类重载
书中的例子,基类中定义三个实用方法,和沙盒接口 activate:
class Superpower
{
public:
virtual ~Superpower() {}
protected:
virtual void activate() = 0;
void move(double x, double y, double z)
{
// Code here...
}
void playSound(SoundId sound, double volume)
{
// Code here...
}
void spawnParticles(ParticleType type, int count)
{
// Code here...
}
};
然后在子类中调用
class SkyLaunch : public Superpower
{
protected:
virtual void activate()
{
// Spring into the air.
playSound(SOUND_SPROING, 1.0f);
spawnParticles(PARTICLE_DUST, 10);
move(0, 0, 20);
}
};
这两个要点其实是独立的。书中主要在讨论第一点。第二点,沙盒接口的作用,一个是执行入口,另一个是调用时机可控,这些大多是和具体业务相关。
看书中的例子,很明显能看出一个问题:
void move(double x, double y, double z) 和其他两个方法playSound,spawnParticles并不是一类功能。假设基类抽象的是场景节点,move是实例本身的功能,放到基类作为公用代码以及公用接口是OOP的标准用法。因此主要考虑的是,实用的功能性方法,如播放声音,播放特效等,应该放到哪里?
主要有几种方案:
直接写:每次在用到的地方编写一遍功能。显然这会造成代码的冗余,给维护造成麻烦。但是往往很多功能一开始写的时候就是写在用到的地方,在开发过程中逐渐由更多的地方需要同样的功能时,再考虑把功能重构到通用的地方。
功能单例:不同类功能封装到不同的单例。如播放音乐时调用
AudioEngine::getInstance()->playSound()。这样做的缺点,如书中所说,是程序中多个地方造成了对AudioEngine这样的系统的引用耦合。我们可以用加一层门面来抵消这种负面效果,如加一个AudioUtils类对原始的AudioEngine进行封装。超级单例:不同类功能封装到同一个单例。如一个GameUtils类或直接放到GameRuntime类中,这样与“功能单例”相比的好处是,从依赖多个系统到依赖一个系统,并且减少整个程序单例的数量,调用的时候非常方便。缺点也是十分严重,就是GameUtils会是一个非常庞大的类,一方面增加这个类本身的阅读和维护成本,另一方面对降低了重用性。
比如一个game有audio, graphics, physics功能,现在有另一个游戏要重用原有代码但是不需要physics特性,但是如果由于GameUtils依赖了physics系统,虽然在业务流程里没有任何调用任何物理相关的功能,要去除对physics系统的依赖也是很困难的。
很多大型系统里,这种通用全能的类往往会变成一个泥潭,没人敢改动和删减其中的功能,只有不断往上加新的功能。
超级基类:把功能封装到基类中。就是书中介绍的方法。我对这种方法持保留态度。它实际是超级单例的一种特殊实现,缺点与其类似,甚至更糟。
如果说超级单例方案造就了一个泥潭,超级基类把这坨泥潭引导每个实例中。它引入了继承这一大耦合,并且使得基类是一个会经常改动的类,这些都是反设计原则的。
最可怕的是,如果你要用基类中的功能,就需要把自己变成它的子类。如果上一种方法是你要一片树叶给你一个森林,这个方法就是要把自己变成一个森林。
一般基类中要有一些真正属于公有的代码,如引用计数,生成id,初始化等等,还有一些真正属于对象功能的方法。如果把功能方法和实用方法混在一起,对象真正的功能就会淹没在大量无关代码里,是阅读和维护代码的人很难抓住真正的功能。
但这种方法在实践中还是用的很多的。我觉得最大的好处是实用方便,大多数语言里调用基类方法比调用其他全局实例方法的语法简洁的多。这常常意味着易用性,用
playSound()显然比AudioEngine::getInstance()->playSound()爽快的多。特别是对初学者,或者非专业人员,比如封装给策划用的脚本,还有现在很多引擎面向的是非专业编程人员,写代码的时候感觉要什么有什么,根本不用考虑这些功能是哪里来的,反正是引擎提供的,只要专注游戏逻辑就好了。这些受众也不会太在意代码架构方面的东西。消息系统:基于消息调用功能。这是依赖最低的方法。模块间的功能使用消息系统调用,如AudioEngine监听 PLAY_SOUND 消息,播放声音时发送
sendMessage(PLAY_SOUND, "a.mp3"),不用真正依赖 audio 系统。这样只要消息接口一致,调用方和被调用方可以独立重构。如果去掉某个模块,只是对应消息无效,不会代码崩溃或者无法编译。如果不想 AudioEngine 依赖消息系统,可以用建立一个 AudioManager 监听消息和调用 AudioEngine缺点也很多。1. 这种方法调用太重,有一些很小的功能或者实用函数不适合这种模式;2. 代码流会变得更乱,不容易阅读和调试;3. 调用变成纯动态,跳过编译器检查,容易出错;4. 效率会更低
游戏设计模式:Subclass Sandbox模式,以及功能方法集的设计思考的更多相关文章
- 设计模式---对象创建模式之工厂方法模式(Factory Method)
前提:“对象创建”模式 通过“对象创建”模式绕开new,来避免对象创建(new)过程中所导致的紧耦合(依赖具体类),从而支持对象创建的稳定.它是接口抽象之后的第一步工作. 典型模式(表现最为突出) 工 ...
- 【C#设计模式——创建型模式】工场方法模式
工场方法模式对简单工场模式进行了乔庙的扩展,不是用一个专门的类来决定实例化哪一个子类.相反,超类把这种决定延迟到每个子类.这种模式实际上没有决策点,就是没有直接选择一个子类实例化的决策. 看书上的例子 ...
- java设计模式之模板模式以及钩子方法使用
1.使用背景 模板方法模式是通过把不变行为搬到超类,去除子类里面的重复代码提现它的优势,它提供了一个很好的代码复用平台.当不可变和可变的方法在子类中混合在一起的时候, 不变的方法就会在子类中多次出现, ...
- 设计模式之工厂模式之工厂方法(php实现)
github: git@github.com:ZQCard/design_pattern.git /** * 工厂方法 * 使用开闭原则来分析下工厂方法模式.当有新的产品产生时,只要按照抽象产品角色. ...
- Delphi 设计模式:《HeadFirst设计模式》Delphi2007代码---工厂模式之工厂方法[转]
1 2{<HeadFirst设计模式>工厂模式之工厂方法 } 3{ 产品类 } 4{ 编译工具 :Delphi20 ...
- 游戏编程技巧 - Subclass Sandbox
Subclass Sandbox 使用场景 你正在开发一款类似LOL的游戏,里面有许多英雄角色,你决定把这些英雄类交给小弟们实现.因为在这些英雄中,释放放技能时,有的要使用粒子系统造成炫酷的效果,有的 ...
- 游戏开发设计模式之状态模式 & 有限状态机 & c#委托事件(unity3d 示例实现)
命令模式:游戏开发设计模式之命令模式(unity3d 示例实现) 对象池模式:游戏开发设计模式之对象池模式(unity3d 示例实现) 原型模式:游戏开发设计模式之原型模式 & unity3d ...
- 游戏开发设计模式之原型模式 & unity3d JSON的使用(unity3d 示例实现)
命令模式:游戏开发设计模式之命令模式(unity3d 示例实现) 对象池模式:游戏开发设计模式之对象池模式(unity3d 示例实现) 实现原型模式 原型模式带来的好处就是,想要构建生成任意独特对象的 ...
- 游戏开发设计模式之命令模式(unity3d 示例实现)
博主才学尚浅,难免会有错误,尤其是设计模式这种极富禅意且需要大量经验的东西,如果哪里书写错误或有遗漏,还请各位前辈指正. 打 算写设计模式的目的就是,首先自己可以理清思路,还有就是国内的设计模式资料很 ...
随机推荐
- Minimum_Window_Substring两种方法求解
题目描述: Given a string S and a string T, find the minimum window in S which will contain all the chara ...
- Java NIO原理图文分析及代码实现
原文: http://weixiaolu.iteye.com/blog/1479656 目录: 一.java NIO 和阻塞I/O的区别 1. 阻塞I/O通信模型 2. java ...
- Android 显示原理简介
作者:yearzhu,2011年进入腾讯公司,从事过Web端及移动端的测试工作,喜爱新鲜事物及新技术,目前在SNG开放平台测试组负责的移动互联SDK的测试工作. 现在越来越多的应用开始重视流畅度方面的 ...
- (转)MyEclipse +Servlet
来自:http://www.cnblogs.com/sunada2005/p/3520788.html 在Win7系统下运行自己的第一个Servlet程序,因为有时候配置不当或系统原因可能会运行不成功 ...
- 素数筛法--SPOJ Problem 2 Prime Generator
质数(prime number)又称素数,除了1和它本身外,不能整除以其他自然数,换句话说就是该数除了1和它本身以外不再有其他的因数:否则称为合数.最小的质数是2. 要判断一个整数N是不是质数很简单, ...
- android下activity中多个listview只允许主界面滚动
之前发现了自己的APP在处理两个listview时产生的一个bug.当两个listview中的item数量多出手机屏幕时,listview不能显示完全.一开始觉得只要加一个scrollview就可以了 ...
- eclipse的安装
eclipse的安装只要记住一条就好,那就是eclipse安装的时候版本要一致(就是,安装eclipse 32位的,同时要配套jdk 32位,tomcat 32位的),同理64位的也是一样的. 本人安 ...
- C#画图解决闪烁问题
导致画面闪烁的关键原因分析: 一.绘制窗口由于大小位置状态改变进行重绘操作时,绘图窗口内容或大小每改变一次,都要调用Paint事件进行重绘操作,该操作会使画面重新刷新一次以维持窗口正常显示 ...
- [HDOJ2818]Building Block(带权并查集,路径压缩)
题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=2818 题意:有N个块,每次有两个操作: M x y表示把x所在的那一堆全部移到y所在的那一堆的下方. ...
- ASCII码常用值
大写字母 A~Z 65~90 小写字母a~z 97~122 数字0~9 48~ 57