微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

使用cocos2d-js制作游戏新手引导-源码篇(三)

上一篇已经详解了关于定位器的实现细节,本篇主要是讲解实现引导的组成模块及整个引导流程,并给出整个引导的源码及演示代码

在线演示:http://www.ixuexie.com/Guide/index.html

整个引导框架分为以下几个部分:
- 引导配置
- 定位器
- 引导任务处理器

引导配置

请下看图:

设计UI引导步骤:

  1. 关闭第一盏灯 控件名 “_fire1”
  2. 关闭第二盏灯 控件名 “_fire2”
  3. 点亮第一盏灯 控件名 “_fire1”
  4. 点亮第二盏灯 控件名 “_fire2”
  5. 点击Home图标 控件名 “_btnHome”
  6. 点击Task图标 控件名 “_btnTask”

UI操作的设计已经确定,控件名字对于引导至关重要,我们首先要把这些准备工作做好。
这里引入任务和步骤的概念:引导是由多个任务组成,我们称为tasks,其中一个任务称为task,一个任务中又包含多个引导步骤称为step。(这里与上一篇中所讲的任务组、任务是一至的,只是换了一个说法而以。在编写代码时,我觉得使用任务与步骤来表达,让代码更清楚)

引导细节需求:

  1. 我们这里把6个步骤分成两个任务:1~4为任务1,5~6为任务2
  2. 当从步骤1~2中断后,重新游戏,需要从步骤1开始。
  3. 当任务1步骤2完成,整个任务1就算完成了,此时重新游戏,直接从任务2开始。因此我们需要在步骤2后添加一个步骤为保存进度

对应的引导配置:

var guideConfig = {
    tasks: {
        1: [
            {
                log: "关闭第一盏灯",command: sz.GuideCommand.GC_FINGER_HINT,locator:"_fire1"
            },{
                log: "关闭第二盏灯",locator:"_fire2"
            },{
                log: "保存进度",command: sz.GuideCommand.GC_SAVE_PROGRESS
            },{
                log: "点亮第一盏灯",{
                log: "点亮第二盏灯",],2: [
            {
                log:'点击home',locator:"_btnHome"
            }
        ],3: [
            {
                log:'点击task',locator:"_btnTask"
            }
        ]
    },locateNodeDurationTime: 0.1,fingerImage: 'res/finger.png'
};

//引导命令
sz.GuideCommand = {
    GC_SET_PROPERTY: 1,//设置属性
    GC_FINGER_HINT: 2,//手型提示
    GC_SAVE_PROGRESS: 3 //保存进度
};

目前sz.GuideCommand只内置了三个命令:设置属性、手型提示、保存进度

引导任务处理器

从上面的任务配置来看,任务步骤的处理是一个顺序执行过程,但UI操作却是一个异步过程,只有UI操作完成后才能进行下一个任务。 对于javascript来说,处理异步事件、回调函数是最拿手的,但为了代码可读性、维护性比较好,我们这里使用了大名顶顶的async库来处理任务和步骤,这样可以解决回调函数的深度。

async

这里只用到async两个函数

async.eachSeries(tasks,iterator,callback)

我们使用async.eachSeries来遍历我们整个任务数组,iterator为迭代器函数,用于处理每一个任务,callback为任务全部处理完后,我们用来离开引导

async.series({},[callback])

我们使用async.series处理一个步骤对象的几个主要动作:步骤开始、步骤处理、步骤完毕
这里核心的是步骤处理,步骤开始和结束我们可以用来扩展步骤对象,增加灵活性。
比如说,当一个步骤开始前,需要检查金币、等级这些,不满足的话可以不触发本步骤的进行。下一篇我会给大家做相关代码的演示。

处理所有任务

//获得任务数组
 var tasks = [...]
 //使用eachSeries函数,遍历所有任务对象
 async.eachSeries(tasks,function(task,cb) {
     //遍历任务中的步骤对象,stepHandle为步骤处理函数
     async.eachSeries(task,stepHandle,function() {
         //当一个任务中的所有步骤完成为,自动保存进度
         self._guideLayer.save(true,cb);
     });
 },function() {
     //当所有任务都完成后,退出引导
     self._exitGuide();
 });

//步骤处理
var stepHandle = function(step,callback) {
     async.series({
         //步骤开始
         stepBegin: function(cb) {
             self._guideLayer._setLocateNode(null);
             if (step.onEnter) {
                 step.onEnter.call(this._guideLayer,cb);
             } else {
                 cb(); //执行cb函数触发下面的stepProcess函数
             }
         },//步骤处理
         stepProcess: function(cb) {
             if (step.delayTime) {
                 self._guideLayer.scheduleOnce(function() {
                     self._processstep(step,cb);
                 })
             } else {
                 //执行具体的步骤处理操作,传入setp对象,当步骤完成后自动执行cb函数,进入下面setpEnd函数
                 self._processstep(step,cb);  
             }
         },//步骤完毕
         stepEnd: function() {
             if (step.onExit) {
                 step.onExit.call(this._guideLayer,callback);
             } else {
                 //callback为stepHandle的参数,执行callback函数本任务全部完成。
                 callback();
             }
         }
     });
 };

任务处理的核心是步骤的处理,步骤的处理最主要是正确执行步骤指令

_processstep: function(step,cb) {
    var self = this;
    //打印步骤日志
    if (step.log) {
        cc.log("guide: <" + step.log + ",step begin >");
    }

    //生成步骤结束函数,统一执行此函数在此打印步骤日志
    var finish = function() {
        if (step.log) {
            cc.log("guide: <" + step.log + ",step finished >");
        }

        if (step.delayTime) {
            setTimeout(cb,step.delayTime * 1000);
        } else {
            cb();
        }
    };

    //处理步骤指令
    switch (step.command) {
        //设置属性
        case sz.GuideCommand.GC_SET_PROPERTY:    
            this._guideLayer.locateNode(step.locator,function(node) {
                var property = step.args[0];
                var args = step.args.slice(1);
                node[property].apply(node,args);
            });
            break;
        //手型提示
        case sz.GuideCommand.GC_FINGER_HINT:
            this._guideLayer.locateNode(step.locator,function(node) {
                self._guideLayer.fingerToNode(node,finish,true);
                if (step.onLocateNode) {
                    step.onLocateNode.call(this._guideLayer,node);
                }
            });
            break;
        //保存进度
        case sz.GuideCommand.GC_SAVE_PROGRESS:
            this._guideLayer.save(false,finish);
            break;
        default:
            cc.log("guide command is not define");
    }
}

关于定位区UI事件的检测

上一篇中讲到关于定位区UI事件的检测是通过观察者模式来实现,我在从原有项目中剥离引导框架时想到过大多数人可能会有自己的观察者的具体实现。而且以观察者来实现UI事件的检测会让代码变的依赖性太强。所以在sz.Guide中没有使用观察者模式来检测事件。
这里UI事件的检测主要使用了javascript语言的动态能力来拦截事件函数来实现,大概思路:

  1. 保存原有事件函数变量
  2. 使用新函数覆盖原有事件函数
  3. 在新函数中先执行原有事件函数
  4. 在新函数中执行自己逻辑

sz.Guide在事件管理上使用了sz.UILoader

//_onWidgetEvent为UILoader上的事件勾子函数,所有UI事件都会触发它
 var widgetEvent = sz.uiloader._onWidgetEvent;
 sz.uiloader._onWidgetEvent = function(sender,type) {
     //先执行原有事件函数
     if (widgetEvent) {
         widgetEvent(sender,type);
     }
     //执行本类中的事件函数
     self._onWidgetEvent(sender,type);
 }

_onWidgetEvent: function(sender,type) {
        var locateNode = this._locateNode;
        //检查定位节点是否为当前事件节点
        if (locateNode && (sender === this._locateNode || sender.getName() === locateNode.getName() )) {
            this._setLocateNode(null);
            //执行步骤回调函数,完成当前步骤
            this._setpCallback();
        }
    },

小结

sz.Guide目前实现了一个简单、基本的引导功能,还有很多不全面的地方还需要继续增加功能和改进。但总体思路是清晰的,在编写引导任务也是非常容易,而且不破坏现有代码的逻辑结构。完全可以在你的游戏全部完成后,轻松将引导置入游戏中去。

有兴趣的朋友可以试着修改下,guideConfig.js中的配置,添加更多的引导步骤,试试引导用记依次点击下面的功能按钮

他们的控件名字分别叫做:

_btnHome、_btnTask、_btnPlunder、_btnBag、_btnFriends、_btnSetting

源码地址:https://github.com/ShawnZhang2015/Guide
在线演示:http://www.ixuexie.com/Guide/index.html

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐