開門見山,一針見血~~先來一張圖片再說!
前言
JBox2D是開源的物理引擎Box2D的Java版本,可以直接用于Android。由于JBox2D的圖形渲染使用的是Processing庫,因此在Android平臺上使用JBox2D時,圖形渲染工作只能自行開發。該引擎能夠根據開發人員設定的參數,如重力、密度、摩擦系數和彈性系數等,自動地進行2D剛體物理運動的全方位模擬。
開發前準備
首先我們得上github上下載對應的jbox2d庫,具體鏈接github.com/jbox2d/jbox2d,我們發現下載下來的是zip包,我們可是要的jar包啊。。ok,這里我們就先自行解壓再說。解壓完畢發現它是一個maven工程,全部是源碼,我擦嘞,這可咋辦,不是gradle結構的。好這里我們就要使用gradle命令把maven工程轉成gradle結構,這里我們需要自己編譯jar包。
方法一:用gradle編譯maven工程
1.先cmd進入到剛下載解壓出來的jbox2d文件目錄,執行maven工程轉gradle工程命令 gradle init --type pom
2.接著我們進去編譯好的工程目錄,進入路徑 jbox2d-master\gradle\wrapper,里面有個gradle-wrapper.properties文件,在這里,我們打開并修改自己gradle已經緩存有的版本,這里我修改成distributionUrl=https\://services.gradle.org/distributions/gradle-3.3-all.zip,不然是編譯不了的。
3.ok,接下來我們導入工程。我們需要編譯給我們自己用的jar也是通過這里的jbox2d-library進行編譯的,我們在右邊的gradle選項卡中找到對應的jbox2d-library/Task/build/assemble,雙擊進行編譯。如下圖:
心里喜滋滋的準備生成jar包....居然提示報錯了,關鍵是沒有提示報錯的內容。這里十分抓狂,github上down下來的難道沒有維護和更新嗎?。。這里我們上去看了看最近都有人在更新,這咋辦....不知道如何下手,很多人都在這里放棄了,不要灰心,我們來看第四步。
4.我們用命令行看看報錯內容。
這里window使用命令是:gradlew :jbox2d-library(模塊名稱):assemble(任務)
mac命令為:gradle :jbox2d-library(模塊名稱):assemble(任務)
5.好這里可以看到報的什么錯,結果只是一個簡單的錯誤,包名引用錯誤,自行改一下源碼,重新編譯即可打包出jar。
方法二:用maven命令直接編譯maven工程
1.首先在本機環境安裝maven :maven.apache.org/download.cgi#,并配置環境
2.在cmd的命令行的輸入mvn install (注:這里是在jbox2d-master目錄下執行的命令)
好吧,maven就兩個步驟就可以了.......
前方高能預警~GO!GO!GO!開始擼碼
我們來先了解一下Jbox2d基本概念:
1. 剛體(rigid body)/物體(body)
一塊十分堅硬的物質,它上面的任何兩點之間的距離是完全不變的。它們就像鉆石那樣的堅硬。
2. 形狀(shape)
一塊嚴格依附于物體的 2D碰撞幾何結構,形狀具有摩擦和恢復的材料性質。
3.固定裝置(fixture):
fixture綁定一個形狀到物體,增加材料屬性,例如密度,摩擦,恢復。
4.約束
一個約束就是消除物體自由度的物理連接,在2D中,一個物體有3個自由度(水平,垂直,旋轉),比如秒針,固定后,消除了想x,y的自由度,只剩下旋轉一個自由度
5.世界 world
一個物理世界就是物體,形狀和約束相互作用的集合。Box2D支持創建多個世界,但這通常是不必要的。
思路:
1.創建一個JboxImpl類,專門用于管理剛體和世界的創建和邏輯計算
2.自定義一個view,這里為了方便直接繼承FrameLayout,并且在真實屏幕中將JboxImpl中計算出剛體運動的坐標綁定給真實的view(也就是這里的image),根據重力感應不停的回調繪制。
3.MainActivity中做重力感應的注冊,回調的變化傳遞到jboxView進行界面重繪。
JboxView類
將屏幕的寬高傳遞給世界
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
jboxImpl.setWorlSize(w,h);
}
初始化世界與創建剛體
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
jboxImpl.createWorld();
//子viwe創建tag 設置body
int childCount = getChildCount();
for (int i =0; i< childCount; i++) {
View view = getChildAt(i);
//body為空時創建剛體
if (!jboxImpl.isBodyView(view) || changed) {
jboxImpl.creatBody(view);
}
}
}
開啟世界與做剛體運動的view繪制
@Override
protected void onDraw(Canvas canvas) {
jboxImpl.startWorld();
int childCount = getChildCount();
for (int i =0; i< childCount; i++) {
View view = getChildAt(i);
if (jboxImpl.isBodyView(view)) {
view.setX(jboxImpl.getViewX(view));
view.setY(jboxImpl.getViewY(view));
view.setRotation(jboxImpl.getViewRotaion(view));
}
}
invalidate();
}
JboxImpl類
創建世界
public void createWorld() {
if (mWorld == null) {
mWorld = new World(new Vec2(0, 10.0f));
//創建左右邊界靜止剛體
updateVertiacalBounds();
//創建上下邊界靜止剛體
updateHorizontalBounds();
}
}
開始世界
public void startWorld(){
if (mWorld != null) {
mWorld.step(dt, mVelocityIterations, mPosiontIterations);
}
}
創建世界的上下邊界,這里上下邊界是一個靜止的剛體
private void updateHorizontalBounds() {
BodyDef bodyDef = new BodyDef();
//創建靜止剛體
bodyDef.type = BodyType.STATIC;
//定義的形狀
PolygonShape box = new PolygonShape();
float boxWidth = switchPositionToBody(mWidth);
//設置邊界高度為1
float boxHeight = switchPositionToBody(mRatio);
box.setAsBox(boxWidth, boxHeight); //確定為矩形
FixtureDef fixtureDef = new FixtureDef();
fixtureDef.shape = box;
fixtureDef.density = mDesity;
fixtureDef.friction = 0.8f;//摩擦系數
fixtureDef.restitution = 0.5f; //補償系數
bodyDef.position.set(0, -boxHeight);
Body topBody = mWorld.createBody(bodyDef); //創建一個真實的上邊 body
topBody.createFixture(fixtureDef);
bodyDef.position.set(0, switchPositionToBody(mHeight) + boxHeight);
Body bottomBody = mWorld.createBody(bodyDef);//創建一個真實的下邊 body
bottomBody.createFixture(fixtureDef);
}
創建運動剛體
public void creatBody(View view) {
BodyDef bodyDef = new BodyDef();
bodyDef.setType(BodyType.DYNAMIC);
bodyDef.position.set(switchPositionToBody( view.getX() + (view.getWidth() / 2) )
,switchPositionToBody(view.getY() + (view.getHeight() / 2))? );
Shape shape = null;
Boolean isCircle = (Boolean) view.getTag(R.id.dn_view_circle_tag);
if (isCircle != null && isCircle) {
shape = craeteCircleShape( switchPositionToBody(view.getWidth() / 2) );
} else {
Log.i("kaka","createBody veiw tag is not circle!!!");
return;
}
FixtureDef fixtureDef = new FixtureDef();
fixtureDef.setShape(shape);
fixtureDef.friction = 0.8f;//摩擦系數
fixtureDef.density = mDesity;
fixtureDef.restitution = 0.5f;//補償系數
Body body = mWorld.createBody(bodyDef);
body.createFixture(fixtureDef);
view.setTag(R.id.dn_view_body_tag, body);
body.setLinearVelocity(new Vec2(mRandom.nextFloat(), mRandom.nextFloat()));
}
MainActivity類
主要實現SensorEventListener接口
@Override
public void onSensorChanged(SensorEvent event) {
if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER) {
float x = event.values[0];
float y = event.values[1] * 2.0f;
jboxView.onSensorChanged(-x, y);
}
}
總結:
其實jbox2d提供的接口運用起來并不是很難,難的物理學部分計算都在jbox2d中計算好了,他會返回給我們坐標值,這里的坐標值是世界中的坐標值。我們需要將它轉化成真實屏幕的坐標值重繪就ok了。一個view對應世界中的一個剛體。
代碼地址:github.com/kakaandfigo/Jbox2dProject
好了今天先寫到這里。~~~~~~~