前言:
要使得一个角色动起来,CocosCreator提供了动作系统,里面有很多的API函数,可以通过调用不同的API函数来控制角色的运动,动作系统可以在一定时间内对节点完成位移,缩放,旋转等各种动作。需要注意的是,动作系统并不能取代 动画系统,动作系统提供的是面向程序员的 API 接口,而动画系统则是提供在编辑器中来设计的。
一、动作系统应用
1. 动作系统API
这里提醒一下:这些动作系统的API都是相对节点而言的,是用来操作节点的,千万别给组件加动作.
一定用在节点上
// 创建一个动作
var action = cc.moveTo(1, 100, 100);// 参数:维持时间、X坐标、y坐标
// 节点执action行动作
node.runAction(action);
// 停止一个动作
node.stopAction(action);
// 停止所有动作
node.stopAllActions();
开发者还可以给动作设置 tag,并通过 tag 来控制动作。
这就相当于给一个动作贴上一个标签,编一个号之类的。
// 给 action 设置 tag
var action_tag = 1;
action.setTag(action_tag);
// 通过 tag 获取 action
node.getActionByTag(action_tag);
// 通过 tag 停止一个动作
node.stopActionByTag(action_tag);
2. 动作类型
由于CocosCreator里面的动作有很多,这里介绍一些很常用的。
基础动作
基础动作就是实现各种形变,位移动画的动作,比如
cc.moveTo
用来移动节点到某个位置;
cc.rotateBy
用来旋转节点一定的角度;
cc.scaleTo
用来缩放节点。
基础动作中分为时间间隔动作和即时动作:
时间间隔动作是在一定时间间隔内完成的渐变动作,前面提到的都是时间间隔动作,它们全部继承自 cc.ActionInterval
。
即时动作则是立即发生的,比如用来调用回调函数的cc.callFunc
;用来隐藏节点的cc.hide
,它们全部继承自cc.ActionInstant
。
容器动作
容器动作就是对已有的一些动作惊进行管理和修饰,使其具有一些特定的效果。
1. 顺序动作cc.sequence
:
顺序动作可以让一系列子动作按顺序一个个执行。
// 让节点在两个点之间来回移动
var seq = cc.sequence(cc.moveBy(1, 200, 0), cc.moveBy(1, -200, 0));
node.runAction(seq);
2. 同步动作cc.spawn
:
同步动作可以同步执行对一系列子动作,子动作的执行结果会叠加起来
修改节点的属性。
// 让节点在向上移动的同时缩放
var spawn = cc.spawn(cc.moveBy(1, 0, 50), cc.scaleTo(0.5, 0.8, 1.4));
node.runAction(spawn);
3. 重复动作 cc.repeat
:
重复动作用来多次重复一个动作。
// 让节点左右来回移动,并重复5次
var seq = cc.repeat(
cc.sequence(
cc.moveBy(2, 200, 0),
cc.moveBy(2, -200, 0)
), 5);
node.runAction(seq);
4. 永远重复动作 cc.repeatForever
:
顾名思义,这个动作容器可以让目标动作一直重复,直到手动停止。
// 让节点左右来回移动并一直重复
var seq = cc.repeatForever(
cc.sequence(
cc.moveBy(2, 200, 0),
cc.moveBy(2, -200, 0)
));
5. 速度动作cc.speed
:
速度动作可以改变目标动作的执行速率,让动作更快或者更慢完成。
// 让目标动作速度加快一倍,相当于原本2秒的动作在1秒内完成
var action = cc.speed(
cc.spawn(
cc.moveBy(2, 0, 50),
cc.scaleTo(2, 0.8, 1.4)
), 2);
node.runAction(action);
缓动动作
缓动动作不可以单独存在,它永远是为了修饰基础动作而存在的,它可以用来修改基础动作的时间曲线,让动作有快入、缓入、快出或其它更复杂的特效
。需要注意的是,只有时间间隔动作才支持缓动:
var action = cc.scaleTo(0.5, 2, 2);
action.easing(cc.easeIn(3.0));
基础的缓动动作类是 cc.ActionEase
。
回调动作
第一个参数是一个执行时函数,第二个参数指定了处理回调方法的 context(也就是绑定 this),第三个参数是向处理回调方法的传参。
// 动作回调函数的声明:两种
var finished = cc.callFunc(this.myMethod, this, opt);// 方法一
var finished = cc.callFunc(function(target, score) { // 方法二
this.score += score;
}, this, 100);//动作完成后会给玩家加100分
// 在声明了回调动作 finished 后,您可以配合 cc.sequence 来执行一整串动作并触发回调:
var myAction = cc.sequence(cc.moveBy(1, cc.v2(0, 100)), cc.fadeOut(1), finished);
// 在同一个 sequence 里也可以多次插入回调:
var myAction = cc.sequence(cc.moveTo(1, cc.v2(0, 0)), finished1, cc.fadeOut(1), finished2);
// 注意:finished1, finished2 都是使用 cc.callFunc 定义的回调动作
注意: 在 cc.callFunc 中不应该停止自身动作,由于动作是不能被立即删除,
如果在动作回调中暂停自身动作会引发一系列遍历问题,导致更严重的 bug。
二、动作汇总
容器动作
动作名称 | 描述 | 动作名称 | 描述 |
---|---|---|---|
cc.sequence | 顺序执行动作 | cc.spawn | 同步执行动作 |
cc.repeat | 重复执行动作 | cc.repeatForever | 永远重复动作 |
cc.speed | 修改动作速率 API 描述 |
即时动作
动作名称 | 描述 | 动作名称 | 描述 |
---|---|---|---|
cc.show | 立即显示 | cc.hide | 立即隐藏 |
cc.toggleVisibility | 显隐状态切换 | cc.removeSelf | 从父节点移除自身 |
cc.flipX | X轴翻转 | cc.flipY | Y轴翻转 |
cc.place | 放置在目标位置 | cc.callFunc | 执行回调函数 |
cc.targetedAction | 用已有动作和一个新的目标节点创建动作 |
时间间隔动作
动作名称 | 描述 | 动作名称 | 描述 |
---|---|---|---|
cc.moveTo | 移动到目标位置 | cc.moveBy | 移动指定的距离 |
cc.rotateTo | 旋转到目标角度 | cc.rotateBy | 旋转指定的角度 |
cc.scaleTo | 将节点大小缩放到指定的倍数 | cc.scaleBy | 按指定的倍数缩放节点大小 |
cc.skewTo | 偏斜到目标角度 | cc.skewBy | 偏斜指定的角度 |
cc.jumpBy | 用跳跃的方式移动指定的距离 | cc.jumpTo | 用跳跃的方式移动到目标位置 |
cc.follow | 追踪目标节点的位置 | cc.bezierTo | 按贝赛尔曲线轨迹移动到目标位置 |
cc.bezierBy | 按贝赛尔曲线轨迹移动指定的距离 | cc.blink | 闪烁(基于透明度) |
cc.fadeTo | 修改透明度到指定值 | cc.cardinalSplineTo | 按基数样条曲线轨迹移动到目标位置 |
cc.fadeOut | 渐隐 | cc.catmullRomTo | 按 Catmull Rom 样条曲线轨迹移动到目标位置 |
cc.tintBy | 按照指定的增量修改颜色 | cc.delayTime | 延迟指定的时间量 |
cc.reverseTime | 反转目标动作的时间轴 | cc.fadeIn | 渐显 |
cc.cardinalSplineBy | 按基数样条曲线轨迹移动指定的距离 | cc.tintTo | 修改颜色到指定值 |
cc.catmullRomBy | 按 Catmull Rom 样条曲线轨迹移动指定的距离 |
缓动动作
cc.easeIn | cc.easeOut | cc.easeInOut | cc.easeExponentialIn |
---|---|---|---|
cc.easeExponentialOut | cc.easeExponentialInOut | cc.easeSineIn | cc.easeSineOut |
cc.easeSineInOut | cc.easeElasticIn | cc.easeElasticOut | cc.easeElasticInOut |
cc.easeBounceIn | cc.easeBounceOut | cc.easeBounceInOut | cc.easeBackIn |
cc.easeBackOut | cc.easeBackInOut | cc.easeBezierAction | cc.easeQuadraticActionIn |
cc.easeQuadraticActionOut | cc.easeQuadraticActionInOut | cc.easeQuarticActionIn | cc.easeQuarticActionOut |
三、缓动系统(cc.tween)
cc.tween
会比 cc.Action
更加简洁易用,因为 cc.tween 提供了链式创建的方法,可以对任何对象进行操作,并且可以对对象的任意属性进行缓动。
动作系统只支持在节点属性上使用,并且如果要支持新的属性就需要再添加一个新的动作。
为了提供更好的 API, cc.tween 在 动作系统 的基础上做了一层 API 封装
。
下面是 cc.Action 与 cc.tween 在使用上的对比:
// cc.Action:
this.node.runAction(
cc.sequence(
cc.spawn(
cc.moveTo(1, 100, 100),
cc.rotateTo(1, 360),
),
cc.scale(1, 2)
)
)
// cc.tween:
cc.tween(this.node)
.to(1, { position: cc.v2(100, 100), rotation: 360 })
.to(1, { scale: 2 })
.start()
1. 链式API
cc.tween 在调用 start 时会将之前生成的 action 队列重新组合生成一个 cc.sequence
队列,所以 cc.tween 的链式结构是依次执行每一个 API 的
,也就是会执行完一个 API 再执行下一个 API
。
cc.tween(this.node)
// 0s 时,node 的 scale 还是 1
.to(1, { scale: 2 })
// 1s 时,执行完第一个 action,scale 为 2
.to(1, { scale: 3 })
// 2s 时,执行完第二个 action,scale 为 3
.start()
// 调用 start 开始执行 cc.tween
2. 设置缓动属性
cc.tween 提供了两个设置属性的 API:
• to
:对属性进行绝对
值计算,最终的运行结果即是设置的属性值;
• by
:对属性进行相对
值计算,最终的运行结果是设置的属性值加上开始
运行时节点的属性值;
cc.tween(node)
.to(1, {scale: 2}) // node.scale === 2
.by(1, {scale: 2}) // node.scale === 4 (2+2)
.by(1, {scale: 1}) // node.scale === 5
.to(1, {scale: 2}) // node.scale === 2
.start()
3. 支持缓动任意对象的任意属性
let obj = { a: 0 }
cc.tween(obj)
.to(1, { a: 100 })
.start()
4. 同时执行多个属性
cc.tween(this.node)
// 同时对 scale, position, rotation 三个属性缓动
.to(1, { scale: 2, position: cc.v2(100, 100), rotation: 90 })
.start()
5. easing
你可以使用 easing 来使缓动更生动, cc.tween 针对不同的情况提供了多种使用方式。
// 传入 easing 名字,直接使用内置 easing 函数
cc.tween().to(1, { scale: 2 }, { easing: 'sineOutIn'})
// 使用自定义 easing 函数
cc.tween().to(1, { scale: 2 }, { easing: t => t*t; })
// 只对单个属性使用 easing 函数
// value 必须与 easing 或者 progress 配合使用
cc.tween().to(1, { scale: 2, position: { value: cc.v3(100, 100, 100), easing: 'sineOutIn' } })
6. 自定义 progress
相对于 easing,自定义 progress 函数可以更自由的控制缓动的过程。
// 对所有属性自定义 progress
cc.tween().to(1, { scale: 2, rotation: 90 }, {
progress: (start, end, current, ratio) => {
return start + (end - start) * ratio;
}
})
// 对单个属性自定义 progress
cc.tween().to(1, {
scale: 2,
position: {
value: cc.v3(),
progress: (start, end, current, t) => {
// 注意,传入的属性为 cc.Vec3,所以需要使用 Vec3.lerp 进行插值计算
return start.lerp(end, t, current);
}
}
})
7. 复制缓动
clone 函数会克隆一个当前的缓动,并接受一个 target 作为参数。
// 先创建一个缓动作为模板
let tween = cc.tween().to(4, { scale: 2 })
// 复制 tween,并使用节点 Canvas/cocos 作为 target
tween.clone(cc.find('Canvas/cocos')).start()
// 复制 tween,并使用节点 Canvas/cocos2 作为 target
tween.clone(cc.find('Canvas/cocos2')).start()
8. 插入其他的缓动到队列中
你可以事先创建一些固定的缓动,然后通过组合这些缓动形成新的缓动来减少代码的编写。
let scale = cc.tween().to(1, { scale: 2 })
let rotate = cc.tween().to(1, { rotation: 90})
let move = cc.tween().to(1, { position: cc.v3(100, 100, 100)})
// 先缩放再旋转
cc.tween(this.node).then(scale).then(rotate)
// 先缩放再移动
cc.tween(this.node).then(scale).then(move)
9. 并行执行缓动
cc.tween 在链式执行时是按照 sequence 的方式来执行的,但是在编写复杂缓动的时候可能会需要同时并行执行多个队列, cc.tween 提供了 parallel 接口来满足这个需求。
let t = cc.tween;
t(this.node)
// 同时执行两个 cc.tween
.parallel(
t().to(1, { scale: 2 }),
t().to(2, { position: cc.v2(100, 100) })
)
.call(() => {
console.log('All tweens finished.')
})
.start()
10. 回调
cc.tween(this.node)
.to(2, { rotation: 90})
.to(1, { scale: 2})
// 当前面的动作都执行完毕后才会调用这个回调函数
.call(() => { cc.log('This is a callback') })
.start()
11. 重复执行
repeat/repeatForever 函数会将前一个 action 作为作用对象。但是如果有参数提供了其他的 action 或者 tween,则 repeat/repeatForever 函数会将传入的 action 或者 tween 作为作用对象。
cc.tween(this.node)
.by(1, { scale: 1 })
// 对前一个 by 重复执行 10次
.repeat(10)
// 最后 node.scale === 11
.start()
// 也可以这样用
cc.tween(this.node)
.repeat(10,
cc.tween().by(1, { scale: 1 })
)
.start()
// 一直重复执行下去
cc.tween(this.node)
.by(1, { scale: 1 })
.repeatForever()
.start()
12. 延迟执行
cc.tween(this.node)
// 延迟 1s
.delay(1)
.to(1, { scale: 2 })
// 再延迟 1s
.delay(1)
.to(1, { scale: 3 })
.start()
这篇动作篇有点长,不过这些都挺重要的,还是要记住,这样在游戏开发时能够能更快,代码也可以更精炼,效果更好!