Egret中使用P2物理引擎

遊戲中的對象按照物理規律移動,體現重力、引力、反作用力、加速度等物體特性,實現自由落體、搖擺運動、拋物線運動,以及物理碰撞現象的模擬。用於模擬物理碰撞、物理運動的引擎稱爲物理引擎。 
來自瑞典斯德哥爾摩大學的Stefan Hedman基於JavaScript,開發了一款面向HTML遊戲的2D物理引擎,P2物理引擎。P2和Box2D物理引擎一樣,集成了各種複雜的物理公式和算法,幫助實現碰撞、加速、自由落體等物理對象的模擬。 
P2是一個開源項目,可在GitHub下載,使用build中的p2.min.js文件,就可以開發物理應用。

1. 創建P2物理項目:

使用P2物理引擎創建物理應用的過程和Box2D類型,步驟是:創建world、創建shape、創建body剛體、實時調用step()函數更新物理模擬計算;基於形狀、剛體使用Egret或其他HTML渲染以顯示物理模擬效果。 
1)世界world:
world是P2物理引擎入口,對應World類,用於承載所有物理模擬對象。world類的構造函數爲: 
function World([options]){options?:{gravity?:number[]=[0,-9.81];}} 
其中,gravity是重力加速度,這是一個Vec2類型的向量對象,默認爲垂直向上的向量[0,-9.81]。將gravity設置爲[0,0]可以取消重力,模擬太空失重狀態。 
2)形狀Shape:
形狀是物理模擬計算的基礎。任何物體都要有對應的形狀,纔可以基於P2進行物理碰撞檢測和模擬。所有形狀對象都需要通過addShape()添加到剛體中,纔可以進行碰撞模擬計算: 
var body:p2.Body=new p2.Body(); 
var shape:p2.Shape=new p2.Shape(); 
body.addShape(shape); 
P2中的Shape類是一個抽象的父類,要使用Box、Circle等子類。 
3)剛體Body:
剛體是P2物理引擎的核心概念和對象,擁有速度、角度、質量等物理屬性,同時包含了形狀對象,使剛體擁有具體的形狀。將剛體添加到world中,World將以剛體爲單位循環遍歷,進行物理模擬計算,並將模擬的結果保存在剛體屬性中,使剛體成爲碰撞對象的原型。所有的剛體都必須通過addBody()添加到P2的world中,纔會進行物理模擬: 
var body:p2.Body=new p2.Body(); 
var shape:p2.Shape=new p2.Shape(); 
body.addShape(shape); 
world.addBody(body); 
4)貼圖Egret:
P2只是一個算法庫,以剛體爲對象模型,模擬並輸出物理碰撞、運動結果。這個過程通過持續調用world中的step()方法來實現: 
function step(dt:number, timeSinceLastCalled?:number=0,maxSubSteps?:number=10) 
其中,參數dt是step方法執行的時間間隔,單位秒,通常取值爲遊戲幀頻的倒數; 當遊戲幀頻降低時計算兩幀之間的時間差作爲timeSinceLastCalled參數值,此時P2會在一次step()中進行count= timeSinceLastCalled/dt次計算,以保證物理模擬的真實性,默認值爲0;參數maxSubSteps是單次step()進行物理模擬計算的最大次數,當timeSinceLastCalled不等於0時,單次step()中進行計算的次數count最大爲maxSubSteps,默認值爲10。 
但P2本身不具備渲染功能,無法顯示模擬結果,需要藉助JavaScript渲染引擎,如Egret、Cocos2d-js、Pixi、phaser等,通過繪製或貼圖來渲染物理模擬結果。在Egret中: 
class SampleP2APP extends egret.DisplayObjectContainer{ 
public constructor(){ 
super(); 
this.createP2App(); 

private world:p2.World; 
private factor:number=30; 
public createP2App():void{ 
this.world=new p2.World(); 
var world=this.world; 
world.gracity=[0,0]; 
var shape:p2.Box=new p2.Box({width:100/this.factor, height:50/this.factor}); 
var body:p2.Body=new p2.Body({mass:1}); 
body.position=[275/this.factor, 100/this.factor]; 
body.addShape(shape); 
worldBody(body); 
this.addEventListener(egret.Event.ENTER_FRAME, this.loop, this); 

private loop(e.egret.Event):void{ 
this.world.step(1/60); 


代碼中創建了一個重力加速度gravity=[0,0]的失重環境world;然後使用Box形狀創建尺寸100x50像素矩形;通過addShape()方法將其添加到位於(275,100)位置的剛體body中;最後通過world的addBody()方法將剛體添加到世界中,完成一個基本的P2物理應用創建。在遊戲的loop更新方法中,每幀持續調用step()方法,實現P2物理模擬計算的持續更新。 
P2物理引擎中的座標單位是米,而大部分的遊戲引擎在屏幕上渲染遊戲時,都是基於像素的,所以在創建剛體對象、設置形狀尺寸時,需要將像素轉換成米後再進行賦值。遊戲中,1米通常看作30px,所以代碼中聲明瞭一個值爲30的factor變量,在設置形狀尺寸或剛體時,都是使用像素座標除以factor變量,轉換成米之後再賦值的。

2. 用p2DebugDraw實現模擬視圖:

因爲P2只是進行了物理模擬計算,沒有對模擬結果進行渲染,可以基於Egret引擎編寫渲染繪圖的類,如p2DebugDraw。p2DebugDraw類的構造函數爲: 
function p2DebugDraw(world:p2.World, sprite:egret.Sprite) 
其中,world是P2中創建的world對象,sprite是Stage中一個Sprite對象。p2DebugDraw將使用基本繪圖的API,在sprite對象中繪製所有剛體、關節等物理對象。修改後的代碼: 
class SampleP2APPWithDebugDraw extends egret.DisplayObjectContainer{ 
public constructor(){ 
super(); 
this.createP2App(); 

private world:p2.World; 
private factor:number=30; 
private debugDraw:p2DebugDraw; 
public createP2App():void{ 
this.world=new p2.World(); 
var world=this.world; 
world.gracity=[0,0]; 
var shape:p2.Box=new p2.Box({width:100/this.factor, height:50/this.factor}); 
var body:p2.Body=new p2.Body({mass:1}); 
body.position=[275/this.factor, 100/this.factor]; 
body.addShape(shape); 
worldBody(body); 
var sprite:egret.Sprite=new egret.Sprite(); 
this.addChild(sprite); 
this.debugDraw=new p2DebugDraw(world, sprite); 
this.addEventListener(egret.Event.ENTER_FRAME, this.loop, this); 

private loop(e.egret.Event):void{ 
this.world.step(1/60); 
this.debugDraw.drawDebug(); 


p2DebugDraw仿照Box2d中的b2DebugDraw,用不同顏色和形狀表示不同類型的剛體、關節等對象,粉色爲動態剛體、紫色爲可動剛體、綠色爲靜態剛體、黑色線段爲關節、紅色線段爲彈簧、綠色線段爲剛體與關節點的偏移量、圓的爲關節節點。 
p2DebugDraw的屬性和方法: 
p2DebugDraw提供了很多屬性和方法: 
1)isDrawAABB:boolean:是否繪製剛體的AABB最小包圍框,默認false 
2)drawDebug():繪製P2物理引擎中的所有剛體、關節等對象,需要實時調用實時更新 
3)drawShape():繪製P2世界中的任意形狀,使用時要確保形狀對應剛體已添加到world 
drawShape()方法的結構爲: 
function drawShape(shape:p2.Shape, color?:number, fillColor?:boolean):void 
其中,shape爲要繪製的Shape對象;color爲要繪製的顏色,缺省時根據類型設置顏色;fillColor指示是否填充顏色,默認true,填充色與邊框色相同。 
4)drawConvex():將vertices數組中保存的任意多個頂點座標,逐個使用線段連接起來,繪製出多邊形。此方法不受P2剛體約束,可在任意需要情況下使用。 
function drawConvex( vertices:number[][], color:number, alpha:number=1, fillColor:boolean = true ):void 
其中,vertices保存了需要繪製的多邊形頂點數組;color爲多邊形的邊框顏色;alpha爲多邊形填充顏色透明度;fillColor爲是否填充顏色,默認true,填充色與邊框色相同。 
5)drawCircle():在指定位置以指定的半徑繪製圓形,該方法不受P2剛體約束,可在任意需要情況下使用。 
function drawCircle( pos:number[], radius:number, color:number, alpha:number=1, fillColor?:boolean ):void 
其中,pos爲圓形繪製的目標位置,radius爲圓形半徑。 
6)drawSegment():在兩點之間繪製線段,結構爲: 
function drawSegment( start:number[], end:number[], color:number):void 
其中,start爲線段起點,end爲線段終點,color爲線段顏色。 
7)drawVecAt():在指定點at處繪製向量v。用來顯示出剛體速度,結構爲: 
function drawVecAt( v:number[],at:number[], color:number, markStart:boolean=false ):void 
其中,v爲要繪製的向量;at爲開始繪製向量的起點;color爲向量顏色;markStart爲是否用圓點表示出向量的起點,默認false。 
8)drawVecTo():繪製向量v,指向指定點to。 
function drawVecTo( v:number[],to:number[], color:number, markStart:boolean=false ):void 
其中,v爲要繪製的向量;to爲向量的終點;color爲繪製的顏色;markStart爲是否用圓點表示出向量的終點,默認false。

3. P2中的形狀:

形狀是物理引擎進行碰撞模擬計算的依據,是剛體最基本的屬性。P2中使用Shape類來表示形狀,通過剛體的addShape()方法,將形狀添加到剛體中之後,就可以隨着剛體的移動、旋轉不斷更新,並進行碰撞檢測了。爲剛體添加形狀的示例代碼爲: 
var shape:p2.Shape=new p2.Shape(); 
var body:p2.Body=new p2.Body(); 
body.addShape(shape); 
Shape類本身並不參與剛體的創建,而是由其幾個子類完成一些常見形狀的模擬。這些形狀包括圓形Circle、矩形Box、膠囊Capsule、粒子Particle、線段Line、平面Plane、海拔形狀HightField、多邊形Convex等。

1)形狀:
遊戲中人物形狀各種各樣,但在碰撞檢測時,出於效率考慮,大都被簡化爲簡單的圓形、矩形等形狀。P2預置了包括圓形、矩形在內的一些常用的、簡單的形狀。 
⑴圓形Circle:
圓形是P2中的基本形狀,構造函數爲: 
function Circle({options?:{radius:number}) 
其中,options是包含所有屬性的集合對象;radius是圓形的半徑,默認值爲1。 
⑵矩形Box:
矩形通過width和height屬性來創建,構造函數爲: 
function Box(options:{width?:number, height?:number}) 
其中,width爲矩形寬度,默認爲1;height爲矩形高度,默認值爲1。 
⑶膠囊Capsule:
可以認爲是一種圓角矩形,長度length,高度2*Radius,兩端是半徑爲Radius的半圓形。 
function Capsule(options?:{length?:number, radius?:number}) 
其中,length爲膠囊長度;radius爲膠囊形狀兩端半圓的半徑,同時也是膠囊的高度。 
⑷粒子Particle:
粒子就是微小的顆粒,P2物理引擎中的粒子半徑和麪積爲0。粒子參與碰撞檢測,效果與半徑爲1px的圓形一樣。構造函數簡單,不需要任何參數: 
function Particle() 
⑸線段Line:
長度爲length的線段,看上去與高度爲1的Box形狀一樣,但算法上省去對高度的檢測。 
function Line(options?:{length?:number}) 
⑹平面Plane:
平面沿y軸負方向無限擴展,同時在x軸方向的寬度無限,像地平面。構造函數爲: 
function Plane() 
創建時沒有任何參數,初始情況下是一個倒置的穹,實際應用時一般要通過調整剛體的角度angle,使plane平面朝向不同的方向,來模擬牆體。示例代碼: 
var shape:p2.Plane=new p2.Plane(); 
var body:p2.Body=new p2.Body({mass:1, position:[274/this.factor, 200/this.factor]}); 
body.addShape(shape); 
body.angle=Math.PI/4; 
this.world.addBody(body); 
代碼中的平面會隨着剛體的角度angle繞座標點[274,200]順時針旋轉45°。 
⑺海拔形狀Heightfield:
這是一個類似於Plane的形狀,但不是平的,而是一組y座標組成的高低不平的山形,這些山丘之間的間隔都是固定的elementWidth。HeightField形狀也是朝y軸負方向無限擴展的,水平方向的寬度是elementWidth與山丘數量的乘積。構造函數爲: 
function Heightfield(options?:{heights:number[], minValue?:number,maxValue?:number, elementWidth?:number}) 
其中,heights是保存每個山丘高度的數組;minValue是山丘高度的最小值,設置後heights中的小於minValue的值設置爲minValue;maxValue爲heights中高度的最大值,設置此值後heights中大於maxValue的值設置爲maxHeight;elementWidth爲每個山丘之間的間隔,默認爲1。 
定義Heightfield形狀就是定義heights屬性中y座標數組,同時x座標以elementWidth爲步長逐一增加。目前,Heightfield不支持旋轉,始終朝y軸負方向擴展,碰撞檢測也不精確,邊緣位置容易出現穿透現象。 
⑻多邊形Convex:
Convex是一個多邊形形狀,可以根據一組定義好的頂點座標創建對應的多邊形形狀。 
function Convex(options?:{vertice?:number[][], axes?:number[][[]}) 
其中,vertices保存了頂點座標的數組,這是一個二維數組,每個元素是由x和y座標組成的一維數組,如vertics=[[-1,-1],[1,-1],[1,1],[-1,-1]];axes表示多邊形各個邊的對稱軸,同樣是一個二維數組,且長度應與vertices一致,此參數通常可以使用默認值,系統根據vertices中的頂點自動計算得出垂直於各個邊的法向量。使用時首先要將多邊形的頂點座標保存到數組中,然後將其作爲vertices參數傳遞給Convex: 
var points=[[-1,-1],[1,-1],[1,1],[-1,-1]]; 
var shape:p2.Convex=new p2.Convex({vertices:points}); 
var body:p2.Body=new p2.Body({mass:1}); 
body.addShape(shape); 
this.world.addBody(body); 
不推薦使用頂點座標直接創建多邊形,因爲可能出現凹多邊形,而P2中的碰撞檢測是基於凸多邊形的,需要將凹多邊形分解成多個小的凸多邊形,還需要重新計算分解後的重心、形狀偏移,這些計算並不在Convex類中。建議使用Body類的fromPolygon()實現。

2)P2中形狀碰撞關係:
P2中形狀碰撞不完善,一些形狀之間無法實現碰撞,各個形狀之間的碰撞關係見表:

 

Circle

Plane

Box

Convex

Particle

Line

Capsule

Heightfield

Ray

Circle

Yes

-

-

-

-

-

-

-

-

Plane

Yes

-

-

-

-

-

-

-

-

Box

Yes

Yes

Yes

-

-

-

-

-

-

Convex

Yes

Yes

Yes

Yes

-

-

-

-

-

Particle

Yes

Yes

Yes

Yes

-

-

-

-

-

Line

Yes

Yes

?

?

-

-

-

-

-

Capsule

Yes

Yes

Yes

Yes

Yes

?

Yes

-

-

Heightfield

Yes

-

Yes

Yes

?

?

?

-

-

Ray

Yes

Yes

Yes

Yes

-

Yes

Yes

Yes

-

其中?部分是有待完善的。

3)形狀屬性: 
p2的幾種內置形狀各有自己的屬性,但都繼承自Shape類,所以有共同的屬性,包含在Shape的構造函數內: 
function Shape(options?:{ 
position?:number[], 
angle?:number, 
collisionGroup?:number, 
collisionMask?:number, 
sensor?:boolean, 
collisionResponse?:boolean, 
type?:number, 
material?:Material}); 
其中,position是形狀相對於本地座標中心的偏移量,這個偏移量會影響剛體的重心;angle爲形狀在剛體本地座標系統中傾斜的角度;collisionGroup爲碰撞分組,與collisionMask一起使用,限制當前形狀只與指定條件的形狀發生碰撞;collisionMask爲碰撞篩選,與collisionGroup一起使用,限制當前形狀只與指定條件的形狀發生碰撞;sensor設置形狀是否爲敏感區域,默認false,如果設置爲true,則該形狀不參與碰撞模擬,只作爲感應區域;collisionResponse設置當與其他剛體發生碰撞時,當前形狀是否進行碰撞模擬,默認true,如果設爲false,碰撞發生時當前形狀會穿過碰撞剛體;type爲剛體類型,取值範圍是Shape. CIRCLE和Shape.BOX等常量之一,不需要設置該參數,系統會自動設置。 
使用collisionGroup和collisionMask屬性時,有兩個形狀si和sj,對si的collisionGroup屬性與sj的collisionMask屬性按位與運算,再對sj的collisionGroup屬性與si的collisionMask屬性按位與運算,如果兩個結果均不爲0,則忽略此次si與sj的碰撞檢測。因爲要按位運算,所以一般設置成2的倍數。 
material爲材質,是一個Material對象,用來設置形狀發生碰撞時表現出的響應特性,如摩擦力、彈性係數等 
實際上,Material類中並不包含摩擦力、彈性係數等屬性,它只是一個標誌類,只是一個id,真正實現碰撞響應特性的是ContactMaterial類。ContactMaterial類用來爲添加了merialA和merialB標識的兩個形狀設置獨特的碰撞響應特性,構造函數爲: 
function ContectMaterial( 
materialA:Material, materialB:Material, 
options?:{ 
friction:number, 
restitution:number, 
stiffness:number, 
relaxation:number, 
frictionStuffness:number, 
frictionRelaxation:number, 
sufaceVelocity:number}) 
其中,materialA是形狀shapeA的材質標識,materialB是形狀shapeB的材質標識,options是兩個形狀發生碰撞時響應特性的設置;friction是兩個形狀接觸面的摩擦係數,默認0.3;restitution是兩個形狀碰撞時的彈性係數,默認0;stiffness等同於ContactEquation的stiffness屬性,只碰撞時形狀表面的硬度,默認1000000,當stiffness較小時形狀之間可以重疊並有一個排斥力促使分離,形成海綿或水平的彈性表面效果;relaxation等同於ContactEquation的relaxation,指當形狀之間重疊後,在一次碰撞模擬計算過程中實施排斥力的次數,可以想象成粘稠度,取值大時剛體速度衰減快;sufaceVelocity是兩個剛體接觸時,接觸面方向上兩個剛體的相對速度,如果其中一個剛體靜止,則sufaceVelocity表示另一個剛體的速度,常用來模擬傳送帶效果。

4)形狀貼圖:
遊戲中,物理對象需要對應的遊戲圖像素材來呈現,需要使用圖片對剛體形狀貼圖。貼圖的過程,實際上是根據物理模擬後剛體的信息,實時更新圖片素材的座標和角度。步驟爲: 
⑴保存剛體與素材圖片的對應關係: 
Egret遊戲中的圖片素材,通常是通過RES對象加載進來,需要爲剛體添加一個自定義屬性,用來保存對應素材的引用。示例: 
body.userData=image; 
⑵遍歷world中所有剛體,查找擁有自定義素材屬性的剛體: 
world中所有的剛體都保存在bodies數組中,通過數組的foreach()方法,可以遍歷其中的每一個body,如果定義的body.userData屬性不爲null,則進行貼圖: 
this.world.bodies.forEach(function(body:p2.Body){ 
if(body.userData!=null){ 
//更新素材座標和角度 


⑶根據剛體信息,實時跟新對應圖像素材的座標和角度: 
在ENTER+FRAME事件處理函數中,將剛體座標和角度屬性賦值給素材,實時更新》 
body.userData.x=body.positon[0]*this.factor; 
body.userData.y=body.positon[1]*this.factor; 
body.userData.rotation=body.angle*180/Math.PI; 
Egret加載的圖片,原點默認爲左上角,而剛體的原點處於其重心,根據形狀不同,重心位置也不同。在更新素材圖片前,需要根據剛體重心的座標偏移量(offsetX,offsetY)設置圖片的anchorOffsetX和anchorOffsetY屬性。示例代碼: 
private bindAsset(body:p2.Body, assetName:string):void{ 
var offset:number[]=[]; 
body.updateAABB(); 
var bodyWidth:number=body.aabb.upperBoumd[0]-body.aabb.lowerBound[0]; 
var bodyHeight:number=body.aabb.upperBoumd[1]-body.aabb.lowerBound[1]; 
var asset:egret.Bitmap=new egret.Bitmap(); 
asset.texture=RES.getRes(assetName); 
asset.scaleX=bodyWidth*this.factor/asset.width; 
asset.scaleY=bodyHeight*this.factor/asset.height; 
this.addChild(asset); 
p2.vec2.subtract(offset, body.position, body.aabb.lowerBound); 
asset.anchorOffsetX=offset[0]/assets.scaleX*this.factor; 
asset.anchorOffsetY=offset[1]/assets.scaleY*this.factor; 
body.userData=asset; 

通過body.updateAABB()更新剛體的aabb屬性,然後才能獲取到正確的upperBoumd和lowerBound數組的值。通過向量的subtract()方法,計算剛體座標position,是相對於剛體最小包圍盒左上角aabb.lowerBound的偏移量,保存在offset變量中。代碼中,按照縮放比例將offset偏移量賦值asset的anchorOffsetX和anchorOffsetY屬性,調整圖片控制點,實現圖片與剛體的完全貼合。

4. 剛體屬性:

P2可以創建多種形狀的剛體,剛體除了形狀外還有各種屬性,有數十種,大概分爲4類,速度相關、角度相關、對象相關,還有其他屬性。

1)速度相關屬性:
⑴position:number[]=[0,0]; 
表示剛體在全局座標系的位置,是一個二維向量Vec2,默認[0,0]。任何物體都需要通過position屬性進行精確定位和移動,p2在進行物理模擬時會自動更新剛體位置座標,也可以在需要時直接修改其屬性值:body.position=[100,100]; 
⑵velocity:number[]=[0,0]; 
表示剛體的線性速度,單位像素/秒,是一個二維向量。P2使用包含兩個元素的數組表示速度,第1個元素表示x分量,第2個元素表示y分量。 
⑶damping:number=0; 
表示線性速度阻尼,也稱速度衰減係數,取值在0~1之間,默認0。假設剛體當前線速度爲v,經過一次模擬計算後,受阻尼影響,剛體線性速度變爲v*(1-damping),也就是剛體的線速度會隨時間的以1-damping爲倍數下降。damping可以用來模擬剛體與地面之間的摩擦力。 
⑷fixedX:boolean=false; 
設定是否固定剛體的x座標,默認false。當設置爲true時,剛體只能在y軸方向上下移動。 
⑸fixedY:boolean=false; 
設定是否固定剛體的y座標,默認false。當設置爲true時,剛體只能在x軸方向左右移動。 
設定了fixedX及fixedY屬性後,需要調用updateMassProperties()方法,才能使其發揮作用。 
⑹force:number[]=[0,0]; 
表示剛體當前受到的作用力大小,是一個二維向量。P2通過剛體的force來模擬外力的作用,施加的外力作用於剛體的中心位置,不會引起旋轉。如果模擬施加一個外力到物體頂部而推倒物體,P2通過Body類的applyForce()方法,可以自定義外力作用的位置,模擬類似旋轉效果。 
因爲world調用step()方法後,force屬性將被置零,如果需要模擬持續施加作用力,需要在step()方法前持續設置force屬性。 
⑺gravityScale:number=1; 
設置當前剛體的重力加速度縮放比例,默認爲1。如果當前重力加速度爲gravity=[x,y],那麼設置gravityScale後,重力加速度爲[x*gravityScale, y*gravityScale]。 
如果設置gravityScale爲0,當前剛體將不受重力加速度影響;若gravityScale設爲負值,那麼剛體將沿重力加速度反方向移動。

2)角度相關屬性:
⑴angle:number=0; 
表示剛體角度,爲弧度值,正值增大爲順時針旋轉。 
⑵angularVelocity:number=0; 
表示剛體角速度,即旋轉速度,默認爲0。如果angularVelocity>0,剛體順時針旋轉;如果angularVelocity<0,剛體逆時針旋轉。angularVelocity單位是弧度/秒,因爲P2更新step()方法是每幀調用,需要轉換成弧度/幀。假設遊戲幀頻爲fps,角速度爲a1,轉換後的每幀弧度爲:a2=a1/fps。 
⑶angularDamping:number=0.1; 
表示剛體角速度阻尼,取值範圍0~1,默認0.1。假設當前角速度爲r,P2進行一次模擬計算後,受阻尼影響,剛體角速度將變爲r*(1-damping)。 
⑷angularForce:number=0; 
表示剛體在角速度方向上受到扭力大小,單位N,默認0。angularForce作用的結果是使剛體旋轉,取值越大,剛體角速度變化越大。如果angularForce>0,扭力會促使剛體順時針旋轉;如果angularForce<0,扭力使剛體逆時針旋轉。實際上,在剛體上施加angularForce扭力後,形成一個旋轉加速度a,單位弧度/平方秒,如果剛體質量爲m,它們之間的關係爲: 
a=angularForce/m 
也即剛體加速度以每秒弧度爲a的變化量遞增。如果使用角度/幀爲單位,公式相應轉換爲: 
a=dt*(angularForce/m)*(180/Math.PI) 
⑸fixedRotation:boolean=false; 
設置是否固定剛體角度,默認false。當fixedRotation爲true時,剛體角度不會因碰撞或運動而發生變化,比如保持人一直處於垂直站立狀態。設置fixedRotation屬性後,也需要調用updateMassProperties()方法才能起作用。

3)對象相關屬性:
⑴shapes;Shape[]; 
表示剛體中包含的所有形狀,均保存在這個shapes數組中。通過遍歷數組,可以對剛體中的每個形狀單獨進行操作。如果要計算剛體整體形狀的面積,就需要遍歷shapes屬性中的所有形狀,通過area屬性,將這些形狀的面積累加起來,得到整體形狀的面積,代碼爲: 
var totalArea=0; 
for(var i=0; i<this.shapes.length;i++){ 
titalArea+=this.shapes[i].area; 

這個功能實際上已經集成到了Body類的getArea()方法中,可以直接調用這個方法。 
⑵world:World; 
表示剛體當前所在的world。通常情況下,一個P2應用中只有一個world世界,但需要時也可以創建多個world。無論是一個還是多個world,都可以通過world屬性獲取剛體所在的world,然後進行addBody()、Raycast()等操作。

4)其他屬性:
⑴id:number; 
表示剛體的唯一標識。P2中的每一個剛體都是唯一的,在碰撞檢測等過程中,可以通過id屬性來對指定的剛體進行識別。 
⑵type:number; 
剛體類型。P2中可用的剛體類型有3種,靜態剛體、可動剛體、動態剛體,默認爲靜態剛體。type的屬性值爲3個常量: 
·靜態剛體Body.STATIC:靜態剛體在world中始終保持靜止,不受重力影響,其座標、角度不會因爲碰撞而發生變化,一般使用綠色表示靜態剛體。 
·可動剛體Body.KINEMATIC:這種剛體是可動的,但不受重力影響,其座標、角度不會因爲碰撞而發生變化。所謂可動,指可通過設置剛體的velocity或angularVelocity等屬性,使其動起來,常使用紫色表示。 
·動態剛體Body.DYNAMIC:是最常用的類型,在重力作用下可進行自由落體運動,通過velocity、angularVelocity、force等屬性可使其動起來;當碰撞發生時,速度和角度相應發生變化,進行物理碰撞模擬,常常使用粉色表示。 
⑶mass:number; 
表示剛體的質量。P2中的剛體,沒有密度density屬性,mass充當了密度角色,默認爲0,此時剛體爲靜態剛體。當mass>0,剛體類型自動轉換爲動態剛體,數值越大,慣性越大。 
⑷ccdIterations:number=10; 
當剛體高速運動時,P2會在一次step()中進行連續多次碰撞檢測,ccdIterations即表示這個檢測次數,默認爲10。ccdIterations越大,碰撞模擬越精確,但計算效率也降低。 
⑸ccdSpeedThreshold:number=-1; 
只CCD碰撞檢測的最低速度,即速度超過ccdSpeedThreshold門限時,P2將對剛體進行連續碰撞檢測,默認爲-1,此時不進行連續碰撞檢測。 
⑹collisionResponse:boolean=true; 
指定當與其他剛體發生碰撞時當前剛體是否會進行碰撞模擬,默認爲true。如果設爲false,則碰撞檢測發生時,當前剛體不進行碰撞模擬,會穿過剛體,但此時仍會觸發碰撞事件,併產生ContactEquation。 
⑺allowSleep:boolean=true; 
指定是否允許剛體進入睡眠狀態,默認true。正常情況下,P2會遍歷world中的每個剛體,對其進行紋理模擬計算。當剛體的速度爲0,處於靜止狀態時,剛體會進入睡眠狀態,P2將不再對其進行物理模擬計算,以提升效率。這個屬性起作用,world的allowSleeping= World.BODY_SLEEPING。 
⑻sleepSpeedLimit:number=0.2; 
爲剛體進入睡眠狀態時的最小速度,默認0.2,即當剛體速度小於sleepSpeedLimit時,剛體進入睡眠狀態。其前提是剛體的allowSleep爲true。 
⑼sleepState:number=Body.AWAKE; 
爲剛體當前睡眠狀態,默認爲喚醒狀態。剛體睡眠狀態有3種: 
·Body.AWAKE:剛體處於喚醒狀態,P2對剛體進行正常的物理模擬計算,更新其屬性 
·Body.SLEEPY:當剛體的速度爲sleepSpeedLimit,剛體進入SLEEPY瞌睡狀態,該狀態下P2也對剛體進行物理模擬計算 
·Body.SLEEPING:當剛體進入SLEEPY狀態超時時間sleepTimeLimit,才進入SLEEPING睡眠狀態。此時,P2在遍歷所有剛體,將通過當前剛體,不對其進行物理模擬計算。 
⑽sleepimeLimit:number=1; 
爲剛體從瞌睡Body.SLEEPY狀態進入睡眠狀態Body.SLEEPING狀態需要的時間,默認爲1秒。 
⑾idleTime:number; 
剛體已經進入睡眠Body.SLEEPING狀態的時長。

5. 剛體操作:

1)addBody和removeBody:
World類中的addBody()和removeBody()分別用來上P2世界添加和刪除剛體。所有創建好的剛體,必須通過addBody()添加到P2世界中,纔可以進行碰撞模擬: 
var body:p2.Body({mass:1, position:[1,1]}); 
this.world.addBody(body); 
當物體被子彈擊中,或超出屏幕範圍時,需要刪除剛體,可以通過removeBody()將其從P2世界中刪除,同時還可以避免不必要的計算。示例代碼: 
this.world.removeBody(bodyToRemove); 
2)addShape和removeShape:
Body類中的addShape()和removeShape()分別用來向剛體中添加和刪除形狀。使用addShape(): 
var shape:p2.Rectangle=new p2.Rectangle(100,50); 
var body:p2.Body=new p2.Body({mass:1, position:[1,1]); 
body.addShape(shape); 
其實,addShape()中還有其他參數,構造函數爲: 
function addShape(shape:p2.Shape, offset:number, angle:number) 
其中參數除了被添加的形狀shape,還有兩個參數,offset爲形狀相對於座標原點的偏移量,angle爲形狀的角度,這兩個參數都是相對於剛體本地座標系而言,即會隨剛體的變化而變化。 
可以通過removeShape()方法將指定的形狀從剛體中刪除。 
形狀的添加或刪除,並不會改變剛體的重心座標,因此可用來模擬不倒翁效果。 
3)adjustCenterOfMass:
調整重心位置。剛體中增加或移去形狀後,重心並不會自動改變,可以使用adjustCenterOf Mass()方法,使剛體重心重新回到中心位置。這個方法不帶任何參數,也沒有返回值。 
4)applyForce:
作用力可以讓剛體狀態改變,通過applyForce()方法,可以在指定點worldPoint對剛體施加一個作用力,形成一個加速度或扭力,改變剛體的線速度或角速度。構造函數爲: 
function applyForce(force:number[], worldPoint:number[]) 
其中,force是要施加的作用力,這是一個二維向量; worldPoint是一個全局座標點,表示force在剛體上的作用點,當此點不在剛體重心位置時,剛體角度也會發生變換。 
5)applyImpulse:
如果要瞬間改變剛體的狀態,如子彈彈出膛效果,需要使用applyImpulse()對其施加衝量,使剛體的速度和角速度瞬間發生變化。 
function applyImpulse(impulse:number[], relativePoint:number[]) 
其中,impulse爲要施加的衝量,衝量有大小和方向,是一個二維向量; relativePoint是一個本地座標點,表示衝量的作用點,當作用點不在剛體中心時剛體角度也會發生變化。 
6)sleep和wakeup: 
Body的sleep()方法強制使剛體進入睡眠狀態,此時除了sleepState處於Body.SLEEPING狀態外,剛體的速度、角速度、受到的作用力、扭力等都會全部清零。 
function sleep(){ 
this.sleepState=Body.SLEEPING; 
this.angularVelocity=0; 
this.angularForce=0; 
vec2.set(this.velocity, 0, 0); 
vec2.set(this.force, 0, 0); 
this.emit(Body.sleepEvent); 
}; 
剛體進入睡眠狀態後,通過調用wakeup()方法,可以將剛體強制喚醒,但僅僅是將sleepState設置爲Body.AWAKE,睡眠狀態前的速度、角度等屬性不會恢復: 
function wakeup(){ 
var s=this.sleepState; 
this.sleepState=Body.AWAKE; 
this.idleTime=0; 
if(s!==Body.AWAKE){ 
this.emit(Body.wakeUpEvent); 


可以使用sleep()方法實現類似冰凍效果。 
7)emit、on、off、has:
P2物理引擎中,通過EventEmitter類實現事件派發機制,並通過emit()、on()、off()、has()方法分別實現派發、監聽、取消監聽,以及檢測是否包含指定事件功能。 
⑴emit():派發自定義事件,事件類型使用任意字符串表示: 
function emit(event:Object) 
其中,event爲要派發的自定義事件,是一個Object類型,用於包含任何需要通過事件傳遞的信息。在自定義事件時,event對象至少要包含名爲type的字符串屬性值,表示事件名稱: 
body.emit({type:"movingUp"}); 
⑵on():監聽對應的自定義函數 
監聽到事件對象,將作爲參數傳遞給事件監聽函數,示例代碼爲: 
var onMyEvent=function(event){ 
console.log("onMyEvent is fired, because I am moving up."); 
}; 
body.on("movingUp", onMyEvent); 
on()方法的參數是字符串類型的事件名稱,並在事件觸發時調用一個處理函數。 
⑶off():取消監聽 
當不需要監聽某個事件時,將事件名稱和事件監聽函數作爲參數傳遞給off()方法,就可以取消監聽。示例代碼: 
body.off("movingUp", onMyEvent); 
⑷has():檢查對象是否包含指定的事件監聽 
Body類因爲繼承了EventEmitter類,所以也擁有上面4個方法。通過這些方法,可以方便地對剛體的某些特定狀態進行監聽,如睡眠或甦醒。Body類內置了睡眠和甦醒事件: 
·sleepEvent:當剛體進入睡眠狀態時派發該事件,對應事件名稱爲sleep,監聽使用body. on("sleep", onMyEvent); 
·wakeupEvent:當剛體從睡眠狀態恢復至甦醒狀態時,派發wakeupEvent事件,事件名爲wake,監聽使用body.on("wake", onMyEvent); 
·sleepyEvent:當剛體進入瞌睡狀態時,派發該事件。Body有一個sleepSpeedLimit屬性,當剛體的速度小於sleepSpeedLimit值時就進入瞌睡狀態;如果該狀態持續時間超過body. sleepTimeLimit,則剛體進入睡眠狀態。sleepyEvent對應的事件名爲sleepy,監聽使用body. on("sleepy", onMyEvent); 
除了上述內置事件,還可以自定義事件,比如在step()方法中持續判斷body.velocity.y是否小於0,來檢測剛體向上運動,派發自定義moveUp事件: 
public loop():void{ 
this.world.step(60/1000); 
this.debugDraw.drawDebug(); 
if(this.bodyRef.velocity[1]<0){ 
this.bodyRef.emit({type:"myEvent"}); 


8)fromPolygon:
fromPolygon用於將多邊形分解成一個個小的形狀,然後組合成完整的多邊形。 
function fromPolygon(path:number[][], [options]):boolean 
其中,path保存了多邊形頂點數組,options爲可選屬性,定義多邊形分解的相關設置。 
options包括的選項有: 
·optimalDecomp=false:是否進行最佳分解,默認false,開啓該選項會降低計算速度 
·skipSimpleCheck=false:是否進行頂點交叉的判斷,如果確定不存在交叉點可設爲true 
·removeCollinearPoints=false:是否剔除共線頂點,false表示不剔除 
如果多邊形創建成功返回true,否則返回false。導致創建失敗有幾種原因,比如創建的形狀中有空洞、頂點和邊之間有交叉。可以編程將鼠標光標移動的軌跡中的點作爲多邊形的頂點。也就是隨手創建剛體。 
9)hitTest:
用於實現指定座標點與剛體的碰撞檢測,並將檢測到的碰撞剛體保存到數組中返回。 
function hitTest(worldPoint:number[][], bodies:Body[], precision:number):Body[] 
其中,worldPoint爲要檢測的座標點,是一個全局座標點;bodies爲要檢測的剛體清單,可以將需要檢測的剛體保存到bodies數組中,實現有針對性的碰撞檢測。如果要對所有的剛體進行檢測,可以直接設置該參數爲world.bodies,即:hitTest(worldPoint, world.bodies); 
參數precision爲檢測精度,默認爲0,取值越大檢測精度越高,計算效率會降低,一般使用默認值,對尺寸極小的物體,如particle和line需要設置。方法的返回值爲保存了與worldPoint發生碰撞所有剛體的數組。 
hitTest()最常用用於檢測鼠標光標點擊、拖曳效果。P2中沒有鼠標光標事件,可以通過hitTest()來判斷鼠標點是否與剛體發生碰撞,然後調整剛體的position至鼠標位置,實現對剛體的拖曳。爲了便於拖曳,在鼠標點擊時通過sleep()方法將剛體設爲睡眠狀態,使其不受重力影響,可以精確地隨鼠標光標移動,當拖曳完成彈起時再使用wakeUp()方法重新喚醒剛體,恢復重力的作用。 
10)getAABB:
獲取剛體的最小包圍盒AABB(Axis Align Bounding Box),最小包圍盒AABB是指包圍形狀的最小矩形框。可以通過設置剛體的isDrawAABB=true,以顯示出剛體的AABB。AABB對象將矩形對象的左下角和右下角座標分別保存在屬性lowerBound和upperBound中。 
11)getArea: 
獲取剛體的當前所有形狀的面積總和。 
12)setDensity:
設置剛體的密度,結構爲: 
function setDensity(density:number) 
其中,density爲要設置的密度,是取值大於0的數值。 
實際上,P2中的剛體並沒有密度屬性,當調用setDensity()方法後,Body會根據剛體當前的面積area計算出對應的質量並保存在mass屬性中。 
13)overlaps:
檢測當前剛體與指定剛體是否有重疊,並返回檢測結果,結構爲: 
function overlaps(body:Body):boolean 
其中,body爲要檢測的目標剛體。如果剛體之間有重疊則返回true,否則返回false。 
目標剛體與被檢測剛體需添加到world中,才能確保overlaps()的正常運行。通常並不需要使用overlaps()進行重疊檢測,因爲P2在實施碰撞檢測和模擬時已經完成了這些內容,只有當剛體不進行碰撞模擬時才需要使用overlaps()。比如在界面中放置一個物體,當此位置已經存在物體時不能放置。 
14)toWorldFrame和toLocalFrame:
toWorldFrame()是將剛體本地座標系統中的座標點轉換成全局座標點,toLocalFrame()是將全局座標點轉換成剛體本地座標系統中的座標點。通過這兩個方法,可以實現本地座標和全局座標之間的轉換。 
function toWorldFrame(out:number[], localPoint:number[]):void 
function toLocalFrame(out:number[],worldPoint:number[]):void 
其中,out爲轉換後的本地座標或全局座標,將保存在該屬性對應的變量中; localPoint爲要轉換的本地座標;worldPoint爲要轉換的全局座標。 
15)raycast:
射線投射技術用於實現線段與形狀的碰撞檢測,通常用來模擬人物的視野、距離探測等。實現射線投射,要先從起點from到終點to構建一條射線ray,然後檢測與該線段發生碰撞的剛體,並保存在一個RaycastResult對象中。raycast結構爲: 
function raycast(result:RaycastResult, ray:Ray) 
其中,result是保存了碰撞剛體、碰撞點、碰撞距離等信息的一個RaycastResult對象;ray是用於檢測的射線,是一個Ray類,包含起點from、終點to等屬性。使用射線投射,需要用到Ray類和RaycastResult類。 
①Ray類:
Ray類的構造函數爲: 
function Ray([options]) 
其中,options參數是一個Object對象,初始化Ray對象時,可以保持options缺省,然後再通過Ray屬性進行設置。Options對象中包含一些參數: 
·from:number[]:線段的起點,一個二維向量 
·to:number[]:線段的終點,一個二維向量 
·mode:number:射線的碰撞檢測模式,取值爲3個常量,分別爲Ray.ALL、Ray.CLOSEST、Ray.ANY。Ray.ALL模式下,raycast()函數會檢測所有與射線ray發生碰撞的剛體;Ray.CLOSEST模式下,返回檢測到的碰撞剛體中距離起點from最近的剛體;Ray.ANY模式下,當ray檢測到第1個碰撞剛體時會立刻停止檢測並返回該剛體,這時的第1個剛體與創建時的順序有關。 
·callback:Function:回調函數,當raycast()檢測到碰撞剛體後會立即調用回調函數,該屬性只適用於rayCastAll() 
·collisionMask:number:與collisionGroup配合使用,對檢測剛體進行篩選 
·collisionGroup:number:與collisionMask配合使用,對檢測剛體進行篩選,只有滿足條件的剛體才參與碰撞檢測,檢測條件爲: 
(this.collisionGroup&body.collisionMask)&&(body.collisionGroup&this.collisionMask)==true 
·skipBackface:是否忽略射線ray反方向與剛體的碰撞點,值爲false時只檢測正方向 
·checkCollisionResponse:配合Body中的collisionResponse屬性,如果剛體的屬性collisionResponse和checkCollisionResponse同時爲true,則不對該剛體進行檢測。 
·direction:射線方向,可以缺省,取決於from和to屬性 
·length:射線從起點from到終點to的間距 
②RaycastResult類:
RaycastResult類用於保存射線與剛體的碰撞信息,包括幾個屬性: 
·body:當前碰撞檢測中,與射線ray發生碰撞的剛體 
·shape:當前碰撞剛體中,與射線ray發生重疊的形狀 
·fraction:射線起點from到碰撞點之間的距離distance與射線長度length的比例 
·normal:垂直於射線碰撞邊的法向量,只讀屬性,默認-1 
RaycastResult類還有一些方法: 
·hasHit():是否檢測到與射線ray發生碰撞的剛體,當檢測到時返回true 
·getHitDistance():當檢測到與射線發生碰撞的剛體時,碰撞點與射線起點from間的距離 
function getHitDistance(ray:Ray):number 
·getHitPoint():獲取碰撞點位置,返回結果是一個全局座標點 
此方法的結構爲function getHitPoint(out:number[], ray:Ray):void 
其中,out爲用於保存碰撞點座標的二維向量,ray爲當前進行碰撞檢測的射線。 
·stop():用於立即停止碰撞檢測 
因爲raycast()方法中的RaycastResult會重複使用,所以回調函數中獲取到的是最後一次碰撞檢測信息。

6. 碰撞處理:

P2可以實現物體碰撞模擬,同時在碰撞過程中派發一些事件實現碰撞檢測,將碰撞信息及時反饋,以添加相應的特效。 
P2中,當兩個剛體的最小包圍盒AABB發生重疊,碰撞就開始了;然後剛體的形狀發生重疊,同時P2會對重疊進行修復,使剛體朝對方的反方向移動,來消除形狀重疊;當形狀不再有重疊時,整個碰撞過程結束。可以把碰撞過程分爲4個階段: 
·postBroadphase:AABB開始發生重疊,但形狀並沒有發生接觸 
·beginContact:剛體形狀開始發生重疊,剛體繼續保持原有速度移動 
·preSolve:剛體形狀發生了重疊,但P2還未進行碰撞處理 
·endContact:P2已經完成了碰撞處理,併爲碰撞剛體重新分配了速度,同時剛體形狀分離,不再有重疊 
1)碰撞事件:
碰撞過程會產生4個事件,postBroadphase、preSolve、beginContact、endContact。在碰撞事件派發後,可以通過world類的on方法來監聽事件,並在監聽處理函數中進行對應的處理。on方法的結構爲: 
function on(type:String, listener:functtion) 
其中,type爲監聽事件名,爲一個字符串,取值爲對應的事件名稱;listener爲事件監聽函數,當監聽到碰撞事件後,會自動調用監聽函數,並將碰撞事件對象作爲參數傳遞給監聽處理函數。碰撞監聽函數的參數event是一個Object對象,對應碰撞事件對象,其中保存碰撞信息。 
在on事件處理函數中,this指的是派發事件的對象context,也就是world對象,而不再是主類中的this。因此,需要在on函數之前,將this指針保存到一個局部變量中,然後才能在事件處理函數中通過這個局部變量訪問主類中的變量和方法。 
on()方法監聽碰撞事件時,會將對應的碰撞事件對象作爲參數傳遞給監聽處理函數,這些碰撞事件對象爲:postBroadphaseEvent、preSolveEvent、beginContactEvent、endContactEvent。 
①postBroadphaseEvent:
當兩個剛體的AABB發生重疊時,不斷派發postBroadphase事件,並將碰撞信息保存到對應的postBroadphaseEvent對象中,該對象包含屬性pairs,即保存碰撞剛體對的數組。因爲尚未進行剛體形狀的碰撞檢測,所以此時對pairs數組中的碰撞進行刪減可以取消對應碰撞剛體之間的碰撞模擬。但刪除pairs後,將不再進行beginContact和endContact事件派發。 
②preSolveEvent:
在剛體形狀發生重疊時,P2會根據動量守恆定律,對碰撞對象的速度和角度重新計算,實現碰撞模擬,這個過程稱爲solve。preSolve事件在碰撞模擬過程前派發,在preSolve事件處理函數中進行一些處理可以取消或干預碰撞的模擬。 
preSolveEvent對象的屬性有: 
·contactEquations:保存當前碰撞產生的所有contactEquation對象的數組 
·frictionEquations:保存當前碰撞產生的所有frictionEquation對象的數組 
preSolve事件會隨step()方法不斷派發,在剛體形狀之間沒有重疊前,contactEquations屬性中並不包括contactEquation對象,所以代碼中要先進行判斷:contactEquations.length>0。 
③beginContactEvent:
當剛體形狀發生重疊時會派發beginContact事件,並將碰撞信息保存在對應的beginContact Event對象中,其中屬性有: 
·shapeA:發生碰撞的形狀A 
·shapeB:發生碰撞的形狀B 
·bodyA:形狀shapeA對應的剛體 
·bodyB:形狀shapeB對應的剛體 
·contactEquations:保存當前碰撞產生的所有contactEquation對象的數組 
④endContactEvent:
當前兩個碰撞剛體的形狀分離而不再重疊時會派發endContact事件,並將碰撞信息保存在對應的endContactEvent對象中,包含的屬性有: 
·shapeA:發生碰撞的形狀A 
·shapeB:發生碰撞的形狀B 
·bodyA:形狀shapeA對應的剛體 
·bodyB:形狀shapeB對應的剛體 
2)碰撞信息Equation:
在P2引擎中,Equation類用來保存碰撞發生時的碰撞點、碰撞向量等信息。一般用其兩個子類ContactEquation和FrictionEquation來保存不同類型的信息。 
①ContactEquation:
碰撞過程中,因接觸而產生的碰撞信息,如碰撞點、碰撞向量、碰撞剛體等,都保存在ContactEquation對象中。在preSolve和beginContact階段,會產生多個ContactEquation對象,並分別保存在preSolveEvent和beginContactEvent事件對象的ContactEquation屬性中。 
·shapeA:發生碰撞的形狀A 
·shapeB:發生碰撞的形狀B 
·bodyA:形狀shapeA對應的剛體 
·bodyB:形狀shapeB對應的剛體 
·contactPointA:自bodyA的座標起,到碰撞點的全局向量 
·contactPointB:自bodyBA的座標起,到碰撞點的全局向量 
·enabled:是否對當前的ContactEquation對象進行碰撞模擬,默認true,如果在preSolve階段將其設爲false可以取消碰撞模擬 
·firstImpact:是否爲第1次碰撞,當第1次碰撞step()完成後,firstImpact立即設爲false 
·normalA:垂直於剛體碰撞邊的法向量,這是一個全局的單位向量 
·restitution:碰撞剛體之間的碰撞彈性係數,取值0~1。該係數隻影響當前碰撞的模擬,如果對shapeA和shapeB設置contactMaterial,其restitution屬性不受影響。 
②FrictionEquation:
碰撞過程中,因剛體相對運動而產生的摩擦等碰撞信息,保存在FrictionEquation對象中。在碰撞的preSolve階段會產生FrictionEquation對象,並保存在preSolveEvent事件對象的FrictionEquation屬性中。FrictionEquation對象包含的屬性有: 
·shapeA:發生碰撞的形狀A 
·shapeB:發生碰撞的形狀B 
·bodyA:形狀shapeA對應的剛體 
·bodyB:形狀shapeB對應的剛體 
·frictionCoefficient:碰撞時剛體之間的碰撞係數,取值越大速度衰減越快,隻影響當前碰撞模擬。如果有對shapeA和shapeB設置contactMaterial,其friction屬性不受影響。 
·t:碰撞邊的切線方向向量 
需要注意,frictionCoefficient屬性需要在world.solver.frictionIterations>0情況下才起作用,frictionIterations默認是0,需要在創建world時設置其值。

7. 關節:

P2中使用Constraint及其子類表示關節,也就是將兩個剛體按照指定的規則約束在一起,形成有規律的、相互限制的運動模擬。P2關節模擬中,兩個剛體沒有通過任何剛體連接,只是通過算法模擬出關節運動軌跡。爲了更加直觀,p2DebugDraw類中使用黑色的線段表示連接剛體的連桿,黑點圓的表示關節節點anchor。 
P2中關節有5種,每一種都有獨特的約束規則,包括距離關節DistanceConstraint、齒輪關節GearConstraint、鎖定關節LockConstaint、位移關節PrismaticConstraint、旋轉關節Revilute Constraint。 
1)距離關節DistanceConstraint:
按照指定的距離distance將兩個剛體約束在一起,其中任何一個剛體的位置發生變化,會牽着另一個剛體運動,以保證兩者的間距爲distance。但是兩個剛體的角度不受約束,可以繞着節點旋轉。DistanceConstraint構造函數爲: 
function DistanceConstraint(bodyA:Body, bodyB:Body, options:object) 
其中,bodyA和bodyB爲受約束的剛體,options爲關節設置選項,可以缺省,P2以默認值設置,其中選項爲: 
·distance:兩個剛體受到約束時保持的間距,默認爲添加關節時兩個剛體之間的間距 
·localAnchorA:關節點相對於剛體bodyA本地座標系統的座標系,默認[0,0] 
·localAnchorB:關節點相對於剛體bodyB本地座標系統的座標系,默認[0,0] 
·maxForce:剛體運動中,如果距離不等於distance,爲保持距離而對剛體施加的最大作用力,默認爲Number.MAX_VALUE 
除了上面的構造函數中的參數,距離關節還包含一些屬性: 
·lowerLimit:設置距離關節約束範圍下限,即bodyA到bodyB的距離最小值,默認爲0,該屬性必須大於0。只有當lowerLimitEnabled爲true時才起作用。 
·lowerLimitEnabled:是否設置距離關節約束範圍下限,默認false。 
·upperLimit:設置距離關節約束範圍上限,即bodyA到bodyB的距離最大值,默認爲0,該屬性必須大於0。只有當upperLimitEnabled爲true時才起作用。 
·upperLimitEnabled:是否設置距離關節約束範圍上限,默認false 
·position:bodyA和bodyB的當前間距 
可以通過joint.collideConnected屬性爲true,避免平臺和車輪之間的碰撞。創建完成後需要使用world的addConstraint(joint)方法將關節加入世界。 
2)齒輪關節GearConstraint:
按照指定的比例ratio,將兩個剛體的角度angleA和angleB約束爲angle=angleB*ratio。其中任何一個剛體的角度變換,都會牽着另一個剛體的角度變化,以確保兩個剛體角度的比例爲ratio。剛體的座標位置不受約束,可以自由向任意方向移動。構造函數爲: 
function GearConstraint(bodyA:body, bodyB:Body, options:Object) 
其中,bodyA和bodyB爲受關節約束的兩個剛體,options爲關節設置選項,可以缺省,P2會按默認值進行設置。選項爲: 
·angle:兩個剛體的相對角度差,齒輪關節會將一個剛體的角度減去該角度差後,再保證角度變化量的比例爲ratio 
·ratio:兩個剛體的角度變化量的比例,當ratio=2時,bodyB旋轉180°,bodyA只轉90° 
·maxForce:當兩個剛體的角度比例不是ratio時,爲將其約束爲ratio而對剛體施加的最大扭力 
齒輪關節還有兩種方法: 
·setMaxForce(force):當bodyB的角度偏離angle,齒輪關節對bodyB施加的最大扭力 
·getMaxForce():number:獲取setMaxForce()中設置的最大作用力 
3)鎖定關節LockConstraint:
將兩個剛體綁定在一起,使其相對座標位置、角度差保存不變,彷彿被釘在一起。此關節中的任何剛體座標或角度發生變化,都會牽着另一個剛體的座標和角度變化,以確保兩個剛體相對座標和角度分別爲localOffsetB和localAngleB。構造函數: 
function LockConstraint(bodyA:Body, bodyB:body, options:Object) 
其中,bodyA和bodyB爲受關節約束的兩個剛體,options爲關節設置選項,可以缺省,P2會按默認值進行設置。選項爲: 
·localOffsetB:剛體bodyB在關節約束下,相對於bodyA本地座標系的偏移量,默認爲添加關節時兩個剛體的相對位置 
·localAngleB:剛體bodyB在關節約束下,相對於bodyA本地座標系統的角度,默認時爲添加關節時兩個剛體的相對角度 
·maxForce:當兩個剛體未達到關節約束的localOffsetB和localAngleB,爲使其達到約束指定狀態,而可以施加的最大作用力,默認Number.MAX_VALUE 
LockConstraint還包含幾個方法: 
·setMaxForce(force):當bodyB的位置偏離localOffsetB,或角度差不等於localAngleB時,鎖定關節對bodyB施加的最大作用力。 
·getMaxForce():number:獲取setMaxForce()中設置的最大作用力 
4)位移關節PrismaticConstraint:
將剛體bodyB的運動方向,限定爲在剛體bodyA本地座標系統中的一個指定向量。構造函數爲: 
function PrismaticContraint(bodyA:Body, bodyB:Body, options:Object) 
其中,bodyA和bodyB爲受約束的剛體,options爲關節設置選項,可以缺省,P2以默認值設置,其中選項爲: 
·maxForce:當bodyB相對於bodyA的位置偏離localAxisA時,爲使其恢復到約束位置,可以施加的最大作用力,默認爲Number.MAX_VALUE 
·localAnchorA:控制點anchorA在bodyA本地座標系下的座標,默認[0,0] 
·localAnchorB:控制點anchorB在bodyB本地座標系下的座標,默認[1,0] 
·localAxisA:剛體受到約束時只可以在該座標軸方向上移動,這是剛體bodyA座標系下的一個向量,默認[1,0] 
·disableRotationalLock:是否禁止bodyB繞節點旋轉,默認false,即bodyB不能自由旋轉,此值只有在構造函數中設置才起作用。 
·upperLimitEnabled:是否開啓bodyB移動方向上限,默認false,此時可以沿localAxisA正方向無限移動 
·upperLimit:設置bodyB沿localAxisA正方向可以移動的最大距離,默認爲1 
·lowerLimitEnabled:是否開啓bodyB移動方向下限,默認false,此時可以沿localAxisA負方向無限移動 
·lowerLimit:設置bodyB沿localAxisA負方向可以移動的最大距離,默認爲0,該屬性值要小於upperLimit 
除了上述在構造函數中的參數,PrismaticConstraint還有其他一些屬性: 
·motorEnabled:是否開啓馬達屬性,與motorSpeed配合使用。開啓後,關節會對bodyB施加作用力,使其線速度達到motorSpeed,並在約束範圍內一直保持該速度。開啓或關閉馬達屬性,要用enableMotor()和disableMotor()方法。 
·motorSpeed:開啓馬達屬性後,bodyB的目標速度值 
·position:在localAxisA上,bodyB相對於bodyA的當前位置 
PrismaticConstraint還有一些方法,用於調整關節的相關屬性: 
·setLimits(lower, upper):設置位移關節的上下限,其中lower一定要小於upper 
·disableMotor():關閉馬達屬性 
·enableMotor():開啓馬達屬性 
可以創建一個空剛體來固定關節。所謂空剛體,就是沒有包含任何形狀對象的剛體,所以不會與任何剛體發生碰撞模擬。 
5)旋轉關節RevoluteConstraint:
限制兩個剛體只能繞指定的控制點旋轉,該控制點是剛體bodyA本地座標系下的座標。其中一個剛體的位置或角度發生變化時,爲了確保控制點和剛體的相對位置不變,另一個剛體也會被牽制發生位置和角度的變化。構造函數爲: 
function RevoluteConstraint(bodyA:Body, bodyB:Body, options:Object) 
其中,bodyA和bodyB爲受關節約束的兩個剛體,options爲關節設置選項,可以缺省,P2會按默認值進行設置。選項爲: 
·worldPivot:全局座標系下的關節節點,bodyA和bodyB均受約束,只能繞該節點旋轉。設置該節點後,旋轉關節會自動計算localPivotA和localPivotB本地節點。 
·localPivotA:節點worldPivot在bodyA剛體本地座標系統下的座標,默認[0,0] 
·localPivotB:節點worldPivot在bodyB剛體本地座標系統下的座標,默認[0,0] 
·maxForce:當剛體座標偏離節點時,爲使其恢復到節點位置,可以施加的最大作用力,默認爲Number.MAX_VALUE 
RevoluteConstraint還包含幾個方法: 
·setLimits(lower:number, upper:number):設置bodyB繞節點旋轉角度的上下限,值爲弧度 
·enableMotor():開啓馬達屬性,與setMotorSpeed()配合使用,關節會對bodyB施加扭力,使其達到setMotorSpeed() 
·disableMotor():關閉馬達屬性 
·setMotorSpeed(speed):設置bodyB的目標角速度,只有開啓馬達屬性後才其作用。 
·getMotorSpeed():number:讀取馬達的當前速度 
旋轉關節常用於模擬小車運動。

8. 彈簧Spring:

P2中用來約束剛體運動的還有彈簧Spring。彈簧除約束兩個剛體之間的運動軌跡外,通過damping阻尼和stiffness剛度係數等屬性,使得剛體在向目標移動時,出現類似彈簧的簡諧運動。Spring只是抽象的父類,參與運動模擬的是兩個子類LinearSpring和RotationalSpring。 
1)LinearSpring:
LinearSpring是線性彈簧,對剛體的約束行爲和距離關節DistanceConstraint相同,按照指定的距離restLength將兩個剛體約束在一起,其中任何一個剛體的位置發生變化,會牽制着另一個剛體運動,以保證兩者的間距爲distance。在運動過程中,剛體bodyB呈現簡諧運動。兩個剛體的角度不受約束,可以繞節點旋轉。構造函數: 
function LinearSpring(bodyA:Body, bodyB:Body, options:Object) 
其中,bodyA和bodyB爲受彈簧約束的兩個剛體,options爲關節設置選項,可以缺省,P2會按默認值進行設置。選項爲: 
·stiffness:彈簧的剛度係數,默認100。 
·damping:彈簧做簡諧運動過程中的阻尼係數,默認1 
·restLength:彈簧不受力狀態下的長度,默認爲worldAnchorA和worldAnchorB間的距離 
·localAnchorA:剛體bodyA本地座標系下的節點座標,默認[0,0] 
·localAnchorB:剛體bodyB本地座標系下的節點座標,默認[0,0] 
·worldAnchorA:彈簧節點在全局座標系下的座標,設置後將自動轉換並覆蓋localAnchorA 
·worldAnchorB:彈簧節點在全局座標系下的座標,設置後將自動轉換並覆蓋localAnchorB 
2)RotationalSpring:
RotationalSpring是扭力彈簧,對剛體的約束類似齒輪關節,按照指定的restAngle約束兩個剛體之間的角度差。當剛體的角度不等於restAngle時,bodyB會進行簡諧運動旋轉,直至角度差恢復至restAngle。兩個剛體的座標位置不受約束,可以自由移動。 
function LinearSpring(bodyA:Bodt, bodyB:Body, options:Object) 
其中,bodyA和bodyB爲受彈簧約束的兩個剛體,options爲關節設置選項,可以缺省,P2會按默認值進行設置。選項爲: 
·restAngle:彈簧不受力無簡諧運動下剛體bodyA和bodyB間的角度差,默認爲創建扭力彈簧時兩個剛體之間的角度差 
·stiffness:彈簧的剛度係數,默認100。 
·damping:彈簧做簡諧運動過程中的阻尼係數,默認1

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章