粒子系統的製作理念都是相同的,難是難在物理或化學等模擬,煙粒子就是如此,輕飄飄的煙動向不定,受周遭的環境而影響運動,例如風吹,在這邊是利用煙粒子重量當作風吹時的受動因子。
您可以先 看看 範例。
- 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>