在上一篇中,簡單的使用界面元素快速實現了一個游戲中的二級頁面,這種直接在游戲頁面上做UI的做法並不太好,原因是,UI會讓游戲的壓力變大,即使它是隱蔽的,如果同樣的功能在其它的地方也是一樣的,那么就要寫多個同樣的邏輯嗎?例如設置界面,游戲中的設置界面基本上功能都是一樣,如果每個UI中都做一遍,是多么愚蠢的辦法?在UI的代碼設計中,一般來說,單獨的功能不會在其它的地方用到,如GameOver,就直接寫在UI里,而如果是通用功能,則最好的做法是做一個通用的單例類或者工廠類在需要的時候將它們初始化,在多個UI中復用邏輯規則。我們就直接用聲音設置來實現上面的核心思想,通用類的UI制作,在開始之前,先把SoundMenager類准備好,顧名思義聲音管理者,來管理這些聲音。
聲音管理和播放
請確保你的assets里有sound目錄,里面的mp3都在,如buttonclick.mp3
直接實現下面的類:
class SoundMenager { private static shared: SoundMenager; public static Shared(): SoundMenager { if(SoundMenager.shared == null) SoundMenager.shared = new SoundMenager(); return SoundMenager.shared; } private _click: egret.Sound;//點擊聲音 private _word: egret.Sound;//點擊字塊的聲音 private _right: egret.Sound;//如果勝利 private _wrong: egret.Sound;//如果錯誤 private _bgm: egret.Sound;//背景音樂 private _bgm_channel: egret.SoundChannel;//保存用來靜音用 public constructor() { this._click = new egret.Sound(); this._click.load("resource/assets/sound/buttonclick.mp3"); this._bgm = new egret.Sound(); this._bgm.load("resource/assets/sound/Music.mp3"); this._right = new egret.Sound(); this._right.load("resource/assets/sound/right.mp3"); this._wrong = new egret.Sound(); this._wrong.load("resource/assets/sound/wrong.mp3"); this._word = new egret.Sound(); this._word.load("resource/assets/sound/type_word.mp3"); } public PlayBGM() { if(this.IsMusic) { this._bgm_channel = this._bgm.play(0,0); } } public StopBGM() { if(this._bgm_channel != null) { this._bgm_channel.stop(); } } public PlayClick() { if(this.IsSound) { this._click.play(0,1); } } public PlayRight() { if(this.IsSound) { this._right.play(0,1); } } public PlayWrong() { if(this.IsSound) { this._wrong.play(0,1); } } public PlayWord() { if(this.IsSound) { this._word.play(0,1); } } //音樂是否播放,保存設置 public set IsMusic(value) { if(!value) { egret.localStorage.setItem("ismusic","0"); this.StopBGM(); } else { egret.localStorage.setItem("ismusic","1"); this.PlayBGM(); } } public get IsMusic(): boolean { var b = egret.localStorage.getItem("ismusic"); if(b == null || b == "") { return true; } else { return b == "1"; } } //聲效是否播放,保存設置 public set IsSound(value) { if(value) { egret.localStorage.setItem("isSound","1"); } else { egret.localStorage.setItem("isSound","0"); } } public get IsSound(): boolean { var b = egret.localStorage.getItem("isSound"); if(b == null || b == "") { return true; } else { return b == "1"; } } }
我想這個代碼就不做太多的解釋了,它是用了異步load聲音文件,通過幾個Play方法來播放,實現了兩個屬性:IsSound和IsMusic,來控制是否靜音和播放音樂,這個類里_bgm_channel保存了bgm的聲音通道,當靜音的時候就會stop聲音,確保UI交互是正確的。
將它們加到Begin.ts里
運行一下,你會發現出現了這個錯誤:
大概的意思是和它所寫的不太一樣,如果你有耐心跟蹤斷點會有驚喜,在這里就不賣關子,出這個錯誤的原因是,egret.Sound.load方法中初始化的一個屬性是null,所以,避免這個問題的方式也很簡單,在LoadingUI的構造函數中調用SoundMenager.Shared()完成預先加載。
public constructor() { super(); //預先加載聲音 SoundMenager.Shared(); this.createView(); }
這個時間差異其實只有幾個毫秒,但是能正確的讓聲音輸出,估計是底層的問題,就不深究其原因了,反正能解決就行,對於異步加載還是預先加載,這屬於個人習慣問題,但聲音在現在的手機游戲中並不是主要的組成部分,每次打開在加載游戲聲音上耗費大量時間得不償失,不如先玩起來再播放體驗來的好。
在你想要加的地方都加上聲音,這里就不一一列舉,只需要提一下關於錯誤聲音,需要在SceneGame類里做一個判斷:
它的意思很簡單,如果拼寫的檢查字段是4個漢字,就會提示錯誤的聲音,勝利的聲音之前就處理過了,所以邏輯上沒有問題。
好了,打開游戲,測試聲音,感覺一下哈
通用的設置界面
設置界面都是通用的,所以我們可以使用一個類配一個皮膚來實現它,建立一個名為GameSettingSkin的exml皮膚文件然后設計設置界面,由於沒有准備相關的素材,只得就地取材,將MoneyBG_png這個圖片做一下改造,變成九宮格的圖形,這樣就可以自由拉伸,當設置界面的底板了:
同樣,利用YesBtn_jpg和其它的素材組成游戲設置界面:
最終的exml的文件應該是這樣的:
<?xml version='1.0' encoding='utf-8'?> <e:Skin class="GameSettingSkin" width="720" height="1136" xmlns:e="http://ns.egret.com/eui" xmlns:w="http://ns.egret.com/wing"> <e:Rect right="0" top="0" bottom="0" left="0" fillAlpha="0.6" locked="true"/> <e:Image source="MoneyBG_png" scale9Grid="17,8,196,51" width="400" height="255" horizontalCenter="0" verticalCenter="0.5"/> <e:Button id="btn_agree" y="623" horizontalCenter="0.5"> <e:skinName> <e:Skin states="up,down,disabled"> <e:Image width="100%" height="100%" source="YesBtn_jpg" source.down="YesBtn1_jpg"/> <e:Label id="labelDisplay" horizontalCenter="0" verticalCenter="0"/> </e:Skin> </e:skinName> </e:Button> <e:Group width="102" height="94" x="238" y="512"> <e:Button id="btn_music" y="0" x="0"> <e:skinName> <e:Skin states="up,down,disabled"> <e:Image width="100%" height="100%" source="btn_music_png" source.down="btn_music_down_png"/> <e:Label id="labelDisplay" horizontalCenter="0" verticalCenter="0"/> </e:Skin> </e:skinName> </e:Button> <e:Image id="img_music_disable" x="6" y="2" source="btn_disable_png" touchEnabled="false"/> </e:Group> <e:Group x="403" y="512" width="102" height="94"> <e:Button id="btn_sound" y="0" x="0"> <e:skinName> <e:Skin states="up,down,disabled"> <e:Image width="100%" height="100%" source="btn_sound_png" source.down="btn_sound_down_png"/> <e:Label id="labelDisplay" horizontalCenter="0" verticalCenter="0"/> </e:Skin> </e:skinName> </e:Button> <e:Image id="img_sound_disable" y="2" x="6" source="btn_disable_png" touchEnabled="false"/> </e:Group> <e:Label text="設置" y="466" horizontalCenter="0"/> </e:Skin>
那么配以.ts類來實現UI的邏輯:
//使用一個全局通用的設置界面 class GameSetting extends eui.Component { private static shared: GameSetting; public static Shared(): GameSetting { if(GameSetting.shared == null) GameSetting.shared = new GameSetting(); return GameSetting.shared; } private btn_agree:eui.Button; //同意按鈕,相當於直接關閉界面 private img_music_disable: eui.Image;//音樂靜音顯示 private img_sound_disable: eui.Image;//聲音靜音顯示 private btn_sound: eui.Button; //聲音按鈕 private btn_music: eui.Button; //音樂按鈕 public constructor() { super(); this.skinName = "src/Game/GameSettingSkin.exml"; this.btn_agree.addEventListener(egret.TouchEvent.TOUCH_TAP,this.click_agree,this); this.btn_sound.addEventListener(egret.TouchEvent.TOUCH_TAP,this.click_sound,this); this.btn_music.addEventListener(egret.TouchEvent.TOUCH_TAP,this.click_music,this); //通過聲音管理類來處理界面顯示 this.update_buttonstate(); } private click_agree(){ SoundMenager.Shared().PlayClick(); this.parent.removeChild(this); } private click_sound(){ SoundMenager.Shared().PlayClick(); SoundMenager.Shared().IsSound = !SoundMenager.Shared().IsSound; this.update_buttonstate(); } private click_music(){ SoundMenager.Shared().PlayClick(); SoundMenager.Shared().IsMusic = !SoundMenager.Shared().IsMusic; this.update_buttonstate(); } private update_buttonstate(){ this.img_music_disable.visible = !SoundMenager.Shared().IsMusic; this.img_sound_disable.visible = !SoundMenager.Shared().IsSound; } }
這個代碼我就不做太多的講解,就是對於一些元素的控制,eui大法真好啊。
下面在SceneBegin、SceneGame、SceneLevels的皮膚文件中分別加入btn_setting,同樣,使用MoneyBG_png來做通用的底版,省事就行了。
在各個類中添加對它的定義和處理事件
//第9章新加設置按鈕 private btn_setting: ui.Button; //第9章設置事件 this.btn_setting.addEventListener(egret.TouchEvent.TOUCH_TAP,this.onclick_setting,this);
實現onclick_setting方法:
private onclick_setting() { SoundMenager.Shared().PlayClick(); this.addChild(GameSetting.Shared()); }
當點擊設置的時候,直接將設置界面的單例UI給添加到本界面中,對應的在GameSetting類中也有this.parent.removeChild(this);將自己移除的方法,所有的設置界面都是一個,結構看起來清晰了很多。
一種比較笨的方式就是挨個添加,還有一種方式是創造一個設置按鈕的獨立按鈕,將它的邏輯寫入自己內部,雖然是一個好方法,可是使用起來比較麻煩,當沒有大量的獨立處理需求時(如ICON),還是用挨個添加比較簡單一些。
本篇已經完結,使用聲音管理類來加載和播放聲音,用單例來實現通用的界面UI的邏輯處理,在多個場景中重復使用。
到此為止這個游戲的完成度已經超過80%,剩下的就是慢慢雕琢以及各種功能的添加,有了前面的基礎,后面的擴展開發已經變的非常easy。此篇拋磚引玉之作能夠幫助新學egret的朋友快速上手,其中的一些做法也許不是最好的,問題也一樣很多,歡迎批評指正。
本篇項目源碼:ChengyuTiaozhan6.zip(由於博客園的文件大小限制,resource資源方面請到第二篇的后面下載)