刚体是组成物理世界的基本对象。
目前引擎支持三种不同的碰撞组件: 盒碰撞组件(BoxCollider2D)、圆形碰撞组件(CircleCollider2D) 和 多边形碰撞组件(PolygonCollider2D)。在 属性检查器 上点击 添加组件 按钮,输入碰撞组件的名称即可添加。
在 属性检查器 上点击 添加组件,输入 PolygonCollider2D 可添加多边形碰撞组件。
物理系统是将游戏世界赋予现实世界的物理属性(重力、推力等),并抽象为刚体模型,使得游戏物体在力的作用下,仿真现实世界的运动及其之间的碰撞过程。即在牛顿经典力学模型基础之上,通过 API 计算游戏物体的运动、旋转和碰撞。
Cocos Creator 支持以下几种物理引擎:
开发者根据开发对物理特性需求或应用场景选择不同的物理引擎,详情请参考:设置物理引擎。
碰撞组件可用于定义需要进行物理碰撞的物体形状,不同的几何形状拥有不同的属性。碰撞体通常分为以下几种:
刚体是组成物理世界的基本对象,它可以使游戏对象的运动方式受物理控制。例如:刚体可以使游戏对象受重力影响做自由下落,也可以在力和扭矩的作用下,让游戏对象模拟真实世界的物理现象。
针对不同的类型,让刚体运动的方式不同:
对于动力学(DYNAMIC)刚体,需要改变其速度,有以下几种方式:
通过重力
刚体组件提供了 UseGravity 属性,需要使用重力时候,需将 UseGravity 属性设置为 true
。
通过施加力
刚体组件提供了 applyForce
接口,根据牛顿第二定律,可对刚体某点上施加力来改变物体的原有状态。
- import { math } from 'cc'
-
- rigidBody.applyForce(new math.Vec3(200, 0, 0));
通过扭矩
力与冲量也可以只对旋转轴产生影响,使刚体发生转动,这样的力叫做扭矩。
刚体组件提供了 applyTorque
接口,通过此接口可以施加扭矩到刚体上,因为只影响旋转,所以不需要指定作用点。
rigidBody.applyTorque(new math.Vec3(200, 0, 0));
通过施加冲量
刚体组件提供了 applyImpulse
接口,施加冲量到刚体上的一个点,根据动量守恒,将立即改变刚体的线性速度。 如果冲量施加到的点不是刚体的质心,那么将产生一个扭矩并影响刚体的角速度。
rigidBody.applyImpulse(new math.Vec3(5, 0, 0));
通过改变速度
刚体组件提供了 setLinearVelocity
接口,可用于改变线性速度。
rigidBody.setLinearVelocity(new math.Vec3(5, 0, 0));
刚体组件提供了 setAngularVelocity
接口,可用于改变旋转速度。
rigidBody.setAngularVelocity(new math.Vec3(5, 0, 0));
恒力组件是一个工具组件,依赖于刚体组件,每帧都会对一个刚体施加给定的力和扭矩。
在物理引擎中,约束 用于模拟物体间的连接情况,如连杆、绳子、弹簧或者布娃娃等。
约束依赖 刚体组件,若节点无刚体组件,则添加约束时,引擎会自动添加刚体组件。
物理材质是一种资源,它记录了物体的物理属性,这些信息用来计算碰撞物体受到的摩擦力和弹力等。
触发器与碰撞器
碰撞组件属性 IsTrigger 决定了组件为触发器还是碰撞器。将 IsTrigger 设置为 true
时,组件为触发器。触发器只用于碰撞检测和触发事件,会被物理引擎忽略。默认设置 false
,组件为碰撞器,可以结合刚体产生碰撞效果。
射线检测是对一条射线和另一个形状进行 相交性判断,如下图所示。
射击苹果——静态网格、凸包、多步模拟(步长调整)
一般的苹果都带有凹面,处理好凹类或带连续平滑不规则曲面的模型都非常棘手,这是因为目前成熟的理论和技术都建立在离散、凸包的世界之上(微积分中用差分近似表示微分就是最典型的范例)。
运动表现与模拟参数有非常大的关系,穿透是最具有代表性的现象,这可以通过缩减步长和增加步数来实现,调整步长有个小技巧:输入分式,即 1/Frame
,其中 Frame
表示帧率。
粒子系统是游戏引擎特效表现的基础,它可以用于模拟的火、烟、水、云、雪、落叶等自然现象,也可用于模拟发光轨迹、速度线等抽象视觉效果。
缓动系统被广泛的应用于游戏开发中,其主要目的之一是用于解决离线动画无法满足需求时的动态动画的问题。
地形系统以一种高效的方式来展示大自然的山川地貌。开发者可以很方便的使用画刷来雕刻出盆地、山脉、峡谷、平原等地貌。
Asset Manager 概述
在游戏的开发过程中,一般需要使用到大量的图片、音频等资源来丰富整个游戏内容,而大量的资源就会带来管理上的困难。所以 Creator 提供了 Asset Manager 资源管理模块来帮助开发者管理其资源的使用,大大提升开发效率和使用体验。
通常我们会把项目中需要动态加载的资源放在 resources
目录下,配合 resources.load
等接口动态加载。你只要传入相对 resources 的路径即可,并且路径的结尾处 不能 包含文件扩展名。
- // 加载 Prefab
- resources.load("test_assets/prefab", Prefab, (err, prefab) => {
- const newNode = instantiate(prefab);
- this.node.addChild(newNode);
- });
-
- // 加载 AnimationClip
- resources.load("test_assets/anim", AnimationClip, (err, clip) => {s
- this.node.getComponent(Animation).addClip(clip, "anim");
- });
图片设置为 sprite-frame 或 texture 或其他图片类型后,将会在 资源管理器 中生成一个对应类型的资源。但如果直接加载 test_assets/image
,得到的类型将会是 ImageAsset
。你必须指定路径到具体的子资源,才能加载到图片生成的 SpriteFrame
:
- // 加载 SpriteFrame,image 是 ImageAsset,spriteFrame 是 image/spriteFrame,texture 是 image/texture
- resources.load("test_assets/image/spriteFrame", SpriteFrame, (err, spriteFrame) => {
- this.node.getComponent(Sprite).spriteFrame = spriteFrame;
- });
- // 加载 texture
- resources.load("test_assets/image/texture", Texture2D, (err: any, texture: Texture2D) => {
- const spriteFrame = new SpriteFrame();
- spriteFrame.texture = texture;
- this.node.getComponent(Sprite).spriteFrame = spriteFrame;
- });
- // 加载 SpriteAtlas(图集),并且获取其中的一个 SpriteFrame
- // 注意 atlas 资源文件(plist)通常会和一个同名的图片文件(png)放在一个目录下, 所以需要在第二个参数指定资源类型
- resources.load("test_assets/sheep", SpriteAtlas, (err, atlas) => {
- const frame = atlas.getSpriteFrame('sheep_down_0');
- sprite.spriteFrame = frame;
- });
在将 FBX 模型或 glTF 模型导入编辑器后,会解析出该模型中包含的相关资源如网格,材质,骨骼,动画等,如下图所示:
- // 加载模型中的网格资源
- resources.load("Monster/monster", Mesh, (err, mesh) => {
- this.node.getComponent(MeshRenderer).mesh = mesh;
- });
-
- // 加载模型中的材质资源
- resources.load("Monster/monster-effect", Material, (err, material) => {
- this.node.getComponent(MeshRenderer).material = material;
- });
-
- // 加载模型中的骨骼
- resources.load("Monster/Armature", Skeleton, (err, skeleton) => {
- this.node.getComponent(SkinnedMeshRenderer).skeleton = skeleton;
- });
resources.loadDir
可以加载相同路径下的多个资源:
- // 加载 test_assets 目录下所有资源
- resources.loadDir("test_assets", function (err, assets) {
- // ...
- });
-
- // 加载 test_assets 目录下所有 SpriteFrame,并且获取它们的路径
- resources.loadDir("test_assets", SpriteFrame, function (err, assets) {
- // ...
- });
因为预加载没有去解析资源,所以需要在预加载完成后配合加载接口进行资源的解析和初始化,来完成资源加载。
- resources.preload('test_assets/image/spriteFrame', SpriteFrame);
-
- // wait for while
- resources.load('test_assets/image/spriteFrame', SpriteFrame, (err, spriteFrame) => {
- this.node.getComponent(Sprite).spriteFrame = spriteFrame;
- });
- // 远程 url 带图片后缀名
- let remoteUrl = "http://unknown.org/someres.png";
- assetManager.loadRemote<ImageAsset>(remoteUrl, function (err, imageAsset) {
- const spriteFrame = new SpriteFrame();
- const texture = new Texture2D();
- texture.image = imageAsset;
- spriteFrame.texture = texture;
- // ...
- });
-
- // 用绝对路径加载设备存储内的资源,比如相册
- const absolutePath = "/dara/data/some/path/to/image.png";
- assetManager.loadRemote<ImageAsset>(absolutePath, function (err, imageAsset) {
- const spriteFrame = new SpriteFrame();
- const texture = new Texture2D();
- texture.image = imageAsset;
- spriteFrame.texture = texture;
- // ...
- });
-
- // 远程音频
- remoteUrl = "http://unknown.org/sound.mp3";
- assetManager.loadRemote(remoteUrl, function (err, audioClip) {
- // play audio clip
- });
-
- // 远程文本
- remoteUrl = "http://unknown.org/skill.txt";
- assetManager.loadRemote(remoteUrl, function (err, textAsset) {
- // use string to do something
- });
场景的自动释放可以直接在编辑器中设置。在 资源管理器 选中场景后,属性检查器 中会出现 自动释放资源 选项。
勾选后,点击右上方的 应用 按钮,之后在切换该场景时便会自动释放该场景所有的依赖资源。建议场景尽量都勾选自动释放选项,以确保内存占用较低,除了部分高频使用的场景(例如主场景)。
另外,所有 Asset
实例都拥有成员函数 Asset.addRef
和 Asset.decRef
,分别用于增加和减少引用计数。一旦引用计数为零,Creator 会对资源进行自动释放(需要先通过释放检查,具体可参考下部分内容的介绍)
- start () {
- resources.load('images/background', Texture2D, (err, texture) => {
- this.texture = texture;
- // 当需要使用资源时,增加其引用
- texture.addRef();
- // ...
- });
- }
-
- onDestroy () {
- // 当不需要使用资源时,减少引用
- // Creator 会在调用 decRef 后尝试对其进行自动释放
- this.texture.decRef();
- }
自动释放的优势在于不用显式地调用释放接口,开发者只需要维护好资源的引用计数,Creator 会根据引用计数自动进行释放。这大大降低了错误释放资源的可能性,并且开发者不需要了解资源之间复杂的引用关系。对于没有特殊需求的项目,建议尽量使用自动释放的方式来释放资源。
当项目中使用了更复杂的资源释放机制时,可以调用 Asset Manager 的相关接口来手动释放资源。例如:
assetManager.releaseAsset(texture);
当开发者在编辑器中没有对资源做任何设置,而是通过代码动态加载资源并设置到场景的组件上,则资源的引用关系不会记录在序列化数据中,引擎无法统计到这部分的引用关系,这些引用关系就是动态引用。
如果开发者在项目中使用动态加载资源来进行动态引用,例如:
- resources.load('images/background/spriteFrame', SpriteFrame, function (err, spriteFrame) {
- self.getComponent(Sprite).spriteFrame = spriteFrame;
- });
此时会将 SpriteFrame 资源设置到 Sprite 组件上,引擎不会做特殊处理,SpriteFrame 的引用计数仍保持 0。如果动态加载出来的资源需要长期引用、持有,或者复用时,建议使用 addRef
接口手动增加引用计数。例如:
- resources.load('images/background/spriteFrame', SpriteFrame, function (err, spriteFrame) {
- self.getComponent(Sprite).spriteFrame = spriteFrame;
- spriteFrame.addRef();
- });
增加引用计数后,可以保证该资源不会被提前错误释放。而在不需要引用该资源以及相关组件,或者节点销毁时,请 务必记住 使用 decRef
移除引用计数,并将资源引用设为 null
,例如:
- this.spriteFrame.decRef();
- this.spriteFrame = null;
Asset Manager 底层使用了多条加载管线来加载和解析资源,每条管线中都使用了 downloader
和 parser
模块,也就是下载器和解析器。开发者可以通过 assetManager.downloader
和 assetManager.parser
来访问。