参考文档动画系统,部分内容与2.0.9IDE不一致。以下所做测试均在本地的polished_project中。
一、编辑器操作
1.Animation组件
之前我们了解了 Cocos Creator 是组件式的结构。那么 Animation 也不例外,它也是节点上的一个组件。
随便点开一个没有动画的Node,在动画编辑器窗口上都会看到一个快捷方式,添加Animation组件。上面两张图添加方式是一致的。
2.Clip动画剪辑
- Default Clip 默认的动画剪辑,如果这一项设置了值,并且 Play On Load 也为true,那么动画会在加载完成后自动播放 Default Clip 的内容
- Clips 列表类型,默认为空,在这里面添加的 AnimationClip 会反映到 动画编辑器里,用户可以在 动画编辑器 里编辑 Clips 的内容
- Play On Load 布尔类型,是否在动画加载完成后自动播放 Default Clip 的内容
按照上面添加完Animation后,动画编辑器窗口会提示,要添加Clip。新建Clip文件,会弹出一个窗口,取一个名字,保存成xxx.anim文件。
Clip 动画剪辑就是一份动画的声明数据,我们将它挂载到 Animation 组件上,就能够将这份动画数据应用到节点上。这里Animation可以挂多个Clip,然后根据名称来播放。(如果想编辑多个Clip,可以在动画编辑器的左下角进行切换)
clip 文件的参数
- sample: 定义当前动画数据每秒的帧率,默认为 60,这个参数会影响时间轴上每两个整数秒刻度之间的帧数量(也就是两秒之内有多少格)。
- speed: 当前动画的播放速度,默认为 1
- duration: 当动画播放速度为 1 的时候,动画的持续时间
- real time: 动画从开始播放到结束,真正持续的时间
- wrap mode: 循环模式
3.动画编辑器
如果一个动画需要包含多个节点,那么一般会新建一个节点来作为动画的根节点,将 Animation (动画)组件添加到这个 根节点上,然后这个根节点下的其他子节点都会自动进入到这个动画中。
左上角那个按钮可以打开/关闭编辑模式,下面的节点以及子节点,就是我们挂载Animation的根节点了。这里我做完一个Clip后,又在根节点BtnStart copy里添加了新的子节点PurpleMonster,但是打开之前的Clip,没有看到新添加进来的PurpleMonster。没办法,只能把Animation重新移除,再重新添加回来。
这里切换BtnStart copy和子节点star,属性列表是不同的。比如,我们可以让父节点向右移动,而子节点向下移动,然后播放时,可以同时发生。属性动画很简单,就是在属性列表里点击Add Property,选一个属性,比如scale。然后在这个属性右边点击三横杠菜单,插入关键帧。再移动那根红线到目标帧位置,再插入关键帧。然后把这两个关键帧的scale属性按照自己的想法改好,就OK了。
现在给PurpleMonster加一个动画,然后退出动画编辑器,回到场景里,把这个节点删除掉。再添加一个别的素材,名字叫mm吧。然后再进动画编辑器,发现哎,节点没刷新。像前面说的一样,只能把Animation重新移除,再重新添加回来。然后就看到提示,节点丢失了。
这时候,点击丢失节点右边的三横杠,选择移动数据,把名字改成新换过来的mm节点,回车,就可以了。
2.帧动画
这里节点,必须要先添加一个Sprite,再添加Clip,否则报错。这里添加的Sprite,可以不指定任何属性,就用默认空的。也可以指定一个图片,作为默认显示状态。具体原因,官方文档的解释是
首先我们需要让节点正常显示纹理,所以需要为节点增加Sprite组件。
注意如果勾选了自动播放,那个Default Clip也要填。
然后还是那一套,添加Animation,添加clip,然后添加属性时,要选cc.Sprite.spriteFrame,如下图:
然后还是那一套,添加关键帧,设置相应属性,随便设置图片
3.编辑时间曲线
我们已经创建了基本的动画了。 但有时候我们会需要在两帧之间实现EaseInOut等缓动效果,那么在动画编辑器中怎么实现呢?
我们首先需要在一条轨道上创建两个不相等的帧,比如在position上创建两帧,从 0,0 到 100,100。 这时候两帧之间会出现一根连接线(连接俩关键帧之间的蓝色线段),双击连接线,则可以打开时间曲线编辑器。
有时候预设的不能够满足动画需求,我们也可以自己修改曲线。 右侧下方预览图内,有两个灰色的控制点,拖拽控制点可以更改曲线的轨迹。 如果控制点需要拖出视野外,则可以使用鼠标滚轮或者右上角的小比例尺缩放预览图,支持的比例从 0.1 到 1。
4.添加事件
在游戏中,经常需要在动画结束或者某一帧的特定时刻,执行一些函数方法。那么在动画编辑器中怎么实现呢?
首先选中某个位置,然后点击按钮区域最左侧的按钮(add event),这时候在时间轴上会出现一个白色的矩形,这就是我们添加的事件。双击刚刚出现的白色矩形,可以打开事件编辑器,在编辑器内,我们可以手动输入需要触发的 function 名字,触发的时候会根据这个函数名,去各个组件内匹配相应的方法。如果需要添加传入的参数,则在 Params 旁点击 + 或者 - ,只支持 Boolean、String、Number 三种类型的参数。
这里事件由Animation所在的Node触发,所以需要在上面挂个脚本,添加一个方法:
myPlayComp(v){
cc.log("my play com",v);
},
myPlayerComp2(v){
cc.log("myPlayerComp2",v);
},
测试了一下,只有第一个myPlayComp是有效的。
二、使用脚本控制动画
Animation 组件提供了一些常用的动画控制函数,如果只是需要简单的控制动画,可以通过获取节点的 Animation 组件来做一些操作。
1.播放
var anim = this.getComponent(cc.Animation);
// 如果没有指定播放哪个动画,并且有设置
// defaultClip 的话,则会播放 defaultClip 动画
anim.play();
// 指定播放 test 动画
anim.play('test');
// 指定从 1s 开始播放 test 动画
anim.play('test', 1);
// 使用 play 接口播放一个动画时,如果还有其他的
// 动画正在播放,则会先停止其他动画
anim.play('test2');
Animation 对一个动画进行播放的时候会判断这个动画之前的播放状态来进行下一步操作。 如果动画处于:
- 停止 状态,则 Animation 会直接重新播放这个动画
- 暂停 状态,则 Animation 会恢复动画的播放,并从当前时间继续播放下去
- 播放 状态,则 Animation 会先停止这个动画,再重新播放动画
var anim = this.getComponent(cc.Animation);
// 播放第一个动画
anim.playAdditive('position-anim');
// 播放第二个动画
// 使用 playAdditive 播放动画时,不会停止其他动画的播放。
// 如果还有其他动画正在播放,则同时会有多个动画进行播放
anim.playAdditive('rotation-anim');
Animation 是支持同时播放多个动画的,播放不同的动画并不会影响其他的动画的播放状态,这对于做一些复合动画比较有帮助。
2.暂停 恢复 停止
var anim = this.getComponent(cc.Animation);
anim.play('test');
// 指定暂停 test 动画
anim.pause('test');
// 暂停所有动画
// anim.pause();
// 指定恢复 test 动画
anim.resume('test');
// 恢复所有动画
// anim.resume();
// 指定停止 test 动画
anim.stop('test');
// 停止所有动画
// anim.stop();
暂停,恢复, 停止 几个函数的调用比较接近。
暂停 会暂时停止动画的播放,当 恢复 动画的时候,动画会继续从当前时间往下播放。 而 停止 则会终止动画的播放,再对这个动画进行播放的时候会重新从开始播放动画。
3.设置动画的当前时间
var anim = this.getComponent(cc.Animation);
anim.play('test');
// 设置 test 动画的当前播放时间为 1s
anim.setCurrentTime(1, 'test');
// 设置所有动画的当前播放时间为 1s
// anim.setCurrentTime(1);
你可以在任何时候对动画设置当前时间,但是动画不会立刻根据设置的时间进行状态的更改,需要在下一个动画的 update 中才会根据这个时间重新计算播放状态。
三、AnimationState
Animation 只提供了一些简单的控制函数,希望得到更多的动画信息和控制的话,需要使用到 AnimationState。
1.AnimationState 是什么?
如果说 AnimationClip 作为动画数据的承载,那么 AnimationState 则是 AnimationClip 在运行时的实例,它将动画数据解析为方便程序中做计算的数值。 Animation 在播放一个 AnimationClip 的时候,会将 AnimationClip 解析成 AnimationState。 Animation 的播放状态实际都是由 AnimationState 来计算的,包括动画是否循环,怎么循环,播放速度 等。
2.获取 AnimationState
var anim = this.getComponent(cc.Animation);
// play 会返回关联的 AnimationState
var animState = anim.play('test');
// 或是直接获取
var animState = anim.getAnimationState('test');
3.获取动画信息
var anim = this.getComponent(cc.Animation);
var animState = anim.play('test');
// 获取动画关联的clip
var clip = animState.clip;
// 获取动画的名字
var name = animState.name;
// 获取动画的播放速度
var speed = animState.speed;
// 获取动画的播放总时长
var duration = animState.duration;
// 获取动画的播放时间
var time = animState.time;
// 获取动画的重复次数
var repeatCount = animState.repeatCount;
// 获取动画的循环模式
var wrapMode = animState.wrapMode
// 获取动画是否正在播放
var playing = animState.isPlaying;
// 获取动画是否已经暂停
var paused = animState.isPaused;
// 获取动画的帧率
var frameRate = animState.frameRate;
从 AnimationState 中可以获取到所有动画的信息,你可以利用这些信息来判断需要做哪些事情。
4.设置动画播放速度
var anim = this.getComponent(cc.Animation);
var animState = anim.play('test');
// 使动画播放速度加速
animState.speed = 2;
// 使动画播放速度减速
animState.speed = 0.5;
speed 值越大速度越快,值越小则速度越慢
5.设置动画 循环模式 与 循环次数
var anim = this.getComponent(cc.Animation);
var animState = anim.play('test');
// 设置循环模式为 Normal
animState.wrapMode = cc.WrapMode.Normal;
// 设置循环模式为 Loop
animState.wrapMode = cc.WrapMode.Loop;
// 设置动画循环次数为2次
animState.repeatCount = 2;
// 设置动画循环次数为无限次
animState.repeatCount = Infinity;
AnimationState 允许动态设置循环模式,目前提供了多种循环模式,这些循环模式可以从 cc.WrapMode中获取到。 如果动画的循环类型为 Loop 类型的话,需要与 repeatCount 配合使用才能达到效果。 默认在解析动画剪辑的时候,如果动画循环类型为:
- Loop 类型,repeatCount 将被设置为 Infinity, 即无限循环
- Normal 类型,repeatCount 将被设置为 1
6.动画事件
在动画编辑器里支持可视化编辑帧事件,在脚本里书写动画事件的回调也非常简单。 动画事件的回调其实就是一个普通的函数,在动画编辑器里添加的帧事件会映射到动画根节点的组件上。
假设在动画的结尾添加了一个帧事件,如下图:
那么在脚本中可以这么写:
cc.Class({
extends: cc.Component,
onAnimCompleted: function (num, string) {
console.log('onAnimCompleted:
param1[%s], param2[%s]', num, string);
}
});
将上面的组件加到动画的 根节点 上,当动画播放到结尾时,动画系统会自动调用脚本中的 onAnimCompleted 函数。 动画系统会搜索动画根节点中的所有组件,如果组件中有实现动画事件中指定的函数的话,就会对它进行调用,并传入事件中填的参数。
7.注册动画回调
除了动画编辑器中的帧事件提供了回调外,动画系统还提供了动态注册回调事件的方式。
目前支持的回调事件有:
- play : 开始播放时
- stop : 停止播放时
- pause : 暂停播放时
- resume : 恢复播放时
- lastframe : 假如动画循环次数大于 1,当动画播放到最后一帧时
- finished : 动画播放完成时
当在 cc.Animation
注册了一个回调函数后,它会在播放一个动画时,对相应的 cc.AnimationState
注册这个回调,在 cc.AnimationState
停止播放时,对 cc.AnimationState
取消注册这个回调。
cc.AnimationState
其实才是动画回调的发送方,如果希望对单个 cc.AnimationState
注册回调的话,那么可以获取到这个 cc.AnimationState
再单独对它进行注册。
实例
var animation = this.node.getComponent(cc.Animation);
// 注册
animation.on('play', this.onPlay, this);
animation.on('stop', this.onStop, this);
animation.on('lastframe', this.onLastFrame, this);
animation.on('finished', this.onFinished, this);
animation.on('pause', this.onPause, this);
animation.on('resume', this.onResume, this);
// 取消注册
animation.off('play', this.onPlay, this);
animation.off('stop', this.onStop, this);
animation.off('lastframe', this.onLastFrame, this);
animation.off('finished', this.onFinished, this);
animation.off('pause', this.onPause, this);
animation.off('resume', this.onResume, this);
// 对单个 cc.AnimationState 注册回调
var anim1 = animation.getAnimationState('anim1');
anim1.on('lastframe', this.onLastFrame, this);
8.动态创建 Animation Clip
var animation = this.node.getComponent(cc.Animation);
// frames 这是一个 SpriteFrame 的数组.
var clip = cc.AnimationClip.createWithSpriteFrames(frames, 17);
clip.name = "anim_run";
clip.wrapMode = cc.WrapMode.Loop;
// 添加帧事件
clip.events.push({
// 准确的时间,以秒为单位。这里表示将在动画播放到 1s 时触发事件
frame: 1,
func: "frameEvent", // 回调函数名称
params: [1, "hello"] // 回调参数
});
animation.addClip(clip);
animation.play('anim_run');
9.异步加载clip放入任意node里
// 把做好的sheeprun放在resources文件夹下使用loadRes加载
// 加载 AnimationClip
var self = this;
cc.loader.loadRes("sheeprun", function (err, clip) {
let ani = self.ground.addComponent(cc.Animation);
ani.addClip(clip, "sheeprun");
ani.play("sheeprun");
ani.on('finished',self.removeAni,self);
});
removeAni(){
let ani = this.myAniNode.getComponent(cc.Animation);
ani.removeClip(ani.currentClip);
this.myAniNode.removeComponent(cc.Animation);
let sp = this.myAniNode.getComponent(cc.Sprite);
sp.spriteFrame = null;
},
这里如果不设置spriteFrame为空,clip移除后,还是会有最后一帧的图像残留在上面。