上一节说的是android
游戏开发之物理小球的应用,而这节介绍的是粒子系统,从名字上就可以得出数量之极多.简单说:粒子系统就是有好多好多小球在一起欲动,那么这就叫做粒子系统,考虑的是整体感觉,而不是单个小球的轨迹.这让我联想到了墨迹和新浪推出的天气动态背景,是不是也是这样实现的呢.总而言之,如下雨,下雨,瀑布等都可以做到,但是做起来很复杂,估计我想远跑偏了,肯定有更好的办法实现动态背景.
我们还是先看一个案例,实现起来和前面讲解小球一样.
首先粒子对象Particle.java
package com.jj.partical;
/***
* 粒子对象
*
* @author zhangjia
*
*/
public class Particle {
int color; // 颜色
int r;// 半径
double vertical_v;// 垂直速度
double horizontal_v;// 水平速度
int startX;
int startY;
int x;
int y;
double startTime;// 起始时间
public Particle(int color, int r, double vertical_v, double horizontal_v,
int x, int y, double startTime) {
super();
this.color = color;
this.r = r;
this.vertical_v = vertical_v;
this.horizontal_v = horizontal_v;
this.startX = x;
this.startY = y;
this.x = x;
this.y = y;
this.startTime = startTime;
}
} |
就是一些属性,下面我们创建一个存放粒子的几个ParticleSet.java其实二者可以整个一个里面,总而言之,达到效果就OK.
package com.jj.partical;
import java.util.ArrayList;
import android.graphics.Color;
/***
* 存放粒子对象集合
*
* @author zhangjia
*
*/
public class ParticleSet {
ArrayList<Particle> particals;
public ParticleSet() {
super();
particals = new ArrayList<Particle>();
}
/***
* 获取相关颜色
*
* @return
*/
public int getColor(int i) {
int color = Color.RED;
switch (i % 4) {
case 0:
color = Color.RED;
break;
case 1:
color = Color.GREEN;
break;
case 2:
color = Color.YELLOW;
break;
case 3:
color = Color.WHITE;
break;
default:
break;
}
return color;
}
/***
* 添加粒子
*
* @param count
* 个数
* @param startTime
* 起始时间
*/
public void addPartical(int count, double startTime, int window_Width) {
for (int i = 0; i < count; i++) {
int color = getColor(i);
int r = 1;
double vertical_v = -30 + 10 * (Math.random());// 垂直速度
double horizontal_v = 10 - 20 * (Math.random());// 水平速度
int startX = window_Width / 2;
int startY = 100;
Particle partical = new Particle(color, r, vertical_v,
horizontal_v, startX, startY, startTime);
particals.add(partical);
}
}
} |
接下来介绍:粒子物理引擎ParticleThread.java
package com.jj.partical;
import java.util.ArrayList;
/***
* 粒子物理引擎
*
* @author zhangjia
*
*/
public class ParticleThread extends Thread {
boolean flag;// 线程标识
ParticleView particalView;
int sleepSpan = 80;
double time = 0;
double span = 0.15;
private int window_Width;
public ParticleThread(ParticleView particleView, int window_Width) {
this.particalView = particleView;
this.flag = true; // 设置线程执行标志位为true
this.window_Width = window_Width;
}
@Override
public void run() {
while (flag) {
particalView.particleSet.addPartical(5, time, window_Width);// 每次添加5粒
ArrayList<Particle> particals = particalView.particleSet.particals;// 获取粒子集合
int count = particals.size();
for (int i = 0; i < count; i++) {
Particle partical = particals.get(i);
double timeSpan = time - partical.startTime;// 计算程序开始到现在的时间差
int tempX = (int) (partical.startX + partical.horizontal_v
* timeSpan);// 计算此时粒子的X值
// 4.9 * timeSpan * timeSpan=1/2*g*t*t;//物理公式,g=9.8m/s;
int tempY = (int) (partical.startY + 4.9 * timeSpan * timeSpan);// 计算此时粒子的Y值
if (tempY > ParticleView.DIE_OUT_LINE) {
particals.remove(partical);// 删除该粒子
count = particals.size();// 重新获取个数
}
partical.x = tempX;
partical.y = tempY;
}
time += span;
try {
Thread.sleep(sleepSpan);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
} |
ParticleView.java
package com.jj.partical;
import java.util.ArrayList;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.RectF;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
//继承自SurfaceView并实现SurfaceHolder.Callback接口的类
public class ParticleView extends SurfaceView implements SurfaceHolder.Callback {
public static int DIE_OUT_LINE = 420;// 粒子的Y坐标超过该值会从粒子集合移除
DrawThread drawThread; // 后台刷新屏幕线程
ParticleSet particleSet; // ParticleSet对象引用
ParticleThread particleThread; // ParticleThread对象引用
String fps = "FPS:N/A"; // 声明帧速率字符串
// 构造器,初始化主要成员变量
public ParticleView(Context context, int window_Width, int window_Height) {
super(context); // 调用父类构造器
DIE_OUT_LINE = window_Height;
this.getHolder().addCallback(this); // 添加Callback接口
drawThread = new DrawThread(this, getHolder()); // 创建DrawThread对象
particleSet = new ParticleSet(); // 创建ParticleSet对象
particleThread = new ParticleThread(this, window_Width); // 创建ParticleThread对象
}
// 方法:绘制屏幕
public void doDraw(Canvas canvas) {
canvas.drawColor(Color.BLACK); // 清屏
ArrayList<Particle> particleSet = this.particleSet.particals; // 获得ParticleSet对象中的粒子集合对象
Paint paint = new Paint(); // 创建画笔对象
for (int i = 0; i < particleSet.size(); i++) { // 遍历粒子集合,绘制每个粒子
Particle p = particleSet.get(i);
paint.setColor(p.color); // 设置画笔颜色为粒子颜色
int tempX = p.x; // 获得粒子X坐标
int tempY = p.y; // 获得粒子Y坐标
int tempRadius = p.r; // 获得粒子半径
RectF oval = new RectF(tempX, tempY, tempX + 2 * tempRadius, tempY
+ 2 * tempRadius);
// canvas.drawOval(oval, paint); // 绘制椭圆粒子
canvas.drawCircle(tempX, tempY, tempRadius, paint);
}
paint.setColor(Color.WHITE); // 设置画笔颜色
paint.setTextSize(18); // 设置文字大小
paint.setAntiAlias(true); // 设置抗锯齿
canvas.drawText(fps, 15, 15, paint);// 画出帧速率字符串
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width,
int height) {// 重写surfaceChanged方法
}
@Override
public void surfaceCreated(SurfaceHolder holder) {// 从写surfaceCreated方法
if (!drawThread.isAlive()) { // 如果DrawThread没有启动,就启动这个线程
drawThread.start();
}
if (!particleThread.isAlive()) { // 如果ParticleThread没有启动,就启动这个线程
particleThread.start();
}
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {// 重写surfaceDestroyed方法
drawThread.flag = false; // 停止线程的执行
drawThread = null; // 将dt指向的对象声明为垃圾
}
} |
用于重绘屏幕和计算帧率DrawThread.java
package com.jj.partical;
import android.graphics.Canvas;
import android.view.SurfaceHolder;
/***
* 用于重绘屏幕和计算帧率
*
* @author zhangjia
*
*/
public class DrawThread extends Thread {
ParticleView particalView;// 自定义View
SurfaceHolder surfaceHolder;
boolean flag = false;// 线程标识
int sleepSpan = 15;// 线程休眠
long start = System.nanoTime();// 其实时间,用于计算帧速率
int count = 0;// 计算帧率
public DrawThread(ParticleView particalView, SurfaceHolder surfaceHolder) {
super();
this.particalView = particalView;
this.surfaceHolder = surfaceHolder;
this.flag = true;
}
@Override
public void run() {
Canvas canvas = null;
while (flag) {
try {
canvas = surfaceHolder.lockCanvas();// 获取canvas.
synchronized (surfaceHolder) {
particalView.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;// 计算帧速率
particalView.fps = "FPS:" + fps;// 将计算出的帧速率设置到BallView的相应字符串对象中
}
try {
Thread.sleep(sleepSpan); // 线程休眠一段时间
} catch (Exception e) {
e.printStackTrace(); // 捕获并打印异常
}
}
}
} |
接下来调用展示View,就不写了,相信大家都明白如何操作,
现在我简单讲解一下原理:首先我们创建DrawThread线程,目的是相隔sleepSpan进行绘制doDraw方法.其实就好比就是刷屏操作.而我们的ParticleThread物理引擎则每隔sleepSpan都要添加5个粒子进去.然后在通过DrawThread线程进行不停的更新UI从而实现粒子系统.
烟火粒子示例:
其实我们稍微整理下逻辑就可以变为瀑布效果.
// double horizontal_v = 10 - 20 * (Math.random());// 水平速度(火焰效果)
double horizontal_v = 10 + 20 * (Math.random());// 水平速度(瀑布效果) |
瀑布效果图:
关于粒子系统就说到这里,有不足的地方,请指示.
这个相比物理小球运动简单一点,不涉及太多的逻辑判断.总而言之:这套实现的步骤和方案值得参考. |