模拟平抛运动

随时随地阅读更多技术实战干货,获取项目源码、学习资料,请关注源代码社区公众号(ydmsq666)

接下来将慢慢涉入游戏的开发,作为第一篇介绍一个模拟平抛运动的小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();
			}
		}
	}
}






 

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值