粒子系統的製作理念都是相同的,難是難在物理或化學等模擬,煙粒子就是如此,輕飄飄的煙動向不定,受周遭的環境而影響運動,例如風吹,在這邊是利用煙粒子重量當作風吹時的受動因子。
您可以先 看看 範例。
- Smoke.java
package cc.openhome.particle;
import java.awt.*;
import java.applet.*;
import static java.lang.Math.*;
public class Smoke extends Applet implements Runnable {
    private final int MAX = 1000;
    private SmokeParticle[] particles; // 煙粒子
    private int xCenter, yCenter;
    private Image offScreen;
    private Graphics gOffScreen;
    public void init() {
        setSize(640, 480);
        setBackground(Color.black); // 背景為黑色
        particles = new SmokeParticle[MAX]; // 建立粒子
        // 煙初始位置
        xCenter = getWidth() / 2;
        yCenter = 2 * getHeight() / 3;
        for (int i = 0; i < MAX; i++) {
            particles[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() {
        int windTime = 0;
        double windX = 0;
        while (true) {
            if (windTime <= 0) {
                windX = 30 * random() - 15; // 風速 x
                windTime = (int) (20 * random());  // 風吹時間
            }
            gOffScreen.clearRect(0, 0, getWidth(), getHeight());
            for (SmokeParticle particle : particles) {
                if (particle.isAlive()) {
                    // 受風動的效果不一
                    double wx = windX / particle.getWeight();
                    double x = particle.getPoint().getX();
                    double y = particle.getPoint().getY();
                    particle.getPoint().setLocation(x + wx, y);
                    x = particle.getPoint().getX();
                    y = particle.getPoint().getY();
                    gOffScreen.setColor(particle.getColor());
                    gOffScreen.fillOval((int) x, (int) y, 4, 4);
                    particle.nextState();
                }
            }
            for (SmokeParticle particle : particles) {
                if (!particle.isAlive()) {
                    particle.setLife((int) (255 * random()));
                    particle.resume(xCenter, yCenter);
                }
            }
            // 重繪畫面
            repaint();
            try {
                Thread.sleep(150);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            windTime--;
        }
    }
}
class SmokeParticle {
    private Point position = new Point(); // 位置
    private double vx, vy;  // 水平與垂直速度
    private double weight;  // 重量
    private int life;       // 生命週期
    void resume(int x, int y) {
        position.setLocation(x, y);
        vx = 0;
        vy = -1;
        weight = 10 * random() + 1;
    }
    void setLife(int life) {
        this.life = life;
    }
    void setWeight(double weight) {
        this.weight = weight;
    }
    Point getPoint() {
        return position;
    }
    double getWeight() {
        return weight;
    }
    Color getColor() {
        return new Color(life, life, life);
    }
    boolean isAlive() {
        return life != 0;
    }
    void nextState() {
        position.setLocation(position.getX(), position.getY() + vy);
        life--;
    }
}
以下是使用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 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 MAX = 1000;
                var particles = []; // 建立粒子
                // 煙初始位置
                var xCenter = canvas1.width / 2;
                var yCenter = 2 * canvas1.height / 3;
                for(var i = 0; i < MAX; i++) {
                    particles[i] = new SmokeParticle();
                }
                
                var windTime = 0;
                var windX = 0;
                
                var context = context2;
                setTimeout(function() {
                    if(windTime <= 0) {
                        windX = 30 * Math.random() - 15; // 風速 x
                        windTime = 20 * Math.random();  // 風吹時間
                    }
                    context.fillStyle = 'rgb(0, 0, 0)';
                    context.fillRect(0, 0, canvas1.width, canvas1.height);
                    for(var i in particles) {
                        if (particles[i].isAlive()) {
                            // 受風動的效果不一
                            var particle = particles[i];
                            var wx = windX / particle.weight;
                            var x = particle.getPoint().x;
                            var y = particle.getPoint().y;
                            particle.getPoint().setLocation(x + wx, y);
                            x = particle.getPoint().x;
                            y = particle.getPoint().y;
                            context.fillStyle = 
                                particle.getColor().toString();
                            context.beginPath();
                            context.arc(x, y, 1, 0, 2 * Math.PI, true);
                            context.closePath();
                            context.fill();
                            particle.nextState();
                        }
                    }
                    if(context === context2) {
                        document.body.replaceChild(canvas2, canvas1);
                        context = context1;
                    }
                    else {
                        document.body.replaceChild(canvas1, canvas2);
                        context = context2;
                    }                        
                    
                    for(var i in particles) {
                        var particle = particles[i];
                        if(!particle.isAlive()) {
                            particle.life = parseInt(255 * Math.random());
                            particle.resume(xCenter, yCenter);
                        }
                    }
                    windTime--;
                                    
                    setTimeout(arguments.callee, 150);
                }, 150);
            };
        </script>
    </head>
    <body>       
        <canvas id="canvas1" width="640" height="480"></canvas>
        <canvas id="canvas2" width="640" height="480"></canvas>
    </body>
</html>

