今天分享一下游戏入口类的编写。
首先游戏中默认的入口为Main.ts,但是因为是游戏入口,我只想在这里看到游戏加载后进入哪个页面,不想看见乱七八糟的游戏加载等事件。所以在这里将游戏入口拆分成两个部分,一个是入口的基类BaseMain,一个是入口类Main。这个地方是借鉴的别人的思想。
游戏入口基类:BaseMain
先说一下基类吧,对于像我这样的不是计算机专业的,也没学过java这种语言的人来说,可能不知道什么是基类。百度的解释是:通过继承机制,可以利用已有的数据类型来定义新的数据类型。在本游戏中,游戏入口基类最要承担游戏资源的加载、egret自带的素材解析、egret自带的主题解析、以及加载完资源后的操作。
资源加载RES
游戏资源加载的作用是在游戏开始时预加载资源
其实egret自动生成的demo对这方面的解释也比较清晰,从每一个方法名来看就可以看出,这个方法的作用。
RES.ResourceEvent.CONFIG_COMPLETE是资源配置文件加载完成;
RES.addEventListener(RES.ResourceEvent.CONFIG_COMPLETE,this.onConfigComplete,this);
RES.ResourceEvent.GROUP_COMPLETE是资源组加载完成,之前调研的createjs也有类似的资源组。这里需要判断加载完什么资源之后要执行的方法。比如我在这里就有当loading资源组加载完成后,加载preload资源组,并且显示loading页面。
RES.addEventListener(RES.ResourceEvent.GROUP_COMPLETE,this.onResourceLoadComplete,this);
RES.ResourceEvent.GROUP_PROGRESS是资源组加载中,这里一般会做一个判断,如果不是loading资源组,就显示loading页面,并且显示所有游戏资源的加载情况(进度条);
RES.addEventListener(RES.ResourceEvent.GROUP_PROGRESS,this.onResourceProgress,this);
RES.ResourceEvent.GROUP_LOAD_ERROR是资源组加载出错;
RES.addEventListener(RES.ResourceEvent.GROUP_LOAD_ERROR,this.onResourceLoadError,this);
RES.ResourceEvent.ITEM_LOAD_ERROR是某个资源加载出错。
RES.addEventListener(RES.ResourceEvent.ITEM_LOAD_ERROR,this.onItemLoadError,this);
RES.loadGroup(groupname)是开始加载某个资源组。
RES.loadGroup("loading");
这里的private是这个类的私有属性方法,外部无法调用。就像
function _alert(){
alert(1);
}
因为js中没有私有、公共这个概念,有时候就会使用” _ “来约定,该属性或方法是私有的,外部不要调用。有addEventListener就有removeEventListener,也就是移除事件监听。因为游戏对性能要求比较高,不用了的监听需要及时移除,省的监听多了造成游戏卡顿。
之前也看到过单独编写资源加载类的,但是自己看了下,没有采用。
其他方法
如打开socket连接、发送第一条请求数据、注入egret定义的素材解析器、添加游戏图层(GameLayerManager)等都会在游戏的基类中编写。
最终贴一下BaseMain的代码吧,因为也是从头开始接触这个,所以注释什么的写的也比较全面。
namespace base {
export class BaseMain extends eui.UILayer {
private loadingView: LoadingView; //加载页面
public constructor() {
super();
this.addEventListener(egret.Event.ADDED_TO_STAGE, this._onAddToStage, this);
}
private _onAddToStage() {
SocketManager.getInstance().connection(); //开始websocket连接
this.stage.registerImplementation('eui.IAssetAdapter',new AssetAdapter()); //注入自定义的素材解析器
var theme = new eui.Theme('resource/default.thm.json', this.stage);
this.addChild(GameLayerManager.gameLayer()); //添加游戏图层
RES.addEventListener(RES.ResourceEvent.CONFIG_COMPLETE,this.onConfigComplete,this); //加载资源配置文件
RES.loadConfig('resource/default.res.json', 'resource/');
}
private onConfigComplete(event: RES.ResourceEvent): void {
RES.removeEventListener(RES.ResourceEvent.CONFIG_COMPLETE,this.onConfigComplete,this); //移除监听事件
//代表什么时候加载资源,完成、预加载等
RES.addEventListener(RES.ResourceEvent.GROUP_COMPLETE,this.onResourceLoadComplete,this); //加载完成
RES.addEventListener(RES.ResourceEvent.GROUP_PROGRESS,this.onResourceProgress,this); //加载进行中
RES.addEventListener(RES.ResourceEvent.GROUP_LOAD_ERROR,this.onResourceLoadError,this); //加载组错误
RES.addEventListener(RES.ResourceEvent.ITEM_LOAD_ERROR,this.onItemLoadError,this); //加载单个文件错误
RES.loadGroup("loading");
}
private onResourceProgress(event: RES.ResourceEvent): void {
if(event.groupName != 'loading') { //如果不是加载loading页面则显示进度条
this.loadingView.createLoadingBar(event.itemsLoaded, event.itemsTotal);
}
}
private onResourceLoadError(event: RES.ResourceEvent): void {
console.error('资源[' + event.groupName + ']:加载失败');
this.onResourceLoadComplete(event);
}
private onItemLoadError(event: RES.ResourceEvent): void {
console.error('Url:' + event.resItem.url + '加载失败');
}
private onResourceLoadComplete(event: RES.ResourceEvent): void {
console.warn('资源[' + event.groupName + ']:加载完成');
this.addChild(ViewManager.getInstance()); //添加场景切换控制
if(event.groupName == 'loading') { //如果加载完成的是loading资源组
this.loadingView = new LoadingView();
GameLayerManager.gameLayer().loadLayer.addChild(this.loadingView);
RES.loadGroup('preload');
}else if(event.groupName == 'preload') { //如果加载完成的是preload资源组
SoundManager.getInstance(); //预先加载声音,放到声音预加载之后再执行
if(GamePlayer.playerInfo.isNew) { //判断是否是新用户
RES.loadGroup('guide');
}else {
let timer = setInterval(() => {
if(SocketManager.getInstance().isConnection) {
clearInterval(timer);
this.start();
GameLayerManager.gameLayer().loadLayer.removeChild(this.loadingView);
}
},100)
}
}else if(event.groupName == 'guide') { //如果是新用户 加载guide资源组
if(SocketManager.getInstance().isConnection && GamePlayer.playerInfo.isNew) {
this.firstStart();
GameLayerManager.gameLayer().loadLayer.removeChild(this.loadingView);
}else if(SocketManager.getInstance().isConnection) {
GameLayerManager.gameLayer().loadLayer.removeChild(this.loadingView);
this.start();
}
}
let timer = setInterval(() => {
if(SocketManager.getInstance().isConnection) {
clearInterval(timer);
SocketManager.getInstance().sendData(GlobalData.Cmd.gameEnter);
}
},100);
}
public start(): void {} //走main.ts
public firstStart(): void {} //走main.ts
}
class AssetAdapter implements eui.IAssetAdapter {
/**
* 解析素材
* @param source 待解析的新素材标识符
* @param compFunc 解析完成回调函数,示例:callBack(content:any,source:string):void;
* @param thisObject callBack的 this 引用
*/
public getAsset(source: string, compFunc:Function, thisObject: any): void {
function onGetRes(data: any): void {
compFunc.call(thisObject, data, source);
}
if (RES.hasRes(source)) {
let data = RES.getRes(source);
if (data) {
onGetRes(data);
}
else {
RES.getResAsync(source, onGetRes, this);
}
}
else {
RES.getResByUrl(source, onGetRes, this, RES.ResourceItem.TYPE_IMAGE);
}
}
}
class ThemeAdapter implements eui.IThemeAdapter {
/**
* 解析主题
* @param url 待解析的主题url
* @param compFunc 解析完成回调函数,示例:compFunc(e:egret.Event):void;
* @param errorFunc 解析失败回调函数,示例:errorFunc():void;
* @param thisObject 回调的this引用
*/
public getTheme(url:string,compFunc:Function,errorFunc:Function,thisObject:any):void {
function onGetRes(e:string):void {
compFunc.call(thisObject, e);
}
function onError(e:RES.ResourceEvent):void {
if(e.resItem.url == url) {
RES.removeEventListener(RES.ResourceEvent.ITEM_LOAD_ERROR, onError, null);
errorFunc.call(thisObject);
}
}
RES.addEventListener(RES.ResourceEvent.ITEM_LOAD_ERROR, onError, null);
RES.getResByUrl(url, onGetRes, this, RES.ResourceItem.TYPE_TEXT);
}
}
}
入口类:Main
游戏入口类继承自BaseMain。
刚刚的游戏入口类中,在preload资源组以及guide资源组加载完成之后,分别调用start跟firstStart方法。但是在基类中,这两个方法没有方法体。那么如何调用呢?
在入口类Main中,有两个公共方法,一个是start方法,一个是firstStart方法。因为这个类是继承自BaseMain的,所以在游戏一开始运行时会先执行基类的方法,等到资源加载完成要调用相应的start跟firstStart方法时,就会先调用当前入口类Main中的方法。如果Main中没有该方法,就会执行基类也就是BaseMain中的方法。
这个没有什么好说的,直接贴一下代码:
class Main extends base.BaseMain {
public constructor() {
super();
}
public firstStart(): void { //新玩家
var changeEvent = new ChangeSceneEvent(ChangeSceneEvent.CHANGE_SCENE_EVENT);
changeEvent.eventType = FirstGuideView.FIRST_GUIDE_VIEW;
changeEvent.obj = this;
ViewManager.getInstance().dispatchEvent(changeEvent);
SoundManager.getInstance().playSoundBg(); //播放背景音乐
}
public start(): void { //老玩家
var changeEvent = new ChangeSceneEvent(ChangeSceneEvent.CHANGE_SCENE_EVENT);
changeEvent.eventType = GameMainView.GAME_MAIN_VIEW;
changeEvent.obj = this;
ViewManager.getInstance().dispatchEvent(changeEvent);
SoundManager.getInstance().playSoundBg(); //播放背景音乐
}
private end(): void {
}
}
下次会分享一下关于声音控制的方法。