積累提供所有操作(的實現)來定義子類的行為
用一個最簡單的例子來講解這個模式
玩家操縱的英雄也就是這個游戲的主角會有許多技能,我們想定義許多不同的技能,來讓玩家使用。
首 先我們定義一個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