Android粒子系统库——DroidParticle

来源:互联网 发布:无人机源码 编辑:程序博客网 时间:2024/05/18 03:43

Android粒子系统库——DroidParticle

今天给大家介绍一款粒子系统库,并简要介绍下粒子系统的工作原理。

首先这款名为DroidParticle的库其实就是我自己没事做的,因为以前看过HGE的C++的粒子系统,觉得很有趣,现在从事Android开发工作就模仿着做了一个,希望对大家有用处。

先给大家看一下效果:


源代码下载地址:

https://github.com/sunty2016/DroidParticle

下面言归正传。

1. 何谓粒子系统

做视觉效果的时候,有时会用到火焰、云雾、光影,而如果用一般贴图的方式制做,则效果比较差。
后来有人便根据这些事物的视觉特性发明了一种方法来模拟:将同一张很简单的图片不断地贴到画布上,每次贴上去的图片都只存在一小段时间,而在这段时间内它的位置,大小,颜色,透明度会发生变化。当大量的贴图诞生、变化、死亡,画布上便会呈现出与源图片完全不同的效果。比如说上面第二张火焰图完全是由一张画着字母“Z”的图片贴图而成的,读者们能看出来么?
粒子系统便是这种方法的软件实现,其中每个贴到画布进行变化的贴图叫做粒子。
目前许多绘图、建模工具支持粒子系统的制作。而本文介绍的DroidParticle则是用编程的方式在Android应用中直接实现的粒子系统。

2. DroidParticle的实现

要实现粒子系统,则“粒子系统”和“粒子”是两个很好识别出的类
其中表示粒子的类代码如下:
public class Particle {    public static class Inter {        public float get(float v0, float v1, float t) {            return v0 + (v1 - v0) * t;        }    }    public static class Var {        public Var() {            inter = new Inter();        }        public void set(float v0, float v1) {            this.v0 = v0;            this.v1 = v1;            this.vt = v0;        }        public void set(Var other) {            this.v0 = other.v0;            this.v1 = other.v1;            this.vt = other.vt;        }        public void update(float t) {            this.vt = inter.get(v0, v1, t);        }        public float v0;        public float v1;        public float vt;        public Inter inter;    }    public Particle() {        velo__ = new Var();        bias__ = new Var();        spin__ = new Var();        scale__ = new Var();        alpha__ = new Var();        red__ = new Var();        green__ = new Var();        blue__ = new Var();        matrix = new Matrix();        colorMatrix = new ColorMatrix();        colorFilter = new ColorMatrixColorFilter(colorMatrix);    }    public long durInMillis;    public long timeInMillis;    public long startTimeInMillis;    public float x, y;  // pixel    public float theta; // degree    public float rot;   // degeee    public float scale; // 1.0    public float alpha; // 1.0    public float red;   // 1.0    public float green; // 1.0    public float blue;  // 1.0    public Bitmap bitmap;    public Matrix matrix;    public ColorMatrix colorMatrix;    public ColorMatrixColorFilter colorFilter;    public boolean deleteMark;    public Var velo__;  // pixel per second    public Var bias__;  // degree per second    public Var spin__;  // degree per second    public Var scale__; // 1.0    public Var alpha__; // 1.0    public Var red__;   // 1.0    public Var green__; // 1.0    public Var blue__;  // 1.0    static private final double PI_D_180 = Math.PI / 180.0;    public void config(Bitmap image, int x, int y, ParticleSystemConfig config, Random random) {        this.bitmap = image;        this.x = x;        this.y = y;        config.setParticle(random, this);    }    public void reset(long timeInMillis) {        this.startTimeInMillis = timeInMillis;        this.timeInMillis = timeInMillis;        this.deleteMark = false;    }    public boolean update(long timeInMillis) {        matrix.reset();        matrix.postTranslate(-bitmap.getWidth() / 2, -bitmap.getHeight() / 2);        matrix.postScale(scale, scale);        matrix.postRotate(rot);        matrix.postTranslate(x, y);        colorMatrix.reset();        colorMatrix.setScale(red, green, blue, alpha);        ColorMatrixFilterHelper.setFilterMatrix(                colorFilter,                colorMatrix);        if (this.timeInMillis - this.startTimeInMillis >= this.durInMillis) {            return true;        }        double dtInSeconds = (double)(timeInMillis - this.timeInMillis) * 0.001;        double rad = theta * PI_D_180;        double dd = velo__.vt * dtInSeconds;        //Log.i("STY", String.format("velo__.vt %f, dt %f, dd %f", velo__.vt, dt, dd));        x += dd * Math.cos(rad);        y += dd * Math.sin(rad);        theta += bias__.vt * dtInSeconds;        rot += spin__.vt * dtInSeconds;        scale = scale__.vt;        alpha = alpha__.vt;        red = red__.vt;        green = green__.vt;        blue = blue__.vt;        this.timeInMillis = timeInMillis;        float t = (float)(this.timeInMillis - this.startTimeInMillis) / (float)this.durInMillis;        velo__.update(t);        bias__.update(t);        spin__.update(t);        scale__.update(t);        alpha__.update(t);        red__.update(t);        green__.update(t);        blue__.update(t);        return false;    }    public void draw(Canvas canvas, Paint paint) {        paint.setColorFilter(colorFilter);        canvas.drawBitmap(bitmap, matrix, paint);    }}
Particle类中,Var类型的成员表示在粒子生命周期中需要变化的参数,Var的v0表示起始值,v1表示结束值,vt表示当前值:
  • velo__: 移动速度大小
  • bias__: 前进路线的偏移(角)速度(比如开始让粒子沿X轴前进,1秒后沿Y轴前进,则该值设为90)
  • spin__: 自旋角速度
  • scale__: 缩放
  • alpha__: 透明度
  • red__, green__, blue__: RGB三色占比
而非Var成员则保存当前的一些状态,节省计算成本:
  • x, y: 位置
  • theta: 移动方向(角度)
  • rot: 自旋角度
  • scale,alpha,red,green,blue: 参考Var成员
当一个Particle对象先被调用config来设置这些参数,然后没隔一小段时间调一次update和draw。在update中先由当前状态变量更新matrix和colorMatrix,再由Var变量更新当前状态变量;在draw中则应用matrix和colorMatrix最终绘图。

实现粒子系统的类中主要有两处重要的地方,一个是粒子的更新相关代码,另一个是生产粒子的代码:
    final private Runnable mUpdatePost = new Runnable() {        @Override        public void run() {            mSimpleTimer.mark();            if (mState != STATE_BUSY && mState != STATE_STOPPING) {                return;            }            long time = System.currentTimeMillis();            ++mFrameCount;            synchronized (mParticles) {                updatePtc(time);                if (mState == STATE_STOPPING && mParticles.size() == 0) {                    mState = STATE_READY;                    if (mOnStateChangeListener != null) {                        mOnStateChangeListener.onStateChanged(STATE_READY, STATE_STOPPING);                    }                }            }            mParticleSystemView.postInvalidate();            long cost = mSimpleTimer.mark();            mHandler.postDelayed(this, mDpf - cost);        }    };    private void updatePtc(long time) {        for (int i = 0; i < mParticles.size(); ++i) {            Particle ptc = mParticles.removeFirst();            if (ptc.deleteMark) {                ParticlePool.get().recycle(ptc);            } else {                ptc.deleteMark = ptc.update(time);                mParticles.addLast(ptc);            }        }        if (mNewBlend != mBlend) {            mBlend = mNewBlend;            if (mBlend == 0) {                mPaint.setXfermode(null);            } else if (mBlend == 1) {                mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.ADD));            }        }    }    final private Runnable mSpawnPost = new Runnable() {        @Override        public void run() {            mSimpleTimer.mark();            if (mState != STATE_BUSY) {                return;            }            long time = System.currentTimeMillis();            if (mPtcImage != null) {                ++mPtcCount;                Particle ptc = ParticlePool.get().obtain();                ptc.config(mPtcImage, mX, mY, mConfig, mRandom);                ptc.reset(time);                ptc.update(time);                synchronized (mParticles) {                    mParticles.push(ptc);                }            }            long cost = mSimpleTimer.mark();            mHandler.postDelayed(this, mDpp - cost);        }    };
这里的实现主要注意了以下几个问题:
  • 粒子更新的速度和粒子产生的速度并不一致。mDpf表示delay per frame,而mDpp表示delay per particle。
  • 由于在此处代码要被大量地调用,根据Google的性能建议,这里应该尽量少new新对象,以避免GC被频繁调用。因此对Particle采取obtain/recycle的模式进行复用。相信Android自己的Parcel和MotionEvent等也是出于相同的考虑。
  • 基于同样的原因,内部的循环不使用“for (Particle ptc : mParticles) {}”这类写法,以避免产生大量隐蔽的enumerator对象
  • 这里mParticles被同步保护的原因是,View会在主线程中访问mParticles并将其中的粒子逐个画出,而这里的mHandler是另外一个HandlerThread的Handler。

除Particle类和ParticleSystem类外,ParticleSystemView类也是重要的类,正是它使粒子系统被画在Android应用里。

ParticleSystemView直接继承自View,可用在layout的资源文件中。

3. DroidParticle的使用

使用方式非常简单,直接给代码:
public class MainActivity extends Activity {    ParticleSystemView mPtcSysView;    ParticleSystem mPtcSys1;    ParticleSystem mPtcSys2;    ParticleSystem mPtcSys3;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        mPtcSysView = (ParticleSystemView) findViewById(R.id.canvas);        mPtcSys1 = mPtcSysView.createParticleSystem();        mPtcSys2 = mPtcSysView.createParticleSystem();        mPtcSys3 = mPtcSysView.createParticleSystem();        BitmapDrawable drawable = (BitmapDrawable) getResources().getDrawable(R.drawable.ptc16);        Bitmap img = drawable.getBitmap();        mPtcSys1.setPtcBlend(1);        mPtcSys1.setFps(40);        mPtcSys1.setPps(30);        mPtcSys1.setPtcImage(img);        mPtcSys1.setConfig(createConfig(1));        mPtcSys2.setPtcBlend(1);        mPtcSys2.setFps(40);        mPtcSys2.setPps(30);        mPtcSys2.setPtcImage(img);        mPtcSys2.setConfig(createConfig(2));        mPtcSys3.setPtcBlend(1);        mPtcSys3.setFps(40);        mPtcSys3.setPps(30);        mPtcSys3.setPtcImage(img);        mPtcSys3.setConfig(createConfig(3));        mPtcSysView.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {            @Override            public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) {                v.removeOnLayoutChangeListener(this);                mPtcSys1.setPtcPosition(v.getWidth() / 6, v.getHeight() / 2);                mPtcSys2.setPtcPosition(v.getWidth() / 2, v.getHeight() / 2);                mPtcSys3.setPtcPosition(v.getWidth() * 5 / 6, v.getHeight() / 2);                mPtcSys1.start();                mPtcSys2.start();                mPtcSys3.start();            }        });    }    @Override    protected void onDestroy() {        super.onDestroy();        mPtcSysView.releaseParticleSystem(mPtcSys1);        mPtcSysView.releaseParticleSystem(mPtcSys2);        mPtcSysView.releaseParticleSystem(mPtcSys3);    }    static private ParticleSystemConfig createConfig(int id) {        ParticleSystemConfig config = new ParticleSystemConfig();        config.duration.set(1000, 0);        config.theta.set(270, 15);        config.startVelocity.set(400, 0);        config.endVelocity.set(400, 0);        config.startAngularRate.set(0, 0);        config.endAngularRate.set(0, 0);        config.startSpinRate.set(360, 0);        config.endSpinRate.set(360, 0);        config.startScale.set(1, 0);        config.endScale.set(1.5f, 0);        config.startAlpha.set(1, 0);        config.endAlpha.set(0.75f, 0);        if (id == 1) {            config.startRed.set(1, 0);            config.endRed.set(1, 0);            config.startGreen.set(0, 0);            config.endGreen.set(1, 0);            config.startBlue.set(0, 0);            config.endBlue.set(0, 0);        } else if (id == 2) {            config.startRed.set(0, 0);            config.endRed.set(0, 0);            config.startGreen.set(1, 0);            config.endGreen.set(1, 0);            config.startBlue.set(0, 0);            config.endBlue.set(1, 0);        } else if (id == 3) {            config.startRed.set(0, 0);            config.endRed.set(1, 0);            config.startGreen.set(0, 0);            config.endGreen.set(0, 0);            config.startBlue.set(1, 0);            config.endBlue.set(1, 0);        }        return config;    }}
可以看出同一个ParticleSystemView中可以同时建立多个particle system。但不建议建立太多system,会影响性能。

总结

一般粒子系统的实现都会基于图形库的支持,比如OpenGL等,但是DroidParticle完全是基于Android标准API。这样做的好处是实现、使用起来比较方便,也很轻量级,但缺点就是一来被束缚在2D上,无法扩展为三维粒子库,二来性能上也会差一些。不过就一般的效果而言DroidParticle是完全够用的。
另外GitHub源代码中含有Demo程序,可以帮助读者理解粒子各个参数的作用:
https://github.com/sunty2016/DroidParticle


0 0
原创粉丝点击