动画系统
可以参考 博客 Cocos Creator Animation 组件
Animation 也不例外,它也是节点上的一个组件。
AnimaitonClip动画剪辑就是一份动画的声明数据,我们将它挂载到 Animation 组件上,就能够将这份动画数据应用到节点上。
动画编辑器
left:向前移动一帧,如果已经在第 0 帧,则忽略当前操作
right:向后移动一帧
delete:删除当前所选中的关键帧
k:正向的播放动画,抬起后停止
j:反向播放动画,抬起后停止
ctrl / cmd + left:跳转到第 0 帧
ctrl / cmd + right:跳转到有效的最后一帧
时间轴上刻度的表示法是 01-05。
该数值由两部分组成,冒号前面的是表示当前秒数,冒号后面的表示在当前这一秒里的第几帧。
01-05 表示该刻度在时间轴上位于从动画开始经过了 1 秒又 5 帧的时间。
节点数据 动画剪辑通过节点的名字定义数据的位置,本身忽略了根节点,其余的子节点通过与根节点的 相对路径 索引找到对应的数据。
动画属性包括了节点自有的 position
、rotation
等属性,也包含了组件 Component
中自定义的属性。 组件包含的属性前会加上组件的名字,比如 cc.Sprite.spriteFrame
。
添加关键帧 在属性列表中点击对应属性轨道右侧的选项按钮,在弹出的菜单中选择 插入关键帧
按钮。也可以直接在编辑模式下直接更改
双击关键帧的连接线,就可以打开时间曲线编辑器
选中某个位置,然后点击按钮区域最左侧的按钮(add event),这时候在时间轴上会出现一个白色的矩形,这就是我们添加的事件。双击刚刚出现的白色矩形,可以打开事件编辑器,在编辑器内,我们可以手动输入需要触发的 function 名字,触发的时候会根据这个函数名,去各个组件内匹配相应的方法。
用脚本控制动画
var anim = this.getComponent(cc.Animation);
// 如果没有指定播放哪个动画,并且有设置 defaultClip 的话,则会播放 defaultClip 动画
anim.play();
// 指定播放 test 动画
anim.play('test');
// 指定从 1s 开始播放 test 动画
anim.play('test', 1);
// 使用 play 接口播放一个动画时,如果还有其他的动画正在播放,则会先停止其他动画
anim.play('test2');
// 播放第一个动画
anim.playAdditive('position-anim');
// 播放第二个动画
// 使用 playAdditive 播放动画时,不会停止其他动画的播放。如果还有其他动画正在播放,则同时会有多个动画进行播放
anim.playAdditive('rotation-anim');
// 指定暂停 test 动画
anim.pause('test');
// 暂停所有动画
anim.pause();
// 指定恢复 test 动画
anim.resume('test');
// 恢复所有动画
anim.resume();
// 指定停止 test 动画
anim.stop('test');
// 停止所有动画
anim.stop();
// 设置 test 动画的当前播放时间为 1s
anim.setCurrentTime(1, 'test');
// 设置所有动画的当前播放时间为 1s
anim.setCurrentTime(1);
Animation State
如果说 AnimationClip 是作为动画数据的承载,那么 AnimationState 则是 AnimationClip 在运行时的实例,它将动画数据解析为方便程序中做计算的数值。
Animation 在播放一个 AnimationClip 的时候,会将 AnimationClip 解析成 AnimationState。
Animation 的播放状态实际都是由 AnimationState 来计算的,包括动画是否循环、怎么循环、播放速度等
获取
var anim = this.getComponent(cc.Animation);
// play 会返回关联的 AnimationState
var animState = anim.play('test');
// 或者直接获取
var animState = anim.getAnimationState('test');
// 获取动画关联的 clip
var clip = animState.clip;
// 获取动画的名字
var name = animState.name;
// 获取动画的播放速度
var speed = animState.speed;
// 使动画播放速度加速
animState.speed = 2;
// 使动画播放速度减速
animState.speed = 0.5;
// 获取动画的播放总时长
var duration = animState.duration;
// 获取动画的播放时间
var time = animState.time;
// 获取动画的重复次数
var repeatCount = animState.repeatCount;
// 获取动画的循环模式
var wrapMode = animState.wrapMode
// 设置循环模式为 Normal
animState.wrapMode = cc.WrapMode.Normal;
// 设置循环模式为 Loop
animState.wrapMode = cc.WrapMode.Loop;
// 设置动画循环次数为 2 次
animState.repeatCount = 2;
// 设置动画循环次数为无限次
animState.repeatCount = Infinity;
// 获取动画是否正在播放
var playing = animState.isPlaying;
// 获取动画是否已经暂停
var paused = animState.isPaused;
// 获取动画的帧率
var frameRate = animState.frameRate;
动画帧事件:
cc.Class({
extends: cc.Component,
onAnimCompleted: function (num, string) {
console.log('onAnimCompleted: param1[%s], param2[%s]', num, string);
}
});
将上面的组件加到动画的 根节点 上,当动画播放到结尾时,动画系统会自动调用脚本中的 onAnimCompleted 函数。动画系统会搜索动画根节点中的所有组件,如果组件中有实现动画事件中指定的函数的话,就会对它进行调用,并传入事件中填的参数。
并且可以提供注册动画回调以及 动态创建 Animation Clip
音乐与音效
audioSource: { type: cc.AudioSource,default: null },
AudioSource 播放
cc.audioEngine.play(audio, loop, volume);
AudioEngine 播放
AudioEngine 与 AudioSource 都能播放音频,它们的区别在于 AudioSource 是组件,可以添加到场景中,由编辑器设置。而 AudioEngine 是引擎提供的纯 API,只能在脚本中进行调用。
一些移动端的浏览器或 WebView 不允许自动播放音频,用户需要在触摸事件中手动播放音频。
cc.Class({
extends: cc.Component,
properties: {
audioSource: cc.AudioSource
},
start () {
let canvas = cc.find('Canvas');
canvas.on('touchstart', this.playAudio, this);
},
playAudio () {
this.audioSource.play();
}
});
微信自动音乐播放,并在引擎启动之后,使用其他方式播放音频的时候停止这个音频的播放。
document.addEventListener('WeixinJSBridgeReady', function () {
cc.resources.load('audio/music_logo', cc.AudioClip, (err, audioClip) => {
var audioSource = this.addComponent(cc.AudioSource);
audioSource.clip = audioClip;
audioSource.play();
});
});
物理与碰撞系统
碰撞系统
Cocos Creator 内置了一个简单易用的碰撞检测系统,支持 圆形、矩形 以及 多边形 相互间的碰撞检测
点击组件的edit
来修改碰撞盒的大小
分组:通过 菜单栏 - 项目 - 项目设置 :设立新的分组,在 分组列表 下面可以进行 碰撞分组配对 表的管理
碰撞系统的脚本控制
当一个碰撞组件被启用时,这个碰撞组件会被自动添加到碰撞检测系统中,并搜索能与之进行碰撞的其他已添加的碰撞组件来生成一个碰撞对。需要注意的是,一个节点上的碰撞组件,无论如何都是不会相互进行碰撞检测的。
获取碰撞检测系统
var manager = cc.director.getCollisionManager();
manager.enabled = true;
manager.enabledDebugDraw = true;
manager.enabledDrawBoundingBox = true;
碰撞系统回调
当碰撞系统检测到有碰撞产生时,将会以回调的方式通知使用者,如果产生碰撞的碰撞组件依附的节点下挂的脚本中有实现以下函数,则会自动调用以下函数,并传入相关的参数。
/*
* @param {Collider} other 产生碰撞的另一个碰撞组件
* @param {Collider} self 产生碰撞的自身的碰撞组件
*/
onCollisionEnter: function (other, self) {
console.log('on collision enter');
// 碰撞系统会计算出碰撞组件在世界坐标系下的相关的值,并放到 world 这个属性里面
var world = self.world;
// 碰撞组件的 aabb 碰撞框
var aabb = world.aabb;
// 节点碰撞前上一帧 aabb 碰撞框的位置
var preAabb = world.preAabb;
// 碰撞框的世界矩阵
var t = world.transform;
// 以下属性为圆形碰撞组件特有属性
var r = world.radius;
var p = world.position;
// 以下属性为 矩形 和 多边形 碰撞组件特有属性
var ps = world.points;
},
onCollisionStay: function (other, self) {
console.log('on collision stay');
},
onCollisionExit: function (other, self) {
console.log('on collision exit');
}
碰撞组件
一个节点上可以挂多个碰撞组件,这些碰撞组件之间可以是不同类型的碰撞组件。
碰撞组件目前包括了 Polygon(多边形),Circle(圆形),Box(矩形) 这几种碰撞组件,这些组件都继承自 Collider 组件,所以 Collider 组件的属性它们也都享有。
物理系统
Cocos-creator 的默认物理范例 这个范例里面有不少很不错的代码方法,包括切割、弹性、速度,引力等。
物理系统将 box2d 作为内部物理系统,并且隐藏了大部分 box2d 实现细节(比如创建刚体,同步刚体信息到节点中等)。 你可以通过物理系统访问一些 box2d 常用的功能,比如点击测试,射线测试,设置测试信息等。
开启物理系统
cc.director.getPhysicsManager().enabled = true;
开启绘制
cc.director.getPhysicsManager().debugDrawFlags = cc.PhysicsManager.DrawBits.e_aabbBit |
cc.PhysicsManager.DrawBits.e_pairBit |
cc.PhysicsManager.DrawBits.e_centerOfMassBit |
cc.PhysicsManager.DrawBits.e_jointBit |
cc.PhysicsManager.DrawBits.e_shapeBit
;
设置物理重力,默认的重力加速度是 (0, -320) 世界单位/秒^2,按照上面描述的转换规则,即 (0, -10) 米/秒^2
cc.director.getPhysicsManager().gravity = cc.v2();
cc.director.getPhysicsManager().gravity = cc.v2(0, -640);
物理刷新率/步长,可以进行修改,或许类似FixedUpdate()
var manager = cc.director.getPhysicsManager();
// 开启物理步长的设置
manager.enabledAccumulator = true;
// 物理步长,默认 FIXED_TIME_STEP 是 1/60
manager.FIXED_TIME_STEP = 1/30;
// 每次更新物理系统处理速度的迭代次数,默认为 10
manager.VELOCITY_ITERATIONS = 8;
// 每次更新物理系统处理位置的迭代次数,默认为 10
manager.POSITION_ITERATIONS = 8;
查询物体
物理系统提供了几个方法来高效快速地查找某个区域中有哪些物体,每种方法通过不同的方式来检测物体,基本满足游戏所需。
点测试
var collider = cc.director.getPhysicsManager().testPoint(point);
//矩形
var colliderList = cc.director.getPhysicsManager().testAABB(rect);
射线检测
var results = cc.director.getPhysicsManager().rayCast(p1, p2, type);
for (var i = 0; i < results.length; i++) {
var result = results[i];
var collider = result.collider;
var point = result.point;
var normal = result.normal;
var fraction = result.fraction;
}
最后一个参数指定了检测的类型:
cc.RayCastType.Any
检测射线路径上任意的碰撞体,一旦检测到任何碰撞体,将立刻结束检测其他的碰撞体,最快。
cc.RayCastType.Closest
检测射线路径上最近的碰撞体,这是射线检测的默认值,稍慢。
cc.RayCastType.All
检测射线路径上的所有碰撞体,检测到的结果顺序不是固定的。在这种检测类型下,一个碰撞体可能会返回多个结果,这是因为 box2d 是通过检测夹具(fixture)来进行物体检测的,而一个碰撞体中可能由多个夹具(fixture)组成的,慢。
cc.RayCastType.AllClosest
检测射线路径上所有碰撞体,但是会对返回值进行删选,只返回每一个碰撞体距离射线起始点最近的那个点的相关信息,最慢。
射线检测的结果
射线检测的结果包含了许多有用的信息,你可以根据实际情况来选择如何使用这些信息。
collider
指定射线穿过的是哪一个碰撞体。
point
指定射线与穿过的碰撞体在哪一点相交。
normal
指定碰撞体在相交点的表面的法线向量。
fraction
指定相交点在射线上的分数。
刚体 RigidBody
刚体是组成物理世界的基本对象,你可以将刚体想象成一个你不能看到(绘制)也不能摸到(碰撞)的带有属性的物体(没有渲染也没有碰撞)。
刚体属性:
质量 var mass = rigidbody.getMass();
刚体的质量是通过 碰撞组件 的 密度 与 大小 自动计算得到的。当你需要计算物体应该受到多大的力时可能需要使用到这个属性。
移动速度 rigidbody.linearVelocity = velocity;
移动速度衰减系数,模拟空气摩擦力 rigidbody.linearDamping = damping;
刚体上某个点的移动速度,
可以传入一个 cc.Vec2 对象作为第二个参数来接收返回值,
刚体的get方法都提供了 out 参数来接收函数返回值。
var velocity = cc.v2();
rigidbody.getLinearVelocityFromWorldPoint(worldPoint, velocity);
旋转速度:rigidbody.angularVelocity = velocity;
旋转衰减速度:var velocity = rigidbody.angularDamping;
旋转固定:rigidbody.fixedRotation = true;
刚体碰撞监听 rigidbody.enabledContactListener = true;
box2d 中只有旋转和位移,并没有缩放,所以如果设置节点的缩放属性时,会重新构建这个刚体依赖的全部碰撞体。一个有效避免这种情况发生的方式是将渲染的节点作为刚体节点的子节点,缩放只对这个渲染节点作缩放,尽量避免对刚体节点进行直接缩放。
每个物理时间步之后会把所有刚体信息同步到对应节点上去,而处于性能考虑,节点的信息只有在用户对节点相关属性进行显示设置时才会同步到刚体上,并且刚体只会监视他所在的节点,即如果修改了节点的父节点的旋转位移是不会同步这些信息的。
刚体类型:
cc.RigidBodyType.Static
静态刚体,零质量,零速度,即不会受到重力或速度影响,但是可以设置他的位置来进行移动。
cc.RigidBodyType.Dynamic
动态刚体,有质量,可以设置速度,会受到重力影响。
cc.RigidBodyType.Kinematic
运动刚体,零质量,可以设置速度,不会受到重力的影响,但是可以设置速度来进行移动。
cc.RigidBodyType.Animated
动画刚体,Animated 是从 Kinematic 类型衍生出来的,一般的刚体类型修改 旋转 或 位移 属性时,都是直接设置的属性,而 Animated 会根据当前旋转或位移属性,与目标旋转或位移属性计算出所需的速度,并且赋值到对应的移动或旋转速度上。添加 Animated 类型主要是防止对刚体做动画时可能出现的奇怪现象,例如穿透。
刚体方法
获取或转换旋转位移属性,使用这些 API 来获取世界坐标系下的旋转位移会比通过节点来获取相关属性更快,因为节点中还需要通过矩阵运算来得到结果,而这些 api 是直接得到结果的。
获取世界坐标
// 直接获取返回值
var out = rigidbody.getWorldPosition();
// 或者通过参数来接收返回值
out = cc.v2();
rigidbody.getWorldPosition(out);
获取世界旋转值 var rotation = rigidbody.getWorldRotation();
世界坐标转换到局部坐标 var localPoint = rigidbody.getLocalPoint(worldPoint);
局部转世界坐标var worldPoint = rigidbody.getWorldPoint(localPoint);
向量转换var worldVector = rigidbody.getWorldVector(localVector);
向量转换var localVector = rigidbody.getLocalVector(worldVector);
获取刚体质心
当对一个刚体进行力的施加时,一般会选择刚体的质心作为施加力的作用点,这样能保证力不会影响到旋转值。
var localCenter = rigidbody.getLocalCenter();
和 var worldCenter = rigidbody.getWorldCenter();
力与冲量
移动一个物体有两种方式,可以施加一个力或者冲量到这个物体上。力会随着时间慢慢修改物体的速度,而冲量会立即修改物体的速度。 当然你也可以直接修改物体的位置,只是这看起来不像真实的物理,你应该尽量去使用力或者冲量来移动刚体,这会减少可能带来的奇怪问题。
// 施加一个力到刚体上指定的点上,这个点是世界坐标系下的一个点
rigidbody.applyForce(force, point);
// 或者直接施加力到刚体的质心上
rigidbody.applyForceToCenter(force);
// 施加一个冲量到刚体上指定的点上,这个点是世界坐标系下的一个点
rigidbody.applyLinearImpulse(impulse, point);
力与冲量也可以只对旋转轴产生影响,这样的力叫做扭矩。
// 施加扭矩到刚体上,因为只影响旋转轴,所以不再需要指定一个点
rigidbody.applyTorque(torque);
// 施加旋转轴上的冲量到刚体上
rigidbody.applyAngularImpulse(impulse);
有些时候需要获取刚体在某一点上的速度时,可以通过 getLinearVelocityFromWorldPoint
来获取,比如当物体碰撞到一个平台时,需要根据物体碰撞点的速度来判断物体相对于平台是从上方碰撞的还是下方碰撞的。
碰撞组件
物理碰撞组件属性
sensor
- 指明碰撞体是否为传感器类型,传感器类型的碰撞体会产生碰撞回调,但是不会发生物理碰撞效果。
density
- 碰撞体的密度,用于刚体的质量计算
friction
- 碰撞体摩擦力,碰撞体接触时的运动会受到摩擦力影响
restitution
- 碰撞体的弹性系数,指明碰撞体碰撞时是否会受到弹力影响
内部细节
物理碰撞组件内部是由 box2d 的 b2Fixture 组成的,由于 box2d 内部的一些限制,一个多边形物理碰撞组件可能会由多个 b2Fixture 组成。
当多边形物理碰撞组件的顶点组成的形状为凹边形时,物理系统会自动将这些顶点分割为多个凸边形。
当多边形物理碰撞组件的顶点数多于 b2.maxPolygonVertices
(一般为 8) 时,物理系统会自动将这些顶点分割为多个凸边形。
一般情况下这些细节是不需要关心的,但是当使用射线检测并且检测类型为 cc.RayCastType.All
时,一个碰撞体就可能会检测到多个碰撞点,原因即是检测到了多个 b2Fixture。
碰撞回调
类似Unity的Oncollision,onTriggerEnter 这两个处理的不一样
需要先在rigidbody中开启碰撞监听
rigidbody.enabledContactListener = true;
,才会有相应的回调产生。回调中的信息在物理引擎都是以缓存的形式存在的,所以信息只有在这个回调中才是有用的,不要在你的脚本里直接缓存这些信息,但可以缓存这些信息的副本。
在回调中创建的物理物体,比如刚体,关节等,这些不会立刻就创建出 box2d 对应的物体,会在整个物理系统更新完成后再进行这些物体的创建。
定义回调函数
cc.Class({
extends: cc.Component,
// 只在两个碰撞体开始接触时被调用一次
onBeginContact: function (contact, selfCollider, otherCollider) {
},
// 只在两个碰撞体结束接触时被调用一次
onEndContact: function (contact, selfCollider, otherCollider) {
},
// 每次将要处理碰撞体接触逻辑时被调用
onPreSolve: function (contact, selfCollider, otherCollider) {
},
// 每次处理完碰撞体接触逻辑时被调用
onPostSolve: function (contact, selfCollider, otherCollider) {
}
});
回调的顺序
当两个碰撞体相互覆盖时,box2d 默认的行为是给每个碰撞体一个冲量去把它们分开,但是这个行为不一定能在一个时间步内完成。 像这里显示的一样,示例中的碰撞体会在三个时间步内相互覆盖直到“反弹”完成并且它们相互分离。
在这个时间里我们可以定制我们想要的行为,onPreSolve 会在每次物理引擎处理碰撞前回调,我们 可以在这个回调里修改碰撞信息,而 onPostSolve 会在处理完成这次碰撞后回调,我们可以在这个回调中获取到物理引擎计算出的碰撞的冲量信息。
下面给出的输出信息能使我们更清楚回调的顺序
...
Step
Step
BeginContact
PreSolve
PostSolve
Step
PreSolve
PostSolve
Step
PreSolve
PostSolve
Step
EndContact
Step
Step
...
回调的参数 contact
回调的参数包含了所有的碰撞接触信息,每个回调函数都提供了三个参数:contact
、selfCollider
、otherCollider
。
selfCollider
和 otherCollider
很容易理解,如名字所示,selfCollider
指的是回调脚本的节点上的碰撞体,ohterCollider
指的是发生碰撞的另一个碰撞体。
最主要的信息都包含在 contact
中,这是一个 cc.PhysicsContact
类型的实例,可以在 api 文档中找到相关的 API。contact
中比较常用的信息就是碰撞的位置和法向量,contact
内部是按照刚体的本地坐标来存储信息的,而我们一般需要的是世界坐标系下的信息,我们可以通过 contact.getWorldManifold
来获取这些信息。
var worldManifold = contact.getWorldManifold();
var points = worldManifold.points;
var normal = worldManifold.normal;
points
碰撞点数组,它们不一定会精确的在碰撞体碰撞的地方上,如下图所示(除非你将刚体设置为子弹类型,但是会比较耗性能),但实际上这些点在使用上一般都是够用的。
normal
碰撞点上的法向量,由自身碰撞体指向对方碰撞体,指明解决碰撞最快的方向。
碰撞法向量并不是碰撞体碰撞的角度,他只会指明可以解决两个碰撞体相互覆盖这一问题最短的方向。
如果你希望知道碰撞的真正的方向,可以使用下面的方式:
var vel1 = triangleBody.getLinearVelocityFromWorldPoint(worldManifold.points[0]);
var vel2 = squareBody.getLinearVelocityFromWorldPoint(worldManifold.points[0]);
var relativeVelocity = vel1.sub(vel2);
修改contact
//禁用
contact.disabled = true;
contact.disabledOnce = true;
在 onPreSolve
中修改 contact
的信息,因为 onPreSolve
是在物理引擎处理碰撞信息前回调的,所以对碰撞信息的修改会影响到后面的碰撞计算。
// 修改碰撞体间的摩擦力
contact.setFriction(friction);
// 修改碰撞体间的弹性系数
contact.setRestitution(restitution);
关节组件
物理系统包含了一系列用于链接两个刚体的关节组件。关节组件可以用来模拟真实世界物体间的交互,比如铰链,活塞,绳子,轮子,滑轮,机动车,链条等。
目前物理系统中提供了以下可用的关节组件:
Revolute Joint
- 旋转关节,可以看做一个铰链或者钉,刚体会围绕一个共同点来旋转。
Distance Joint
- 距离关节,关节两端的刚体的锚点会保持在一个固定的距离。
Prismatic Joint
- 棱柱关节,两个刚体位置间的角度是固定的,它们只能在一个指定的轴上滑动。
Weld Joint
- 焊接关节,根据两个物体的初始角度将两个物体上的两个点绑定在一起。
Wheel Joint
- 轮子关节,由 Revolute 和 Prismatic 组合成的关节,用于模拟机动车车轮。
Rope Joint
- 绳子关节,将关节两端的刚体约束在一个最大范围内。
Motor Joint
- 马达关节,控制两个刚体间的相对运动。
属性
connectedBody
- 关节链接的另一端的刚体
anchor
- 关节本端链接的刚体的锚点
connectedAnchor
- 关节另一端链接的刚体的锚点
collideConnected
- 关节两端的刚体是否能够互相碰撞