博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
使用Cocos2d-JS制作游戏新手引导(四)应用篇
阅读量:4627 次
发布时间:2019-06-09

本文共 6992 字,大约阅读时间需要 23 分钟。

讲解实现引导的组成模块及整个引导流程,并给出整个引导的源码及演示代码。本文来看看怎么应用。

 

sz.Guide引导库已经可以简单地工作了,但离真实的游戏项目、使用场景时还需要自己做一些事情。

进度读取与保存

sz.Guide默认对进度的读取和保存,是记录在localStorage中的,请看如下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
* 读取进度
*/
loadProgress: function() {
    
//获取localStorage对象,同时兼容jsb
    
var localStorage = localStorage || cc.sys.localStorage;    
//sz.GuideIndexName为一字符串常量做为key,读取进度,不存在时进度为0
    
this
._index = parseInt(localStorage.getItem(sz.GuideIndexName)) || 0;
},
/**
 
* 保存进度
 
* @param isForward 进度是否前进
 
* @param cb        保存完的回调
 
*/
saveProgress: function(isForward, cb) {
    
var localStorage = localStorage || cc.sys.localStorage;
    
localStorage.setItem(sz.GuideIndexName, isForward ? ++
this
._index : 
this
._index + 1);    
if 
(cb) {
        
cb();
    
}
}

_index即是进度的记录器,又是任务队列的索引下标,因此进度保存有两种情况: 

1.当任务完成(任务中的步骤都解决掉时),使用++this._index保存进度,并修改任务索引,进入下一个任务。 

2.作为保存进度的步骤时, 使用this._index + 1保存,并不修改当前任务进度,但游戏重启后,这这任务将会跳过。

有人可能会问saveProgress函数的cb回调参数是什么用了?请看下面的使用场景。

自定义进度的读取与保存

因为sz.Guide只简单实现了本地保存,对于现在大多数网络游戏来说并不适合,所以你需要根据自己的项目情况来扩展sz.Guide,这里简单说明几种方法:

  1. 修改sz.Guide的源码来适应你的项目,但不推荐这种做法,因为sz.Guide还会持续改进、修改BUG。

  2. 写两个新函数来覆盖sz.GuideLayer上的loadProgress、saveProgress方法。 此方法可以,但不够完美,如果一个项目中有多个引导实例时怎么办呢?

  3. 继承sz.GuideLayer生成一个子类,重写loadProgress、saveProgress方法,比较推荐使用这个方法。

以下是我在项目中具体使用方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
xl.MyGuideLayer = sz.GuideLayer.extend({    
//保存进度到服务器上
    
saveProgress: function(isForward, cb) {
        
//生成当前进度
        
var index = isForward ? ++
this
._index : 
this
._index + 1;        
//通过网络服务器NetClient发送进度,服务器做保存
        
NetClient.send(ActionCode.SAVE_NEW_PLAYER_GUIDE_STEP, index, function(isSucc) {
            
//当接收到保存成功的服务器响应后,执行cb回调函数 
            
if 
(isSucc && cb) {
                
cb()
            
}
        
})  
    
},
 
    
loadProgress: function() {
        
//缓存对象上获取玩家对象,并读取新手引导步骤id
        
this
._index = CacheManager.getPlayer().newPlayerGuideStep || 0;
    
},  
});

这时应该能明白saveProgress函数的cb函数的意义了吧!因为将进度保存到网络时,绝大多数是异步操作,当服务器真实保存成功能,才能继续。因此cb函数就是在通知引导框架,进度保存完毕了,可以进行下一个任务或步骤了。

步骤对象上的事件函数

为了使用引导配置适应更多的需求,在步骤对象上目前可以配置三个事件函数,分别为:

  • onEnter 当步骤将要开始时 

  • onLocateNode 当定位到节点时(需要配合定位器和相应指令时) 

  • onExit 当步骤结束时

肯定有很多人看到我的演示程序,发现下面这个BUG:

1.jpg

这是bug的原因是,表示灯火的粒子对象,默认没有高、宽,锚点为0,高、宽是在代码启动时设置上去的。 手型图标默认指向的位置是 node.getPosition(),也就是锚点位置。

这确实是一个bug,在不修改sz.Guide源码的情况下,我们使用步骤配置来解决这个问题

onLocateNode

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
1: [
      
{
          
log
"关闭第一盏灯"
,
          
command: sz.GuideCommand.GC_FINGER_HINT,
          
locator:
"_fire1"
,          
//onLocateNode,当定位到_fire1节点时响应些函数
          
onLocateNode: function(node) {
              
//node为'_fire1'节点对象
              
var pt = node.getPosition();
              
pt = node.getParent().convertToWorldSpace(pt);
              
pt.x += node.width / 2;
              
pt.y += node.height / 2;              
//this为sz.GuideLayer对象实例,调用_fingerToPoint函数指向新的位置
              
this
._fingerToPoint(pt, 
true
);
          
}
      
},
      
...

重新运行代码,效果如下: 

2.jpg

步骤中事件函数的this上下文为sz.GuideLayer对象实例,你可以在这里方便调用sz.GuideLayer上的方法,做一些事情。这里就使用了sz.Guide._fingerToPoint方法修正手指的位置。

但是这里也有问题,可以从遮罩区看出,定位矩形并没有把灯火全部包裹住,在体验上很差。 我们重新再改进一次配置onLocateNode函数:

1
2
3
4
5
6
7
onLocateNode: function(node) {
    
//修改_touchRect触摸矩形的起点
    
this
._touchRect.x -= node.width / 2;    
this
._touchRect.y -= node.height / 2;    
//计算矩形中心位置
    
var point = cc.p(
this
._touchRect.x + node.width / 2, 
this
._touchRect.y + node.height / 2);    
//刷新遮罩显示
    
this
.showMask(
true
);    
//指向新的位置
    
this
._fingerToPoint(point, 
true
);
    
}

再次运行,效果如下: 

3.jpg

onEnter&onExit

onEnter与onExit从名字上就应该很好理解,是由步骤处理开始前和处理完成后触发。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
//步骤开始_processTasks: function() {
    
...    
//一个step
    
var stepHandle = function(step, callback) {
        
self._curStepConfig = step;
        
async.series({            
//步骤开始
            
stepBegin: function(cb) {
                
self._guideLayer._setLocateNode(null);                
if 
(step.onEnter) {                    
//执行步骤对象上的onEnert函数,注意cb函数参数
                    
step.onEnter.call(self._guideLayer, cb);
                
else 
{
                    
cb();
                
}
            
},            
//步骤处理
            
stepProcess: function(cb) {
                
if 
(step.delayTime) {
                    
self._guideLayer.scheduleOnce(function() {
                        
self._processStep(step, cb);
                    
}, step.delayTime);
                
else 
{
                    
self._processStep(step, cb);
                
}
            
},            
//步骤完毕
            
stepEnd: function() {
                
if 
(step.onExit) {                    
//步骤完毕,退出时执行onExit方法,注意第二个callback参数
                    
step.onExit.call(self._guideLayer, callback);
                
else 
{
                    
callback();
                
}
            
}
        
});
    
};
 
    
...
}

onEnter、onExit事件函数都有一个cb回调函数的参数,表示事件完成后的通知。 

使用场景常会出现在onEnter时播放一个动画或显示一个临时窗口, 需要动画完成后执行步骤命令。 这都是一个异步过程,所以需要招待一次cb()操作才能让步骤向下执行。

1
2
3
4
5
6
7
8
9
10
11
12
13
1:[
{
    
...    
//在步骤开始时,播放一个动画
    
onEnter: function(cb) {
        
var label = 
new 
cc.LabelTTF(
"关闭第一盏灯"
"宋体"
, 48);        var pt = cc.p(
this
.width / 2, 
this
.height / 2);
        
label.setPosition(pt);        
this
.addChild(label);        var faceOut = cc.fadeOut(3);        var call = cc.callFunc(function() {
            
label.removeFromParent();
            
cb(); 
//当执行cb函数时才进入指令处理
        
}, 
this
);
        
label.runAction(cc.sequence(faceOut, call))
    
}
    
...
}

引导配置中的参数功能

1
2
3
4
5
6
7
8
9
var guideConfig = {    
//编写具体引导任务
    
tasks: {
        
...
    
}    
//定位器搜索节点的间隔时间
    
locateNodeDurationTime: 0.1,    
//手型提示图片资源路径
    
fingerImage: 
'res/finger.png'
,    
//常规事件响应的事件类型:0=touchBegan,1=touchMoved,2=touchEnded
    
eventType: 2, 
//表示在touchEnded中检查事件是否完成
    
//是否显示遮罩
    
isShowMask: 
true  
};

这里解释下这些参数的功能可能的使用场景:

locateNodeDurationTime

1
locateNodeDurationTime: 0.1 
//定位器搜索节点的间隔时间

有时在引导步骤中定位一个节点时,这个节点并未创建在当前场景的渲染树中,在第一次定位节点时并没有找到节点对象。因此需要一个持续的节点定位的操作,操作的间隔时间由此参数控制。

fingerImage

1
fingerImage: ‘res/finger.png’ 
//手型提示图片资源路径

fingerImage非常简单,就不做过多解释了。

eventType

1
eventType: 2 
//表示在touchEnded中检查事件是否完成

前面几篇文章中介绍了,如何检查定位节点的事件函数已经被执行。之前的讲解中说到一般都是在Widget控件的touchEnded中来处理事件函数。这样的设定不够灵活,万一有时需要在touchBegan时呢? 

eventType:2为引导的全局配置,如果某一个步骤定位节点的事件处理函数放在touchBegan时可以如下处理:

1
2
3
4
5
6
7
...
//一个任务步骤对象{
    
log
:
'点击home'
,
    
command: sz.GuideCommand.GC_FINGER_HINT,
    
locator:
"_btnHome"
,
    
eventType:0  
//"_btnHome"控件的事件函数为touchBegan}
...
eventType: 2

在演示代码中你可以发现 _onBtnHomeTouchBegan: function(){…}函数,所以这里上步骤中的事件检测需要在eventType:0

isShowMask

1
isShowMask: 
true 
//是否显示遮罩

此开关方便开始遮罩,特别是在调试时,可以方便看到我们的触摸矩形区大小。 

而且在单个任务步骤中也可以临时开打或关闭遮罩的显示:

1
2
3
4
5
{
     
log
"点亮第二盏灯"
,
     
command: sz.GuideCommand.GC_FINGER_HINT,
     
locator:
"_fire2"
,
     
showMask: 
true 
//强制打开当前步骤的遮罩显示},

上面总结了sz.Guide引导库的基本功能的使用,可以通过配置、事件函数灵活实现特殊的引导需求。

 

补充

delayTime

步骤对象上还有一个隐藏的属性为delayTime,值为一个Number,它是间于步骤的onEnter事件与步骤处理操作之间的延时。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
async.series({
      
//步骤开始
      
stepBegin: function(cb) {
          
... 
      
},
      
//步骤处理
      
stepProcess: function(cb) {
          
//如果配置有delayTime属性,使用scheduleOnce推迟步骤处理
          
if 
(step.delayTime) {
              
self._guideLayer.scheduleOnce(function() {
                  
self._processStep(step, cb);
              
}, step.delayTime);
          
else 
{
              
self._processStep(step, cb);
          
}
      
},
    
//步骤完毕
    
stepEnd: function() {
        
...
    
}

 

我主要的使用场景是这样的情况:

一个UI界面中有一个按钮button,创建时在A位置,一个子类继承成了他,修改为B位置。如果立即定位button节点,所指向的位置并不是按钮的最终位置,这里使用delayTime可以轻松解决此问题。

 

指令扩展计划

sz.Guide内部定义了四个指令,其中实现了三个:

1
2
3
4
5
6
sz.GuideCommand = {
    
GC_NULL: undefined, 
//空指令
    
GC_SET_PROPERTY: 1, 
//设置属性
    
GC_FINGER_HINT: 2,  
//手型提示
    
GC_SAVE_PROGRESS: 3 
//保存进度
};

对于一个上线的游戏项目来说,估计这三个指令是远远不够的。我们也能通过步骤对象上的onEnter和onExite事件来丰富一些操作,但需要编写较多的代码,且代码是为一个特定的操作而做的,不能够很好的复用。比如在步骤处理开始前,先滚动TableView。

 

目前sz.Guide的指令是简单的用switch后分别调用不同的函数实现的。

1
2
3
4
5
6
7
8
9
10
switch 
(step.command) {
    
case 
sz.GuideCommand.GC_SET_PROPERTY:
        
...;
break
;
    
case 
sz.GuideCommand.GC_FINGER_HINT:
        
...;
break
;
    
case 
sz.GuideCommand.GC_SAVE_PROGRESS:
        
...;
break
;
    
default
:
        
cc.
log
(
"guide command is not define"
);  
}

本人计划将指令的实现独立于引导框架,可以灵活的向引导框架注册指令,你可以编写自己的指令操作。

 

源码地址:

本篇代码演示:git checkout step1

转载于:https://www.cnblogs.com/cooka/p/4497647.html

你可能感兴趣的文章
【python】函数返回值
查看>>
[原创]java WEB学习笔记26:MVC案例完整实践(part 7)---修改的设计和实现
查看>>
[JavaScript-PHP]无刷新Ajax+POST使用阿里云短信平台发送短信
查看>>
个人对习惯培养的拙见
查看>>
Java基础50题test9—求完数
查看>>
【记忆法】心智绘图
查看>>
Jzoj4458 密钥破解——Pollard-rho
查看>>
Service类onStartCommand()返回值讲解.
查看>>
[Docker] Build a Simple Node.js Web Server with Docker
查看>>
npm安装
查看>>
git中如何合并某个指定文件?
查看>>
Pandas 中 SettingwithCopyWarning 的原理和解决方案
查看>>
Service bound(三)
查看>>
Android Camera拍照 压缩
查看>>
用OpenCV实现Otsu算法
查看>>
5.Spring+Struts+Hibernate配置文件整合
查看>>
Unable to create request (bad url?) 解决方案
查看>>
网络对抗技术_实验三_密码破解技术
查看>>
vue-状态管理
查看>>
css实现等高布局
查看>>