cocos 动画系统
前面的话
cocos 动画系统支持任意组件属性和用户自定义属性的驱动,再加上可任意编辑的时间曲线和移动轨迹编辑功能,就可以制作出各种动态效果
概述
Animation 组件可以以动画方式驱动所在节点和子节点上的节点和组件属性,包括用户自定义脚本中的属性
点击属性检查器下面的添加按钮,然后从添加其他组件中选择Animation,即可添加 Animation 组件到节点上
【属性】
Default Cilp: 默认的动画剪辑,如果这一项设置了值,并且 Play On Load 为 true,那么动画会在加载完成后自动播放 Default Clip 的内容
Clips: 列表类型,默认为空,在这里面添加的 AnimationClip 会反映到动画编辑器中,用户可以在动画编辑器里编辑 Clips 的内容
Play On Load: 布尔类型,是否在动画加载完成后自动播放 Default Clip 的内容
CLIP
cocos Animation是节点上的一个组件,clip 动画剪辑是一份动画的声明数据,将它挂载在 Animation 组件上,就可以将这份动画数据应用到节点上
数据中索引节点的方式是以挂载 Animation 组件的节点为根节点的相对路径,所以在同一个父节点下的同名节点,只能够产生一份动画数据,并且只能应用到第一个同名节点上
【参数】
sample: 定义当前动画数据每秒的帧率,默认为60,这个参数会影响时间轴上每两个整数秒刻度之间的帧数量,也就是两秒之内有多少格
speed: 当前动画的播放速度,默认为1
duration: 当动画播放速度为1时,动画的持续时间
real time: 动画从开始播放到结束,真正持续的时间
wrap mode: 循环模式
【动态创建】
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({ frame: 1, // 准确的时间,以秒为单位。这里表示将在动画播放到 1s 时触发事件 func: "frameEvent", // 回调函数名称 params: [1, "hello"] // 回调参数 }); animation.addClip(clip); animation.play('anim_run');
动画编辑器
动画在普通模式下是不允许编辑的,只有在动画编辑模式下,才能够编辑动画文件,但是在编辑模式下,无法对节点进行 增加/删除/改名操作
动画编辑器一共可以划分为 6 个主要部分
1、常用按钮区域,这里负责显示一些常用功能按钮,从左到右依次是:开关录制状态、返回第一帧、上一帧、播放/暂停、下一帧、新建动画剪辑、插入动画事件
2、时间轴与事件,这里主要是显示时间轴,添加的自定义事件也会在这里显示
3、层级管理(节点树),当前动画剪辑可以影响到的节点数据
4、节点内关键帧的预览区域,这里主要是显示各个节点上的所有帧的预览时间轴
5、属性列表,显示当前选中的节点在选中的动画剪辑中已经包含了的属性列表
6、关键帧,每个属性相对应的帧都会显示在这里
【时间轴】
时间轴上刻度的表示法是 01-05,该数值由两部分组成,冒号前面的是表示当前秒数,冒号后面的表示在当前这一秒里的第几帧
01-05 表示该刻度在时间轴上位于从动画开始经过了 1 秒又 5 帧的时间
因为帧率可以随时调整,因此同一个刻度表示的时间点也会随着帧率变化而有所不同
当帧率为 30 时,01-05 表示动画开始后 1 + 5/30 = 1.1666 秒
当帧率为 10 时,01-05 表示动画开始后 1 + 5/10 = 1.5 秒
虽然当前刻度表示的时间点会随着帧率变化,但一旦在一个位置添加了关键帧,该关键帧所在的总帧数是不会改变的,假如我们在帧率 30 时向 01-05 刻度上添加了关键帧,该关键帧位于动画开始后总第 35 帧。之后把帧率修改为 10,该关键帧仍然处在动画开始后第 35 帧,而此时关键帧所在位置的刻度读数为 03-05,换算成时间以后正好是之前的 3 倍
【基本操作】
更改时间轴缩放比例:滚动鼠标滚轮,可以放大,或者缩小时间轴的显示比例 移动显示区域: 按下鼠标右键拖拽 更改当前选中的时间轴节点: 在时间轴区域内点击任意位置或拖拽,或者在上图 4 区域拖拽标示的红线 修改 clip 属性: 在插件底部,修改对应属性,在输入框失去焦点时会更新到实际的 clip 数据中
【快捷键】
left 向前移动一帧,如果已经在第 0 帧,则忽略当前操作 right 向后移动一帧 delete 删除当前选中的关键帧 k 正向播放动画,抬起后停止 j 反向插话动画,抬起后停止 cmd + left 跳转到第 0 帧 cmd + right 跳转到有效的最后一帧
动画剪辑
【创建 Animation 组件】
如果要在节点上创建动画,必须为它新建一个 Animation 组件,创建的方法有两种:
1、选中相应的节点,在属性检查器中点击右上方的 +,或者下方的 添加组件, 在其他组件中选择 Animation
2、打开动画编辑器,然后在层级管理器中选中需要添加动画的节点,在动画编辑器中点击 添加Animation组件 按钮
【创建与挂载动画剪辑】
动画剪辑有两种创建方式:
1、在资源管理器中点击左上方的 +,或者右键空白区域,选择 Animation Clip,这时会在管理器中创建一个名为 'New AnimationClip'的剪辑文件。在层级管理器中点选刚刚的节点,在属性检查器中找到 Animation,这时的 Clips 显示的是 0,将它改成1,然后将刚刚在资源管理器中创建的'New AnimationClip',拖入刚刚出现的 animation-clip 选择框内
2、如果 Animation 组件中还没有添加动画剪辑文件,则可以在动画编辑器中直接点击 新建 AnimationClip 按钮,根据弹出的窗口创建一个新的动画剪辑文件。要注意的是,如果选择覆盖已有的剪辑文件,被覆盖的文件内容会被清空
【数据剪辑】
一个动画剪辑内可能包含了多个节点,每一个节点上挂在多个动画属性,每个属性内的数据才是实际的关键帧
动画剪辑通过节点的名字定义数据的位置,本身忽略了根节点,其余的子节点通过与根节点的相对路径索引找到对应的数据
如果在制作完成动画后,将节点重命名,会造成动画数据出现问题,如下图所示
这时,要手动指定数据对应的节点,可以将鼠标移入节点,点击节点右侧出现的更多按钮,并选择“移动数据”
如上图所示,New Node/test 节点没有数据,想将 /New Node/efx_flare 上的数据移到这里
1、鼠标移动丢失的节点 /New Node/efx_flare 上
2、点击右侧出现的按钮
3、选择移动数据
4、将路径改为 /New Node/test,并回车
编辑动画序列
动画属性包括了节点自有的 position、rotation 等属性,也包含了组件 Component 中自定义的属性。组件包含的属性会加上组件的名字,比如 cc.Sprite.spriteFrame
【添加新的属性轨道】
先选中节点,然后在属性区域右上角点击 +。弹出菜单中,选中想要添加的属性,就会对应新增一个轨道
【删除一个属性轨道】
将鼠标焦点移动到要删除的属性轨道上,右边会显示一个“三道杠”按钮,点击按钮,在弹出菜单中选择删除属性,选中后对应的属性就会从动画数据中删除
【添加关键帧】
在属性列表中点击对应属性轨道右侧的“三道杠”按钮,在弹出的菜单中选择 插入关键帧 按钮
【选择关键帧】
点击创建的关键帧后,关键帧会呈现选中状态,此时关键帧由蓝变白,如果需要多选,可以按住 ctrl 再次选择其他关键帧,或者直接在属性区域拖拽框选择
【移动关键帧】
将鼠标移动到任意一个被选中的关键帧上,按下鼠标左键,鼠标会变换成左右箭头,这时就可以拖拽所有被选中的节点了
【更改关键帧】
在时间轴上需要修改的关键帧,直接在属性检查器内修改相对应的属性即可(要确保动画编辑器处于编辑状态)。例如,属性列表中 position、x、y 三个属性轨道,选中关键帧后,可以修改属性检查器中的 position、x、y 属性
或者在时间轴上选择一个没有关键帧的位置,然后在属性检查器中修改相对应的属性,便会自动插入一帧
【删除关键帧】
选中关键帧后,点击对应属性轨道的“三道杠”按钮,选择删除选中帧,或者直接按下键盘上的 delete 按键,则所有被选中的节点都会被删除
【复制关键帧】
在动画编辑器内选中关键帧后,可以按下 cmd + c 复制当前的关键帧,然后选中某一个时间轴上的点,按下 cmd + v 将刚刚复制的关键帧粘贴到选中的时间点上
【节点操作】
动画是按照节点的名字来进行索引关联的,有时会在层级管理器内改变节点的层级关系,而动画编辑器内的动画就会找不到当初指定对应的节点
这时我们需要手动更改一下动画上节点的搜索路径:
1、鼠标移动到要迁移的节点上,点击右侧出现的菜单按钮
2、选择移动节点数据
3、修改节点的路径数据
编辑序列帧动画
下面来看具体怎么创建一个帧动画
1、首先需要让节点正常显示纹理,为节点增加 Sprite 组件,选中节点后在属性检查器中通过 添加组件 按钮,选择 添加渲染组件 -> Sprite
2、节点可以正常显示纹理后,还需要为纹理创建一个动画轨道。在动画编辑器中点击 add Property,然后选择 cc.Sprite.spriteFrame
3、从资源管理器中,将纹理拖拽到属性帧区域,放在 cc.Sprite.spriteFrame 轨道上,再将下一帧需要显示的纹理拖到指定位置,然后点击播放就可以预览刚刚创建的动画了
编辑时间曲线
有时,我们需要在两帧之间实现 EaseInOut 等缓动效果,需要在一条轨道上创建两个不相等的帧,比如在 position 上创建两帧,从 0,0 到 100,100,这里两帧之间会出现一根连接线(连接两关键帧之间的蓝色线段),双击连接线,则可以打开时间曲线编辑器
在曲线编辑器左侧可以选择预设的各种效果,比如 EaseIn等,选中后右侧上方还会出现一些预设的参数,可以根据需求选择
当然,也可以自己修改曲线,右侧预览图内,有两个灰色的控制点,拖拽控制点可以更改曲线的轨迹。如果控制点需要拖出视野外,则可以使用鼠标滚轮或右上角的小比例尺缩放预览图,支持的比例从 0.1 到 1
添加动画事件
【添加事件】
首先选中某个位置,然后点击按钮区域最右侧的按钮,这时在时间轴上会出现一个白色的小块,这就是添加的事件
【删除事件】
双击刚刚出现的白色小块,打开事件编辑器后点击 function 后面的回收图标,会提示是否删除这个 event,点击确认则删除。也可以在动画编辑器中右键点击 event,选择 删除
【设置事件】
双击刚刚出现的白色小块,打开事件编辑器,在编辑器内,可以手动输入需要触发的 funtion 名字,触发时会根据这个函数名,去各个组件内匹配相应的方法
如果需要添加传入的参数,则在 Params 旁点击 + 或者 -,只支持 Boolean、String、Number 三种类型的参数
脚本控制Animation
Animation 组件提供了一些常用的动画控制函数,如果只是需要简单的控制动画,可以通过获取节点的 Animation 组件来做一些操作
【播放】
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 会先停止这个动画,再重新播放动画
【播放多个】
Animation 支持同时播放多个动画,播放不同的动画并不会影响其他动画的播放状态,这对于做一些复合动画有帮助
var anim = this.getComponent(cc.Animation); // 播放第一个动画 anim.playAdditive('position-anim'); // 播放第二个动画 // 使用 playAdditive 播放动画时,不会停止其他动画的播放。如果还有其他动画正在播放,则同时会有多个动画进行播放 anim.playAdditive('rotation-anim');
【暂停和停止】
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();
【设置动画当前时间】
可以在任何时候对动画设置当前时间,但是动画不会立刻根据设置的时间进行状态的更改,需要在下一个动画的 update 中才会根据这个时间重新计算播放状态
var anim = this.getComponent(cc.Animation); anim.play('test'); // 设置 test 动画的当前播放时间为 1s anim.setCurrentTime(1, 'test'); // 设置所有动画的当前播放时间为 1s // anim.setCurrentTime(1);
AnimationState
Animation 只提供了一些简单的控制函数,希望得到更多的动画信息和控制的话,需要使用到 AnimationState
如果说 AnimationClip 作为动画数据的承载,那么 AnimationState 则是 AnimationClip 在运行时的实例,它将动画数据解析为方便程序中做计算的数值。 Animation 在播放一个 AnimationClip 的时候,会将 AnimationClip 解析成 AnimationState。 Animation 的播放状态实际都是由 AnimationState 来计算的,包括动画是否循环,怎么循环,播放速度等
【获取 AnimationState】
var anim = this.getComponent(cc.Animation); // play 会返回关联的 AnimationState var animState = anim.play('test'); // 或是直接获取 var animState = anim.getAnimationState('test');
【获取动画信息】
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;
【设置动画播放速度】
speed 值越大,速度越快,值越小则速度越慢
var anim = this.getComponent(cc.Animation); var animState = anim.play('test'); // 使动画播放速度加速 animState.speed = 2; // 使动画播放速度减速 animState.speed = 0.5;
【设置动画循环模式和循环次数】
AnimationState 允许动态设置循环模式,目前提供了多种循环模式,这些循环模式可以从 cc.WrapMode中获取到。如果动画的循环类型为 Loop 类型的话,需要与 repeatCount 配合使用才能达到效果。 默认在解析动画剪辑的时候,如果动画循环类型为 Loop 类型,repeatCount 将被设置为 Infinity,即无限循环;如果动画循环类型为 Normal 类型,repeatCount 将被设置为 1
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;
动画事件
动画事件的回调其实就是一个普通的函数,在动画编辑器里添加的帧事件会映射到动画根节点的组件上
假设在动画的结尾添加了一个帧事件,如下图
那么,在脚本中可以这么写:
cc.Class({ extends: cc.Component, onAnimCompleted: function (num, string) { console.log('onAnimCompleted: param1[%s], param2[%s]', num, string); } });
将上面的组件加到动画的 根节点 上,当动画播放到结尾时,动画系统会自动调用脚本中的 onAnimCompleted
函数。 动画系统会搜索动画根节点中的所有组件,如果组件中有实现动画事件中指定的函数的话,就会对它进行调用,并传入事件中填的参数
要特别注意的是,该脚本必须绑定到 node 节点上,否则脚本中的函数将不会被执行
【注册事件回调】
除了动画编辑器中的帧事件提供了回调外,动画系统还提供了动态注册回调事件的方式
目前支持的回调事件有:
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);
好的代码像粥一样,都是用时间熬出来的