zoukankan      html  css  js  c++  java
  • Html5 Egret游戏开发 成语大挑战(五)界面切换和数据处理

    经过前面的制作,使用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储存游戏进度数据。

  • 相关阅读:
    《DSP using MATLAB》Problem 6.17
    一些老物件
    《DSP using MATLAB》Problem 6.16
    《DSP using MATLAB》Problem 6.15
    《DSP using MATLAB》Problem 6.14
    《DSP using MATLAB》Problem 6.13
    《DSP using MATLAB》Problem 6.12
    《DSP using MATLAB》Problem 6.11
    P1414 又是毕业季II
    Trie树
  • 原文地址:https://www.cnblogs.com/nowpaper/p/5154207.html
Copyright © 2011-2022 走看看