经过前面的制作,使用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. 从零开始一个iOS项目(一)——基本准备以及cocopods的安装

    项目开发分为:独立开发和迭代开发,我主要讲独立开发,若读者是迭代开发,希望公司的代码一定要规范,便能省去许多麻烦,也能从中获益,闲时也可接些外包,赚一些外快,也是美差,然而独立开发最能锻炼人的能力. ...

  2. MAC OS 系统使用心得

    1.Windows快捷键在 mac os 里怎么调用 今天用teamviewer链接我在公司的电脑.我想调试程序,我程序默认F5是启动调试,但在mac os里,F5是调节屏幕亮度的. 这时候遇到快捷键 ...

  3. 回到过去美好的时光——源代码版本管理Always Use source code Control

    Don't Repeat Yourself Don't Repeat Yourself,这是程序员修炼之道中的经典名言.源代码版本管理对程序员来说是非常重要的工作.因为它让你能够回到过去,而不用做重复 ...

  4. mysql下优化表和修复表命令使用说明(REPAIR TABLE和OPTIMIZE TABLE)

    随着mysql的长期使用,可以修复表来优化,优化时减少磁盘占用空间.方便备份. REPAIR TABLE `table_name` 修复表 OPTIMIZE TABLE `table_name` 优化 ...

  5. Mongodb Manual阅读笔记:CH5 安全性

    5 安全性 Mongodb Manual阅读笔记:CH2 Mongodb CRUD 操作Mongodb Manual阅读笔记:CH3 数据模型(Data Models)Mongodb Manual阅读 ...

  6. The Accidental DBA

    The Accidental DBA (Day 1 of 30): Hardware Selection: CPU and Memory Considerations 本文大意:      全篇主要讲 ...

  7. [20130704] Intra-Query Parallel Thread Deadlocks

    今天碰到了  Intra-Query Parallel Thread Deadlocks 简单的说就是并发查询把自己给锁住了. 原理: 在并发查询运行是,有一个生产者和一个消费者,生产者等待消费者产生 ...

  8. Maven详细介绍

    Maven 目录 1 什么是Maven? 2 Maven 的好处 3 获取和安装 3.1 获取 3.2 安装 3.2.1 环境变量的配置 4 设置本地仓库 5 创建简单的Maven实例 5.1 使用骨 ...

  9. 总结eclipse中安装maven插件

    当自己越来越多的接触到开源项目时,发现大多数的开源项目都是用maven来够建的.并且在开发应用时,也越来越意识到maven的确会解决很多问题,如果你要了解maven,可以参考:Maven入门指南(一) ...

  10. 烂泥:CentOS6.5挂载windows共享文件夹

    本文由秀依林枫提供友情赞助,首发于烂泥行天下. 由于工作需要,需要把本机的文件夹共享出去,然后让CentOS服务器临时使用下. 服务器使用的是CentOS系统,而本机使用的win7系统.考虑到是临时使 ...