作者: andy. 时间: 2020-06-01 01:48:03
星期天想了一天,才完成这么一个引导。一直想抽时间写下来,但是最近实在太忙了。第一次用这么大的一个标题,谈谈对新手引导这个模块的一点儿看法和实现思路。里面的伪代码都以Cocos Creator为准,同样的,Cocos2dx也是一样的。主要还是思路,
看看新手引导需要完成的内容:
1、屏蔽层和半透明屏蔽层。
2、新手引导聚焦(指定按钮等等)的位置需要镂空。
3、可以完成几种情况的点击:1、点击指定位置跳转到下一个引导。2、点击屏幕任意位置跳转到下一步。3、文字引导。
4、同一按钮可能需要点击多次才跳转到下一步步骤。
5、需要某个界面打开后(也可以是等待网络消息等)才能开始新手引导步骤。
6、所有上述信息可以配置相应配置表,相应配置表修改后不需要修改代码。
那么构造我们撸代码的大概思路(略过不需要新手引导,跳过新手引导等等):
->开始一个步骤新手引导(可能从最开始,也可能是从某个步骤接着引导)。
->是否需要等待界面打开(如果需要的话,等待界面打开,期间屏蔽所有操作)。
->开始该步骤。
->渲染屏蔽层(半透明的话,镂空位置等等)。
->是否弹出文字提示。
->监听玩家点击和点击数量。
->此步骤引导结束。
->开始下一步骤新手引导。
思路非常的清晰,为了应对这些操作,我们首先需要设计新手引导的配置表。根据上面的叙述,每一步骤的新手引导需要的信息如下:
1、Id 引导Id。
2、NextId 下一个引导的Id。
3、RestartId 中断后重新开始的Id。
4、Interrupt 点击的方式:不屏蔽所有的操作,全屏点击触发,只允许玩家点击监控的控件。
5、Language 如果有文本,文本内容。
6、TriggerNeedOperate 引导需要的条件,比如:某个界面打开。
7、StartOperate 如果需要第六个字段,那么,由事件来触发这个引导开始。
8、MonitorOperate 监控的操作。这个字段表达,玩家需要点击哪个按钮、滑动屏幕还是怎么的。预设定义在程序代码中,如果后面修改的引导还需要这个步骤的话,不修改代码也可以完成引导的修改。
9、IsMaskScreen 是否显示遮罩。比如某些引导不显示遮罩,弹出一段话,点击后,引导结束。
10、ClickTimesContinue 点击多少次后开始下个引导。比如某些挂机点点点的操作。
好了,准备工作都做完了,比如我们游戏的开始界面上面有两个按钮:
我们要点击完按钮1后点击按钮2。我们就可以加入2条配置:
{ 1: { Id: 1, NextId: "2", ReStartId: 2, Interrupt: 2, Language: "点击 button1", TriggerNeedOperate: "-1", StartOperate: "-1", MonitorOperate: "ClickButton1", IsMaskScreen: true, ClickTimesContinue: 2, }, 2: { Id: 2, NextId: "3", ReStartId: 3, Interrupt: 2, Language: "点击 button2", TriggerNeedOperate: "-1", StartOperate: "-1", MonitorOperate: "ClickButton2", IsMaskScreen: true, ClickTimesContinue: 1, }, }
开始引导效果:
我们有一个GuideManager管理整个引导过程,在引导过程中,有一个GuideNode盖在整个界面上面。开始引导的时候,如果不是中断开始或者引导已经完成了,就读取第一条引导配置开始执行逻辑。
StartGuide() { if (!this.currentNoviceConfig || this.currentNoviceConfig.StartOperate == "-1") this.StartNoviceGuide(); else { // 移除黑色遮罩 this.noviceNodeComponent.RemoveWordText((() => { if (this.currentNoviceConfig.TriggerNeedOperate != -1) // 发送事件让相应操作完成 }).bind(this)); } } StartNoviceGuide() { if (!this.currentNoviceConfig) { this.noviceNode.active = false; return; } this.noviceNode.active = true; this.noviceNodeComponent.SetShieldAllTouchs(true); this.noviceNodeComponent.SetNoviceGuideConfig(this.currentNoviceConfig); // 显示文字 this.noviceNodeComponent.ShowWordText((() => { }).bind(this)); // 显示遮罩 this.noviceNodeComponent.ShowMask(); }
第一条配置需要把button1突出显示出来(对于形状不规则的图形突出显示效果还不错),实现思路:监控的操作是ClickButton1,所以我们可以在代码里面配置:ClickButton1 = { WatchNode: "Canvas/button1", };这里表示监控的是button1这个node。这一条配置的好处是在于如果后面的引导还会引导点击按钮1的话,在配置表里面直接配置监控的操作就行,不用修改代码了。这里就比较简单了:我们用一个FrameBuffer来渲染一张贴图(Opengl里面术语,不知WebGL里面叫什么,就直接用了)。1、Clear ColorBuffer。2、我们将button1克隆一个放在引导的面板上面。3、设置克隆后的按钮混合模式,sfactor的混合模式为GL_ZERO,dfactor的混合模式为GL_ONE_MINUS_SRC_COLOR。
这样克隆按钮位置为Zero,就不会显示,因为克隆按钮的alpha值是255,dfactor也不会显示,这个位置就空了一块。实现镂空的效果。显示出来。大概代码:
var nodeWatchNode = cc.find(operationConfig.WatchNode, cc.director.getScene()); // operationConfig就是ClickButton1 = { WatchNode: "Canvas/button1", }; nodeWatchNode = cc.instantiate(nodeTarget); nodeWatchNode.active = true; nodeWatchNode.getComponent(cc.Sprite).srcBlendFactor = cc.macro.BlendFactor.ZERO; nodeWatchNode.getComponent(cc.Sprite).dstBlendFactor = cc.macro.BlendFactor.ONE_MINUS_SRC_ALPHA;
在引导node上面监听点击:
// 这里的operationConfig同上面。 var nodeWatchNode = cc.find(operationConfig.WatchNode, cc.director.getScene()); var touchPosition = node.parent.convertToNodeSpaceAR(touchPosition); if (!nodeWatchNode.getBoundingBox().contains(touchPosition)) { this.node.nodeShield._touchListener.setSwallowTouches(true); return; } this.node.nodeShield._touchListener.setSwallowTouches(false);
没点中就把点击吞噬掉就OK了。
最后在按钮的点击回调中告知guideManager这个点击完成了:
noviceGuideManager.CompleteOperate("ClickButton1"); // 下面部分为guideManager CompleteOperate(operateKey) { if (!this.currentNoviceConfig) return; if (this.currentNoviceConfig.MonitorOperate == operateKey) this.TriggerCurrentOperation(); } TriggerCurrentOperation() { ++this.noviceClickTimes; if (this.noviceClickTimes >= this.currentNoviceConfig.ClickTimesContinue) { this.noviceClickTimes = 0; this.EndCurrentGuideStep(); } } EndCurrentGuideStep() { this.noviceNodeComponent.SetShieldAllTouchs(true); // 这里需要保存当前进度 var nextNoviceConfig = NoviceGuideConfig[this.currentNoviceConfig.NextId]; var removeCallBack = function() { this.currentNoviceConfig = nextNoviceConfig; this.StartGuide(); }.bind(this); if (!nextNoviceConfig || nextNoviceConfig.Language != null) this.noviceNodeComponent.RemoveWordText(removeCallBack); else removeCallBack(); }
接着就开始下一步骤的引导。
需要界面打开和这里的Compete引导类似。
xiuxile