前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >设计一个简易的引导任务框架(2) | 4.23粉丝赠书

设计一个简易的引导任务框架(2) | 4.23粉丝赠书

作者头像
张晓衡
发布2021-05-07 16:49:03
6950
发布2021-05-07 16:49:03
举报
文章被收录于专栏:Creator星球游戏开发社区

参与方式:

  1. 本文点赞留言,必须超过20字,以及你想要的图书名字参与活动
  2. 积赞最多的前3名读者,将会获得赠书,三选一
  3. 活动截止时间:2021-4-24 20:00 明天晚上8点

请获奖读者,通过公众号后台发送截图和您的快递联系方式领取赠书,24小时未来领取的视为放弃。

设计一个简易的引导任务框架

前文导读

上一篇分析了如何定位节点,如何显示节点遮罩,以及节点事件的确认,原理和方法是有了但要将整个逻辑链条串连起来,还需要下一翻功夫。

编写了一个简单的引导任务框架,想仅通过 JSON 配置的方式,完成上述步骤、任务的执行,实现一个配置式、可编程的引导框架,期望的是让非程序人员经过简单的学习,也能实现引导内容的制作,我们先看一个任务配置案例:

代码语言:javascript
复制
module.exports = {
    name: '进入商店',
    debug: true,
    steps: [
        {
            desc: '点击主界面主页按钮',
            command: { cmd: 'locator', args: 'Home > main_btns > btn_home'},
            delayTime: 1,
        },

        {
            desc: '点击主界面设置按钮',
            command: { cmd: 'finger', args: 'Home > main_btns > btn_setting'},
        },
    ]
};

下面是按此配置执行的效果:

引导框架—串联异步引导步骤

前面讲过,一个引导步骤中节点定位函数 godGuide.find() 是通过回调函数异步返回目标节点,用户对目标节点的点击确定也是异步的,因此任务中的每一个 step 都是异步的,为了方便对流程的异步控制,在这里使用了 async 这个三方库,如果你不习惯也可以更换为你熟悉的异步编程方式。

首先,我们看看任务配置中的 steps 异步串行处理:

代码语言:javascript
复制
run() {
    //串行处理 steps 数组中的每一项目元素
    async.eachSeries(this._task.steps, (step, cb) => {
        //_processStep 是一个异步函数,通过回调通知 step 处理完毕
        this._processStep(step, cb);
    }, () => {
        //this._task.steps 中所有步骤处理完成,关闭引导
        this._task = null;
        //引导结束,隐藏遮罩、手指节点
        this._mask.node.active = false;
        if (this._finger) {
            this._finger.active = false;    
        }
    });
}

async.eachSeries 是将 steps 数组中的元素依次迭代处理,具体的步骤处理我们封装在 引导类的 this._processStep 成员函数中,当 steps 数组中所有步骤执行完毕,async.eachSeries 最后一个回调函数被触发,退出引导状态。

引导步骤—步骤生命周期回调与步骤指令

上面是控制的是引导整体流程,我们再深入到 this._processStep 函数:

代码语言:javascript
复制
_processStep(step, callback) {
     async.series({
         //步骤开始
         stepStart: (cb) => { 
             step.onStart ? step.onStart(this, cb) : cb();
         },
         
         //步骤指令
         stepCommand: (cb) =>  {
             this.scheduleOnce(() => {
                 this._processStepCommand(step, () => {
                        cb();
                    });
             }, step.delayTime || 0);  
         },
         
         //步骤结束
         stepEnd: (cb) => {
             step.onEnd ? step.onEnd.call(this, cb) : cb();
         }
     }, callback);
 },

1. 步骤生命周期回调

async.series 帮助我们串行执行多个异步函数,这里为 step 设计了 onStart、onEnd 两个生命周期回调,分别在上面 stepStart 和 stepEnd 中执行,我们可以在这两个函数中做一些初始化、条件检查等异步等待操作,例如:

  • 在 onStart 中等待玩家等级达到多少级,或某个事件发生;
  • 在 onEnd 中等待服务器返回某个消息、操作后等待某个动画的完成,可以通过监听事件进行确认。

下面代码是模拟道具购买的引导实现,可以具体了解到 onStart、onEnd 的使用方法

代码语言:javascript
复制
{
...
steps: [
        {
            desc: '10 级提示购买道具',
            //步骤开始
            onStart(callback) {
                let obj = {};
                //监听玩家等级变化
                cc.director.on('player-lv-up', (player) => {
                 //到达 10 级,显示商店界面
                 if (player.lv >= 10) {
                  cc.director.emit('show-shop');
                  //移除事件监听
                  cc.director.targetOff(obj);
                  //执行回调,执行步骤指令
                  callback();
                 }
                }, obj);
            },
            
            //步骤指令,定位商店界面中的购买按钮
            command: { cmd: 'finger', args: 'Shop > btnBuy'},
            
            //当玩家点击购买按钮,进入 onEnd 事件回调
            onEnd(callback) {
                //使用网络代理模块,监听指定网络事件
             NetProxy.once('message-buy-item', (msg) => {
                 //事件发生,执行 callback 回调步骤结束
                 callback();
             });
            }
        }
    ]
}

如果游戏比较简单 onStart 和 onEnd 不是必须的,通过 step 上 delayTime 属性可以做简单的延时控制,同样你也可以将游戏中增加事件、网络消息的广播编写成 step 配置中的 command 指令,以降低配置的复杂度。

2. 步骤指令

使用 step 指令,可以让步骤配置简化,特别是 UI 的点击引导。

步骤的异步处理过程中 this._processStepCommand 是关键,因为经常刚一进入某个场景时,可能需要定位的节点还未准备好(未创建或在动画运动过程中),我们又不想每个步骤都去写 onStart,因此步骤上提供了一个 delayTime 的属性,以延迟 step 指令的执行。

这里实现了两个指令:locatorfinger,他们的本质都是异步执行的函数 this._processStepCommand 中在对指令函数的调用,看下面代码:

代码语言:javascript
复制
let godCommand = require('GodCommand');
...
//处理步骤中的指令
_processStepCommand(cb) {
   let cmd = godCommand[step.command.cmd];
   if (cmd) {
       this.log(`执行步骤【${step.desc}】指令: ${step.command.cmd}`);
       cmd(this, step, () => { 
           this.log(`步骤【${step.desc}】指令: ${step.command.cmd} 执行完毕`);
           cb();
       });
   } else {
       cc.error(`执行步骤【${step.desc}】指令: ${step.command.cmd} 不存在!`);
   }
}

这里将指令函数编写在了名为 GodCommand.js 文件中,向指令函数传入当前引导对象step 配置对象,下面看定位指令的实现:

代码语言:javascript
复制
let GodCommand = {
    //定位节点
    locator(godGuide, step, callback) {
        //取出指令参数 
        let { args } = step.command;
        //调用引导类提供的定位节点
        godGuide.find(args, (node) => {
            //设置目标节点,用于遮罩显示和点击放行
            godGuide._targetNode = node;
            //点击确认
            node.once(cc.Node.EventType.TOUCH_END, () => {
                cc.log('节点被点击');
                //调用 callback 任务完成
                callback();
            });
        });
    },
}

可以看出这里又是一系列的回调:

  1. 从 step 中获取参数,调用 godGuide.find 定位节点;
  2. 目标节点定位成功,使用 node.once 注册临时触摸监听;
  3. 当目标节点触摸事件发生,执行 locator 输入的 callback 回调,指令完成。

需要注意,任务完成时一定要执行 callback,不然无法继续流程。有了该指令函数,就可以在任务配置文件中使用了,使用方式:

代码语言:javascript
复制
{
    desc: '点击主界面主页按钮',
    command: { cmd: 'locator', args: 'Home > main_btns > btn_home' },
    delayTime: 1,
}

step.command 中的 args 参数,由指令函数自行解释。

指令设计—实现手指动画指令

我们可以根据自己游戏的业务需求设计步骤指令,上一小节只是实现了节点的定位,并没有手指动画,在前面的基础上,我们为节点定位增加一个手指动画。

在 GodGuide 预制体上增加了一个手指预制体的属性,你可以根据自己的美术风格任意更换手指提示的表现,看下图:

手指预制体编辑界面:

注意 GodFinger 预制体,锚点设置在了手指指尖位置。

手指动画提示可能比遮罩还常用,因此将手指动画的调用封装在了 GodGruid 组件代码中,提供了一个 fingerToNode 的函数,代码如下:

代码语言:javascript
复制
fingerToNode(node, cb) {
   // 手指节点不存在,直接回调
   if (!this._finger) {
       cb();
   }

   this._finger.active = true;
   // 转换节点位置
   let p = node.parent.convertToWorldSpaceAR(node.position)
   p = this.node.convertToNodeSpaceAR(p);
   
   // move 动作
   let duration = p.sub(this._finger.position).mag() / cc.winSize.height;
   let moveTo = cc.moveTo(duration, p);
   let callFunc = cc.callFunc(() => {
       cb(); // 完成回调
   })
   let sequnce = cc.sequence(moveTo, callFunc);
   this._finger.runAction(sequnce);
}

手指动画很简单,就是一个 moveTo 的动作,需要注意的是节点坐标转换和动作完成回调,下面是 finger 指令的实现:

代码语言:javascript
复制
let GodCommand = {
    //定位节点
    locator(godGuide, step, callback) {
        ...
    },

    //定位节点,显示一个手指动画
    finger(godGuide, step, callback) {
        let { args } = step.command;
        //定位节点
        godGuide.find(args, (node) => {
            godGuide._targetNode = node;
            //手指动画
            godGuide.fingerToNode(node, () => {
                //点击确认
                node.once(cc.Node.EventType.TOUCH_END, () => {
                    cc.log('节点被点击');
                    //任务完成
                    callback();
                });
            });
        });
    }
};

其实没什么东西,就在前面 locator 函数的基础上增加了 godGuide.fingerToNode 的调用。

指令设计—文本提示

在引导流程中,更为常规的做法是手指动画 + 提示文本,读者可以思考一下如何设计一个 text 的指令。

我们以测试驱动的方式,先给出引导配置:

代码语言:javascript
复制
{
...
steps: [
    {
     desc:'文本提示'
     command: { cmd: 'text', args: ['欢迎来到 xxx 游戏', 10001, 10002] };
    },
 {
     desc: '点击主界面主页按钮',
     command: { cmd: 'locator', args: 'Home > main_btns > btn_home'},
 },
]}

... ...

text 指令的参数是一个数组,可接受两种类型:

  1. 文本字符串:直接显示即可
  2. 语言配置 ID:有些游戏支持多国语言,在此直接配置语言 ID

同样,我们使用异步控制串行逐一输出 args 中的文本,当玩家点击屏幕时输出下一条文本,这里就不在帖出代码了。

小结

Step指令 都是可扩展、可编程的, 在实际的项目中,我们可能需要根据具体业务需求,设计出更多的指令,方便引导任务的配置,例如:ScrollView 列表滑动指令、节点关闭指令、玩家等级变化指令、玩家过关指令等等,指令的设计主要是对事件的监听和异步流程的控制 。

下一次,我们再给大家介绍,关于自动化的实现。

GodGuide 框架已经上架 Cocos Store ,而且最近开发者们又来上新了一大堆好玩、有趣、有价值的内容,点击【阅读原文】来看看吧!

4.23粉丝福利,快来参与吧!

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2021-04-23,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Creator星球游戏开发社区 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 引导框架—串联异步引导步骤
  • 引导步骤—步骤生命周期回调与步骤指令
    • 1. 步骤生命周期回调
      • 2. 步骤指令
        • 小结
    • 指令设计—实现手指动画指令
    • 指令设计—文本提示
    领券
    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档