Android游戲開發(fā)學(xué)習(xí)①?gòu)椞∏驅(qū)崿F(xiàn)方法
本文實(shí)例講述了Android游戲開發(fā)學(xué)習(xí)①?gòu)椞∏驅(qū)崿F(xiàn)方法。分享給大家供大家參考。具體如下:
在學(xué)習(xí)了一點(diǎn)點(diǎn)Android之后,覺得有必要記錄下來,于是就開了這個(gè)新坑,慢慢來填吧。
1.運(yùn)動(dòng)體Movable類
本例主要模擬了一組大小不一的球以一定的水平初速度從高處落下的運(yùn)動(dòng)軌跡。其中的小球?yàn)橐粋€(gè)可移動(dòng)物體Movable對(duì)象,該類中除了包含小球圖片對(duì)象之外,還包括了如位置坐標(biāo)、水平速度、垂直速度等一系列用于模擬小球運(yùn)動(dòng)的成員變量和一些方法。
Movable類:
package com.ball;
import android.graphics.Bitmap;
import android.graphics.Canvas;
public class Movable {
int startX = 0; // 初始X坐標(biāo)
int startY = 0; // 初始Y坐標(biāo)
int x; // 實(shí)時(shí)X坐標(biāo)
int y; // 實(shí)時(shí)Y坐標(biāo)
float startVX = 0f; // 初始水平方向的速度
float startVY = 0f; // 初始豎直方向的速度
float v_x = 0f; // 實(shí)時(shí)水平方向的速度
float v_y = 0f; // 實(shí)時(shí)豎直方向的速度
int r; // 可移動(dòng)物體半徑
double timeX; // X方向上的運(yùn)動(dòng)時(shí)間
double timeY; // Y方向上的運(yùn)動(dòng)時(shí)間
Bitmap bitmap=null; // 可移動(dòng)物體圖片
BallThread bt=null; // 負(fù)責(zé)小球移動(dòng)
boolean bFall=false; // 小球是否已經(jīng)從木板上下落
float impactFactor=0.25f; // 小球撞地后速度的損失系數(shù)
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(); // 獲取系統(tǒng)時(shí)間初始化timeX
this.v_x=BallView.V_MIN+(int)((BallView.V_MAX-BallView.V_MIN)*Math.random());
bt=new BallThread(this); // 創(chuàng)建并啟動(dòng)BallThread
bt.start();
}
public void drawSelf(Canvas canvas) {
canvas.drawBitmap(bitmap,x,y,null);
}
}
startX和startY變量記錄每一個(gè)運(yùn)動(dòng)階段(如從最高點(diǎn)下落到最低點(diǎn))開始時(shí)小球的初始X、Y坐標(biāo),在隨后的物理計(jì)算中,小球的實(shí)時(shí)X、Y坐標(biāo)將會(huì)是初始坐標(biāo)加上這段時(shí)間內(nèi)的位移。
startVX和startVY是小球每一個(gè)運(yùn)動(dòng)階段初始時(shí)刻在水平方向X和豎直方向Y方向上的速度,兩者將用于計(jì)算小球的實(shí)時(shí)速度v_x和v_y。
timeX和timeY分別代表小球在水平和豎直方向上運(yùn)動(dòng)的持續(xù)時(shí)間,當(dāng)小球從一個(gè)階段運(yùn)行到下一個(gè)階段時(shí)(如從下落階段彈起后轉(zhuǎn)入上拋階段),timeX和timeY將會(huì)被重置。
BallThread對(duì)象繼承自Thread線程類,起到了物理引擎的作用,負(fù)責(zé)根據(jù)物理公式對(duì)球的位置坐標(biāo)等屬性進(jìn)行修改,從而改變球的運(yùn)動(dòng)軌跡。
布爾變量bFall用于標(biāo)識(shí)小球是否已經(jīng)從木板上落下,在程序運(yùn)行時(shí)屏幕的左上部分會(huì)有一個(gè)木板,所有的小球從木板開始向右進(jìn)行平拋運(yùn)動(dòng)。bFall為false時(shí)代表小球仍然在木板上移動(dòng),還未落下。
float變量impactFactor作用是當(dāng)小球撞到地面上后根據(jù)其值對(duì)小球水平和豎直方向的速度進(jìn)行衰減。
構(gòu)造函數(shù)中對(duì)部分成員變量進(jìn)行初始化,并啟動(dòng)物理引擎。
構(gòu)造函數(shù)中BallView類的兩個(gè)常量V_MIN和V_MAX分別代表小球水平方向速度的最小值和最大值,此處用于生成小球的隨機(jī)水平速度。
2.小球物理引擎BallThread類
首先解釋一下此物理引擎的工作機(jī)制,了解其是如何改變小球的運(yùn)動(dòng)軌跡的。
運(yùn)動(dòng)階段,本例中將小球的運(yùn)動(dòng)按照豎直方向的速度分為若干個(gè)階段,每個(gè)階段中小球在豎直方向上的速度的大小或者是一直增大(下落),或者是一直減?。ㄉ仙<疵慨?dāng)小球在豎直方向上的速度發(fā)生改變時(shí)(如撞地或達(dá)到空中最高點(diǎn)),小球就結(jié)束該階段的運(yùn)動(dòng)進(jìn)入一個(gè)新的階段。
數(shù)值計(jì)算,在每個(gè)階段開始,都會(huì)記錄下開始的時(shí)間,同時(shí)還會(huì)記錄在這個(gè)階段小球的初始X、Y坐標(biāo),初始X、Y方向上的速度等物理量。之后在這個(gè)階段的運(yùn)動(dòng)中,小球的各項(xiàng)實(shí)時(shí)數(shù)據(jù)都根據(jù)這些記錄的物理量以及當(dāng)前時(shí)間計(jì)算得出。
為零判斷,在小球上升的運(yùn)動(dòng)中和小球撞擊地面后,都需要判斷小球的速度是否為零。但是不同于真實(shí)的世界,在程序中小球的各項(xiàng)物理量都是離散的(即每隔固定的時(shí)間計(jì)算出這些物理量的值),小球?qū)嶋H的運(yùn)動(dòng)軌跡為一個(gè)個(gè)離散點(diǎn)。這種情況下如果還采用判斷是否為零的方式就有可能出現(xiàn)錯(cuò)誤(如在前一次的計(jì)算中小球速度為正,下一次的計(jì)算為負(fù),跳過了速度為零這個(gè)轉(zhuǎn)折點(diǎn),小球?qū)⒂肋h(yuǎn)不可能出現(xiàn)為零這個(gè)時(shí)刻)。所以在程序中使用了閾值的方式,小球的速度一旦小于某個(gè)閾值,就將其認(rèn)定為零。
BallThread類:
package com.ball;
public class BallThread extends Thread {
Movable father; // Movable對(duì)象引用
boolean flag = false; // 線程執(zhí)行標(biāo)識(shí)位
int sleepSpan = 40; // 休眠時(shí)間
float g = 200; // 球下落的加速度
double current; // 記錄當(dāng)前時(shí)間
public BallThread(Movable father) {
this.father = father;
this.flag = true;
}
@Override
public void run() {
while (flag) {
current = System.nanoTime(); // 獲取當(dāng)前時(shí)間,單位為納秒,處理水平方向上的運(yùn)動(dòng)
double timeSpanX = (double) ((current - father.timeX) / 1000 / 1000 / 1000); // 獲取水平方向走過的時(shí)間
father.x = (int) (father.startX + father.v_x * timeSpanX);
if (father.bFall) { // 處理豎直方向上的運(yùn)動(dòng),判斷球是否已經(jīng)移出擋板
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);
//此處先省略檢測(cè)和處理特殊事件的代碼,隨后補(bǔ)全
} else if (father.x + father.r / 2 >= BallView.WOOD_EDGE) {// 通過X坐標(biāo)判斷球是否移出了擋板
father.timeY = System.nanoTime();
father.bFall = true; // 確定下落
}
try {
Thread.sleep(sleepSpan);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
代表重力加速度的變量g初始化為200,此值是經(jīng)過測(cè)試得出的較為合理的值。
run方法處理小球在水平方向的運(yùn)動(dòng)時(shí),先根據(jù)當(dāng)前時(shí)間獲得本階段中從開始到現(xiàn)在小球在水平方向上運(yùn)動(dòng)的時(shí)間,然后用該階段中到目前為止小球的位移加上小球在該階段的初始位置,求出小球此時(shí)的X坐標(biāo)。
run方法處理小球在豎直方向的運(yùn)動(dòng)時(shí),先檢查小球的bFall是否為true,如果為true表明小球已經(jīng)可以下落,進(jìn)行物理計(jì)算;如果為false則使用X坐標(biāo)進(jìn)行判斷是否需要將其設(shè)置為true。
使用到的BallView類的常量WOOD_EDGE記錄著木板圖片的最右邊的X坐標(biāo)值,如果小球的水平位置超過該值就需要下落了。
剛才省略的檢測(cè)代碼如下:
// 判斷小球是否到達(dá)最高點(diǎn)
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) { // 判斷撞地衰減后的速度,太小就停止運(yùn)動(dòng)
this.flag = false;
} else {
// 撞地后的速度還可以彈起繼續(xù)下一階段的運(yùn)動(dòng)
father.startX = father.x;
father.timeX = System.nanoTime();
father.startY = father.y;
father.timeY = System.nanoTime();
father.startVY = father.v_y;
}
}
3.視圖類BallView
BallView是負(fù)責(zé)畫面渲染的視圖類,其中聲明了一些物理計(jì)算時(shí)要使用的靜態(tài)常量,同時(shí)還聲明了程序中要繪制的圖片資源以及要繪制的小球?qū)ο罅斜?。BallView類繼承自android.view包下SurfaceView類。SurfaceView不同于普通的View,其具有不同的繪制機(jī)理,適合用于開發(fā)游戲程序。使用SurfaceView需要實(shí)現(xiàn)SurfaceHolder.Callback接口,該接口可以對(duì)SurfaceView進(jìn)行編輯等操作,還可以監(jiān)控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坐標(biāo),小球下落到此會(huì)彈起
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; //小球數(shù)目
ArrayList<Movable> alMovable=new ArrayList<Movable>(); //小球?qū)ο髷?shù)組
DrawThread dt; //后臺(tái)屏幕繪制線程
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) { //如果是初始化前一半球,就從大球中隨機(jī)找一個(gè)
tempBitmap=bitmapArray[3+index%3];
} else { //如果是初始化前一半球,就從小球中隨機(jī)找一個(gè)
tempBitmap=bitmapArray[index%3];
}
//創(chuàng)建Movable對(duì)象
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) { //遍歷繪制每個(gè)Movable對(duì)象
m.drawSelf(canvas);
}
Paint p=new Paint();
p.setColor(Color.BLUE);
p.setTextSize(18);
p.setAntiAlias(true); //設(shè)置抗鋸齒
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;
}
}
因?yàn)閎itmapArray數(shù)組中的圖片分為大尺寸和小尺寸,為了繪制圖片時(shí),小尺寸圖片不會(huì)被擋住,所以先使用大尺寸圖片。
doDraw方法會(huì)在DrawThread中調(diào)用,用于繪制圖片和幀速率。
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(); //記錄起始時(shí)間,該變量用于計(jì)算幀速率
int count=0 ; //記錄幀數(shù)
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) { //計(jì)滿20幀時(shí)計(jì)算一次幀速率
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();
}
}
}
}
代碼中調(diào)用了BallView的屏幕重繪函數(shù)doDraw,其實(shí)現(xiàn)機(jī)制是現(xiàn)將BalView的畫布加鎖,然后調(diào)用BallView的doDraw方法對(duì)BallView的畫布進(jìn)行重新繪制。最后解鎖BallView的畫布并將其傳回。
計(jì)算幀速率的方法是,首先求出程序繪制20幀所消耗的時(shí)間span,然后計(jì)算100s內(nèi)能夠包含幾個(gè)span。100s內(nèi)包含的span個(gè)數(shù)乘以20就能得出100s內(nèi)能夠繪制幾幀,再除以100就可求得1s內(nèi)繪制的幀數(shù)。
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); //設(shè)置不顯示標(biāo)題
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN); //設(shè)置全屏
bv=new BallView(this);
setContentView(bv);
}
}
運(yùn)行效果圖:


使用到的資源文件:








希望本文所述對(duì)大家的Android程序設(shè)計(jì)有所幫助。
- Android自定義圓形View實(shí)現(xiàn)小球跟隨手指移動(dòng)效果
- Android實(shí)現(xiàn)拖動(dòng)小球跟隨手指移動(dòng)效果
- Android實(shí)現(xiàn)跟隨手指拖動(dòng)并自動(dòng)貼邊的View樣式(實(shí)例demo)
- Android中View跟隨手指移動(dòng)效果
- Android自定義View圓形和拖動(dòng)圓、跟隨手指拖動(dòng)效果
- Android實(shí)現(xiàn)View拖拽跟隨手指移動(dòng)效果
- Android中View跟隨手指滑動(dòng)效果的實(shí)例代碼
- Android自定義View圓形和拖動(dòng)圓跟隨手指拖動(dòng)
- Android開發(fā)實(shí)現(xiàn)跟隨手指的小球效果示例
相關(guān)文章
Android Presentation雙屏異顯開發(fā)流程詳細(xì)講解
最近開發(fā)的一個(gè)項(xiàng)目,有兩個(gè)屏幕,需要將第二個(gè)頁面投屏到副屏上,這就需要用到Android的雙屏異顯(Presentation)技術(shù)了,研究了一下,這里做下筆記2023-01-01
關(guān)于Android SDCard存儲(chǔ)的問題
本篇文章小編為大家介紹,關(guān)于Android SDCard存儲(chǔ)的問題。需要的朋友參考下2013-04-04
Android提高之SurfaceView的基本用法實(shí)例分析
這篇文章主要介紹了Android提高之SurfaceView的基本用法,非常實(shí)用的功能,需要的朋友可以參考下2014-08-08
Android實(shí)現(xiàn)調(diào)取支付寶健康碼
大家好,本篇文章主要講的是Android實(shí)現(xiàn)調(diào)取支付寶健康碼,感興趣的同學(xué)趕快來看一看吧,對(duì)你有幫助的話記得收藏一下2022-01-01
Android開發(fā)Viewbinding委托實(shí)例詳解
這篇文章主要為大家介紹了Android開發(fā)Viewbinding委托實(shí)例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-06-06
Android仿微信列表滑動(dòng)刪除 如何實(shí)現(xiàn)滑動(dòng)列表SwipeListView
這篇文章主要為大家詳細(xì)介紹了Android仿微信列表滑動(dòng)刪除,如何實(shí)現(xiàn)滑動(dòng)列表SwipeListView,感興趣的小伙伴們可以參考一下2016-08-08

