22

Anybody has any idea how to make a fireworks effect by drawing on canvas? There is one nice example in wireworks live wallpaper (free app).

There, many points move and leave trails in a star like explosion and gradually disappear. I think that there is some kind of motion blur on each point moving, which I am not sure how to create.

Any ideas or links to relevant examples are welcome.

Lumis
  • 20,819
  • 7
  • 57
  • 66

3 Answers3

32

I just released the v1.1 of a library that does particle system for normal android views: https://github.com/plattysoft/Leonids

I know the question is quite old, and you probably had implemented it yourself, but in case you are still interested on using a library, check it out.

shalafi
  • 3,748
  • 2
  • 20
  • 27
  • 1
    Thanks for your hard work, just a quick question - I would like to have particles popping out in random places and then fading out in 2 secs. I have been looking how I can achieve random particle placement with using your library. – Alex Aug 21 '16 at 05:12
  • In theory, you could have a view that is as big as the screen and make sure that gravity is not set to CENTER, that should do it. – shalafi Aug 22 '16 at 08:31
  • I tried your way. But it generates one star at a time. What I really wanted is. Let's say 20/30 small particle(star) generate in random size at random position and then start to fade out in random time and as each one fade out another star fades in at random position. – Alex Aug 23 '16 at 01:42
  • 1
    You can do it with 2 particle systems then. one with 20 and one with 10. I tried Gravity.NO_GRAVITY and works as expected. However we should move this discussion to a ticket in github. – shalafi Sep 21 '16 at 08:59
16
import java.util.Random;

import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.os.Handler;
import android.os.Message;
import android.view.SurfaceHolder;
import android.view.SurfaceView;

enum AnimateState
{
    asReady, asRunning, asPause;
}

class Rocket
{
    public boolean sleep = true;

    private float energy, length, mx, my, gravity, ox, oy, x, y, t;
    private float vx[], vy[];
    private int patch, red, green, blue;
    private Random random;

    public Rocket( int a, int b, int g )
    {
        mx = a;
        my = b;
        gravity = g;
    }

    public void init( int e, int p, int l, long seed )
    {
        energy = e;
        patch = p + 20;
        length = l;

        random = new Random( seed );

        vx = new float[patch];
        vy = new float[patch];

        red = ( int )( random.nextFloat() * 128 ) + 128;
        blue = ( int )( random.nextFloat() * 128 ) + 128;
        green = ( int )( random.nextFloat() * 128 ) + 128;

        ox = ( random.nextFloat() * mx / 2 ) + mx / 4;
        oy = ( random.nextFloat() * my / 2 ) + my / 4;

        for( int i = 0; i < patch; ++i )
        {
            vx[i] = ( ( random.nextFloat() + random.nextFloat() / 2 ) * energy ) - energy / ( random.nextInt( 2 ) + 1 );
            vy[i] = ( ( random.nextFloat() + random.nextFloat() / 2 ) * energy * 7 / 8 ) - energy / ( random.nextInt( 5 ) + 4 );
        }
    }

    public void start()
    {
        t = 0;
        sleep = false;
    }

    public void doDraw( Canvas canvas, Paint paint )
    {
        if ( ! sleep )
        {
            if ( t < length )
            {
                int i, cr, cg, cb;
                double s;

                cr = ( int )( random.nextDouble() * 64 ) - 32 + red;
                cg = ( int )( random.nextDouble() * 64 ) - 32 + green;
                cb = ( int )( random.nextDouble() * 64 ) - 32 + blue;

                if ( cr >= 0 && cr <= 256 )
                    red = cr;
                if ( cg >= 0 && cg <= 256 )
                    green = cg;
                if ( cb >= 0 && cb <= 256 )
                    blue = cb;

                int _red = red == 256 ? 255 : red;
                int _green = green == 256 ? 255 : green;
                int _blue = blue == 256 ? 255 : blue;

                int color = Color.rgb( _red, _green, _blue );

                paint.setColor( color );

                for ( i = 0; i < patch; ++i )
                {
                    s = ( double )t / 100;
                    x = ( int )( vx[i] * s );
                    y = ( int )( vy[i] * s - gravity * s * s );

                    canvas.drawCircle( ox + x, oy - y, 2f, paint );
                }

                paint.setColor( Color.BLACK );

                for ( i = 0; i < patch; ++i )
                {
                    if ( t >= length / 2 )
                    {
                        for ( int j = 0; j < 2; ++j )
                        {
                            s = ( double ) ( ( t - length / 2 ) * 2 + j ) / 100;
                            x = ( int )( vx[i] * s );
                            y = ( int )( vy[i] * s - gravity * s * s );

                            canvas.drawCircle( ox + x, oy - y, 2f, paint );
                        }
                    }
                }

                ++t;
            }
            else
                sleep = true;
        }
    }
}

class Fireworks
{
    /**
     * Maximum number of rockets.
     */
    public int MaxRocketNumber = 9;
    /**
     * Controls "energy" of firwork explosion. Default value 850.
     */
    public int MaxRocketExplosionEnergy = 950;
    /**
     * Controls the density of the firework burst. Larger numbers give higher density.
     * Default value 90.
     */
    public int MaxRocketPatchNumber = 90;
    /**
     * Controls the radius of the firework burst. Larger numbers give larger radius.
     * Default value 68.
     */
    public int MaxRocketPatchLength = 68;

    /**
     * Controls gravity of the firework simulation.
     * Default value 400.
     */
    public int Gravity = 400;

    transient private Rocket rocket[];
    transient private boolean rocketsCreated = false;

    private int width;
    private int height;

    Fireworks( int width, int height )
    {
        this.width = width;
        this.height = height;
    }

    void createRockets()
    {
        rocketsCreated = true;

        Rocket tempRocket[] = new Rocket[MaxRocketNumber];

        for ( int i = 0; i < MaxRocketNumber; i++ )
            tempRocket[i] = new Rocket( width, height, Gravity );

        rocket = tempRocket;
    }

    public synchronized void reshape( int width, int height )
    {
        this.width = width;
        this.height = height;

        rocketsCreated = false;
    }

    public void doDraw( Canvas canvas, Paint paint )
    {
        canvas.drawColor( Color.BLACK );

        int i, e, p, l;
        long s;

        boolean sleep;

        if ( ! rocketsCreated )
        {
            createRockets();
        }

        if ( rocketsCreated )
        {
            sleep = true;

            for ( i = 0; i < MaxRocketNumber; i++ )
                sleep = sleep && rocket[i].sleep;

            for ( i = 0; i < MaxRocketNumber; ++i )
            {
                e = ( int )( Math.random() * MaxRocketExplosionEnergy * 3 / 4 ) + MaxRocketExplosionEnergy / 4 + 1;
                p = ( int )( Math.random() * MaxRocketPatchNumber * 3 / 4 ) + MaxRocketPatchNumber / 4 + 1;
                l = ( int )( Math.random() * MaxRocketPatchLength * 3 / 4 ) + MaxRocketPatchLength / 4 + 1;
                s = ( long )( Math.random() * 10000 );

                Rocket r = rocket[i];
                if ( r.sleep && Math.random() * MaxRocketNumber * l < 2 ) 
                {
                    r.init( e, p, l, s );
                    r.start();
                }

                if ( rocketsCreated )
                    r.doDraw( canvas, paint );
            }
        }
    }
}

public class FireworkLayout extends SurfaceView implements SurfaceHolder.Callback
{

    class GameThread extends Thread
    {
        private boolean mRun = false;

        private SurfaceHolder surfaceHolder;
        private AnimateState state;
        private Context context;
        private Handler handler;
        private Paint paint;
        Fireworks fireworks;

        GameThread( SurfaceHolder surfaceHolder, Context context, Handler handler )
        {
            this.surfaceHolder = surfaceHolder;
            this.context = context;
            this.handler = handler;

            fireworks = new Fireworks( getWidth(), getHeight() );

            paint = new Paint();
            paint.setStrokeWidth( 2 / getResources().getDisplayMetrics().density );
            paint.setColor( Color.BLACK );
            paint.setAntiAlias( true );
        }

        public void doStart()
        {
            synchronized ( surfaceHolder )
            {
                setState( AnimateState.asRunning );
            }
        }

        public void pause()
        {
            synchronized ( surfaceHolder )
            {
                if ( state == AnimateState.asRunning )
                    setState( AnimateState.asPause );
            }
        }

        public void unpause()
        {
            setState( AnimateState.asRunning );
        }

        @Override
        public void run()
        {
            while ( mRun )
            {
                Canvas c = null;
                try
                {
                    c = surfaceHolder.lockCanvas( null );

                    synchronized ( surfaceHolder )
                    {
                        if ( state == AnimateState.asRunning )
                            doDraw( c );
                    }
                }
                finally
                {
                    if ( c != null )
                    {
                        surfaceHolder.unlockCanvasAndPost( c );
                    }
                }
            }
        }

        public void setRunning( boolean b )
        {
            mRun = b;
        }

        public void setState( AnimateState state )
        {
            synchronized ( surfaceHolder )
            {
                this.state = state;
            }
        }

        public void doDraw( Canvas canvas )
        {
            fireworks.doDraw( canvas, paint );
        }

        public void setSurfaceSize( int width, int height )
        {
            synchronized ( surfaceHolder )
            {
                fireworks.reshape( width, height );
            }
        }
    }

    private GameThread thread;

    @SuppressLint( "HandlerLeak" )
    public FireworkLayout( Context context )
    {
        super( context );

        SurfaceHolder holder = getHolder();
        holder.addCallback( this );

        getHolder().addCallback( this );

        thread = new GameThread( holder, context, new Handler() {
            @Override
            public void handleMessage( Message m ) {

            }} );

        setFocusable( true );
    }

    @Override
    public void surfaceChanged( SurfaceHolder holder, int format, int width, int height )
    {
        thread.setSurfaceSize( width, height );
    }

    @Override
    public void surfaceCreated( SurfaceHolder holder )
    {
        thread.setRunning( true );
        thread.doStart();
        thread.start();
    }

    @Override
    public void surfaceDestroyed( SurfaceHolder holder )
    {
        boolean retry = true;
        thread.setRunning( false );

        while ( retry )
        {
            try
            {
                thread.join();
                retry = false;
            }
            catch ( InterruptedException e )
            {
            }
        }
    }
}

use:

    firework = new FireworkView( this );
    LinearLayout surface = (LinearLayout) findViewById( R.id.surface );
    surface.addView( firework );
  • Thanks, I guess the next step is to make it leave trails and not to disappear so quickly. – Lumis Feb 07 '13 at 18:49
  • Can somebody help to edit this answer to provide example code for caller/usage ? I'm not even know which method is the starting point. – Fruit Jun 09 '16 at 20:09
11

That's a very abstract (and interesting) question but far too complex to answer in details.

What you're really looking for is probably a sort of a modified particle system. A particle system is basically an engine for particles that includes a physics engine.

However, what you should focus on are these:

Once you built it (or gotten an understanding about it) you can easily convert it to the Canvas system, although OpenGL is the preferable alternative.

Wroclai
  • 26,380
  • 7
  • 72
  • 67
  • Thank you Victor for a useful answer (as opposed to another). I am trying to create it on SurfaceView. If you check this live wallpaper http://market.android.com/details?id=com.aoi.livewallpaper.Fireworks you'll see that a point makes a blurry tail. All I need is to make a commet like movement of a point then the rest would be mathematics. I think that paint filters could help. – Lumis Mar 23 '11 at 22:08
  • 1
    [Particle system tutorial](https://web.archive.org/web/20130307040049/http://www.bayninestudios.com/2010/04/particle-system-tutorial-on-android/), backup from the Internet Wayback Machine. – Rodia Jun 09 '18 at 13:53