在上一篇中,简单的使用界面元素快速实现了一个游戏中的二级页面,这种直接在游戏页面上做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资源方面请到第二篇的后面下载)