Android游戏开发学习①弹跳小球实现方法

本文实例讲述了Android游戏开发学习①弹跳小球实现方法。分享给大家供大家参考。具体如下:

在学习了一点点Android之后,觉得有必要记录下来,于是就开了这个新坑,慢慢来填吧。

1.运动体Movable类

本例主要模拟了一组大小不一的球以一定的水平初速度从高处落下的运动轨迹。其中的小球为一个可移动物体Movable对象,该类中除了包含小球图片对象之外,还包括了如位置坐标、水平速度、垂直速度等一系列用于模拟小球运动的成员变量和一些方法。

Movable类:

package com.ball;
import android.graphics.Bitmap;
import android.graphics.Canvas;
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(); // 获取系统时间初始化timeX
  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);
 }
}

startX和startY变量记录每一个运动阶段(如从最高点下落到最低点)开始时小球的初始X、Y坐标,在随后的物理计算中,小球的实时X、Y坐标将会是初始坐标加上这段时间内的位移。

startVX和startVY是小球每一个运动阶段初始时刻在水平方向X和竖直方向Y方向上的速度,两者将用于计算小球的实时速度v_x和v_y。

timeX和timeY分别代表小球在水平和竖直方向上运动的持续时间,当小球从一个阶段运行到下一个阶段时(如从下落阶段弹起后转入上抛阶段),timeX和timeY将会被重置。

BallThread对象继承自Thread线程类,起到了物理引擎的作用,负责根据物理公式对球的位置坐标等属性进行修改,从而改变球的运动轨迹。

布尔变量bFall用于标识小球是否已经从木板上落下,在程序运行时屏幕的左上部分会有一个木板,所有的小球从木板开始向右进行平抛运动。bFall为false时代表小球仍然在木板上移动,还未落下。

float变量impactFactor作用是当小球撞到地面上后根据其值对小球水平和竖直方向的速度进行衰减。

构造函数中对部分成员变量进行初始化,并启动物理引擎。

构造函数中BallView类的两个常量V_MIN和V_MAX分别代表小球水平方向速度的最小值和最大值,此处用于生成小球的随机水平速度。

2.小球物理引擎BallThread类

首先解释一下此物理引擎的工作机制,了解其是如何改变小球的运动轨迹的。
运动阶段,本例中将小球的运动按照竖直方向的速度分为若干个阶段,每个阶段中小球在竖直方向上的速度的大小或者是一直增大(下落),或者是一直减小(上升)。即每当小球在竖直方向上的速度发生改变时(如撞地或达到空中最高点),小球就结束该阶段的运动进入一个新的阶段。

数值计算,在每个阶段开始,都会记录下开始的时间,同时还会记录在这个阶段小球的初始X、Y坐标,初始X、Y方向上的速度等物理量。之后在这个阶段的运动中,小球的各项实时数据都根据这些记录的物理量以及当前时间计算得出。

为零判断,在小球上升的运动中和小球撞击地面后,都需要判断小球的速度是否为零。但是不同于真实的世界,在程序中小球的各项物理量都是离散的(即每隔固定的时间计算出这些物理量的值),小球实际的运动轨迹为一个个离散点。这种情况下如果还采用判断是否为零的方式就有可能出现错误(如在前一次的计算中小球速度为正,下一次的计算为负,跳过了速度为零这个转折点,小球将永远不可能出现为零这个时刻)。所以在程序中使用了阈值的方式,小球的速度一旦小于某个阈值,就将其认定为零。

BallThread类:

package com.ball;
public class BallThread extends Thread {
 Movable father; // Movable对象引用
 boolean flag = false; // 线程执行标识位
 int sleepSpan = 40; // 休眠时间
 float g = 200; // 球下落的加速度
 double current; // 记录当前时间
 public BallThread(Movable father) {
  this.father = father;
  this.flag = true;
 }
 @Override
 public void run() {
  while (flag) {
   current = System.nanoTime(); // 获取当前时间,单位为纳秒,处理水平方向上的运动
   double timeSpanX = (double) ((current - father.timeX) / 1000 / 1000 / 1000); // 获取水平方向走过的时间
   father.x = (int) (father.startX + father.v_x * timeSpanX);
   if (father.bFall) { // 处理竖直方向上的运动,判断球是否已经移出挡板
    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);
    //此处先省略检测和处理特殊事件的代码,随后补全
   } else if (father.x + father.r / 2 >= BallView.WOOD_EDGE) {// 通过X坐标判断球是否移出了挡板
    father.timeY = System.nanoTime();
    father.bFall = true; // 确定下落
   }
   try {
    Thread.sleep(sleepSpan);
   } catch (Exception e) {
    e.printStackTrace();
   }
  }
 }
}

代表重力加速度的变量g初始化为200,此值是经过测试得出的较为合理的值。

run方法处理小球在水平方向的运动时,先根据当前时间获得本阶段中从开始到现在小球在水平方向上运动的时间,然后用该阶段中到目前为止小球的位移加上小球在该阶段的初始位置,求出小球此时的X坐标。

run方法处理小球在竖直方向的运动时,先检查小球的bFall是否为true,如果为true表明小球已经可以下落,进行物理计算;如果为false则使用X坐标进行判断是否需要将其设置为true。

使用到的BallView类的常量WOOD_EDGE记录着木板图片的最右边的X坐标值,如果小球的水平位置超过该值就需要下落了。

刚才省略的检测代码如下:

// 判断小球是否到达最高点
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) { // 判断撞地衰减后的速度,太小就停止运动
  this.flag = false;
 } else {
  // 撞地后的速度还可以弹起继续下一阶段的运动
  father.startX = father.x;
  father.timeX = System.nanoTime();
  father.startY = father.y;
  father.timeY = System.nanoTime();
  father.startVY = father.v_y;
 }
}

3.视图类BallView

BallView是负责画面渲染的视图类,其中声明了一些物理计算时要使用的静态常量,同时还声明了程序中要绘制的图片资源以及要绘制的小球对象列表。BallView类继承自android.view包下SurfaceView类。SurfaceView不同于普通的View,其具有不同的绘制机理,适合用于开发游戏程序。使用SurfaceView需要实现SurfaceHolder.Callback接口,该接口可以对SurfaceView进行编辑等操作,还可以监控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坐标,小球下落到此会弹起
 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; //小球数目
 ArrayList<Movable> alMovable=new ArrayList<Movable>(); //小球对象数组
 DrawThread dt; //后台屏幕绘制线程
 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) { //如果是初始化前一半球,就从大球中随机找一个
    tempBitmap=bitmapArray[3+index%3];
   } else { //如果是初始化前一半球,就从小球中随机找一个
    tempBitmap=bitmapArray[index%3];
   }
   //创建Movable对象
   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) { //遍历绘制每个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.isAlive()) {
   dt.start();
  }
 }
 @Override
 public void surfaceDestroyed(SurfaceHolder arg0) {
  dt.flag=false;
  dt=null;
 }
}

因为bitmapArray数组中的图片分为大尺寸和小尺寸,为了绘制图片时,小尺寸图片不会被挡住,所以先使用大尺寸图片。

doDraw方法会在DrawThread中调用,用于绘制图片和帧速率。

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(); //记录起始时间,该变量用于计算帧速率
 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);
    }
   } 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;
    double fps=Math.round(100000000000.0/span*20)/100.0;
    bv.fps="FPS:"+fps;
   }
   try {
    Thread.sleep(sleepSpan);
   } catch (InterruptedException e) {
    e.printStackTrace();
   }
  }
 }
}

代码中调用了BallView的屏幕重绘函数doDraw,其实现机制是现将BalView的画布加锁,然后调用BallView的doDraw方法对BallView的画布进行重新绘制。最后解锁BallView的画布并将其传回。

计算帧速率的方法是,首先求出程序绘制20帧所消耗的时间span,然后计算100s内能够包含几个span。100s内包含的span个数乘以20就能得出100s内能够绘制几帧,再除以100就可求得1s内绘制的帧数。

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); //设置不显示标题
  getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN); //设置全屏
  bv=new BallView(this);
  setContentView(bv);
 }
}

运行效果图:

使用到的资源文件:

希望本文所述对大家的Android程序设计有所帮助。

(0)

相关推荐

  • Android开发之经典游戏贪吃蛇

    前言 这款游戏实现的思路和源码参考了Google自带的Snake的例子,其中修改了一些个人认为还不够完善的地方,加入了一些新的功能,比如屏幕上的方向操作盘,暂停按钮,开始按钮,退出按钮.另外,为了稍微增加些用户体验,除了游戏的主界面,本人自己新增了5个界面,分别是登陆界面,菜单界面,背景音乐设置界面,难度设置界面,还有个关于游戏的介绍界面.个人觉得在新手阶段,参考现成的思路和实现方式是难以避免的.重要的是我们需要有自己的理解,读懂代码之后,需要思考代码背后的实现逻辑,形成自己的思维.这样在下次开

  • Android游戏开发实践之人物移动地图的平滑滚动处理

    如图所示为程序效果动画图 地图滚动的原理 在本人之前博客的文章中介绍过人物在屏幕中的移动方式,因为之前拼的游戏地图是完全填充整个手机屏幕的,所以无需处理地图的平滑滚动.这篇文章我着重的向 大家介绍一下控制人物移动后地图滚动的处理方式.举个例子 如上图所示 比如人物向右移动,如果地图贴在屏幕左边边界 将先移动人物在地图的坐标,当人物在屏幕中超过三分之二后 则将地图向人物行走的反方向移动给玩家一种人物还在向右移动的假象,其实这时候人物只是播放向右行走的动画 在屏幕中的坐标不变 ,当地图向人物行走反方

  • android游戏载入的activity跳转到游戏主菜单的activity具体实现

    复制代码 代码如下: public class LoadActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); requestWindowFeature(Window.FEATURE_NO_TITLE);// 去掉标题栏 getWindow().setFlags(WindowManager.Layou

  • Android游戏源码分享之2048

    引言 程序猿们,是否还在为你的老板辛辛苦苦的打工而拿着微薄的薪水呢,还是不知道如何用自己的应用或游戏来赚钱呢! 在这里IQuick将教您如何同过自己的应用来赚取自己的第一桶金! 你是说自己的应用还没有做出来? 不,在這里已经为你提供好了一个完整的游戏应用了,在文章的下面有源码的地址哦.你只要稍做修改就可以变成一个完全属于自己的应用了,比如将4*4换成5*5,甚至是其它的.如果你实在是慵懒至极的话,你只要将本应用的包名及广告换成自己的,就可以上传到市场上轻轻松松赚取自己的第一桶金了. 如果你觉得本

  • Android实现疯狂连连看游戏之实现游戏逻辑(五)

    在上一篇<我的Android进阶之旅------>Android疯狂连连看游戏的实现之加载界面图片和实现游戏Activity(四)>中提到的两个类: GameConf:负责管理游戏的初始化设置信息. GameService:负责游戏的逻辑实现. 其中GameConf的代码如下:cn\oyp\link\utils\GameConf.java package cn.oyp.link.utils; import android.content.Context; /** * 保存游戏配置的对象

  • Unity3D游戏引擎实现在Android中打开WebView的实例

    本文讲述了如何在Unity中调用Android中的WebView组件,实现内部浏览器样式的页面切换.首先打开Eclipse创建一个Android的工程: UnityTestActivity.java 入口Activity ,Unity中会调用这个Activity中的方法从而打开网页. package com.xys; import android.content.Context; import android.content.Intent; import android.os.Bundle; i

  • Android 游戏引擎libgdx 资源加载进度百分比显示案例分析

    因为案例比较简单,所以简单用AndroidApplication -> Game -> Stage 搭建框架 一.主入口,无特殊 复制代码 代码如下: public class App extends AndroidApplication { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); //初始化Demo initialize(new Demo()

  • Android五子棋游戏程序完整实例分析

    最近学习了五子棋的课程,感觉挺不错.然后自己写了个关于五子棋的android程序,从中还是能够学习到很多东西的.现在我们开始今天五子棋程序的编写历程.程序的源码请参见友情链接: 好了,我们现在开始一步步的构建出项目来,首先是如下的项目结构图: 运行的效果图: 一些前期做准备的代码 1. 主活动类MainActivity,在菜单中加入了再来一局的功能: public class MainActivity extends AppCompatActivity { private ChessBoardV

  • 解析Android游戏中获取电话状态进行游戏暂停或继续的解决方法

    对智能手机有所了解的朋友都知道其中一个应用广泛的手机操作系统Android 开源手机操作系统.那么在这一系统中想要实现通话的监听功能的话,我们应当如何操作呢?在这里就为大家详细介绍了Android监听通话的相关实现方法. 开发应用程序的时候,我们希望能够监听电话的呼入,以便执行暂停音乐播放器等操作,当电话结束之后,再次恢复播放.在Android平台可以通过TelephonyManager和PhoneStateListener来完成此任务.TelephonyManager作为一个Service接口

  • Android 游戏开发之Canvas画布的介绍及方法

    Canvas,在英语中,这个单词的意思是帆布.在Android中,则把Canvas当做画布,只要我们借助设置好的画笔(Paint类)就可以在画布上绘制我们想要的任何东西:另外它也是显示位图(Bitmap类)的核心类.随用户的喜好,Canvas还可设置一些关于画布的属性,比如,画布的颜色.尺寸等.Canvas提供了如下一些方法:    Canvas(): 创建一个空的画布,可以使用setBitmap()方法来设置绘制具体的画布.    Canvas(Bitmap bitmap): 以bitmap对

随机推荐