『GodGuide——新手引导框架』是晓衡 2019 年发布在 GitChat 上的付费教程。代码在 Github 已经开源很久了,但最近有伙伴说 Github 打不开,我灵光一闪机会来了!
代码和完整文档发布到了 Cocos Store,同时也将原来的付费文档公开,希望能帮助到更多的人。如果你也有更好的新手引导解决方案、框架、Demo,也欢迎分享到全球最大的 Cocos 技术社交平台 Cocos Store。
悄悄告诉大家, Cocos 官方布道师们制作的范例,正在陆续登录 Cocos Store ,不方便访问 GitHub 的问题,有救了!
正文之前,我先来上一段小视频
一、实现新手引导的困难
通常实现新手引导的困难在于,它与当前需求、功能密切相关,而且稍有不慎连正常流程都走不通,下面一起看看新手引导到底有那些痛点。
开发中的痛点
在正常游戏逻辑中插入引导代码,干扰流程;
引导代码的增加会影响原有代码逻辑,增加维护、测试成本;
界面或需求的变化会可能会使引导功能大幅修改,甚至重做;
手指提示对应的矩形区定位麻烦,不能简单使用固定的位置和矩形大小,而且只有游戏开发人员才做到;
编写引导功能代码困难,需要策划—程序之间高度配合,开发效率低下。
期望的编程体验
在了解到传统的引导制作过程中的难点与弊端后,不断地在思考没有更好的实现方式呢?我心中的引导功能的编程方式希望有以下几点:
引导功能代码,不能混入正常游戏逻辑代码中,后患无穷,应尽量分离;(难以忍受优雅的代码被无情的打乱,更难忍受糟糕的代码被弄的支离破碎)
界面只发生简单的 UI 位移、Size 大小、节点层次的调整,不需要修改引导代码;
定位 UI 指引矩形区域应尽可能简单,能自适应不同的屏幕尺寸;
最好能做到策划人员都可以来制作部分流程引导;
在引导需求明确、游戏功能正常的情况下,制作一个常规的引导步骤应该非常快捷。
二、分离引导与游戏逻辑
上面对存在的问题难点进行了分析,也设定了期望目标,我们首要解决的问题是“如何分离引导与游戏之间的逻辑”,做到各不相关。
分析引导操作流程
引导的主要任务是在特定界面中,需要按照指定流程操作游戏,其核心是:
目标节点位置确定
目标节点操作提示
非目标节点的操作限制
目标节点操作完成监视
继续下一个目标节点
通常来说引导功能并不会影响游戏业务逻辑,只是限制游戏的操作流程,同时还需要在过程中以文字、动画、与后端通信等方式与玩家进行交互。
观察任意场景中的节点与事件—上帝模式
引导功能要限制游戏的流程,就必须站在更高的位置,统领整个游戏场景,我将它称之为上帝模式,如下图:
![](https://i-blog.csdnimg.cn/blog_migrate/51f7fca267d5829f4139a23a95b102cb.jpeg)
进入上帝模式很简单:
在当前场景最上层放置一个与屏幕相同大小的节点
在上帝引导节点上拦截触摸事件
拦截触摸事件具体细节我们下一节再讲,既然称之为「上帝」,还需要具有两项目能力:检索任意节点&节点事件监听
检索任意节点
使用引擎提供的 cc.find 就可以搞定,但这里为了对节点进行高效检索,从 CSS 中吸取了一点灵感,定义了一种称之为**“定位器”**的场景中节点表达方式。
例如:字符串Home > btn_home
,描述的是 Home 节点下名为 btn_home
的节点,只要 btn_home 节点的名字在 Home 节点下是唯一的,不用理会它在 Home 下第几层,注意下图中绿色箭头:
![](https://i-blog.csdnimg.cn/blog_migrate/d4597e5411e1d87a9d71180b938c50a7.jpeg)
检索节点并获取节点的位置、大小,用于我们的上帝引导层,进行有取舍的事件拦截、遮罩显示等, 关于定位器的详细内容,下一节会有详细说明。
任意节点事件监听
为了使表述简洁,我们这里重点只关注 UI 节点的点击触摸引导,监听任意节点的事件至少 2 种方法:
将引导层节点放置在场景最顶层,所有的触摸操作首先需要经过引导;
利用 JavaScript 语言的动态特性,对节点触摸分发函数进行 Hook(流程录制会使用到该技术)。
![](https://i-blog.csdnimg.cn/blog_migrate/fddc5f8946ac2a81e6e4574f648be34f.jpeg)
如上图所示,通过定位节点的位置和大小,我们在上帝引导层上“挖”开一个空洞,当点击到空洞位置,继续分发触摸事件,使用户操作进入正常的游戏层。
同时还需要监听目标节点的触摸事件发生(仅一次),当点击操作发生,当前目标节点的引导完成。
引导任务
这里把一个完整的引导流程进行分解和抽象,引入两个概念:步骤&任务
步骤 step
将引导流程中的一个最小操作称之为步骤 step,比如提示点击某个按钮、显示一行提示文本等。
任务 task
任务是步骤的有序集合 ,一个任务中可能存在一个或多个步骤,当任务中的步骤全部引导完成,当前任务就算完成。
任务进度管理
多个任务又可以组成任务组,比如引导用户购买游戏中的道具是一个任务,将道具装备到身上又是一个任务,它们之前有明确的先后关系。
对于任务组的管理,外部使用 index 表示正在执行第 n 个任务,当任务被完成 index++,重新进入游戏将不会再执行 index 之前的任务,如果一个任务中的步骤没有被执行完,重新进入游戏则从该任务的第一个步骤开始执行。
因此在每一个任务的最后一个步骤完成时,我们还需要将 index 变量持久化下来,一种是保存到本地,一种是保存到后台服务器。
引导任务的 JSON 表达
用 JSON 配置的方式表示引导任务,如下:
tasks:[
{
name:'从主界面进入商店,购买道具',
steps: [{步骤 1}, {步骤 2} ... {步骤 n}],
},
{
name:'将购买的道具,装备到玩家角色上',
steps: [{步骤 1}, {步骤 2} ... {步骤 n}],
},
...
{
name:'50 级引导玩家,进入竞技场',
steps: [{步骤 1}, {步骤 2} ... {步骤 n}],
}
]
steps 中的最后一个步骤,通常会与后台服务器进行网络通信,此前的操作流程将不能再被回溯,此时用来结束一个相对完整的引导任务,而且还可以将上面的任务单元编写在不同的文件中,具体的做法在后面小节中有详细说明。
三、点击操作的引导实现细节
游戏引导最多的是UI 点击操作,首要解决的是对 UI 节点的定位,获取节点对象,取出节点的位置(Position)、包围盒(BoundingBox)、锚点(AnchorPoint)等信息,如何才能拿到任意节点呢?
这里有两种方法:
遍历场景树,将节点检索出来;
在游戏逻辑代码中,将目标节点对象传递到引导逻辑代码中。
因为要保持引导与游戏尽可能分离,这里选择的是第一种方式。
定位器的设计实现
cc.find 的局限
Cocos Creator 引擎提供了 cc.find 函数,传入节点路径可搜索出 Canvas 下任意节点,代码如下:
cc.find('Canvas/Home/lower/main_btns/layout/btn_home')
下图是在浏览器控制台中进行测试:
其中 cc.find 的第一个参数节点路径字符串可以精确地定位到 btn_home 节点,但是在实际开发中会感觉很繁琐,有下面两个原因:
当界面层级较深时,节点路径字符串太长,拼写容易出错;
节点名字、层级变化,节点路径字符串就失效了,特别容易被误伤。
定位器字符串
为了使路径表达更简洁且可靠,引入了两个定位符号:
/: 右斜杠,代表 1 级子节点(与 cc.find 相同)
>: 大于符号,表示 1~n 级子节点
我们可以将上面 btn_home 节点的节点路径字符串改为,如下方式:
godGuide.find('Canvas > btn_home');
如果我们默认从 Canvas 节点开始检索,也可以直接写成下面这样:
//注意:确保场景中只有一个名字为 btn_home 的节点
godGuide.find('btn_home');
这将从 Canvas 节点一层层开始遍历,要提高检索节点的效率可以改为:
godGuide.find('Home > main_btns > btn_home');
如果场景中有同名节点,也可以使用 '>'
符号解决,但同一层级不能有同名节点(如果你需要检索该节点的话)。
'>'
符号也可与'\'
混合使用,如下:
let btn_home = godGuide.find('Home>main_btns/btn_home');
let btn_level = godGuide.find('Home>main_btns/btn_level');
需要注意的是,节点名命不要使用'>'
、'\'
这两个字符 。
异步
在实际开发中会遇到这样一种现象,发出了一个节点定位指令,但此时节点并未出现在场景中,我们得到的是一个 null 对象,因此还需要将 godGuide.find 函数设计成一个异步方式,通过回调函数返回检查到的节点,当第一次定位不到节点时,间隔一定时间继续定位节点:
godGuide.find('Home>layout/btn_home', (error, node) => {
cc.log('定位节点成功:', node.name);
});
godGuide.find 内部设置一个超时,通过 error 返回错误。
节点遮罩的实现
不少游戏,为了突出引导节点,需要将不能操作的区域叠上一层半透明的遮罩,镂空引导节点,引擎提供的 cc.Mask 组件可以帮助我们实现这个功能。
在这里创建一个 GodGride 的预制体,在内部放置一个 cc.Mask 节点,看下图:
![](https://i-blog.csdnimg.cn/blog_migrate/ff440af5b7e2b0772f6fda3fc99ac0ab.jpeg)
Mask 组件内部是用 cc.Graphics 实现,成员变量 _graphics 是 cc.Graphics 的实例。
开启 Mask 组件的 inverted 反转属性,使用_graphics 绘制节点矩形,即可实现镂空遮罩效果,下面是具体代码:
start() {
//获取 Mask 组件
this._mask = this.node.getComponentInChildren(cc.Mask);
this._mask.inverted = true;
},
//镂空指定节点
_focusToNode(node) {
this._mask._graphics.clear();
let rect = node.getBoundingBoxToWorld();
let p = this.node.convertToNodeSpaceAR(rect.origin);
rect.x = p.x;
rect.y = p.y;
this._mask._graphics.fillRect(rect.x, rect.y, rect.width, rect.height);
}
下面是运行效果,可以看到图中左下角设置按钮位置被镂空。
![](https://i-blog.csdnimg.cn/blog_migrate/66463015af628f705a13b2157ba66bd9.gif)
监听全局触摸事件
一旦定位到目标节点,目标节点的位置、大小就可以通过遮罩显示出来,接下来要做的就是对触摸事件的拦截、放行、监听(点击确认),成为真正的上帝引导层,掌控整个游戏。
拦截与放行
只需要将 GodGride 预制节点放到场景的最顶层,并为它添加触摸事件即可,看下面代码:
start() {
//在引导层上注册 TOUCH_START 触摸事件
this.node.on(cc.Node.EventType.TOUCH_START, (event) => {
//目标节点不存在,拦截
if (!this._targetNode) {
this.node._touchListener.setSwallowTouches(true);
return;
}
//目标区域存在,击中放行
let rect = this._targetNode.getBoundingBoxToWorld();
if (rect.contains(event.getLocation())) {
this.node._touchListener.setSwallowTouches(false);
cc.log('未命中目标节点,放行')
} else {
this.node._touchListener.setSwallowTouches(true);
cc.log('未命中目标节点,拦截');
}
}, this);
}
上面代码中 this._targetNode 变量是定位到的目标节点,通过事件对象 event.getLocation 获取的是触摸到的世界坐标点,因此可直接使用 node.getBoundingBoxToWorld 方法获取节点在世界坐标上的包围盒矩形。当点击的位置正好落在矩形区内,使用 node._touchListener.setSwallowTouches(false)
放行触摸事件(不吞噬),事件继续向下层节点派发,当未命中节点则使用 setSwallowTouches(true)
拦截当前触摸事件(吞噬事件),停止事件的派发,下图是运行效果:
目标节点的触摸监听
最后一步,如何知晓目标节点被点击呢?其实很简单,只需在目标节点上注册个触摸监听 TouchEnd 事件即可,看下面代码:
//定位到目标节点
godGuide.find('Home > main_btns > btn_home', (node) => {
//定位成功,注册 TouchEnd 事件,仅响应一次
node.once(cc.Node.EventType.TOUCH_END, () => {
cc.log('节点被点击');
callback();
});
});
我们这里使用 node.once
进行注册,仅接收一次点击事件,当该事件发生后,callback 通过回调函数进行确认(第四小节引导任务框架中详细讲解流程),当前步骤处理完毕,进入一下个引导步骤,直到所有步骤全部被完成。
四、未完待续
由于篇幅较长,为了不给大家造成阅读负担,以及我想多安利一些伙伴,我会在公众号上多分享几次,感谢大家的支持!
如果感兴趣的伙伴,可以扫码进入 Cocos Store 资源介绍阅读完整全文。