Cocos Creator 使用物理引擎示例

大家好,我是Mark,Cocos Creator的忠实粉丝。
前几天看到有人询问关于物理引擎的事情,所以在这里简单介绍一下,面向初学者,大神级的就不要来喷啦。希望本文抛砖引玉,大家能够贡献更好的解决方案,互相学习进步。
其实本文写了一半才发现论坛里很早就有人发表了Box2D引擎在Cocos Creator里的使用方法……早知道就不写了……仔细一看还是有些许不同之处,毕竟Cocos也更新了不少,还是发出来让大家看看吧。

先看一下我做的效果。

我选择的物理引擎是P2,因为大名鼎鼎的Phaser和白鹭引擎都使用P2物理,我就随大流啦。还有一个原因就是P2的Y轴是向上的,这一点和Cocos Creator一致,这样可以免去一些转换上的麻烦。

首先,下载P2物理引擎。
地址:https://github.com/schteppe/p2.js
(可能需要自备小梯子)

上面的地址里包括英文的教程和许多实例,这对快速了解P2非常有帮助。页面的releases下面有源码的下载链接,其中p2.js是合并好的全部代码,开发时主要用这个;p2.min.js是用于发布的,它是p2.js的压缩形式;source code是给引擎开发者和物理研究发烧友用的,Mark对此敬而远之。

把下载好的p2.js放到自己项目的代码文件夹里,切换到Cocos Creator,发现它已经殷切的弹出窗口询问是否把这个js文件作为插件导入,这里当然选择是。

作为插件导入的代码库一般比较大,不是必要的话不要随便在资源管理器里点它,否则会卡一下。

下面马上开始试用。
新建一个场景demo.fire,默认场景中已经有Canvas了。
新建一个脚本文件Level.js,把它拖到场景的Canvas上。
用喜欢的IDE编辑Level.js,在properties里面建立world属性,公开出物理世界。

properties: {
world: {default: null}
}

在onLoad方法中建立这个world。

onLoad: function() {
this.world = new p2.World({gravity: [0, -5]});
this.world.sleepMode = p2.World.BODY_SLEEPING;
this.world.on(“beginContact”, this.onContact, this);
}

其中gravity是重力加速度,因为是向下的所以Y轴是负数。这个值一般取-9.8,这里为了让物体下落慢一点,取-5。
物理世界的碰撞检测也像Cocos Creator一样用on方法监听事件(很好)。物理碰撞其实也很简单,所需要的信息基本都保存在碰撞事件的bodyA,bodyB和contactEquations里,分别存储了互相碰撞的两个刚体和碰撞点、法线等等,另外每个body还贴心的设置了displays数组用于储存和控制可视的节点,后面就会看到。

onContact: function(event) {
cc.log(event.bodyA.name);
cc.log(event.bodyB.name);
}

篇幅原因这里就不进行碰撞处理了,先输出一下碰撞对象名字就好了,更详细的使用方法请参考上面提到的P2文档。

要想让物理世界的物体动起来,就要在每一帧计算一次物理世界step,然后根据计算结果更新每个物体的位置和旋转。

update: function(dt) {
this.world.step(1 / 60);
}

默认每秒60帧,所以参数是1/60。物理计算是比较耗费cpu的,物体多了可能会在手机浏览器上感觉比较卡,影响用户体验。所以说一个项目最好是能不用物理引擎就不要用,自己模拟一下速度,加速度和碰撞也许效果会更好。
对于更新可视对象,可以在Level的update里,遍历world里的bodys,然后更新每个body的可视节点。
或者以可视对象为中心,每个节点自己更新自己,这样控制起来更加灵活一些。

下面建立三个物体:Ball是个往下掉的普通小球;Black是个不会动的地面;Green虽然也不会动,但是点它一下就会消失,再点一下就恢复回来。
找几个图片丢到Canvas下作为这三个物体的可视节点,然后写脚本。

先新建Black.js,建立body和shape两个属性。

properties: {
body: {default: null},
shape: {default: null}
}

每个物体都有这两个属性,以后就不复述了。

start: function() {
this.body = new p2.Body({
position: [this.node.x / 100, this.node.y / 100],
angle: -this.node.rotation / 180 * Math.PI,
type: p2.Body.STATIC
});
this.body.name = “black”;
this.body.displays = [this.node];
this.shape = new p2.Box({width: this.node.width / 100, height: this.node.height / 100});
this.body.addShape(this.shape);
this.node.parent.getComponent(“Level”).world.addBody(this.body);
}

先有世界再有物体,所以在start里写。物理世界以米为单位,在此换算成100像素。旋转用弧度制,做一下转换即可。注意body的displays是一个数组,里面可以存储当前的node。很多情况下需要使用这个引用,比如一个泡泡碰撞后破掉,用body就可以方便的取得这个node了。
shape是方形,把shape放到body里,再把body加到world里。因为Level加到了Canvas里,而每个node又都在Canvas下,所以用parent就能访问到Level了。
小球类Ball.js类似。

this.body = new p2.Body({
mass: 1,
position: [this.node.x / 100, this.node.y / 100],
angle: -this.node.rotation / 180 * Math.PI,
type: p2.Body.DYNAMIC,
allowSleep: false
});
this.body.name = “ball”;
this.body.displays = [this.node];
this.shape = new p2.Circle({radius: this.node.width / 200});
this.body.addShape(this.shape);
this.node.parent.getComponent(“Level”).world.addBody(this.body);

注意body的type是Dynamic,shape是Circle。
会动的东西要在lateUpdate里更新位置和旋转角度,写在lateUpdate里是Unity的习惯,比update节省一点资源。

lateUpdate: function(dt) {
this.node.x = this.body.position[0] * 100;
this.node.y = this.body.position[1] * 100;
this.node.rotation = -(this.body.angle * 180 / Math.PI);
}

注意单位换算,还有P2里旋转正方向和Cocos Creator里相反,所以加个负号。
话说我没在API里看到CC_RADIANS_TO_DEGREES之类的常量,有发现的请告知。

Green物体分方块和虚线两部分,把虚线外框作为bg放到green的下面,让二者位置重合。

来写Green的脚本。

start: function()
{
this.body = new p2.Body({
mass: 0,
position: [this.node.x / 100, this.node.y / 100],
angle: -this.node.rotation / 180 * Math.PI,
type: p2.Body.STATIC
});
this.body.name = “green”;
this.body.displays = [this.node];
this.shape = new p2.Box({width: this.node.width / 100, height: this.node.height / 100});
this.body.addShape(this.shape);
this.node.parent.getComponent(“Level”).world.addBody(this.body);
this.shape.sensor = !this.node.getComponent(cc.Sprite).enabled;
this.node.on(cc.Node.EventType.TOUCH_START, this.change, this);
}

用方块节点的可视性,也就是Sprite的enabled,来控制物理shape的sensor属性。如果方块不可见就把它设置成sensor,这样其他物体就会穿透它。
是否可视可以一开始在Cocos Creator中设置,然后可以用Touch事件控制。

change: function(event)
{
this.getComponent(cc.Sprite).enabled = !this.getComponent(cc.Sprite).enabled;
this.body.shapes[0].sensor = !this.body.shapes[0].sensor;
}

最后别忘了在节点销毁的时候取消掉Touch监听。

onDestroy: function()
{
this.node.off(cc.Node.EventType.TOUCH_START, this.change, this);
}

现在把三个脚本分别加到Cocos Creator里三个可视节点上,摆好位置,测试吧!

(本示例在ios Native、ios Safari 和 Windows10 Chrome上测试成功)

37赞

赞!谢谢楼主!

1赞

感觉不错啊 先收藏了

赞!!!!!!!!!!!!!!!!!!!!!!学习了!

纯js的库,iOS native方式也可以运行么?

JSB方式~

正文没有提到jsb,jsb部分是怎么完成的呢?只看到一个p2.js

在ios上帧数如何?

赞楼主!非常给力

楼主知道有那种适合creator的确定性物理引擎啊?就是通过固定的随机种子每次得到的结果都是一样的。

做联机啊?确不确定不知道……
等你做好了展示一下给我们看看~~

LZ 你这个demo开源吗。 能否看下

请问P2引擎能运用在哪些平台上?IOS和安卓都可以用吗?

楼主上面是demo还是上线的游戏,demo的话有源码吗?线上的游戏的话游戏名是什么?

楼主的Demo 方便开源学习吗?

楼主的Demo 方便开源学习吗?

mark