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储存游戏进度数据。

  • 相关阅读:
    jsp——java服务页面
    会话管理——cookie和session技术
    [Java]如何为一个自定义类型的List排序。
    silverlight——获取控件相对位置
    C#操作xml文件
    [转载]Unicode中对中文字符的编码
    silverlight——多次异步调用的顺序执行
    内心需要跟我一起长大
    生活总是问题叠着问题,而任务就是一件问题一件问题的解决~
    真的该学点新的东西了。
  • 原文地址:https://www.cnblogs.com/nowpaper/p/5154207.html
Copyright © 2011-2022 走看看