游戏设计模式: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 示例实现)
博主才学尚浅,难免会有错误,尤其是设计模式这种极富禅意且需要大量经验的东西,如果哪里书写错误或有遗漏,还请各位前辈指正. 打 算写设计模式的目的就是,首先自己可以理清思路,还有就是国内的设计模式资料很 ...
随机推荐
- cojs 白树黑 黑树白 题解报告
黑树白 首先如果不是强制在线,这个题用莫队+树状数组就可以在O(n*sqrt(n)*log(n))的时间内搞定 如果没有修改操作,可以直接上主席树就可以辣 我们考虑修改操作,某一个修改操作对于某一个查 ...
- lintcode 中等题:partition array 数组划分
题目 数组划分 给出一个整数数组nums和一个整数k.划分数组(即移动数组nums中的元素),使得: 所有小于k的元素移到左边 所有大于等于k的元素移到右边 返回数组划分的位置,即数组中第一个位置i, ...
- ios UIView常用动画效果
一 //调用 1 2 3 4 5 6 if(m_viewScenario.superview == nil)<br>{ m_viewScenario.alpha = 1.0; ...
- JavaWeb项目开发案例精粹-第3章在线考试系统-005action层
1. <?xml version="1.0" encoding="UTF-8" ?><!-- XML声明 --> <!DOCTYP ...
- ARM CPU与Intel x86 CPU性能比较
Qualcomm ARM CPU与Intel x86 CPU性能比较 随着移动互联网时代的到来,Qualcomm(高通).Texas Instruments(德州仪器)等基于ARM架构的CPU受到越来 ...
- SELinux开启与关闭
SELinux是「Security-Enhanced Linux」的简称,是美国国家安全局「NSA=The National Security Agency」 和SCC(Secure Computin ...
- 24个有用的PHP类库分享
目前,PHP是用于Web开发的最流行的脚本语言.你可以在互联网上随手找到关于PHP大量资料,包括文档.教程.工具等等.PHP不仅是一种功能丰富的语言,它还能帮助开发人员轻松地创建更好的网络环境.为了进 ...
- Linux下检查是否安装过某软件包
1.rpm包安装的,可以用 rpm -qa 看到,如果要查找某软件包是否安装,用 rpm -qa | grep "软件或者包的名字" 2.以deb包安装的,可以用 dpkg -l ...
- POJ 1166 The Clocks (爆搜 || 高斯消元)
题目链接 题意: 输入提供9个钟表的位置(钟表的位置只能是0点.3点.6点.9点,分别用0.1.2.3)表示.而题目又提供了9的步骤表示可以用来调正钟的位置,例如1 ABDE表示此步可以在第一.二.四 ...
- UVa 12304 (6个二维几何问题合集) 2D Geometry 110 in 1!
这个题能1A纯属运气,要是WA掉,可真不知道该怎么去调了. 题意: 这是完全独立的6个子问题.代码中是根据字符串的长度来区分问题编号的. 给出三角形三点坐标,求外接圆圆心和半径. 给出三角形三点坐标, ...