上一节说的是小球运动,而这节介绍的是粒子系统,从名字上就可以得出数量之极多.简单说:粒子系统就是有好多好多小球在一起欲动,那么这就叫做粒子系统,考虑的是整体感觉,而不是单个小球的轨迹.这让我联想到了墨迹和新浪推出的天气动态背景,是不是也是这样实现的呢.总而言之,如下雨,下雨,瀑布等都可以做到,但是做起来很复杂,估计我想远跑偏了,肯定有更好的办法实现动态背景.
我们还是先看一个案例,实现起来和前面讲解小球一样.
首先粒子对象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());// 水平速度(瀑布效果)瀑布效果图:
关于粒子系统就说到这里,有不足的地方,请指示.
这个相比物理小球运动简单一点,不涉及太多的逻辑判断.总而言之:这套实现的步骤和方案值得参考.