接下来将慢慢涉入游戏的开发,作为第一篇介绍一个模拟平抛运动的小DEMO。主要核心是两个线程类:BallThread负责改变小球的状态和坐标位置,会用到一些常用的物理公式,相信大家并不陌生。DrawThread负责绘制界面图片,包括背景,木板,小球。这两个线程相结合,就实现了模拟小球的平抛运动并将其绘制在界面上。BallThread就相当于一个物理引擎,对小球的运动进行分阶段处理,包括开始运动到滑至木板边缘、小球下落、小球反弹、小球达到最高点再下落、循环直至运动停止。每个阶段的开始都对小球位置和状态进行初始操作。保证小球坐标的准确性。
MainActivity:
package com.home.simulationthrow;
import android.os.Bundle;
import android.view.Window;
import android.view.WindowManager;
import android.app.Activity;
public class MainActivity extends Activity {
BallView bv;
@Override
protected 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);
}
}
BallView:
package com.home.simulationthrow;
import java.util.ArrayList;
import java.util.List;
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.SurfaceView;
public class BallView extends SurfaceView implements SurfaceHolder.Callback {
public static final int V_MAX = 45; // 小球水平速度的最大值
public static final int V_MIN = 20; // 小球竖直速度的最大值
private Bitmap[] bitmapArray = new Bitmap[6];// 6个小球图片
private Bitmap bmpBack; // 背景图片对象
private Bitmap bmpWood; // 木板图片对象
private int ballNumber = 6; // 小球数目
public static final int WOOD_EDGE = 60; // 木板的右边沿的x坐标
public static final int GROUND_LING = 600;// 游戏中代表地面y坐标,小球下落到此会弹起
public static final int UP_ZERO = 30; // 小球在上升过程中,如果速度大小小于该值就算为0
public static final int DOWN_ZERO = 60; // 小球在撞击地面后,如果速度大小小于该值就算为0
List<Movable> movables = new ArrayList<Movable>();
private DrawThread dt; // 后台屏幕绘制线程
String fps = "FPS:N/A"; // 用于显示帧速率的字符串,调试使用
public BallView(Context context) {
super(context);
getHolder().addCallback(this);
initBitmaps(getResources());
initMovables();
dt = new DrawThread(this, getHolder());
}
/**
* 初始化图片
*
* @param resources
*/
private 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);
}
/**
* 初始化小球集合
*/
private 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 m = new Movable(0, 70 - tempBitmap.getHeight(),
tempBitmap.getWidth() / 2, tempBitmap); // 创建Movable对象
movables.add(m); // 将新建的Movable对象添加到ArrayList列表中
}
}
/**
* 绘制程序中所需要的图片等信息
*
* @param canvas
*/
public void doDraw(Canvas canvas) {
canvas.drawBitmap(bmpBack, 0, 0, null); // 绘制背景图片
canvas.drawBitmap(bmpWood, 0, 60, null);// 绘制木板图片
for (Movable m : movables) { // 遍历Movable列表,绘制每个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 != null && !dt.isAlive()) { // 如果DrawThread没有启动,就启动这个线程
dt.start();
}
}
@Override
public void surfaceDestroyed(SurfaceHolder arg0) {
if (dt != null) {
dt.flag = false;// 停止线程的执行
dt = null; // 将dt指向的对象声明为垃圾
}
}
}
Movable实体类:
package com.home.simulationthrow;
import android.graphics.Bitmap;
import android.graphics.Canvas;
/**
* 小球实体类
*
* @author Administrator
*
*/
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(); // 获取系统时间初始化
// 随机获取小球初始时水平方向的速度(介于最大值和最小值之间)
this.v_x = BallView.V_MIN
+ (int) ((BallView.V_MAX - BallView.V_MIN) * Math.random());
bt = new BallThread(this);// 创建并启动BallThread
bt.start();
}
/**
* 把自己绘制在屏幕上
*
* @param canvas
*/
public void drawSelf(Canvas canvas) {
canvas.drawBitmap(bitmap, x, y, null);
}
}
BallThread:
package com.home.simulationthrow;
/**
* 负责修改小球位置坐标的线程
* @author Administrator
*
*/
public class BallThread extends Thread{
private Movable movable; //Movable对象
private boolean flag = false; //线程执行标志位
private int sleepSpan = 30; //休眠时间
private float g = 200; //球下落的加速度
private double current; //记录当前时间
//构造器:初始化Movable对象引用及线程执行标志位
public BallThread(Movable movable){
this.movable = movable;
this.flag = true; //设置线程执行的标志位为true
}
//方法:负责根据物理公式修改小球位置
public void run(){
while(flag){
current = System.nanoTime();//获取当前时间,单位为纳秒
double timeSpanX = (double)((current-movable.timeX)/1000/1000/1000);//获取从玩家开始到现在水平方向走过的时间
//处理水平方向上的运动(获得实时x坐标)
movable.x = (int)(movable.startX + movable.v_x * timeSpanX);
//处理竖直方向上的运动
if(movable.bFall){//判断球是否已经移出挡板
double timeSpanY = (double)((current - movable.timeY)/1000/1000/1000);//Y方向上走过的时间
//获得实时y坐标
movable.y = (int)(movable.startY + movable.startVY * timeSpanY + timeSpanY*timeSpanY*g/2);
//获得实时Y方向上的速度
movable.v_y = (float)(movable.startVY + g*timeSpanY);
//判断小球是否到达最高点(速度方向向上,并且小于了某个阀值,则认为达到了最高点)
if(movable.startVY < 0 && Math.abs(movable.v_y) <= BallView.UP_ZERO){
movable.timeY = System.nanoTime(); //设置新的运动阶段竖直方向上的开始时间
movable.v_y = 0; //设置新的运动阶段竖直方向上的实时速度
movable.startVY = 0; //设置新的运动阶段竖直方向上的初始速度
movable.startY = movable.y; //设置新的运动阶段竖直方向上的初始位置
}
//判断小球是否撞地
if(movable.y + movable.r*2 >= BallView.GROUND_LING && movable.v_y >0){//判断撞地条件
//改变水平方向的速度
movable.v_x = movable.v_x * (1-movable.impactFactor); //衰减水平方向上的速度
//改变竖直方向的速度
movable.v_y = 0 - movable.v_y * (1-movable.impactFactor); //衰减竖直方向上的速度并改变方向
if(Math.abs(movable.v_y) < BallView.DOWN_ZERO){ //判断撞地后的速度,太小就停止
this.flag = false;
}else{ //撞地后的速度还可以弹起继续下一阶段的运动
//撞地之后水平方向的变化
movable.startX = movable.x; //设置新的运动阶段的水平方向的起始位置
movable.timeX = System.nanoTime(); //设置新的运动阶段的水平方向的开始时间
//撞地之后竖直方向的变化
movable.startY = movable.y; //设置新的运动阶段竖直方向上的起始位置
movable.timeY = System.nanoTime(); //设置新的运动阶段竖直方向开始运动的时间
movable.startVY = movable.v_y; //设置新的运动阶段竖直方向上的初速度
}
}
}
else if(movable.x + movable.r/2 >= BallView.WOOD_EDGE){//判断球是否移出了挡板
movable.timeY = System.nanoTime(); //记录球竖直方向上的开始运动时间
movable.bFall = true; //设置表示是否开始下落标志位
}
try{
Thread.sleep(sleepSpan); //休眠一段时间
}
catch(Exception e){
e.printStackTrace();
}
}
}
}
DrawThread:
package com.home.simulationthrow;
import android.graphics.Canvas;
import android.view.SurfaceHolder;
public class DrawThread extends Thread {
BallView bv; // BallView对象引用
SurfaceHolder 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;// 声明一个Canvas对象
while (flag) {
try {
canvas = surfaceHolder.lockCanvas(null);// 获取BallView的画布
synchronized (surfaceHolder) {
bv.doDraw(canvas); // 调用BallView的doDraw方法进行绘制
}
} 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; // 为start重新赋值
double fps = Math.round(100000000000.0 / span * 20) / 100.0;// 计算帧速率
bv.fps = "FPS:" + fps;// 将计算出的帧速率设置到BallView的相应字符串对象中
}
try {
Thread.sleep(sleepSpan);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}