热门标签 | HotTags
当前位置:  开发笔记 > Android > 正文

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

这篇文章主要介绍了Android游戏开发学习①弹跳小球实现方法,涉及Android通过物理引擎BallThread类模拟小球运动的相关技巧,具有一定参考借鉴价值,需要的朋友可以参考下

本文实例讲述了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) 

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

因为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程序设计有所帮助。


推荐阅读
  • HDU 2372 El Dorado(DP)的最长上升子序列长度求解方法
    本文介绍了解决HDU 2372 El Dorado问题的一种动态规划方法,通过循环k的方式求解最长上升子序列的长度。具体实现过程包括初始化dp数组、读取数列、计算最长上升子序列长度等步骤。 ... [详细]
  • Android中高级面试必知必会,积累总结
    本文介绍了Android中高级面试的必知必会内容,并总结了相关经验。文章指出,如今的Android市场对开发人员的要求更高,需要更专业的人才。同时,文章还给出了针对Android岗位的职责和要求,并提供了简历突出的建议。 ... [详细]
  • 在说Hibernate映射前,我们先来了解下对象关系映射ORM。ORM的实现思想就是将关系数据库中表的数据映射成对象,以对象的形式展现。这样开发人员就可以把对数据库的操作转化为对 ... [详细]
  • baresip android编译、运行教程1语音通话
    本文介绍了如何在安卓平台上编译和运行baresip android,包括下载相关的sdk和ndk,修改ndk路径和输出目录,以及创建一个c++的安卓工程并将目录考到cpp下。详细步骤可参考给出的链接和文档。 ... [详细]
  • 【Windows】实现微信双开或多开的方法及步骤详解
    本文介绍了在Windows系统下实现微信双开或多开的方法,通过安装微信电脑版、复制微信程序启动路径、修改文本文件为bat文件等步骤,实现同时登录两个或多个微信的效果。相比于使用虚拟机的方法,本方法更简单易行,适用于任何电脑,并且不会消耗过多系统资源。详细步骤和原理解释请参考本文内容。 ... [详细]
  • Android Studio Bumblebee | 2021.1.1(大黄蜂版本使用介绍)
    本文介绍了Android Studio Bumblebee | 2021.1.1(大黄蜂版本)的使用方法和相关知识,包括Gradle的介绍、设备管理器的配置、无线调试、新版本问题等内容。同时还提供了更新版本的下载地址和启动页面截图。 ... [详细]
  • 本文介绍了在SpringBoot中集成thymeleaf前端模版的配置步骤,包括在application.properties配置文件中添加thymeleaf的配置信息,引入thymeleaf的jar包,以及创建PageController并添加index方法。 ... [详细]
  • 知识图谱——机器大脑中的知识库
    本文介绍了知识图谱在机器大脑中的应用,以及搜索引擎在知识图谱方面的发展。以谷歌知识图谱为例,说明了知识图谱的智能化特点。通过搜索引擎用户可以获取更加智能化的答案,如搜索关键词"Marie Curie",会得到居里夫人的详细信息以及与之相关的历史人物。知识图谱的出现引起了搜索引擎行业的变革,不仅美国的微软必应,中国的百度、搜狗等搜索引擎公司也纷纷推出了自己的知识图谱。 ... [详细]
  • 本文讲述了作者通过点火测试男友的性格和承受能力,以考验婚姻问题。作者故意不安慰男友并再次点火,观察他的反应。这个行为是善意的玩人,旨在了解男友的性格和避免婚姻问题。 ... [详细]
  • 安卓select模态框样式改变_微软Office风格的多端(Web、安卓、iOS)组件库——Fabric UI...
    介绍FabricUI是微软开源的一套Office风格的多端组件库,共有三套针对性的组件,分别适用于web、android以及iOS,Fab ... [详细]
  • 本文详细介绍了Linux中进程控制块PCBtask_struct结构体的结构和作用,包括进程状态、进程号、待处理信号、进程地址空间、调度标志、锁深度、基本时间片、调度策略以及内存管理信息等方面的内容。阅读本文可以更加深入地了解Linux进程管理的原理和机制。 ... [详细]
  • 1,关于死锁的理解死锁,我们可以简单的理解为是两个线程同时使用同一资源,两个线程又得不到相应的资源而造成永无相互等待的情况。 2,模拟死锁背景介绍:我们创建一个朋友 ... [详细]
  • 后台获取视图对应的字符串
    1.帮助类后台获取视图对应的字符串publicclassViewHelper{将View输出为字符串(注:不会执行对应的ac ... [详细]
  • XML介绍与使用的概述及标签规则
    本文介绍了XML的基本概念和用途,包括XML的可扩展性和标签的自定义特性。同时还详细解释了XML标签的规则,包括标签的尖括号和合法标识符的组成,标签必须成对出现的原则以及特殊标签的使用方法。通过本文的阅读,读者可以对XML的基本知识有一个全面的了解。 ... [详细]
  • Google Play推出全新的应用内评价API,帮助开发者获取更多优质用户反馈。用户每天在Google Play上发表数百万条评论,这有助于开发者了解用户喜好和改进需求。开发者可以选择在适当的时间请求用户撰写评论,以获得全面而有用的反馈。全新应用内评价功能让用户无需返回应用详情页面即可发表评论,提升用户体验。 ... [详细]
author-avatar
daadhkiw_267
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有