经过前面的制作,使用Egret的Wing很快完成了开始界面和选关卡界面,下面通常来说就是游戏界面,但此时界面切换和关卡数据还没有准备好,这次讲解界面的切换和关卡数据的解析。前面多次修改了Main.ts文件中startCreateScene的方法,这个方法就是当游戏的前置Loading全部完成之后,执行的开始方法,Main本身就是UI容器,所以直接Add进去做好的UI逻辑即可。

界面切换

这里涉及到一个界面切换的代码设计问题,以开始界面为例,当“开始游戏”按钮点击之后,应将开始界面移除,进入到选关卡界面,写法看起来简单,但是可能我们需要获取Main并将其移出的同时还要new一个新的选关界面进来,此时很多人的写法可能将Main保存,方便其他类访问,或是在Main里面写上一大堆的UI处理逻辑,这样看来代码就不怎么优雅了,而且,为了性能和安全问题,通常还要手动移出掉内部使用的EventListener,比较周全的做法是,添加RemoveStage监听,然后移除自己的同时,将子对象给移除,这就是为什么Main中LoadingUI处理时,出现了大量的addEventListener和removeEventListener的原因。

在本项目中,使用单例模式来保证代码的简洁性,让它看起来没有那么多调来调去的方法,单例会一直保证一个实例在内存中,如果这个对象会被频繁使用,而且游戏中一只有一个的话,就比较适用,比如说游戏中的界面类,以本项目来说,总计也就是3-4个界面,而且总是它们跳来跳去,所以,在这里使用单例控制,为SceneBegin和SceneLevels添加单例,以下是SceneBegin类的代码。

class SceneBegin extends eui.Component {
//单例
private static shared: SceneBegin;
public static Shared() {
if(SceneBegin.shared == null) {
SceneBegin.shared = new SceneBegin();
}
return SceneBegin.shared;
}
private btn_begin:eui.Button;
public constructor() {
super();
this.skinName = "src/Game/SceneBeginSkin.exml";
this.btn_begin.addEventListener(egret.TouchEvent.TOUCH_TAP,this.onclick_begin,this);
}
private onclick_begin(){
//console.log("game begin!");
this.parent.addChild(SceneLevels.Shared());
this.parent.removeChild(this);
}
}

上面的onclick_begin()使用了一个常用的父对象便捷操作法,直接调用父对象的addChild把另外一个界面添加进来,同时把自己移除掉,比起获取全局对象处理跳转是不是简单清晰了呢?想进阶的童鞋,可以研究一下更深入的玩法,同样的SceneLevels就不写全了,直接在onclick_back()使用上面同样的技巧:

//单例
private static shared: SceneLevels;
public static Shared() {
if(SceneLevels.shared == null) {
SceneLevels.shared = new SceneLevels();
}
return SceneLevels.shared;
}
private onclick_back() {
this.parent.addChild(SceneBegin.Shared());
this.parent.removeChild(this);
}

现在打开Main.ts把startCreateScene修改一下:

    protected startCreateScene(): void {
this.addChild(SceneBegin.Shared());
}

现在运行起来看看跳转效果吧,其他的类似的界面同理可以用这种方法,

关卡数据处理

下面就要考虑游戏数据问题,游戏的数据已经做好了一个json文件:questions.json

将这个文件添加到资源中,并保证提前读取完毕,在给出的项目代码中,是放在了项目根目录下,拖进去的时候会自动复制到assets目录下面一份。

当然,你也可以异步来处理,异步会带来更多逻辑复杂性,这里仅仅是简单的教程,以后再说更复杂的,现在创建一个LevelDataManager类,用来管理关卡数据:

//每个问题(关卡)的数据结构
class LevelDataItem{
public answer:string;
public img:string;
public word:string;
public tip:string;
public content:string;
}
//关卡数据管理器
class LevelDataManager {
//单例
private static shared:LevelDataManager;
public static Shared(){
if(LevelDataManager.shared == null){
LevelDataManager.shared = new LevelDataManager();
}
return LevelDataManager.shared;
}
//一个关卡的保存数据组
private items:LevelDataItem[] = []; public constructor() {
//使用RES读取和构建JSON数据,JSON数据可以直接解析到目标结构
this.items = RES.getRes("questions_json");
}
//通过关卡号获得一个关的数据
public GetLevel(level:number):LevelDataItem{
if(level < 0) level = 0;
if(level >= this.items.length) level = this.items.length - 1;
return this.items[level];
}
//获得当前的游戏最远进度
public get Milestone():number{
var milestone = egret.localStorage.getItem("CYDTZ_Milestone");
//如果没有数据,那么默认值就是第一关
if(milestone == "" || milestone == null){
milestone = "1";
}
return parseInt(milestone);
}
//设置当前的游戏最远进度
public set Milestone(value:number){
egret.localStorage.setItem("CYDTZ_Milestone",value.toString());
}
}

同样也使用了单例方式,我们利用这种方式来保存游戏的关卡数据,并能随时方便的访问它,而且在构造的时候就把数据给处理好了,使用egret.localStorage来管理游戏的进度,你会发现在最顶部写了一个LevelDataItem类,这个类用来解析每个关卡的具体数据,可以通过json来对应其中各种属性。

组合到选关界面中

那么我们现在有了游戏关卡数据,要组合到SceneLevels类中,做数据的访问,以方便在进入到选关界面的时候,能够正确的显示游戏关卡状态,所以要对SceneLevels.ts做一些修改:

 class SceneLevels extends eui.Component {
//单例
private static shared: SceneLevels;
public static Shared() {
if(SceneLevels.shared == null) {
SceneLevels.shared = new SceneLevels();
}
return SceneLevels.shared;
}
private btn_back: eui.Button;
private group_levels:eui.Group;
private img_arrow: eui.Image;
private sel_level: number = 0;
private LevelIcons:LevelIcon[] = [];
public constructor() {
super();
this.skinName = "src/Game/SceneLevelsSkin.exml";
this.btn_back.addEventListener(egret.TouchEvent.TOUCH_TAP,this.onclick_back,this);
//创建地图选项
var row = 20;
var col = 10;
var spanx = 720 / col; //计算行x间隔
var spany = 1136 / row; //计算列y间隔
var group = new eui.Group();//地图背景
group.width = 720;
group.height = (spany * 400 );//算出最大尺寸
//填充背景
for(var i = 0;i <= (group.height / 1138) ;i++) {
var img = new eui.Image();
img.source = RES.getRes("GameBG2_jpg");
img.y = i * 1138;
img.touchEnabled = false;
this.group_levels.addChildAt(img,0);
}
//以正弦曲线绘制关卡图标的路径
var milestone: number = LevelDataManager.Shared().Milestone;
for(var i = 0; i<400;i++){
var icon = new LevelIcon();
icon.Level = i + 1;
icon.y = spany * i /2;
icon.x = Math.sin(icon.y / 180 * Math.PI) * 200 + group.width / 2;
icon.y += spany * i /2;
icon.y = group.height - icon.y - spany;
group.addChild(icon);
icon.addEventListener(egret.TouchEvent.TOUCH_TAP,this.onclick_level,this);
//依据进度设置关卡显示
icon.enabled = i < milestone;
//保存到一个列表中
this.LevelIcons.push(icon);
}
//开启位图缓存模式
group.cacheAsBitmap = true;
this.group_levels.addChild(group);
//卷动到最底层
this.group_levels.scrollV = group.height - 1100;
//跟踪箭头
this.img_arrow = new eui.Image();
this.img_arrow.source = RES.getRes("PageDownBtn_png");
this.img_arrow.anchorOffsetX = 124 / 2 - group.getChildAt(0).width / 2;
this.img_arrow.anchorOffsetY = 76;
this.img_arrow.touchEnabled = false;
this.img_arrow.x = group.getChildAt(0).x;
this.img_arrow.y = group.getChildAt(0).y;
group.addChild(this.img_arrow); }
private onclick_back() {
this.parent.addChild(SceneBegin.Shared());
this.parent.removeChild(this);
}
private onclick_level(e:egret.TouchEvent){
var icon = <LevelIcon>e.currentTarget;
if(this.sel_level != icon.Level){
this.img_arrow.x = icon.x;
this.img_arrow.y = icon.y;
this.sel_level = icon.Level;
}else{
//进入并开始游戏
}
}
//打开指定的关卡,如果大于最远关卡,则保存数据也跟着调整
public OpenLevel(level:number){
var icon = this.LevelIcons[level - 1];
icon.enabled = true;
if(level > LevelDataManager.Shared().Milestone){
LevelDataManager.Shared().Milestone = level;
//同时将选定标记置于其上
this.img_arrow.x = icon.x;
this.img_arrow.y = icon.y;
this.sel_level = icon.Level;
}
}
}

SceneLevels.ts

对于这里简单讲一下,参看第47行,在创建图标的时候,我们将通过判断当前的进度来决定按钮的开启状态,71行的onclick_level()方法增加了一个当前选择sel_level变量来帮助完成选定后进入的关卡才开始游戏,最后,OpenLevel是为了将来做准备,当关卡完成后就会开启对应关卡icon。

数据创建优化

现在可以试试运行一下看看效果,但仍然不完美,因为当在开始界面点开始的时候,就会产生一次卡顿,是因为解析JSON数据源造成的,要知道有400多道题目,光json文件就有100多KB,卡顿是必然发生的事情,优化的方法有以下几种:

1、展现层面,在Loading的时候进行处理,如在资源读取完成之后,调用一下LevelDataManager.Shared(),这样就得要求预读

2、将数据源拆分,分开加载处理,50个题目一个文件,或者1道题1个文件,我的连连看数据就是这样处理

3、将数据源变成源码的一部分,如.ts或.js,这种方式读取处理效率倒是提升了,但是维护起来比较麻烦

本篇项目源码:ChengyuTiaozhan2.zip(由于博客园的文件大小限制,resource资源方面请到第二篇的后面下载

本篇主要学习使用了单例设计模式、JSON数据使用、egret.localStorage储存游戏进度数据。

Html5 Egret游戏开发 成语大挑战(五)界面切换和数据处理的更多相关文章

  1. Html5 Egret游戏开发 成语大挑战(一)开篇

    最近接触了Egret白鹭引擎,感觉非常好用,提供了各种各样的开发工具让开发者和设计者更加便捷,并且基于typescript语言开发省去了很多学习成本,对于我们这种掉微软坑许久的童鞋来说,确实很有吸引力 ...

  2. Html5 Egret游戏开发 成语大挑战(二)干净的eui项目和资源准备

    现在我们使用egret来起步开发一个名叫<成语大挑战>的小游戏,关于egret的开发环境就不在这里啰嗦了,直接去官方下载安装就可,egret是我见过开发环境部署最简单的解决方案,这个系列教 ...

  3. Html5 Egret游戏开发 成语大挑战(八)一般性二级页面处理

    在游戏中,我们一般会有各种各样的二级页面,比如游戏暂停界面或者游戏结束界面,这些界面组成了对玩家交互主要手段,在游戏开发中,对于这些界面的coding组织是非常有学问的,如果倒退到十年前,游戏开发的老 ...

  4. Html5 Egret游戏开发 成语大挑战(六)游戏界面构建和设计

    本篇将主要讲解游戏界面的构建和设计,会应用到egret.eui的自定义组件,可以很直观的构建一个游戏整体,这里我们仍然只需要使用EgretWing就可以达到目的,本篇可能是篇幅最少的一个,但是涉及自定 ...

  5. Html5 Egret游戏开发 成语大挑战(九)设置界面和声音管理

    在上一篇中,简单的使用界面元素快速实现了一个游戏中的二级页面,这种直接在游戏页面上做UI的做法并不太好,原因是,UI会让游戏的压力变大,即使它是隐蔽的,如果同样的功能在其它的地方也是一样的,那么就要写 ...

  6. Html5 Egret游戏开发 成语大挑战(七)游戏逻辑和数据处理

    本篇在前面的基础上,将进行逻辑的编码开发让游戏能够正式的玩起来,这里没有注重太多的体验细节,而是直接实现游戏的规则逻辑,将分成两个部分说明:数据处理和游戏逻辑. 初始化游戏数据 在前面的第五篇中,我们 ...

  7. Html5 Egret游戏开发 成语大挑战(四)选关界面

    通过前面的开始界面基本上了解了eui的使用方法,可以简单快速的制作一个UI界面,本篇使用第二界面选关界面展示更为难一点的代码控制,来展现关卡地图的内容,请确保素材和资源完整,可以在前面的教程中找到下载 ...

  8. Html5 Egret游戏开发 成语大挑战(三)开始界面

    本篇需要在前面的素材准备完毕,才可以开始,使用egret的eui结合代码编辑,快速完成基本的界面搭建,这里写的可能比较细,目的是减少大家对于其中一些操作疑问,我去掉了很多无用的步骤,以最精简的流程来完 ...

  9. Phaser是一款专门用于桌面及移动HTML5 2D游戏开发的开源免费框架

    Phaser是一款专门用于桌面及移动HTML5 2D游戏开发的开源免费框架,提供JavaScript和TypeScript双重支持,内置游戏对象的物理属性,采用Pixi.js引擎以加快Canvas和W ...

随机推荐

  1. Mac终端常见命令

    Mac的使用 1.Finder资源管理器 Mac的硬盘不分区,只有一个根目录 2.系统偏好设置-控制面板 快捷键: 1.win下面的快捷键由ctrl换成common开头: commond+space ...

  2. OS开发UI篇—使用UItableview完成一个简单的QQ好友列表

    本文转自:http://www.cnblogs.com/wendingding/p/3763330.html 一.项目结构和plist文件 二.实现代码 1.说明: 主控制器直接继承UITableVi ...

  3. eclipse如何优化构建的速度(Building)

    小Alan在平时的开发中很喜欢做一件事,那就是盯着eclipse的状态栏,看着Build Workspace的进度条一点一点慢慢的走,小Alan坐在电脑面前默默不想说话,就是心中总是有十万匹马在奔腾, ...

  4. 在网页中显示CHM (c# csharp .net asp.net winform)

    CHM即“已编译的帮助文件”,主要由.hhc(目录文件)..hhk(索引文件)以及相应的帮助主题文件(.html,.htm)这些内容编译而成. 方法对比 在网页中显示CHM内容,大致有以下几种办法: ...

  5. 坑!坑!坑!防不胜防的unsigned int的运算

    我很早之前就知道,unsigned int与int运算的时候,int会被转化为unsigned int来进行运算.一直觉得定这条规则的人是极度反人类的,虽说unsigned int可以表示更大的正值, ...

  6. dubbox 编译 和 测试

    因为 dubbox 并没有发布到maven中央仓库仓库中,所以需要我们自己到官网下载,自己编译,install 到本地. 1. 首先安装git客户端工具 TortoiseGit, 然后使用它将 dub ...

  7. oracle学习笔记系列------oracle 基本操作之基本函数的用法

    --创建一个accout账户表 CREATE TABLE account( id ) NOT NULL, recommender_id ), login_name ) NOT NULL, login_ ...

  8. android RelativeLayout 动态设置高度

    定义: private RelativeLayout mrlay; 调高度: mrlay = (RelativeLayout) findViewById(R.id.rlay_1); android.v ...

  9. linux 接口地址全部清除才清理从此接口发出的下一跳路由

    接口地址全部清除才清理从此接口发出的下一跳路由 如: eth7配置两个地址 eth7: 192.168.1.1 10.1.1.1 添加一条路由: route add -net 2.2.2.0/24 g ...

  10. 通过vmstat命令判断服务器瓶颈

    linux命令vmstat介绍 vmstat介绍 通过STATSPACK收集服务器信息,主要通过收集VMSTAT的信息来展现服务器状况.VMSTAT工具是最常见的UNIX监控工具,可以展现给定时 间间 ...