虽然P2被开发者吐槽 “刚体碰撞出错,刚体穿透时常发生,社区也不活跃,资料少,物理效果欠佳” ,但作为Egret官方推荐的物理引擎,还是有必要学习使用。

官方P2物理引擎的引入:

为了方便不同项目共用代码库,我喜欢将Egret的第三方类库集中放到EgretLibs的文件夹,并且跟其他的Egret项目并列。

GeeM2引2插件延迟小退设置 geem2引擎 说明书_物理引擎

egret-game-library/physics/的libsrc文件夹下载到EgretLibs文件夹,并且重命名为:physics

修改项目的egretProperties.json文件,modules数组里增加:

{
			"name": "physics",
			"path": "../EgretLibs/physics"
		}

编译项目,完毕之后,项目的libs/modules文件夹应该会自动创建physics文件夹和三个js/ts文件

GeeM2引2插件延迟小退设置 geem2引擎 说明书_物理引擎_02

对于egret-game-library所有的第三方类库,都可以用这个方式引入。

 

P2物理引擎概述:

物理引擎模拟了近似真实的物理系统,为刚性物体赋予物理效果,比如重力、旋转、弹跳、碰撞、反弹、滚动、加减速等效果,让物体的行为更加真实。一般来说物理引擎并不处理和画面渲染相关的事务,而只完成计算仿真。所以物理引擎都需要跟各种的渲染引擎配合使用。而这种配合是“低耦合”的,只需要注意各自的坐标系映射即可,开发者可以自由选择喜欢的引擎。

常用物理引擎:P2, Matter.js, Box2D, PhysicsJS, arcade...

常用渲染/游戏引擎:Egret, Layabox, Phaser, Cocos2d-x, CreateJS...

具体到Egret + P2,它们的坐标映射是:

GeeM2引2插件延迟小退设置 geem2引擎 说明书_GeeM2引2插件延迟小退设置_03

文字描述一下:

1、P2与Egret横坐标一致,纵坐标相反

2、P2物体的原点为中心点,Egret显示对象的原点为左上角,设置display.anchorOffsetX = display.width/2;display.anchorOffsetY = display.height/2

3、P2的长度单位(米)与Egret长度单位(像素)的factor(系数)为50,计算坐标时要实时转换更新

P2引擎大致分为:世界World,形状Shape,刚体Body,然后加上Egret的绘制或者贴图,形成一个模拟的物理世界。

借用其他教程的话:

  • 世界:world是P2物理引擎的入口,它类似于渲染引擎中的stage或显示对象。和addChild()函数一样,所有的物理对象都是必须通过addBody(),添加到P2的world世界中,才会进行物理模拟。
  • 形状:物理游戏中进行碰撞模拟的一个前提,是有固定的形状,或者矩形,又或者圆形,或者不规则多边形,但这个形状必须是固定不变的。而类似于水这类液体的物理运动,因为没有固定的形状,无法使用P2进行模拟。
  • 刚体:刚体是P2物理引擎的核心概念和对象,形状依附于刚体,将刚体添加到世界中,才能进行物理模拟,刚体是所有碰撞对象的原型。
  • 渲染贴图:P2只是一个算法库,以刚体为对象模型,模拟并输出物理碰撞、运动结果。它本身不具备渲染功能,无法显示模拟的结果,而是要借助于渲染引擎,通过绘制或贴图来渲染物理模拟结果。

 

创建Egret + P2项目:

基于以上概述,项目中使用P2引擎大致分为以下步骤:

1,创建世界World

打开项目的main.ts文件,粘贴以下代码,在childrenCreated()方法调用this.CreateWorld();

//声明world和factor两个全局变量
	private world: p2.World;
	private factor: number = 50;


    private CreateWorld() {
		this.world = new p2.World();
		//设置world为睡眠状态
		this.world.sleepMode = p2.World.BODY_SLEEPING;
		this.world.gravity = [0, -10];
	}

 

2,创建形状Shape

现实中物体的形状千奇百怪,但是为了减少碰撞检测等的计算量提高运行效率,P2简化了形状:矩形Box,胶囊形状Capsule,圆形Circle,凸形状Convex,起伏平面Heightfield,线形Line,粒子Particle 和 平面Plane

//矩形
var boxShape: p2.Shape = new p2.Box({ width: 2, height: 1 });
//圆形
var circleShape:p2.Shape = new p2.Circle({radius:60});

 

3,创建刚体Body

刚体有各种属性,参见:Body类的属性

//Egret与P2的坐标转换
var positionX: number = Math.floor(200 / this.factor);
var positionY: number = Math.floor((this.stage.stageHeight-200) / this.factor);

var boxBody: p2.Body = new p2.Body(
			{ 
				mass: 1,           //质量
				position: [positionX, positionY], 
				angularVelocity: 1 //默认:0。表示刚体角速度,即旋转速度
			});
boxBody.addShape(boxShape); //添加上一步创建的shape
this.world.addBody(boxBody);//所有刚体都需要加入到world才会参与物理计算

 

4,创建egret.DisplayObject显示物体

绘制的图形、Bitmap、还是Sprite,都可以显示。

//绘制Shape
//http://developer.egret.com/cn/apidoc/index/name/egret.Shape
private drawRect():egret.Shape {
        var shape:egret.Shape = new egret.Shape();
        shape.graphics.beginFill(0x0000ff);
        shape.graphics.drawRect(200, 200, 100, 100);
        shape.graphics.endFill();
        return shape;
}

//Bitmap
private createBitmapByName(): egret.Bitmap {
		let name = "tank_blue_png";//自定义图片资源
		var result: egret.Bitmap = new egret.Bitmap();
		var texture: egret.Texture = RES.getRes(name);
		result.texture = texture;
		return result;
}

//自定义的Sprite类
class Tank extends egret.Sprite {

	private isTankStoped: boolean = true;


	public constructor(isEnemy: boolean) {
		super();

		let tankResName;
		if (isEnemy) {
			let randomIndex = util.GetRandomNum(1, 4);
			tankResName = "tank_red_" + randomIndex.toString() + "_png";
		}
		else {
			tankResName = "tank_blue_" + "1" + "_png";
		}

		let bmp: egret.Bitmap = new egret.Bitmap();
		bmp.texture = RES.getRes(tankResName);

		this.addChild(bmp);
	}

	public StartTank() {
		this.isTankStoped = false;
	}

	public StopTank() {
		this.isTankStoped = true;
	}
}
var display: egret.DisplayObject;
display = this.drawRect();//new Tank(false);// this.createBitmapByName();
display.width = (<p2.Box>boxShape).width * this.factor; //长度单位不同,注意转换
display.height = (<p2.Box>boxShape).height * this.factor;
display.anchorOffsetX = display.width / 2; //anchoroffset跟P2一样设置在中心点
display.anchorOffsetY = display.height / 2;
boxBody.displays = [display]; //修改刚刚创建的刚体的display属性
this.addChild(display);//将DisplayObject添加到舞台

绘制图形一般用于展示demo,Bitmap是静态贴图,Sprite较为灵活。实际使用看需求选择。

 

5,实时更新物理世界

两个步骤:

  • 调用World.Step方法更新物理计算
  • 将egret.DisplayObject与P2 Body的位置和角度映射
this.addEventListener(egret.Event.ENTER_FRAME, this.onEnterFrame, this);
private onEnterFrame(e: egret.Event) {
		this.world.step(1 / 60);//游戏帧数的倒数
		var stageHeight: number = egret.MainContext.instance.stage.stageHeight;
		var l = this.world.bodies.length;
		for (var i: number = 0; i < l; i++) { //遍历所有刚体
			var boxBody: p2.Body = this.world.bodies[i];
			var box: egret.DisplayObject = boxBody.displays[0];
			if (box) {

                //位置与角度的转换
				box.x = boxBody.position[0] * this.factor;
				box.y = stageHeight - boxBody.position[1] * this.factor;
				box.rotation = 360 - (boxBody.angle + boxBody.shapes[0].angle) * 180 / Math.PI;

                //此代码仅用作展示p2.Body.SLEEPING状态的物体
				if (boxBody.sleepState == p2.Body.SLEEPING) {
					box.alpha = 0.5;
				}
				else {
					box.alpha = 1;
				}
			}
		}
}

---------------------------------------------------------------------------------------------------------------

参考资料:

P2官方Github                    P2作者的Github,包括demo,示例,下载,等等

P2引擎中文维基                P2引擎官方Wiki的中文翻译

P2物理引擎部分文档         中文文档,介绍了World, Body, Sharp等类的属性、方法、事件,以及其他的一些开发小结

Egret Game Library           Egret官方的第三方类库,包括Tiled, 键盘鼠标操作,EUI的扩展,P2引擎等等等

拉登的P2物理引擎教程      拉登的个人网站关于Egret + P2的系列文章,历史久远但介绍翔实。另有Egret + Box2D的资料。

Egret中使用P2物理引擎     比较详细的P2属性、方法等的介绍