Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> Android開發實例 >> 【Android+box2D游戲開發彈弓類游戲】4、體驗box2d物理引擎的強大功能和創建發射器

【Android+box2D游戲開發彈弓類游戲】4、體驗box2d物理引擎的強大功能和創建發射器

編輯:Android開發實例

前一章中,我們已經把游戲世界創建完成了,但是,出了一個不能顯示的地面之外,還沒有添加其他的物體。不用著急,接下來,我們就要去體驗box2d物理引擎的強大功能了。

七.創建發射器

1. 世界中加入發射器

世界和大地已經創建完成,我們模擬的世界已經比較完善了,接下來在世界中加入一些動態的物體,首先創建發射器。這個發射器將會使用到旋轉關節和鼠標關節。

由於接下來創建的動態物體,例如:發射器,子彈,磚塊等等都是一個單獨的對象。隨意我們需要為每一個類別的物體創建一個類。分析可以發現這些類需要實現的方法基本上類似,所以我們創建一個BodyInterface接口,所有的動態物體都繼承這個接口。

  1. public interface BodyInterface {  
  2.     float x=0; //x軸  
  3.     float y=0; //y軸  
  4.     float width=0; //寬度  
  5.     float height=0; //高度  
  6.     float angle=0; //角度  
  7.     Bitmap bitmap = null; //顯示圖片  
  8.      
  9.     void draw(Canvas canvas,Paint paint,float w); //畫圖  
  10.     public void setX(float x); //設置x軸  
  11.     public void setY(float y); //設置y軸  
  12.     public void setAngle(float angle); //設置角度  
  13.     public float getX(); //獲取x軸  
  14.     public float getY(); //獲取y軸  
  15.     public float getWidth(); //獲取寬度  
  16.     public float getHeight(); //獲取高度  

接下來創建發射器類CatapultArm.java。實現BodyInterface接口。實現構造函數用於初始化發射器對象。

  1. public CatapultArm(Bitmap bitmap, float x, float y, float width,float height, float angle) {  
  2.        this.bmp = bitmap;  
  3.        this.x = x;  
  4.        this.y = y;  
  5.        this.width = width;  
  6.        this.height = height;  
  7.        this.angle = angle;  
  8.     } 

實現畫圖方法

  1. public void draw(Canvas canvas, Paint paint, float position_X) {  
  2.        canvas.save();  
  3.        canvas.rotate(this.angle, x - position_X + width / 2, y + height / 2);  
  4.        canvas.drawBitmap(this.bmp, this.x - position_X, this.y, paint);  
  5.        canvas.restore();  
  6.     } 

這裡對canvas.save()與canvas.restore()進行分析。

這裡canvas.save();和canvas.restore();是兩個相互匹配出現的,作用是用來保存畫布的狀態和取出保存的狀態的。這裡稍微解釋一下, 當我們對畫布進行旋轉,縮放,平移等操作的時候其實我們是想對特定的元素進行操作,比如圖片,一個矩形等,但是當你用canvas的方法來進行這些操作的時候,其實是對整個畫布進行了操作,那麼之後在畫布上的元素都會受到影響,所以我們在操作之前調用canvas.save()來保存畫布當前的狀態,當操作之後取出之前保存過的狀態,這樣就不會對其他的元素進行影響

首先創建發射器物體對象,以及發射臂圖片對象

  1. private Body catapultArmBody;  
  2. private Bitmap catapultArmBitmap; 

在MainView構造方法中為發射臂圖片賦值

  1. catapultArmBitmap = BitmapFactory.decodeResource(res, R.drawable.catapult_arm); 

接下來創建獲取發射臂物體的方法CreateCatapultBody().

  1. public Body CreateCatapultBody(Bitmap bitmap,float x,float y,float width,float height)  
  2.     {  
  3.     PolygonShape ps = new PolygonShape();  
  4.     ps.setAsBox(width/2/RATE, (height/2-40)/RATE);  
  5.      
  6.     FixtureDef fd = new FixtureDef();  
  7.     fd.shape = ps;  
  8.     fd.density = 1;  
  9.     fd.friction = 0.8f;  
  10.     fd.restitution = 0.3f;  
  11.      
  12.     BodyDef bd = new BodyDef();  
  13.     bd.position.set((x+width/2)/RATE, (y+height/2)/RATE);  
  14.     bd.type = BodyType.DYNAMIC;  
  15.      
  16.     Body body = world.createBody(bd);  
  17.     body.m_userData = new CatapultArm(bitmap, x, y, width,height,0);  
  18.     body.createFixture(fd);  
  19.      
  20.     return body;  

這裡需要注意的就是body.m_userData方法。

Body的m_userData屬性,是一個object類型,其主要用途就是保存一個object實例,這樣就可以講自定義的類型保存到body的這個m_userData裡面,通過便利body時取出其實例並進行操作。將會在下面的程序中使用m_userData屬性。

在surfaceCreated方法中,創建發射器物體。

  1. catapultArmBody = CreateCatapultBody(catapultArmBitmap, 290, ScreenH-FLOOR_HEIGHT-catapultArmBitmap.getHeight()-catapult_base_2.getHeight()/2, catapultArmBitmap.getWidth(), catapultArmBitmap.getHeight()); 
 

由於物體在模擬世界中每時每刻都會變化,所以需要創建一個方法來改變物體的參數,以便畫出物體,在draw方法之前調用這個方法。

  1. public void logic() {  
  2.        //喚醒世界  
  3.        world.step(timeStep, iterations, 6);  
  4.        //物體位置參數  
  5.        Vec2 positionBuller;  
  6.        //世界中的所有物體的集合  
  7.        Body body = world.getBodyList();  
  8.    
  9.        //遍歷所有的物體  
  10.        for (int i = 0; i < world.getBodyCount(); i++) {  
  11.            //獲取當前物體的位置  
  12.            positionBuller = body.getPosition();  
  13.            //判斷當前物體是否為發射器  
  14.            if ((body.m_userData) instanceof CatapultArm) {  
  15.               //獲取發射器對象  
  16.               CatapultArm catapultArm = (CatapultArm) body.m_userData;  
  17.               //設置發射器的x軸  
  18.               catapultArm.setX(positionBuller.x * RATE - catapultArm.width/ 2);  
  19.               //設置發射器的y軸  
  20.               catapultArm.setY(positionBuller.y * RATE - catapultArm.height/ 2);  
  21.               //設置發射器的角度  
  22.               catapultArm.setAngle((float) (body.getAngle() * 180 / Math.PI));  
  23.            }  
  24.            //遍歷下一個對象  
  25.            body = body.m_next;  
  26.        }  
  27.     } 

這個方法計算每個物體在模擬世界中變化狀態。然後再draw方法中畫出發射器。

((CatapultArm)catapultArmBody.m_userData).draw(canvas, paint, move_X);

此時可以運行程序。已經有了真實世界中的效果(雖然不是我們想要的結果),發射器自由落體到地面上。

雖然已經把發射器放到了世界中。但是與想象的有些差別,游戲運行,發射器會自由落體到地面,而沒有和發射器底座進行固定。接下來我們就要引入旋轉關節,時發射器固定在發射器底座上,並且可以旋轉移動。

2. 發射器旋轉

現在運行游戲可以發現,發射器是模擬世界中一個孤立的物體。我們需要某種約束來限制發射器的轉動在一定角度內。借助“旋轉關節”可以完美的解決這個問題。想象一個釘子將2個物體釘在一個特殊的點,但仍然允許他們轉動。

回到MainView.java主文件中加入旋轉關節

 
  1. RevoluteJointDef rjd; //旋轉關節 

然後編寫創建選裝關節的方法。

  1. public RevoluteJoint createRevoluteJoint() {  
  2.        //創建一個旋轉關節的數據實例  
  3.        rjd =new RevoluteJointDef();  
  4.        //初始化旋轉關節數據  
  5.        rjd.initialize(catapultArmBody,floorBody ,new Vec2(catapultArmBody.getWorldCenter().x, catapultArmBody.getWorldCenter().y+catapultArmBitmap.getHeight()/2/RATE) );  
  6.        rjd.maxMotorTorque = 100;// 馬達的預期最大扭矩  
  7.        rjd.motorSpeed =1000;//馬達最終扭矩  
  8.        rjd.enableMotor = true;// 啟動馬達  
  9.         
  10.        //利用world創建一個旋轉關節  
  11.        RevoluteJoint rj = (RevoluteJoint)world.createJoint(rjd);  
  12.        return rj;  
  13.     } 

在上面代碼中,初始化選裝關節數據的函數為initialize(Body body1,Body body2,Vec2 anchor),它的參數含義說明如下:

  • 第一個參數:做旋轉運動的Body實例。
  • 第二個參數:旋轉關節中的第二個Body實例
  • 第三個參數:旋轉錨點。

當我們創建關節時你不得不修改2個物體和連接點。你可能會想:“我們不應該把發射器連接到發射器底部嗎?”。在現實世界中,沒錯。但是在Box2D中這可不是必要的。你可以這樣做但你不得不再為發射器底部創建另一個物體並增加了模擬的復雜性。由於發射器底部在何時都是靜態的並且在Box2d中樞紐物體(hinge body)不必要在其他的任何物體中,我們可以只使用我們已擁有的大地物體(floorBody)。

這裡我們使用發射器底部為旋轉關節的錨點。catapultArmBody.getWorldCenter().x屬性是得到發射器在世界中的x軸位置。catapultArmBody.getWorldCenter().y屬性石獲取發射器在世界中的y軸位置。為了是錨點在發射器的底部我們需要為y軸加上發射器圖片一半的高度。

當旋轉關節的數據初始化之後,旋轉Body是不會運動的,因為沒有力的作用,所以需要一個“馬達”來進行驅動Body,讓Body開始做旋轉運動。

“馬達”也是旋轉關節中的數據,所以利用RevoluteJointDef實例來進行設置,啟動一個“馬達”驅動旋轉Body進行旋轉運動,至少需要設置下面三個屬性:

  • maxMotorTorque:馬達的預期最大扭矩。
  • motorSpeed:馬達最終扭矩。
  • enablemotor:啟動馬達。

馬達預期最大扭矩指的是Body在進行旋轉運動開始的一個扭矩值,可以簡單理解為旋轉速度。馬達最終 指的是馬達最終會以一個固定的轉速來運動,而這個轉速就是取決最終扭矩。當兩者屬性設置完畢,最後啟動馬達即可。

方法創建完成之後,在surfaceCreated方法中,調用,創建旋轉關節。

createRevoluteJoint();

此時運行游戲程序,可以查看一下效果。

此時運行游戲,可以看到發射器已經開始旋轉了。但是,確實無休止的旋轉。這是我們需要對旋轉關節進行一些改進。加入旋轉關節的起始角度和終止角度。對角度進行限制,可以使我們的發射器更加像真實世界中的發射器。

然後我們還需要通過設置”enableLimit“,”lowerAngle“,”upperAngle“激活關節。這讓關節活動范圍角度10°到70°。這如我們所想的那樣模擬投射器運動。

因為box2d中默認的度量單位都是弧度,所有這裡應用到了弧度與角度的轉換。

1°=π/180°≈0.01745

1弧度=(180/π)º≈57.30º=57º18‘

所以

10°=10*π/180弧度

70°=70*π/180弧度

下面在createRevoluteJoint()方法中添加對旋轉角度的限制。

 
  1. rjd.enableLimit = true;  
  2. rjd.lowerAngle = (float) (10f*Math.PI/180);  
  3. rjd.upperAngle = (float) (70f*Math.PI/180);  

接下來運行游戲。

游戲開始發射器有了10度的弧度。

發射器最大的角度為75度。

3. 推動發射器

這樣看起來與真實場景有些相似了。但是發射器現在是自動旋轉,這個與需要的效果有些不同。希望的效果應該是,游戲開始發射器處於靜止狀態,用手指去推動發射器,當松開手指的時候,發射器自動復原,這樣才像一個真實的發射器。為了實現這個效果,接下來使用另一個關節。---鼠標關節。

鼠標關節:指利用鼠標提供力的作用,拖拽物體,物體朝向鼠標點擊的位置進行移動,其效果如同在物體與鼠標之間綁定一個橡皮筋。

創建鼠標關節之前需要分析一下整個創建的過程。何時創建鼠標關節,何時移動鼠標關節,何時銷毀鼠標關節。

創建鼠標關節:當我們用手指觸摸屏幕區域,當觸摸的區域內部的物體只有是發射器時,創建鼠標關節。這裡需要我們通過回調函數獲取觸摸的對象。

移動鼠標關節:當按下觸摸屏時,並且獲取了發射器對象,此時再在屏幕上移動。

銷毀鼠標關節:當創建了鼠標關節,並且手指離開觸摸屏,銷毀鼠標關節。

首先我們需要更改旋轉關節的最大扭曲力與最終扭曲力。是發射器處於靜止狀態。

  1. rjd.maxMotorTorque = 400;// 馬達的預期最大扭矩  
  2. rjd.motorSpeed =-100;//馬達最終扭矩  

然後為了能觸摸屏幕時獲取物體對象,需要新建一個回調類。

新建一個類TouchCallBack.java 並且實現QueryCallback接口。

 

  1. public class TouchCallback implements QueryCallback{  
  2.     public final Vec2 point;   
  3.     public Fixture fixture;   
  4.    
  5.     public TouchCallback() {   
  6.         point = new Vec2();   
  7.         fixture = null;   
  8.     }  
  9.     public boolean reportFixture(Fixture argFixture) {  
  10.        Body body = argFixture.getBody();  
  11.        //獲取動態的物體  
  12.        if (body.getType() == BodyType.DYNAMIC) {   
  13.            //當前觸摸點是否有物體  
  14.             boolean inside = argFixture.testPoint(point);   
  15.             if (inside) {   
  16.                 fixture = argFixture;   
  17.                 return false;   
  18.             }   
  19.         }   
  20.        return true;  
  21.     }   

回到MainView.java主類中,創建一個回調類對象。

  1. private final TouchCallback callback = new TouchCallback();  

接下來開始完成鼠標關節的創建。

下面創建鼠標關節所需要的諸多變量;

  1. MouseJoint mj;//首先創建鼠標關節對象  
  2. private Vec2 touchPoint; //手指觸摸屏幕點的位置  
  3. private final AABB queryAABB = new AABB();  //物理模擬世界的范圍  
  4. private Body curBody;   //手指觸摸的對象  
  5. private boolean withMouse = false; //判斷是否創建了鼠標關節  

在手指觸摸屏幕的時候去檢測是否碰觸到發射器,如果碰觸到了就創建鼠標關節。

首先計算當前觸摸點的位置。

  1. touchPoint = new Vec2((event.getX()+move_X)/RATE, event.getY()/RATE);  
  2. if(event.getAction() == MotionEvent.ACTION_DOWN)  
  3.        {  
  4.            position_X =move_X+ event.getX();  
  5.            if (mj != null) { //如果已經創建了鼠標關節,就直接返回  
  6.                 return true;   
  7.             }   
  8.             
  9.            //得到當前模擬世界中的最小范圍  
  10.             queryAABB.lowerBound.set(touchPoint.x - .001f, touchPoint.y - .001f);  
  11.            //得到當前模擬世界中的最大范圍  
  12.             queryAABB.upperBound.set(touchPoint.x + .001f, touchPoint.y + .001f);  
  13.             callback.point.set(touchPoint);   
  14.             callback.fixture = null;   
  15.             world.queryAABB(callback, queryAABB);   
  16.                
  17.             if(callback.fixture != null){   
  18.                     curBody = callback.fixture.getBody();  
  19.                     if(curBody.m_userData instanceof CatapultArm){  
  20.                        withMouse = true;  
  21.                         MouseJointDef def = new MouseJointDef();   
  22.                         def.bodyA = floorBody;   
  23.                         def.bodyB = curBody;   
  24.                         def.target.set(touchPoint);   
  25.                         def.maxForce = 3000;  //鼠標關節的馬力  
  26.                         mj = (MouseJoint) world.createJoint(def);   
  27.                         curBody.setAwake(true);  
  28.                     }  
  29.             }  
  30.        } 

當創建了鼠標關節之後,手指離開了觸摸屏,這是需要銷毀鼠標關節。

  1. else if(event.getAction()==MotionEvent.ACTION_UP)  
  2.        {  
  3.            if (withMouse ) {  
  4.               withMouse = false;  
  5.                 //同時刪除鼠標關節  
  6.                 if (mouseJoint != null) {  
  7.                     world.destroyJoint(mouseJoint);  
  8.                     mouseJoint = null;  
  9.                 }  
  10.             }  
  11.             
  12.        } 

手指移動的時候,發射器會跟隨手指移動的位置去移動。這樣就是想了推動發射器的效果。

  1. else if(event.getAction() == MotionEvent.ACTION_UP)  
  2.        {  
  3.            if(withMouse)  
  4.            {  
  5.               withMouse = false;  
  6.               //同時刪除鼠標關節  
  7.                 if (mj != null) {  
  8.                     world.destroyJoint(mj);//在世界中銷毀鼠標關節  
  9.                     mj = null;  
  10.                 }  
  11.            }  
  12.        } 

當手指移動的時候,需要隨時對鼠標關節的觸摸點進行更新。

  1. else if(event.getAction() == MotionEvent.ACTION_MOVE)  
  2.        {  
  3.            if (mj != null) {   
  4.               mj.setTarget(touchPoint); //設置鼠標關節的變化位置  
  5.             }  
  6.            move_X = position_X-event.getX();  
  7.            move_X = move_X<0?0:(move_X>gameWidth-ScreenW?gameWidth-ScreenW:move_X);  
  8.        } 

到此為止,鼠標關節已經創建完成了。可以運行一下游戲,進行測試。

 

這裡需要重點說明一下鼠標關節的馬力與旋轉關節的最大扭曲力有關。如果你發現創建的鼠標關節不能拖動發射器。那麼增大鼠標關節的馬力試一試,應該就會得到你想要的結果。

 

現在的游戲的結果既讓我們興奮,也讓我們失望。

興奮是因為,我們可以拖動發射器了。

失望是因為,在我們拖動發射器的同時,這個場景跟隨者手指進行了移動,使得我們剛剛拖拽發射器,游戲場景卻已經改變了。

我們希望得到的效果是,當手觸摸到發射器的時候,場景不要進行改變。當手觸摸到發射器以外的場景時,才進行場景的切換。

下面進行優化

其實很簡單,只需要加上一個判斷,如果鼠標關節存在,就不要進行場景的移動。在觸摸屏移動的時間中,加入以下判斷。

 
  1. if(!withMouse)  
  2.            {  
  3.               move_X = position_X-event.getX();  
  4.               move_X = move_X<0?0:(move_X>gameWidth-ScreenW?gameWidth-ScreenW:move_X);  
  5.            }  

然後再次運行游戲。大功告成了。發射器已經完成了。並且當我們拖拽發射器之後,發射器能自動復原。

現在我們的游戲已經初具規模了。因為我們的發射器已經完成了。在下一章節中,我們將會加入子彈,並且發射子彈!敬請關注哦!

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved