求知 文章 文库 Lib 视频 iPerson 课程 认证 咨询 工具 讲座 Modeler   Code  
会员   
 
  
 
 
     
   
分享到
android 游戏开发之物理小球的应用
 

作者:jj120522,发布于2012-1-6,来源:CSDN

 

游戏开发不同于应用,一切都是自定义,所以对于控件什么的也没有什么好讲的,下面简单作一个案例,其实也是书本上的,最近在学习,顺便总结下.

首先我们要开发对象.Movable.java

package com.jj.ball;

import android.graphics.Bitmap;
import android.graphics.Canvas;

/***
 * 小球对象
 * 
 * @author zhangjia
 * 
 */
public class Moveable {
	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;// 水平运动时间
	double timeY;// 竖直运动时间

	Bitmap bitmap = null;// 小球

	BallThread bt = null;

	boolean bFall = false;// 小球是否已经从木板下落

	float impactFactor = 0.25f;// 小球撞地后速度的损失系数.

	/***
	 * 构造方法
	 * 
	 * @param x
	 * @param y
	 * @param r
	 * @param bitmap
	 */
	public Moveable(int x, int y, int r, Bitmap bitmap) {
		super();
		this.x = x;
		this.startX = 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();
	}

	/***
	 * 绘画
	 */
	public void drawSelf(Canvas canvas) {
		canvas.drawBitmap(bitmap, x, y, null);
	}

} 

这里面无外乎就是一些小球的属性,涉及到了BallThread物理引擎,下面接着介绍.

首先你要明白:

微妙,纳秒,秒之间的换算微秒,时间单位,符号μs1,000,000 微秒 = 1秒

纳秒,时间单位,符号ns

1,000,000,000纳秒=1秒另外你要明白:一些物理公式:比如说如何求:竖直方向的距离,水平方向移动距离,以及移动过程中的水平,竖直的速度等等,这些都是我们高一的物理知识,虽说我们N年没有触碰过了,但是现在回忆起来还是瞒不错的.在这里我感慨一下:中国教育课程五花八门,真正现实中我们用得到的又有几个?NND,坑爹青春啊.还有一点:就是小球在撞击地面和上升到最高处都是极端位置,然后再次进行运动的话,都是一次新的运动.不过想大家都明白的,只是我看后感慨游戏确实比应用好多了,起码逻辑思维是应用不能媲美的.

package com.jj.ball;

/***
 * 小球物理引擎
 * 
 * @author zhangjia
 * 
 */
public class BallThread extends Thread {
	private Moveable moveable;// 小球对象
	private boolean flag = false;// 线程标识
	private int sleepSpan = 30;// 休眠时间
	private float g = 200;// 重力加速度
	private double currentTime;// 记录当前事件

	/***
	 * 构造方法
	 * 
	 * @param moveable
	 */
	public BallThread(Moveable moveable) {
		super();
		this.moveable = moveable;
		this.flag = true;
	}

	@Override
	public void run() {
	  while (flag) {
		currentTime = System.nanoTime();// 获取当前时间,单位为纳秒
		double timeSpanX = (double) ((currentTime - moveable.timeX) / 1000 / 1000 / 1000);
// 获取从玩家开始到现在水平方向走过的时间
		// 处理水平方向上的运动
		moveable.x = (int) (moveable.startX + moveable.v_x * timeSpanX);
		// 处理竖直方向上的运动
		if (moveable.bFall) {
		   double timeSpanY = (double) ((currentTime - moveable.timeY) / 1000 / 1000 / 1000);
		   moveable.y = (int) (moveable.startY + moveable.startVY
			 * timeSpanY + timeSpanY * timeSpanY * g / 2);
		   moveable.v_y = (float) (moveable.startVY + g * timeSpanY);
		   // 判断小球是否到达最高点
		   if (moveable.startVY < 0
			 && Math.abs(moveable.v_y) <= BallView.UP_ZERO) {
			   moveable.timeY = System.nanoTime(); // 设置新的运动阶段竖直方向上的开始时间
			   moveable.v_y = 0; // 设置新的运动阶段竖直方向上的实时速度
			   moveable.startVY = 0; // 设置新的运动阶段竖直方向上的初始速度
			   moveable.startY = moveable.y; // 设置新的运动阶段竖直方向上的初始位置
			}
			// 判断小球是否撞地
			if (moveable.y + moveable.r * 2 >= BallView.GROUND_LING
				&& moveable.v_y > 0) {// 判断撞地条件
			  // 改变水平方向的速度
			  moveable.v_x = moveable.v_x * (1 - moveable.impactFactor);
             // 衰减水平方向上的速度
			  // 改变竖直方向的速度
			  moveable.v_y = 0 - moveable.v_y
				  * (1 - moveable.impactFactor); // 衰减竖直方向上的速度并改变方向
				if (Math.abs(moveable.v_y) < BallView.DOWN_ZERO) { 
              // 判断撞地后的速度,太小就停止
					this.flag = false;
				} else { // 撞地后的速度还可以弹起继续下一阶段的运动
				// 撞地之后水平方向的变化
				moveable.startX = moveable.x; // 设置新的运动阶段的水平方向的起始位置
				moveable.timeX = System.nanoTime(); // 设置新的运动阶段的水平方向的开始时间
				// 撞地之后竖直方向的变化
				moveable.startY = moveable.y; // 设置新的运动阶段竖直方向上的起始位置
				moveable.timeY = System.nanoTime(); // 设置新的运动阶段竖直方向开始运动的时间
				moveable.startVY = moveable.v_y; // 设置新的运动阶段竖直方向上的初速度
				}
			}
		} else if (moveable.x + moveable.r / 2 >= BallView.WOOD_EDGE) {// 判断球是否移出了挡板
			moveable.timeY = System.nanoTime(); // 记录球竖直方向上的开始运动时间
			moveable.bFall = true; // 设置表示是否开始下落标志位
		}
		try {
			Thread.sleep(sleepSpan); // 休眠一段时间
		} catch (Exception e) {
			e.printStackTrace();
		}
	  }
  }

}

在这里我要简单补充一点,或许大家都明白懂得,但是我在这里疑惑了,问题是:判断小球是否撞地moveable.y + moveable.r * 2 >= BallView.GROUND_LING在这里为什么半径要乘以2呢,如果这样想的话,那么你就是把中心放在小球的球心了,但是我们里面涉及的X,Y等等都是按照小球的左上角来计算的.就相当于最外面有一个矩形框在包裹着.其实这是用到碰撞检测技术中的(矩(圆柱)形检测).这样我们就很自然的明白了为什么半径乘以2了.判断小球是否移出挡板moveable.x + moveable.r / 2 >= BallView.WOOD_EDGE.在现实中,不可能小球还没有离开挡板就开始下落吧,如果是这样就不符合现实状况了,所以就在小球还差1/4之离开挡板之时开始下落,这样就和现实接轨了.(其实看起来没有什么区别,但是你要明白).大家认真看的话一定可以理解的,毕竟代码都不难理解,关键是思维,你得在脑海中有小球运动的轨迹模型.

小球运动的物理引擎开发完毕后,得让小球显示出来吧,接下来我们要开发View 视图.在这里我说明一点:在应用中,我们一般都不会自定义View,如果非要自定义的话,无外乎也是对继承已有的控件进行改造实现自己想要的效果,(特例除外),但是游戏不同,因为游戏的界面都不是android自带控件,都是通过Canvas画上去的.所以在游戏中我们一般直接继承自View和SurfaceView进行画图.游戏的核心是不断地绘图和刷新界面,图我们已经通过onDraw 方法绘制了,下面来分析如何刷新界面。

View与SurfaceView的区别在哪里呢?简单说明:在View中android为我们提供了两种方法更新UI,invalidate postInvalidate,然而我们也要通过Handler,而postInvalidate不需要,直接可以在线程中更新,而SurfaceView可以在自定义线程中进行更新视图.说的有点模糊,给大家提供篇博客,强烈建议大家先看一下这篇博客:http://byandby.iteye.com/blog/824535.接下来我们看BallView.java

package com.jj.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.graphics.RectF;
import android.util.AttributeSet;
import android.view.SurfaceHolder;
import android.view.SurfaceView;

/***
 * 自定义View
 * 
 * @author zhangjia
 * 
 */
public class BallView extends SurfaceView implements SurfaceHolder.Callback {
	static final int V_MAX = 35;// 小球水平速度的最大值
	static final int V_MIN = 15;// 小球水平速度的最小值

	public static final int WOOD_EDGE = 60;// 木板右边沿的X坐标

	static final int GROUND_LING = 730;// 代表地面Y的坐标

	static final int UP_ZERO = 30;// 小球在上升的过程中,如果小于此数则为0,

	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 arrayList = new ArrayList();// 小球对象数组

	private SurfaceHolder surfaceHolder;
	private DrawThread drawThread;

	public BallView(Context context) {
		super(context);
		surfaceHolder = getHolder();
		surfaceHolder.addCallback(this);
		initBitmaps(getResources());
		initMovables();
		drawThread = new DrawThread(this, surfaceHolder);
	}

	public BallView(Context context, AttributeSet attrs) {
		super(context, attrs);
	}

	/***
	 * 初始化图片数据
	 * 
	 * @param resources
	 */
	void initBitmaps(Resources resources) {
		bitmapArray[0] = BitmapFactory.decodeResource(resources,
				R.drawable.ball_red_small); // 红色较小球
		bitmapArray[1] = BitmapFactory.decodeResource(resources,
				R.drawable.ball_purple_small); // 紫色较小球
		bitmapArray[2] = BitmapFactory.decodeResource(resources,
				R.drawable.ball_green_small); // 绿色较小球
		bitmapArray[3] = BitmapFactory.decodeResource(resources,
				R.drawable.ball_red); // 红色较大球
		bitmapArray[4] = BitmapFactory.decodeResource(resources,
				R.drawable.ball_purple); // 紫色较大球
		bitmapArray[5] = BitmapFactory.decodeResource(resources,
				R.drawable.ball_green); // 绿色较大球
		bmpBack = BitmapFactory.decodeResource(resources, R.drawable.back); // 背景砖墙
		bmpWood = BitmapFactory.decodeResource(resources, R.drawable.wood); // 木板
	}

	/***
	 * 初始化小球
	 */
	public void initMovables() {
		Random random = new Random();
		for (int i = 0; i < ballNumber; i++) {
			int index = random.nextInt(32); // 产生随机数
			Bitmap tempBitmap = null; // 声明一个Bitmap图片引用
			if (i < ballNumber / 2) {
				tempBitmap = bitmapArray[3 + index % 3];// 如果是初始化前一半球,就从大球中随机找一个
			} else {
				tempBitmap = bitmapArray[index % 3];// 如果是初始化后一半球,就从小球中随机找一个
			}
			Moveable m = new Moveable(0, 70 - tempBitmap.getHeight(),
					tempBitmap.getWidth() / 2, tempBitmap); // 创建Movable对象
			arrayList.add(m); // 将新建的Movable对象添加到ArrayList列表中
		}
	}

	/***
	 * 绘制图片信息
	 */
	public void doDraw(Canvas canvas) {
		// 绘制全屏
		RectF rectF = new RectF(0, 0, getWidth(), getHeight());
		canvas.drawBitmap(bmpBack, null, rectF, null);// 绘制背景
		canvas.drawBitmap(bmpWood, 0, 60, null);// 绘制木板
		// 绘制一系列小球
		for (Moveable moveable : arrayList) {
			moveable.drawSelf(canvas);
		}
		Paint paint = new Paint();
		paint.setColor(Color.BLUE);
		paint.setTextSize(18);
		paint.setAntiAlias(true);
		canvas.drawText(fps, 30, 30, paint);// 绘制文字

	}

	@Override
	public void surfaceCreated(SurfaceHolder holder) {
		if (!drawThread.isAlive())
			drawThread.start();
	}

	@Override
	public void surfaceChanged(SurfaceHolder holder, int format, int width,
			int height) {

	}

	@Override
	public void surfaceDestroyed(SurfaceHolder holder) {
		drawThread.flag = false;
		drawThread = null;

	}

} 

注释已经相当的清晰,相信大家都看的明白,这里不过多介绍,我们接下来看DrawThread.用于重绘屏幕和计算帧率

DrawThread.java

package com.jj.ball;

import android.graphics.Canvas;
import android.view.SurfaceHolder;
import android.view.SurfaceView;

/***
 * 用于重绘屏幕和计算帧率
 * 
 * @author zhangjia
 * 
 */
public class DrawThread extends Thread {
	BallView ballView;// 自定义View
	SurfaceHolder surfaceHolder;
	boolean flag = false;// 线程标识
	int sleepSpan = 30;// 线程休眠
	long start = System.nanoTime();// 其实时间,用于计算帧速率
	int count = 0;// 计算帧率

	public DrawThread(BallView ballView, SurfaceHolder surfaceHolder) {
		super();
		this.ballView = ballView;
		this.surfaceHolder = surfaceHolder;
		this.flag = true;
	}

	@Override
	public void run() {
		Canvas canvas = null;
		while (flag) {
			try {
				canvas = surfaceHolder.lockCanvas();// 获取canvas.
				synchronized (surfaceHolder) {
					ballView.doDraw(canvas);// 进行绘制ballView.

				}
			} catch (Exception e) {
				e.printStackTrace();
			} finally {
				if (canvas != null) {
					surfaceHolder.unlockCanvasAndPost(canvas);// 解锁
				}
			}
			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;// 计算帧速率
				ballView.fps = "FPS:" + fps;// 将计算出的帧速率设置到BallView的相应字符串对象中
			}
			try {
				Thread.sleep(sleepSpan); // 线程休眠一段时间
			} catch (Exception e) {
				e.printStackTrace(); // 捕获并打印异常
			}
		}
	}

} 

在这里获取BallView的surfaceHolder,我们要在这个线程中不停的进行绘制屏幕,已达到动画的效果.surfaceHolder的应用,至于什么时候lockCanvas和unlockCanvasAndPost等等,大家还是参考刚才推荐的那篇文章.

在这里我要详细讲解一个东东:帧速率.

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;// 计算帧速率
		ballView.fps = "FPS:" + fps;// 将计算出的帧速率设置到BallView的相应字符串对象中
	}

首先我们先获取20帧所消耗的时间span.然后在计算100s中包含了多少个span.然后在乘以20得到的就是100s中获得了多少帧,最后在除以100,就获得了1s钟得到的帧数,及帧速率(FPS).

这样我们就为小球准备好了所以工作,最后只是在Activity中调用即可.

requestWindowFeature(Window.FEATURE_NO_TITLE); // 设置不显示标题
		getWindow().setFlags(
				// 设置为全屏模式
				WindowManager.LayoutParams.FLAG_FULLSCREEN,
				WindowManager.LayoutParams.FLAG_FULLSCREEN);
		window_Height = getWindowManager().getDefaultDisplay().getHeight();
		window_Width = getWindowManager().getDefaultDisplay().getWidth();

		ballView = new BallView(this); // 创建BallView对象
		setContentView(ballView); // 将屏幕设置为BallView对象 

最后给大家展示一下效果吧.(说来说去如果没有图的话,兴趣会减去一大半,一点也不夸张,我看别人博客也一样,如果没有图或许就不看了.呵呵)示例如下:

效果看起来不错吧.

确实如此,游戏就是fun!!!最后要说明一点:在游戏开发中,对屏幕分辨率要求很高,比如说这个案例,书上用的是320*480.而我的是480*800.没有处理运行起来很难看.原因:给的图是一个整体.就是墙壁和地面,所以我无法判断地面所处的位置的Y值.获取有办法吧,在图片中算好地面占图片的比例,不过只是猜想,因为俺才初来咋到.如果你是搞游戏开发的话,那么还望你留下你们的解决方案.


 
分享到
 
 


android人机界面指南
Android手机开发(一)
Android手机开发(二)
Android手机开发(三)
Android手机开发(四)
iPhone消息推送机制实现探讨
手机软件测试用例设计实践
手机客户端UI测试分析
手机软件自动化测试研究报告
更多...   


Android高级移动应用程序
Android应用开发
Android系统开发
手机软件测试
嵌入式软件测试
Android软、硬、云整合


领先IT公司 android开发平台最佳实践
北京 Android开发技术进阶
某新能源领域企业 Android开发技术
某航天公司 Android、IOS应用软件开发
阿尔卡特 Linux内核驱动
艾默生 嵌入式软件架构设计
西门子 嵌入式架构设计
更多...