編輯:關於Android編程
本文實例講述了Android游戲開發學習①彈跳小球實現方法。分享給大家供大家參考。具體如下:
在學習了一點點Android之後,覺得有必要記錄下來,於是就開了這個新坑,慢慢來填吧。
1.運動體Movable類
本例主要模擬了一組大小不一的球以一定的水平初速度從高處落下的運動軌跡。其中的小球為一個可移動物體Movable對象,該類中除了包含小球圖片對象之外,還包括了如位置坐標、水平速度、垂直速度等一系列用於模擬小球運動的成員變量和一些方法。
Movable類:
package com.ball; import android.graphics.Bitmap; import android.graphics.Canvas; public class Movable { int startX = 0; // 初始X坐標 int startY = 0; // 初始Y坐標 int x; // 實時X坐標 int y; // 實時Y坐標 float startVX = 0f; // 初始水平方向的速度 float startVY = 0f; // 初始豎直方向的速度 float v_x = 0f; // 實時水平方向的速度 float v_y = 0f; // 實時豎直方向的速度 int r; // 可移動物體半徑 double timeX; // X方向上的運動時間 double timeY; // Y方向上的運動時間 Bitmap bitmap=null; // 可移動物體圖片 BallThread bt=null; // 負責小球移動 boolean bFall=false; // 小球是否已經從木板上下落 float impactFactor=0.25f; // 小球撞地後速度的損失系數 public Movable(int x,int y,int r,Bitmap bitmap) { this.startX=x; this.x=x; this.startY=y; this.y=y; this.r=r; this.bitmap=bitmap; timeX=System.nanoTime(); // 獲取系統時間初始化timeX this.v_x=BallView.V_MIN+(int)((BallView.V_MAX-BallView.V_MIN)*Math.random()); bt=new BallThread(this); // 創建並啟動BallThread bt.start(); } public void drawSelf(Canvas canvas) { canvas.drawBitmap(bitmap,x,y,null); } }
startX和startY變量記錄每一個運動階段(如從最高點下落到最低點)開始時小球的初始X、Y坐標,在隨後的物理計算中,小球的實時X、Y坐標將會是初始坐標加上這段時間內的位移。
startVX和startVY是小球每一個運動階段初始時刻在水平方向X和豎直方向Y方向上的速度,兩者將用於計算小球的實時速度v_x和v_y。
timeX和timeY分別代表小球在水平和豎直方向上運動的持續時間,當小球從一個階段運行到下一個階段時(如從下落階段彈起後轉入上拋階段),timeX和timeY將會被重置。
BallThread對象繼承自Thread線程類,起到了物理引擎的作用,負責根據物理公式對球的位置坐標等屬性進行修改,從而改變球的運動軌跡。
布爾變量bFall用於標識小球是否已經從木板上落下,在程序運行時屏幕的左上部分會有一個木板,所有的小球從木板開始向右進行平拋運動。bFall為false時代表小球仍然在木板上移動,還未落下。
float變量impactFactor作用是當小球撞到地面上後根據其值對小球水平和豎直方向的速度進行衰減。
構造函數中對部分成員變量進行初始化,並啟動物理引擎。
構造函數中BallView類的兩個常量V_MIN和V_MAX分別代表小球水平方向速度的最小值和最大值,此處用於生成小球的隨機水平速度。
2.小球物理引擎BallThread類
首先解釋一下此物理引擎的工作機制,了解其是如何改變小球的運動軌跡的。
運動階段,本例中將小球的運動按照豎直方向的速度分為若干個階段,每個階段中小球在豎直方向上的速度的大小或者是一直增大(下落),或者是一直減小(上升)。即每當小球在豎直方向上的速度發生改變時(如撞地或達到空中最高點),小球就結束該階段的運動進入一個新的階段。
數值計算,在每個階段開始,都會記錄下開始的時間,同時還會記錄在這個階段小球的初始X、Y坐標,初始X、Y方向上的速度等物理量。之後在這個階段的運動中,小球的各項實時數據都根據這些記錄的物理量以及當前時間計算得出。
為零判斷,在小球上升的運動中和小球撞擊地面後,都需要判斷小球的速度是否為零。但是不同於真實的世界,在程序中小球的各項物理量都是離散的(即每隔固定的時間計算出這些物理量的值),小球實際的運動軌跡為一個個離散點。這種情況下如果還采用判斷是否為零的方式就有可能出現錯誤(如在前一次的計算中小球速度為正,下一次的計算為負,跳過了速度為零這個轉折點,小球將永遠不可能出現為零這個時刻)。所以在程序中使用了阈值的方式,小球的速度一旦小於某個阈值,就將其認定為零。
BallThread類:
package com.ball; public class BallThread extends Thread { Movable father; // Movable對象引用 boolean flag = false; // 線程執行標識位 int sleepSpan = 40; // 休眠時間 float g = 200; // 球下落的加速度 double current; // 記錄當前時間 public BallThread(Movable father) { this.father = father; this.flag = true; } @Override public void run() { while (flag) { current = System.nanoTime(); // 獲取當前時間,單位為納秒,處理水平方向上的運動 double timeSpanX = (double) ((current - father.timeX) / 1000 / 1000 / 1000); // 獲取水平方向走過的時間 father.x = (int) (father.startX + father.v_x * timeSpanX); if (father.bFall) { // 處理豎直方向上的運動,判斷球是否已經移出擋板 double timeSpanY = (double) ((current - father.timeY) / 1000 / 1000 / 1000); father.y = (int) (father.startY + father.startVY * timeSpanY + timeSpanY * timeSpanY * g / 2); father.v_y = (float) (father.startVY + g * timeSpanY); //此處先省略檢測和處理特殊事件的代碼,隨後補全 } else if (father.x + father.r / 2 >= BallView.WOOD_EDGE) {// 通過X坐標判斷球是否移出了擋板 father.timeY = System.nanoTime(); father.bFall = true; // 確定下落 } try { Thread.sleep(sleepSpan); } catch (Exception e) { e.printStackTrace(); } } } }
代表重力加速度的變量g初始化為200,此值是經過測試得出的較為合理的值。
run方法處理小球在水平方向的運動時,先根據當前時間獲得本階段中從開始到現在小球在水平方向上運動的時間,然後用該階段中到目前為止小球的位移加上小球在該階段的初始位置,求出小球此時的X坐標。
run方法處理小球在豎直方向的運動時,先檢查小球的bFall是否為true,如果為true表明小球已經可以下落,進行物理計算;如果為false則使用X坐標進行判斷是否需要將其設置為true。
使用到的BallView類的常量WOOD_EDGE記錄著木板圖片的最右邊的X坐標值,如果小球的水平位置超過該值就需要下落了。
剛才省略的檢測代碼如下:
// 判斷小球是否到達最高點 if (father.startVY < 0 && Math.abs(father.v_y) <= BallView.UP_ZERO) { father.timeY = System.nanoTime(); father.v_y = 0; father.startVY = 0; father.startY = father.y; } // 判斷小球是否撞地 if (father.y + father.r * 2 >= BallView.GROUND_LING && father.v_y > 0) { father.v_x = father.v_x * (1 - father.impactFactor); // 衰減水平方向的速度 father.v_y = 0 - father.v_y * (1 - father.impactFactor); // 衰減豎直方向的速度並改變方向 if (Math.abs(father.v_y) < BallView.DOWN_ZERO) { // 判斷撞地衰減後的速度,太小就停止運動 this.flag = false; } else { // 撞地後的速度還可以彈起繼續下一階段的運動 father.startX = father.x; father.timeX = System.nanoTime(); father.startY = father.y; father.timeY = System.nanoTime(); father.startVY = father.v_y; } }
3.視圖類BallView
BallView是負責畫面渲染的視圖類,其中聲明了一些物理計算時要使用的靜態常量,同時還聲明了程序中要繪制的圖片資源以及要繪制的小球對象列表。BallView類繼承自android.view包下SurfaceView類。SurfaceView不同於普通的View,其具有不同的繪制機理,適合用於開發游戲程序。使用SurfaceView需要實現SurfaceHolder.Callback接口,該接口可以對SurfaceView進行編輯等操作,還可以監控SurfaceView的變化。
BallView類:
package com.ball; import java.util.ArrayList; import java.util.Random; import android.content.Context; import android.content.res.Resources; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.view.SurfaceHolder; import android.view.SurfaceHolder.Callback; import android.view.SurfaceView; import com.bp.R; public class BallView extends SurfaceView implements Callback { public static final int V_MAX=35; public static final int V_MIN=15; public static final int WOOD_EDGE=60; public static final int GROUND_LING=450; //代表地面的Y坐標,小球下落到此會彈起 public static final int UP_ZERO=30; //小球在上升過程中,速度小於該值就算0 public static final int DOWN_ZERO=60; //小球在撞擊地面後,速度小於該值就算0 Bitmap[] bitmapArray =new Bitmap[6]; Bitmap bmpBack; //背景圖片 Bitmap bmpWood; // 擋板圖片 String fps="FPS:N/A"; //用於顯示幀速率的字符串 int ballNumber =8; //小球數目 ArrayList<Movable> alMovable=new ArrayList<Movable>(); //小球對象數組 DrawThread dt; //後台屏幕繪制線程 public BallView(Context activity) { super(activity); getHolder().addCallback(this); initBitmaps(getResources()); //初始化圖片 initMovables(); //初始化小球 dt=new DrawThread(this,getHolder()); //初始化重繪線程 } public void initBitmaps(Resources r) { bitmapArray[0]=BitmapFactory.decodeResource(r, R.drawable.ball_red_small); bitmapArray[1]=BitmapFactory.decodeResource(r, R.drawable.ball_purple_small); bitmapArray[2]=BitmapFactory.decodeResource(r, R.drawable.ball_green_small); bitmapArray[3]=BitmapFactory.decodeResource(r, R.drawable.ball_red); bitmapArray[4]=BitmapFactory.decodeResource(r, R.drawable.ball_purple); bitmapArray[5]=BitmapFactory.decodeResource(r, R.drawable.ball_green); bmpBack=BitmapFactory.decodeResource(r, R.drawable.back); bmpWood=BitmapFactory.decodeResource(r, R.drawable.wood); } public void initMovables() { Random r=new Random(); for(int i=0;i<ballNumber;i++) { int index=r.nextInt(32); Bitmap tempBitmap=null; if(i<ballNumber/2) { //如果是初始化前一半球,就從大球中隨機找一個 tempBitmap=bitmapArray[3+index%3]; } else { //如果是初始化前一半球,就從小球中隨機找一個 tempBitmap=bitmapArray[index%3]; } //創建Movable對象 Movable m=new Movable(0, 70-tempBitmap.getHeight(), tempBitmap.getWidth()/2, tempBitmap); alMovable.add(m); //加入列表中 } } public void doDraw(Canvas canvas) { //繪制程序中需要的圖片等信息 canvas.drawBitmap(bmpBack, 0, 0,null); canvas.drawBitmap(bmpWood, 0, 60,null); for (Movable m : alMovable) { //遍歷繪制每個Movable對象 m.drawSelf(canvas); } Paint p=new Paint(); p.setColor(Color.BLUE); p.setTextSize(18); p.setAntiAlias(true); //設置抗鋸齒 canvas.drawText(fps, 30, 30, p); //畫出幀速率字符串 } @Override public void surfaceChanged(SurfaceHolder arg0, int arg1, int arg2, int arg3) { } @Override public void surfaceCreated(SurfaceHolder arg0) { if(!dt.isAlive()) { dt.start(); } } @Override public void surfaceDestroyed(SurfaceHolder arg0) { dt.flag=false; dt=null; } }
因為bitmapArray數組中的圖片分為大尺寸和小尺寸,為了繪制圖片時,小尺寸圖片不會被擋住,所以先使用大尺寸圖片。
doDraw方法會在DrawThread中調用,用於繪制圖片和幀速率。
4.繪制線程DrawThread類
DrawThread類:
package com.ball; import android.graphics.Canvas; import android.view.SurfaceHolder; public class DrawThread extends Thread { BallView bv; SurfaceHolder surfaceHolder; boolean flag=false; int sleepSpan=30; long start =System.nanoTime(); //記錄起始時間,該變量用於計算幀速率 int count=0 ; //記錄幀數 public DrawThread(BallView bv,SurfaceHolder surfaceHolder) { this.bv=bv; this.surfaceHolder=surfaceHolder; this.flag=true; } public void run() { Canvas canvas=null; while(flag) { try { canvas=surfaceHolder.lockCanvas(null); //獲取BallView的畫布 synchronized (surfaceHolder) { bv.doDraw(canvas); } } catch (Exception e) { e.printStackTrace(); } finally { if(canvas!=null) { surfaceHolder.unlockCanvasAndPost(canvas); // surfaceHolder解鎖,並將畫布傳回 } } this.count++; if(count==20) { //計滿20幀時計算一次幀速率 count=0; long tempStamp=System.nanoTime(); long span=tempStamp-start; start=tempStamp; double fps=Math.round(100000000000.0/span*20)/100.0; bv.fps="FPS:"+fps; } try { Thread.sleep(sleepSpan); } catch (InterruptedException e) { e.printStackTrace(); } } } }
代碼中調用了BallView的屏幕重繪函數doDraw,其實現機制是現將BalView的畫布加鎖,然後調用BallView的doDraw方法對BallView的畫布進行重新繪制。最後解鎖BallView的畫布並將其傳回。
計算幀速率的方法是,首先求出程序繪制20幀所消耗的時間span,然後計算100s內能夠包含幾個span。100s內包含的span個數乘以20就能得出100s內能夠繪制幾幀,再除以100就可求得1s內繪制的幀數。
5.MainActivity類
MainActivity類:
package com.ball; import android.app.Activity; import android.os.Bundle; import android.view.Window; import android.view.WindowManager; public class MainActivity extends Activity { BallView bv; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); requestWindowFeature(Window.FEATURE_NO_TITLE); //設置不顯示標題 getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN); //設置全屏 bv=new BallView(this); setContentView(bv); } }
運行效果圖:
使用到的資源文件:
希望本文所述對大家的Android程序設計有所幫助。
一.通知(Notification)的相關概念Notification是一種具有全局效果的通知,它展示在屏幕的頂端,首先會表現為一個圖標的形式,當用戶向下滑動的時候,展示
介紹Android 2.2(API 8)開始提供了一個可管理和操作設備的類DevicePolicyManager,通過這個類可以進行鎖屏、設置密碼、清除密碼、恢復出廠設置
本周五在SDK工具包中發布了一個新的模擬器2.0預覽版. 自從第一個模擬器2.0預覽版(25.0.0)發布到現在(25.0.7)發生了很多變化,下面會詳細列舉出來.模擬器
通過Espresso測試錄制器來創建UI注意:Espresso測試記錄器在Android Stuido 2.2中只是一個測試版。工具Espresso測試錄制器可以讓你不寫