事實上之前的煙火在施放時並沒有煙,只有火花,可以結合兩個粒子系統來製作煙火在燃燒過程中所產生的煙硝軌跡。
在結合兩個粒子系統時,要注意,每個粒子都是獨立的,火花粒子就是火花粒子,不會與煙粒子相互影響,只不過煙粒子產生的位置是火花的目前位置,只要將位置的資訊傳遞給煙粒子物件就可以了。
指定煙粒子的方式採用比較簡單的方式,首先產生足夠的煙粒子,它們的狀態都為false,然後等待重新指定它們為復活狀態,每次繪製完火花後,在一堆煙粒子物件中尋找狀態為false的煙粒子,然後指定新的初始位置給這些粒子,如此煙粒子就會跟隨著火花的軌跡而產生了。
這邊需要之前製作的FireworkParticle與SmokeParticle類別,您可以直接看看 範例。
- FireworkAndSmoke.java
package cc.openhome.particle;
import java.awt.*;
import javax.swing.JApplet;
import static java.lang.Math.*;
public class FireworkAndSmoke extends JApplet implements Runnable {
    private final int FIREWORK_MAX = 255;
    private final int SMOKE_MAX = FIREWORK_MAX * 10;
    private FireworkParticle[] fireworks; // 煙火粒子
    private SmokeParticle[] smokes; // 煙粒子
    private int xCenter, yCenter;
    private Image offScreen;
    private Graphics gOffScreen;
    public void init() {
        setSize(640, 480);
        setBackground(Color.black); // 背景為黑色
        // 建立粒子
        fireworks = new FireworkParticle[FIREWORK_MAX]; 
        smokes = new SmokeParticle[SMOKE_MAX];
        // 煙火初始位置
        xCenter = (int) (getWidth() / 2 + random() * 150 - 150);
        yCenter = (int) (getHeight() / 2 + random() * 150 - 150);
        
        for (int i = 0; i < FIREWORK_MAX; i++) {
            fireworks[i] = new FireworkParticle();
        }
        for (int i = 0; i < SMOKE_MAX; i++) {
            smokes[i] = new SmokeParticle();
        }
        // 建立次畫面
        offScreen = createImage(getWidth(), getHeight());
        gOffScreen = offScreen.getGraphics();
    }
    public void start() {
        (new Thread(this)).start();
    }
    public void update(Graphics g) {
        paint(g);
    }
    public void paint(Graphics g) {
        g.drawImage(offScreen, 0, 0, this);
    }
    public void run() {
        while (true) {
            boolean replay = true;
            for (FireworkParticle firework : fireworks) {
                if (firework.isAlive()) {
                    replay = false;
                    break;
                }
            }
            // 是否重新施放
            if (replay) {
                for (FireworkParticle firework : fireworks) {
                    firework.resume(xCenter, yCenter, FIREWORK_MAX);
                    firework.setLife((int) (random() * 20));
                }
            }
            
            gOffScreen.clearRect(0, 0, getWidth(), getHeight());
            for(SmokeParticle smoke : smokes) {
                if (smoke.isAlive()) {
                    double sx = smoke.getPoint().getX();
                    double sy = smoke.getPoint().getY();
                    gOffScreen.setColor(smoke.getColor());
                    gOffScreen.fillOval((int) sx, (int) sy, 4, 4);
                    smoke.nextState();
                }
            }
            for (FireworkParticle firework : fireworks) {
                if (firework.isAlive()) {
                    double x = firework.getPoint().getX();
                    double y = firework.getPoint().getY();
                    gOffScreen.setColor(firework.getColor());
                    gOffScreen.fillOval((int) x, (int) y, 3, 3);
                    // 為煙火加上煙
                    for(SmokeParticle smoke : smokes) {
                        if (!smoke.isAlive()) {
                            smoke.setLife((int) (50 * random()));
                            smoke.resume((int) x, (int) y);
                            break;
                        }
                    }
                    firework.nextState();
                }
            }
            // 重繪畫面
            repaint();
            // 暫停執行緒 150 毫秒
            try {
                Thread.sleep(150);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}以下是使用HTML5 Canvas的方式(如果瀏覽器支援HTML5 Canvas,例如最新版的Firexfox、Chrome、IE9等,可以直接將下面的內容存為HTML或按下檔名連結,直接載入瀏覽器執行觀看結果:
<!DOCTYPE html>
<html>
    <head>
        <meta content="text/html; charset=Big5" http-equiv="content-type">
        <script type="text/javascript">
            window.onload = function() {
                function Color(r, g, b) {
                    this.r = r;
                    this.g = g;
                    this.b = b;
                    this.toString = function() {
                        return 'rgb(' + 
                           [this.r, this.g, this.b].join() + ')';
                    };
                }
                
                function Point(x, y) {
                    this.x = x || 0;
                    this.y = y || 0;
                    this.setLocation = function(x, y) {
                        this.x = x;
                        this.y = y;
                    };
                }
                
                function FireworkParticle() {
                    var LIFE_LESS_5 = new Color(255, 0, 0);
                    var LIFE_LESS_2 = new Color(0, 0, 255);
                    var position = new Point();  // 粒子的位置
                    var vx = vy = 0;             // 粒子的速度
                    var color = null;            // 粒子的顏色
                    var time = 0;                // 粒子存活至今的時間
                    this.life = 0;               // 粒子的生命值
                    this.resume = function(x, y, max) {
                        position.setLocation(x, y);
                        vx = Math.random() * max - Math.random() * max;
                        vy = Math.random() * max - Math.random() * max;
                        color = new Color(parseInt(Math.random() * 255),
                                          parseInt(Math.random() * 255),
                                          parseInt(Math.random() * 255));
                        time = 0;
                    };
                    this.isAlive = function() {
                        return this.life > 0;
                    };
                    this.getPoint = function() {
                        return position;
                    };
                    this.getColor = function() {
                        return color;
                    };
                    this.nextState = function() {
                         vy += 9.8 * time;
                         position.setLocation(
                             position.x + vx * 0.1,
                             position.y + vy * 0.01);
                         this.life--;
                         time++;
                         if (this.life < 2) {
                             color = LIFE_LESS_2;
                         } else if(this.life < 5) {
                             color = LIFE_LESS_5;
                         }
                    };
                }
                function SmokeParticle() {
                    var position = new Point();  // 位置
                    var vx = vy = 0;             // 水平與垂直速度
                    this.weight = 0;              // 重量 
                    this.life = 0;                // 生命週期
                    this.resume = function(x, y) {
                        position.setLocation(x, y);
                        vx = 0;
                        vy = -1;
                        this.weight = 10 * Math.random() + 1;
                    };
                    this.getPoint = function() {
                        return position;
                    };
                    this.getColor = function() {
                        return new Color(this.life, this.life, this.life);
                    };
                    this.isAlive = function() {
                        return this.life > 0;
                    };
                    this.nextState = function() {
                        position.setLocation(position.x, position.y + vy);
                        this.life--;
                    };
                }
                var canvas1 = document.getElementById('canvas1');
                var canvas2 = document.getElementById('canvas2');
                var context1 = canvas1.getContext('2d');
                var context2 = canvas2.getContext('2d');
                
                
                var FIREWORK_MAX = 200;
                var SMOKE_MAX = FIREWORK_MAX * 10;
                
                var fireworks = []; // 建立粒子
                                  
                for(var i = 0; i < FIREWORK_MAX; i++) {
                    fireworks[i] = new FireworkParticle();
                }
                
                var smokes = [];
                for(var i = 0; i < SMOKE_MAX; i++) {
                    smokes[i] = new SmokeParticle();
                }
                var context = context2;
                setTimeout(function() {
                    var replay = true;
                    for(var i in fireworks) {
                       var firework = fireworks[i];
                       if(firework.isAlive()) {
                           replay = false;
                           break;
                       }
                    }
                    
                    // 是否重新施放
                    if(replay) {
                        for(var i in fireworks) {
                            // 煙火初始位置
                            var xCenter = canvas1.width / 2 + 
                                  Math.random() * 150 - 150;
                            var yCenter = canvas1.height / 2.5 + 
                                  Math.random() * 150 - 150;
                            var firework = fireworks[i];
                            firework.resume(xCenter, yCenter, FIREWORK_MAX);
                            firework.life = 
                                  parseInt(Math.random() * 20);
                        }
                    }
                    
                    context.fillStyle = 'rgb(0, 0, 0)';
                    context.fillRect(0, 0, canvas1.width, canvas1.height);
                    for(var i in smokes) {
                        var smoke = smokes[i];
                        if(smoke.isAlive()) {
                            var sx = smoke.getPoint().x;
                            var sy = smoke.getPoint().y;
                            context.fillStyle = smoke.getColor().toString();
                            context.beginPath();
                            context.arc(sx, sy, 2, 0, 2 * Math.PI, true);
                            context.closePath();
                            context.fill();
                            smoke.nextState();
                        }
                    }
                    
                    for(var i in fireworks) {
                        var firework = fireworks[i];
                        if(firework.isAlive()) {
                            var x = firework.getPoint().x;
                            var y = firework.getPoint().y;
                           
                            context.fillStyle = 
                                firework.getColor().toString();
                            context.beginPath();
                            context.arc(x, y, 2, 0, 2 * Math.PI, true);
                            context.closePath();
                            context.fill();        
                            
                            firework.nextState();
                        }
                    }          
                    // 為煙火加上煙
                    for(var i in fireworks) {
                        var firework = fireworks[i];
                        if(firework.isAlive()) {       
                            var x = firework.getPoint().x;
                            var y = firework.getPoint().y;
                            for(var j in smokes) {
                                var smoke = smokes[j];
                                if (!smoke.isAlive()) {
                                    smoke.life = 
                                        parseInt(50 * Math.random());
                                    smoke.resume(x, y);
                                    break;
                                }
                            }
                        }
                    }
                                                            
                    if(context === context2) {
                        document.body.replaceChild(canvas2, canvas1);
                        context = context1;
                    }
                    else {
                        document.body.replaceChild(canvas1, canvas2);
                        context = context2;
                    }
                    
                    setTimeout(arguments.callee, 200);
                }, 200);
            };
        </script>
    </head>
    <body>       
        <canvas id="canvas1" width="640" height="480"></canvas>
        <canvas id="canvas2" width="640" height="480"></canvas>
    </body>
</html>

