cc.Tween 缓动系统
cc.tween
能够对对象的任意属性进行缓动,功能类似于 cc.Action(动作系统)。但是 cc.tween
会比 cc.Action
更加简洁易用,因为 cc.tween
提供了链式创建的方法,可以对任何对象进行操作,并且可以对对象的任意属性进行缓动。
动作系统 是从 Cocos2d-x 迁移到 Cocos Creator 的,提供的 API 比较繁琐,只支持在节点属性上使用,并且如果要支持新的属性就需要再添加一个新的动作。为了提供更好的 API,cc.tween
在 动作系统 的基础上做了一层 API 封装。
动作系统目前已不推荐使用,未来将逐步移除,建议使用 缓动系统 做为替代。
使用对比
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()
链式 API
cc.tween
的每一个 API 都会在内部生成一个 action,并将这个 action 添加到内部队列中,在 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
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()
tween 可以支持任意对象的任意属性
let obj = { a: 0 }
cc.tween(obj).to(1, { a: 100 }).start()
修改缓动函数 Ease EaseType(动画曲线)
查看cocos的Easing类型
// 传入 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' } })
可以用自定义 progress
函数来控制缓动
可以复制当前缓动并且接受一个target
:tween.clone(cc.find('Canvas/cocos')).start()
可以不用.start()
,而是创建一些固定缓动然后再组合
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)
缓动可以借助 parallel
实现并行,而不是按照原本的sequence
的方式
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()
可以在动作执行中进行函数回调
cc.tween(this.node)
.to(2, { rotation: 90})
.to(1, { scale: 2})
// 当前面的动作都执行完毕后才会调用这个回调函数
.call(() => { cc.log('This is a callback') })
.start()
重复执行 repeat
repeatForever
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()
延迟执行
cc.tween(this.node)
// 延迟 1s
.delay(1)
.to(1, { scale: 2 })
// 再延迟 1s
.delay(1)
.to(1, { scale: 3 })
.start()
计时器
在 Cocos Creator 中,我们为组件提供了方便的计时器,这个计时器源自于 Cocos2d-x 中的 cc.Scheduler
,我们将它保留在了 Cocos Creator 中并适配了基于组件的使用方式。
也许有人会认为 setTimeout
和 setInterval
就足够了,开发者当然可以使用这两个函数,不过我们更推荐使用计时器,因为它更加强大灵活,和组件也结合得更好!
下面这个计时器将每隔 5s 执行一次。
component.schedule(function() {
this.doSomething();
}, 5);
下面的计时器将在 10 秒后开始计时,每 5 秒执行一次回调,执行 3 + 1 次。
// 以秒为单位的时间间隔
var interval = 5;
// 重复次数
var repeat = 3;
// 开始延时
var delay = 10;
component.schedule(function() {
// 这里的 this 指向 component
this.doSomething();
}, interval, repeat, delay);
只执行一次的计时器
component.scheduleOnce(function() {
this.doSomething();
}, 2);
用回调函数取消计时器
this.count = 0;
this.callback = function () {
if (this.count === 5) {
// 在第六次执行回调时取消这个计时器
this.unschedule(this.callback);
}
this.doSomething();
this.count++;
}
component.schedule(this.callback, 1);
schedule
:开始一个计时器
scheduleOnce
:开始一个只执行一次的计时器
unschedule
:取消一个计时器
unscheduleAllCallbacks
:取消这个组件的所有计时器
控制脚本的执行顺序
使用统一的控制脚本来初始化其他脚本
其中在 Player.js、Enemy.js 和 Menu.js 中需要实现 init 方法,并将初始化逻辑放进去。这样我们就可以保证 Player、Enemy 和 Menu 的初始化顺序。
// Game.js
const Player = require('Player');
const Enemy = require('Enemy');
const Menu = require('Menu');
cc.Class({
extends: cc.Component,
properties: {
player: Player,
enemy: Enemy,
menu: Menu
},
onLoad: function () {
this.player.init();
this.enemy.init();
this.menu.init();
}
});
在 Update 中用自定义方法控制更新顺序
同理如果要保证以上三个脚本的每帧更新顺序,我们也可以将分散在每个脚本里的 update 替换成自己定义的方法:updatePlayer: function (dt) { // do player update}
,然后在Game.js
中脚本的update
里调用这些
// Game.js
update: function (dt) {
this.player.updatePlayer(dt);
this.enemy.updateEnemy(dt);
this.menu.updateMenu(dt);
}
控制同一个节点上的组件执行顺序
在同一个节点上的组件脚本执行顺序:
- 可以通过组件在 属性检查器 里的排列顺序来控制。排列在上的组件会先于排列在下的组件执行。我们可以通过组件右上角的齿轮按钮里的 Move Up 和 Move Down 菜单来调整组件的排列顺序和执行顺序。
- 可以设置组件执行优先级 ,设置组件的
executionOrder
。executionOrder
会影响组件的生命周期回调的执行优先级。executionOrder
越小,相对于其他组件就会越优先执行。
cc.Class({
extends: cc.Component,
editor: {
executionOrder: -1
},
onLoad: function () {
cc.log('Player onLoad!');
}
});
标准网络接口
在 Cocos Creator 中,我们支持 Web 平台上最广泛使用的标准网络接口:
XMLHttpRequest:用于短连接
WebSocket:用于长连接
这部分暂时不了解 ,参考文档 标准网络接口
对象池
cocos自带有cc.NodePool
的对象池,使用可参考文档 使用对象池
模块化脚本
Cocos Creator 允许你将代码拆分成多个脚本文件,并且让它们相互调用。要实现这点,你需要了解如何在 Cocos Creator 中定义和使用模块,这个步骤简称为 模块化。感觉这个东西类似于引用变量,然后提供了一个public的引用进行代码之间的耦合。
如果你还不确定模块化究竟能做什么,模块化相当于:
- Java 和 Python 中的
import
- C# 中的
using
- C/C++ 中的
include
- HTML 中的
<link>
模块化使你可以在 Cocos Creator 中引用其它脚本文件:
- 访问其它文件导出的参数
- 调用其它文件导出的方法
- 使用其它文件导出的类型
- 使用或继承其它 Component
Cocos Creator 中的 JavaScript 使用和 Node.js 几乎相同的 CommonJS 标准来实现模块化,简单来说:
- 每一个单独的脚本文件就构成一个模块
- 每个模块都是一个单独的作用域
- 以 同步 的
require
方法来引用其它模块 - 设置
module.exports
为导出的变量
在本文中,“模块”和“脚本”这两个术语是等价的。所有“备注”都属于进阶内容,一开始不需要了解。
不论模块如何定义,所有用户代码最终会由 Creator 编译为原生的 JavaScript,可直接在浏览器中运行。
引用模块
var Rotate = require("Rotate");
require 返回的就是被模块导出的对象,通常我们都会将结果立即存到一个变量(var Rotate
)。传入 require
的字符串就是模块的文件名,这个名字不包含路径也不包含后缀,而且大小写敏感。
派生范例
var Rotate = require("Rotate");
var SinRotate = cc.Class({
extends: Rotate,
update: function (dt) {
this.rotation += this.speed * Math.sin(dt);
}
});
定义模块
如果你的模块是extends:cc.Component
的话,在脚本中声明了一个组件,Creator 会默认把它导出,其它脚本直接 require 这个模块就能使用这个组件。
但是模块导出的也可以是任意的JavaScript对象,只要通过 module.exports = xxx;
就可以导出
module.exports
默认是一个空对象({}),可以直接往里面增加新的字段。
module.exports = {
FOO: function () {
this.type = "foo";
},
bar: "bar"
};
在脚本内定义的变量可以通过封装来进行访问:
// foobar.js:
var dirty = false;
module.exports = {
setDirty: function () {
dirty = true;
},
isDirty: function () {
return dirty;
},
};