积累提供所有操作(的实现)来定义子类的行为
用一个最简单的例子来讲解这个模式
玩家操纵的英雄也就是这个游戏的主角会有许多技能,我们想定义许多不同的技能,来让玩家使用。
首
先我们定义一个skillBase类作为基类,我们所有技能的动作都在这里实现。我们可以从这些基本元动作中组合出各种各样的技能,甚至成百上千种,可以
设计一个doc文档来设计各种技能的操作,及操作顺序。这就是之所以为什么叫子类沙盒的原因,把实现技能的方法作为沙盒,向这个沙盒里加入各种各样的元动
作来组成各种各样的技能。
以传说系列的凤凰天驱为例,如下图(源自世界传说-换装迷宫2),这个技能分为,后移、跳跃、播放动画、前冲、粒子效果、播放动画等等元动作组成的
如果我们不使用子类沙盒模式,而一个一个写技能的话会有如下缺点:
1. 会产生大量重复代码,造成代码冗余,因为每个技能都有重复的地方,比如说播放声音,播放动画等。使用这种模式之后各个操作方法就像一个一个的组件一样,随意使用,不会有重复。
2.
每一个技能类都会与游戏系统和游戏引擎耦合,比如声音、动画播放,而如果把操作实现都写在基类里让子类组合的话就只有基类与之耦合而已。这个原因这种模
式带来的好处是,如果有某个操作增删改的话,就不用每个技能类都增删改一遍,而只改基类就好,方便简单,简约。而且重用性相当好,这些代码能用在大量游戏
上(比如说传说系列每个系列的传承->魔神剑)
在基类skillBase中,我们需要实现一些技能的元动作,比如move移动,jump跳,playAnimation播放动画,playSound播放声音等等,我们把他们组合在一起实现各种技能,这些方法是受保护的。
然后我们需要一个virtual 方法action是最终的技能在子类中定义实现,这就是为什么上面的原方动作是受保护的了,因为我们不需要调用这些元动作,只需要他们组合成的技能action()方法就好,所以只让子类获取元动作方法用protected标记。
于是乎我们做一些受保护的方法,在子类中拼装实现一个组合在一起的整体。
我们要做这些技能类
1. 建立一个继承于基类skillBase的技能类起名为skill1
2. 重写skillBase的action方法
3. 把元动作在action中实现
基
类(skillBase)中提供了一个抽象的沙盒方法(action)和一些标记为protected的元动作操作(move、jump、
playSound等),这个类派生了一些沙盒子类,每个沙盒子类(skill1、skill2。。。)都实现了沙盒方法(action),沙盒方法包含
着各种元动作(move、jump、playSound等)。
当符合以下条件时可以使用子类沙盒模式
1. 一个基类和大量派生子类
2. 基类可以提供所有派生子类需要的操作
3. 子类之间的操作、方法有重复
4. 想要减少子类和游戏系统、游戏引擎等的耦合
注意:因为此时子类与基类密切相关,所以可能会产生brittle base class问题,也就是你看似安全的修改了基类,但是子类却可能因此发生问题。
代码实现
代码如下:
在skillBase中简单实现几个元动作和抽象的沙盒方法:
protected void act(int actID) { hero.Act(actID);//播放攻击动画 } protected void playSound(int soundID) { audioSource.PlayOneShot(audio[soundID]); } protected void move(Vector3 dir, float moveSpeed, bool isRun) { moveFunc.move(dir, moveSpeed, isRun); } protected void jump() { moveFunc.jump(); } protected void particalEffect() { 。。粒子效果。。 } virtual public void action()//沙盒方法 { }
再看skill1实现的沙盒方法,比如说我们想实现一个跳斩,就把这些元动作组合在一起
override public void action() { playSound(0); act(0); jump(); particalEffect(); }
很简单对吧
我们再来看看基类初始化的方法
基类的初始化
方法一:构造函数
通过构造函数传参来赋值,但是这种有参构造函数必须要在每个沙盒子类中base一下,所以如果base类的构造函数这些参数有个增删改,子类也得跟着全部增删改。
public SkillBase(AudioSource _audioSource, AudioClip[] _audio, Move1 _moveFunc, Hero2 _hero, GameObject _heroObject) { this.audio = _audio; this.audioSource = _audioSource; this.moveFunc = _moveFunc; this.hero = _hero; this.heroObject = _heroObject; }
方法二:初始化函数init
不过记得要在一开始调用,否则小心游戏崩溃
public void init(AudioSource _audioSource, AudioClip[] _audio, Move1 _moveFunc, Hero2 _hero, GameObject _heroObject) { this.audio = _audio; this.audioSource = _audioSource; this.moveFunc = _moveFunc; this.hero = _hero; this.heroObject = _heroObject; }
方法三:静态初始化方法init和类变量(静态变量)
这样所有沙盒子类也就是所有skill都共用一种变量了,有好处也有坏处
static private AudioSource audioSource; static private AudioClip[] audio; static private Move1 moveFunc; static private Hero2 hero; static protected GameObject heroObject; static public void init(AudioSource _audioSource, AudioClip[] _audio, Move1 _moveFunc, Hero2 _hero, GameObject _heroObject) { audio = _audio; audioSource = _audioSource; moveFunc = _moveFunc; hero = _hero; heroObject = _heroObject; }
总结
小模式,大用处。很简单的一个设计模式,却很有用处,成功的减少了沙盒子类与其他类之间的耦合,也增加了重用性,更重要的是这种组合的方式增加了多样性,这正是游戏所需要的。
全部代码已上传至GitHub
博主近期渲染:最近用unity5弄的一些渲染
---- by wolf96