android游戏物理引擎开发——重力引擎(一)

来源:互联网 发布:java中md5加密怎么写 编辑:程序博客网 时间:2024/04/30 15:54

物理引擎开发是游戏开发中的一环,许多游戏都需要程序来模拟物理环境

研究这个简单的引擎研究了一天,主要实现的是一个小球从高处落下,然后弹起的,知道速度为0 ,静止的过程。

什么都不说了,放出源代码,我做了非常详细的注释,请大家看代码吧!

 

 小球类,具体见代码

/*这个类是定义一个小球具有的属性和方法*/public class Movable {int startX = 0;int startY = 0;//小球的初始坐标X,Y,小球的实时坐标应该等于初始坐标加位移int currentX;int currentY;//实时坐标float startVX = 0f;float startVY = 0f;//初始时水平和竖直方向的速度float currentVX = 0f;float currentVY = 0f;//实时速度int r;//可移动物体的半径double timeX;double timeY;//物体在X,Y上移动的时间,当物体从一个阶段进入到另一个阶段,此属性被重置Bitmap bitmap = null;//声明一个要使用的图片BallThread bt = null;//自己写的物理引擎,为一个线程,将根据物理公式对小球的位置坐标等属性修改boolean bFall = false;//判断小球是否从木板上落下来了float impactFactory = 0.25f;//小球撞地后的速度衰减系数public Movable(int x, int y, int r, Bitmap bitmap){this.startX = x;this.startY = y;this.currentX = x;this.currentY = y;//构造函数初始化的时候,初始位置和实时位置相同this.r = r;this.bitmap = bitmap;/* * System.nanoTime是专门用来算间隔的(衡量时间段)。System.nanoTime()返回的是纳秒,nanoTime而返回的可能是任意时间, * 甚至可能是负数 * System.currentTimeMillis()返回的毫秒,这个毫秒其实就是自1970年1月1日0时起的毫秒数,Date()其实就是相当于 * Date(System.currentTimeMillis());因为Date类还有构造Date(long date),用来计算long秒与1970年1月1日之间的毫秒差。 */timeX = System.nanoTime();this.currentVX = BallView.V_MIN + (int)((BallView.V_MAX - BallView.V_MIN) * Math.random());//上面这行是随机在水平向的最小和最大速度之间随便取个数作为水平初速度,正的,小球向右运动bt = new BallThread(this);bt.start();//创建并启动物理引擎线程}public void drawSelf(Canvas canvas){//将自己绘制(Bitmap贴图)画到画布上canvas.drawBitmap(bitmap, currentX, currentY, null);}}


 下面这个类是这整篇博客的精华所在,物理引擎类

/* * 该类为一个物理引擎,主要功能是改变小球运动轨迹!另,在android中,坐标系以Y向下为正 * 全是初中就学过的物理,我觉得大家应该能看懂 * 这个类就是这整个程序的核心了~~~,物理引擎啊,好好看吧,我尽量注释详细点 */public class BallThread extends Thread {Movable father;//声明一个小球的实例boolean flag = false;//线程是否执行的标记int sleepSpan = 40;//休眠时间float g = 200;//求落下的加速度double current;//记录当前时间public BallThread(Movable father){//让每个小球都要自己的物理引擎,按自己的引擎去运动this.father = father;this.flag = true;//一旦初始化线程就开始执行}//run方法进行两项工作:(run方法又是整个这个类的核心)//(1)按照物理公式移动改变小球位置//(2)检测并处理小球达到最高点以及撞击地面的事件//而且,请各位注意,每个阶段一开始的时候,要把所有的时间的记录啊,X,Y的速度啊,X,Y位置啊之类的全更新public void run(){while(flag){//只要线程在,就一直循环current = System.nanoTime();//取得当前的时间//开始处理水平方向的运动//获取水平方向走过的时间,当前时间减掉小球初始化时候的时间,就是时间差,除以3个1000就得到s,nanotime得到的是nsdouble timeSpanX = (double)((current - father.timeX)/1000/1000/1000);father.currentX = (int)(father.startX + father.currentVX * timeSpanX);//初始位置+速度*时间=当前位置,水平向是匀速的//下面处理竖直方向的运动if(father.bFall){//bFall是判断小球有没有从木板上落下来,落下来了才有竖向速度double timeSpanY = (double)((current - father.timeY)/1000/1000/1000);father.currentY = (int)(father.startY + father.startVY * timeSpanY + timeSpanY * timeSpanY * g/2);//s=vt+1/2atfather.currentVY = (float)(father.startVY + g * timeSpanY);//求竖向当前速度,当前速度=v + at(高中物理)//下面这个是判断小球是否达到最高点if(father.startVY < 0 && Math.abs(father.currentVY) <= BallView.UP_ZERO){//startVY<0证明小球是向上运动的father.timeY = System.nanoTime();//设置新的运动阶段竖直方向的开始时间father.currentVY = 0;//设置新的运动阶段竖直方向的实时速度,达到最高点是时,速度肯定为0father.startVY = 0;//设置新的运动阶段竖直方向的初始速度,达到最高点以后开始改变方向,起始速度为0father.startY = father.currentY;//设置新的运动阶段竖直方向的初始位置,这时刚刚要改变运动方向,所以初始位置=当前位置}//判断小球是否落地if(father.currentY + father.r*2 >= BallView.GROUND_LING && father.currentVY > 0){father.currentVX = father.currentVX * (1 - father.impactFactory);//衰减水平方向的速度father.currentVY = 0 - father.currentVY * (1 - father.impactFactory);//衰减竖向的速度,并改变速度的方向(反弹)if(Math.abs(father.currentVY) < BallView.DOWN_ZERO){//如果衰减后的速度小于DOWN_ZERO,就停止线程的执行,Math.adb求绝对值this.flag = false;}else{//如果衰减后速度还够,就弹起father.startX = father.currentX;//弹起时水平方向的起始位置father.timeX = System.nanoTime();//记录弹起时的时间father.startY = father.currentY;father.timeY = System.nanoTime();father.startVY = father.currentVY;}}}else if(father.currentX + father.r/2 >= BallView.WOODEDGE){//当前位置加上小球半径的一半,大于这个值,小球就掉落了//BallView.WOODEDGE是木板的长度father.timeY = System.nanoTime();//记录竖直方向上的开始运动时间father.bFall = true;//一定会先执行这个else,才执行上面这个if,即timeY会先有值。看判断条件}try{Thread.sleep(sleepSpan);}catch(Exception e){e.printStackTrace();}}}}

可能有的行比较长,自动换行了,大家克服一下~

下面是显示用的类

 

/* * 这个类的作用有以下几个 * (1)作为一个主界面显示 * (2)在此类中声明物理计算时使用的静态常量 * (3)初始化图片资源 * (4)初始化小球 * 多说一点,其实这里可以把刷新画面的线程直接写在这个类里面,只要implements Runnable就行了,但是单独重写了一个 * 线程类,也是为了把功能分开,尽量不要把功能都放在一个类里面,分开放,这才是面向对象不是? * 另,这些 * 代码都不是我原创,不过看完书以后理解了,详细注释以后来给大家分享分享*/public class BallView extends SurfaceView implements SurfaceHolder.Callback{public static final int V_MAX = 55;//小球水平速度最大值public static final int V_MIN = 30;//小球水平速度的最小值public static final int WOODEDGE = 140;//木板的长度public static int GROUND_LING;//屏幕的底部,即屏幕最下方的Y坐标,表示小球落到底了//根据你用的手机来设定public static final int UP_ZERO = 30;//小球上升过程中,如果小于这个值,速度就为算为0public static final int DOWN_ZERO = 60;//小球撞击地面后,如果速度小于该值就算为0Bitmap[] bitmapArray = new Bitmap[6];//各种小球的引用Bitmap bmpBack;//背景图片Bitmap bmpWood;//木板图片String fps = "FPS:N/A";//用于显示帧速率的字符串,初始时为:"FPS:N/A",调试程序用int ballNumber = 8;//小球的数量ArrayList<Movable> alMovable = new ArrayList<Movable>();//小球对象的列表DrawThread dt;//后台屏幕绘制线程private SurfaceHolder sfh;public BallView(Context Activity) {super(Activity);sfh = this.getHolder();sfh.addCallback(this);initBitmaps(getResources());//自己写的方法,初始化图片initMovables();//自己写的方法,初始化小球dt = new DrawThread(this, sfh);//初始化重绘线程// TODO Auto-generated constructor stub}//初始化小球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{//关于大球还是小球,请看initBitmaps()这个函数tempBitmap = bitmapArray[index % 3];//如果初始化后一半的球,就从小球中随机找一个}Movable m = new Movable(0, 70 - tempBitmap.getHeight(), tempBitmap.getWidth()/2, tempBitmap);alMovable.add(m);//每次循环放一个到arrayList里}}//初始化图片public void initBitmaps(Resources r){bitmapArray[0] = BitmapFactory.decodeResource(r, R.drawable.ball_red_small);//红色小球bitmapArray[1] = BitmapFactory.decodeResource(r, R.drawable.ball_purplev_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 doDraw(Canvas canvas){canvas.drawBitmap(bmpBack, 0, 0, null);//绘制背景,中间两个参数是图片的左边和上边的位置canvas.drawBitmap(bmpWood, 0, 60, null);//绘制木板for(Movable m : alMovable){//遍历所以小球,挨个绘制m.drawSelf(canvas);}Paint p = new Paint();//初始化一个画笔p.setColor(Color.BLUE);//设置画笔颜色p.setTextSize(18);//设置字体大小p.setAntiAlias(true);//设置画笔抗锯齿canvas.drawText(fps, 30, 30, p);//绘制fps到画布上}@Overridepublic void surfaceChanged(SurfaceHolder holder, int format, int width,int height) {// TODO Auto-generated method stub}//surfaceView被创建时调用,所以类似启动后台刷屏线程的代码就写在这@Overridepublic void surfaceCreated(SurfaceHolder holder) {// TODO Auto-generated method stubGROUND_LING  = this.getHeight();//只有surface创建以后,才能取到屏幕宽高if(!dt.isAlive()){dt.start();//启动后台刷屏线程}}@Overridepublic void surfaceDestroyed(SurfaceHolder holder) {// TODO Auto-generated method stubdt.flag = false;dt = null;}}


 

画图的线程类

/* * 这个类的作用有两个 * (1)定时重绘屏幕 * (2)计算帧速率 */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);//调用BallView的doDraw方法进行绘制}}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;//记录时间间隔,即记满20帧需要的时间start = tempStamp;double fps = Math.round(100000000000.0 / span * 20)/100.0;//计算帧速率//(100s内包含的时间间隔(span)个数*20,即100s能绘制的帧数)bv.fps = "FPS:" + fps;//将帧速率}try{Thread.sleep(sleepSpan);}catch(Exception e){e.printStackTrace();}}}}


最后是android必须有的mainActivity类,我们把显示画面设置为BallView类

public class MainActivity extends Activity {BallView bv;@Overrideprotected 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);}@Overridepublic boolean onCreateOptionsMenu(Menu menu) {// Inflate the menu; this adds items to the action bar if it is present.getMenuInflater().inflate(R.menu.main, menu);return true;}}


 

源码地址:http://download.csdn.net/detail/lxtalx/6544169