Nowpaper 十五英寸的世界

Rich Games Developer

  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

经过前面的制作,使用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做一些修改:

 1 class SceneLevels extends eui.Component {
 2     //单例
 3     private static shared: SceneLevels;
 4     public static Shared() {
 5         if(SceneLevels.shared == null) {
 6             SceneLevels.shared = new SceneLevels();
 7         }
 8         return SceneLevels.shared;
 9     }
10     private btn_back: eui.Button;
11     private group_levels:eui.Group;
12     private img_arrow: eui.Image;
13     private sel_level: number = 0;
14     private LevelIcons:LevelIcon[] = [];
15     public constructor() {
16         super();
17         this.skinName = "src/Game/SceneLevelsSkin.exml";
18         this.btn_back.addEventListener(egret.TouchEvent.TOUCH_TAP,this.onclick_back,this);
19         //创建地图选项
20         var row = 20;
21         var col = 10;
22         var spanx = 720 / col;      //计算行x间隔
23         var spany = 1136 / row;     //计算列y间隔
24         var group = new eui.Group();//地图背景
25         group.width = 720;
26         group.height = (spany * 400 );//算出最大尺寸
27         //填充背景
28         for(var i = 0;i <= (group.height / 1138) ;i++) {
29             var img = new eui.Image();
30             img.source = RES.getRes("GameBG2_jpg");
31             img.y = i * 1138;
32             img.touchEnabled = false;
33             this.group_levels.addChildAt(img,0);
34         }
35         //以正弦曲线绘制关卡图标的路径
36         var milestone: number = LevelDataManager.Shared().Milestone;
37         for(var i = 0; i<400;i++){
38             var icon = new LevelIcon();
39             icon.Level = i + 1;
40             icon.y = spany * i /2;
41             icon.x = Math.sin(icon.y / 180 * Math.PI) * 200 + group.width / 2;
42             icon.y += spany * i /2;
43             icon.y = group.height - icon.y - spany;
44             group.addChild(icon);
45             icon.addEventListener(egret.TouchEvent.TOUCH_TAP,this.onclick_level,this);
46             //依据进度设置关卡显示
47             icon.enabled = i < milestone;
48             //保存到一个列表中
49             this.LevelIcons.push(icon);
50         }
51         //开启位图缓存模式
52         group.cacheAsBitmap = true;
53         this.group_levels.addChild(group);
54         //卷动到最底层
55         this.group_levels.scrollV = group.height - 1100;
56         //跟踪箭头
57         this.img_arrow = new eui.Image();
58         this.img_arrow.source = RES.getRes("PageDownBtn_png");
59         this.img_arrow.anchorOffsetX = 124 / 2 - group.getChildAt(0).width / 2;
60         this.img_arrow.anchorOffsetY = 76;
61         this.img_arrow.touchEnabled = false;
62         this.img_arrow.x = group.getChildAt(0).x;
63         this.img_arrow.y = group.getChildAt(0).y;
64         group.addChild(this.img_arrow);
65         
66     }
67     private onclick_back() {
68         this.parent.addChild(SceneBegin.Shared());
69         this.parent.removeChild(this);
70     }
71     private onclick_level(e:egret.TouchEvent){
72         var icon = <LevelIcon>e.currentTarget;
73         if(this.sel_level != icon.Level){
74             this.img_arrow.x = icon.x;
75             this.img_arrow.y = icon.y;
76             this.sel_level = icon.Level;
77         }else{
78             //进入并开始游戏
79         }
80     }
81     //打开指定的关卡,如果大于最远关卡,则保存数据也跟着调整
82     public OpenLevel(level:number){
83         var icon = this.LevelIcons[level - 1];
84         icon.enabled = true;
85         if(level > LevelDataManager.Shared().Milestone){
86             LevelDataManager.Shared().Milestone = level;
87             //同时将选定标记置于其上
88             this.img_arrow.x = icon.x;
89             this.img_arrow.y = icon.y;
90             this.sel_level = icon.Level;
91         }
92     }
93 }
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储存游戏进度数据。

posted on 2016-01-26 10:00  nowpaper  阅读(9822)  评论(2编辑  收藏  举报