Android 应用开发学习 (1):物理小球 与 粒子特效

来源:互联网 发布:微软编程一小时官网 编辑:程序博客网 时间:2024/06/05 04:13

Android 应用开发学习 (1):物理小球 与 粒子特效

该学习内容是出自 Android 游戏开发大全 里的。物理小球与粒子特效在测试效果的时候,最终是通过自定义的View来测试的。所以在下面写下自己的学习笔记。

实现效果

这里写图片描述

整体结构

以粒子特效为例:
其内容上的结构如下所示
其中每一块分别的用途为:

Particle: 粒子特效 的 “粒子” 。是Demo里最基本的数据结构。

ParticleSet: “粒子”的集合,数据上是”粒子”的 ArrayList. 同时拥有添加粒子时,对每一个粒子进行属性初始化的工作。

ParticleThread: [extends Thread] “粒子” 每时每刻都在发生着运动状态的变化,这种变化的逻辑将通过该线程来实现。

DrawThread:[extends Thread] “粒子”的数据发生了改变后,如果没有一个能对视图进行重绘的线程是看不到变化的效果的。所以重绘工作的调用将通过该线程来实现。

ParticleView:[extends SurfaceView,implements SurfaceHolder.Callback] “粒子视图” 在初始化的时候,会自动开启两个线程,然后测试能够看到“粒子瀑布”效果。


工作细节

继承与实现

ParticleView 需要 继承 SurfaceView 并且 实现 SurfaceHolder.Callback 接口。
【该段代码 From ParticleView】

public class ParticleView extends SurfaceView implements SurfaceHolder.Callback{    ....//省略}

【该段代码 From ParticleView】

   public ParticleView(Context context) {        super(context);        this.getHolder().addCallback(this);   // 该行必须有    /*实例化的过程*/        ps = new ParticleSet();        pt = new ParticleThread(this);        dt = new DrawThread(this, getHolder());    }

视图重绘

ParticleView 中需要写出 doDraw()方法。
当然该 方法也可以不叫 “doDraw” ,你可以任取名字,毕竟不是覆写方法。
【该段代码 From ParticleView】

    public void doDraw(Canvas canvas) {        ArrayList<Particle> particles = ps.particles;        for (int i = 0; i < particles.size(); i++) {            paint = new Paint();            Particle p = particles.get(i);     //得到 每一个粒子,并且将他们在视图上进行绘制            paint.setColor(p.color);            canvas.drawCircle(p.x, p.y, p.r, paint); //属性分别是:横坐标,纵坐标,半径,画笔(主要是每个"粒子"的颜色可能是不同的)        }    }

因为doDraw()方法是 自己写的,而不是我们直接去覆写 onDraw()方法,如果我们不在其他地方调用它,它是肯定不会生效的。所以我们需要写出DrawThread 的调用逻辑,并让它去调用这个方法。
【该段代码 From DrawThread】

while (flag){            try {                canvas = surfaceHolder.lockCanvas(null);  // surfaceholder 得到画布                synchronized (surfaceHolder){                    particleView.doDraw(canvas);        //将得到的画布作为参数让ParticleView 用doDraw()方法去进行绘制。此处是死循环,会不断的绘制。从而实现动画效果。                }            }catch (Exception e){                e.printStackTrace();            }finally {                if (canvas != null){                    surfaceHolder.unlockCanvasAndPost(canvas); //【重要】每绘制完一次,不要忘了 使用这个方法。                }            }            try {                Thread.sleep(120);            } catch (InterruptedException e) {                e.printStackTrace();            }        }

调用线程

如果 doDraw() 是通过DrawThread 调用的,那么DrawThread 也一定会有一个被调用的过程。
【该段代码 From ParticleView】

@Override    public void surfaceCreated(SurfaceHolder surfaceHolder) {        if (!dt.isAlive()) {            dt.start();   //开启DrawThread线程        }        if (!pt.isAlive()) {            pt.start();   //开启ParticleThread线程        }         //两个线程都将在SurfaceView 被创建后 就执行。该方法的执行 紧随 构造函数之后。    }    @Override    public void surfaceChanged(SurfaceHolder surfaceHolder, int i, int i1, int i2) {    }    @Override    public void surfaceDestroyed(SurfaceHolder surfaceHolder) {        dt.flag = false;        dt = null;        pt.flag = false;        pt = null;        //关闭线程的时候,都应先将它的开关关掉,再执行置空的操作。不然可能会报错。        //所以写线程的时候,线程也会有两个属性。    }

线程的格式

自定义线程时,不要遗忘的两个属性:
第一个是flag,也就是线程的开关。
第二个是sleepSpan ,更新的线程是死循环执行,为了限制执行的频率,每次执行完了以后,通常会让线程休眠一段时间。

【该段代码 From DrawThread】

@Override    public void run() {        Canvas canvas = null;        while (flag){    //这是flag            try {                canvas = surfaceHolder.lockCanvas(null);                synchronized (surfaceHolder){                    particleView.doDraw(canvas);                }            }catch (Exception e){                e.printStackTrace();            }finally {                if (canvas != null){                    surfaceHolder.unlockCanvasAndPost(canvas);                }            }            try {                Thread.sleep(120);   //这是sleepSpan            } catch (InterruptedException e) {                e.printStackTrace();            }        }    }

【该段代码 From ParticleThread】

 @Override    public void run() {        while (flag) {   //这是flag            ArrayList<Particle> particles = pv.ps.particles;            pv.addParticles(5);            Log.i(TAG, "run: "+particles.size());            int count = particles.size();            for (int i = 0; i < count; i++) {                Particle particle = particles.get(i);                long timeNow = System.nanoTime();                long timeSpan =(timeNow - particle.startTime)/1000/1000/1000;                int x = (int) (particle.startX + particle.horizontal_v * timeSpan);                int y = (int) (particle.startY + particle.vertical_v * timeSpan + 4.9 * timeSpan * timeSpan);                if (y > OUTLINE) {                    particles.remove(particle);                    count = particles.size();                    i--;                }                particle.x = x;                particle.y = y;            }            try {                Thread.sleep(sleepSpan);  //这是sleepSpan 数值在ParticleThread的成员变量中            } catch (InterruptedException e) {                e.printStackTrace();            }        }    }

粒子运动原理

计算小球运动的距离,主要运用了高中物理的公式:
X=Vot+1/2gt^2;
或者X=Vot;
其中这个t的获取 在测试的demo 里主要是通过 给每一个粒子一个初始的时间点 to,这个时间点的获得是通过方法 System.nanotime() 来获得的。
同样,每次要刷新粒子的位置的时候,会重新去得到系统的时间t1,然后两个时间 作差,即t = t1-to 得到的差值t即为运动公式里的t
也就是说,to 在最开始测了一次后,在运动状态未改变的情况下,是不会发生改变的,而为了得到运动方程里的t,t1 将时常发生改动。

[只有发生了碰撞,或者是其他的改变方程的情况时, to 才会发生改变,然而 “粒子瀑布”Demo 中不会有碰撞,并不需要to 发生改变。 ]
【该段代码 From ParticleThread】

 @Override    public void run() {        while (flag) {             ArrayList<Particle> particles = pv.ps.particles;  //ParticleThread 先得到ParticleSet 里的 Particle 的列表            pv.addParticles(5);    //每次执行都会添加五个粒子,从而实现粒子瀑布            for (int i = 0; i < particles.size(); i++) {  //对每一个粒子进行 运动模拟。                Particle particle = particles.get(i);                long timeNow = System.nanoTime();   //得到当前的时间                long timeSpan =(timeNow - particle.startTime)/1000/1000/1000; /*【注意】因为System.nanoTime() 得到的是纳秒,如果你不 除以 10^9 ,那么timeSpan将会变的非常大。*/                int x = (int) (particle.startX + particle.horizontal_v * timeSpan);                int y = (int) (particle.startY + particle.vertical_v * timeSpan + 4.9 * timeSpan * timeSpan);                if (y > OUTLINE) {                    particles.remove(particle);                    i--;                }                particle.x = x;                particle.y = y;                // 更新 每一个点的 X,Y  坐标。            }            try {                Thread.sleep(sleepSpan);              } catch (InterruptedException e) {                e.printStackTrace();            }        }    }

源码

http://download.csdn.net/detail/qq_34107083/9742162

0 0
原创粉丝点击