一路進行簡單的物理模擬,到了〈彈性碰撞〉的範例時,幾個球互相碰撞,感覺自成一套系統?是的!可以進一步地把這些球收集起來,成為碰撞系統,這只要將〈彈性碰撞〉範例中的 setup
流程重構一下,成為 CollisionSystem
建構式,然後 draw
的部份也抽取出來,成為 update
:
class CollisionSystem {
constructor(number, width, height, minR = 5, maxR = 20, maxVx = 5, maxVy = 5) {
const minX = 1.5 * maxR;
const maxX = width - 1.5 * maxR;
const minY = 1.5 * maxR;
const maxY = height - 1.5 * maxR;
this.circles = [];
while(this.circles.length < number) {
const r = random(5, 20);
const coordinate = createVector(random(minX, maxX), random(minY, maxY));
if(this.circles.every(c => p5.Vector.sub(c.body.coordinate, coordinate).mag() > (c.radius + r))) {
this.circles.push(new Circle(
new Body(
coordinate,
createVector(random(-maxVx, maxVx), random(-maxVy, maxVy)),
PI * r * r
),
r
));
}
}
}
update() {
this.circles.forEach(c => c.update());
checkCollision(this.circles);
this.circles.forEach(c => checkEdges(c));
}
}
現在 CollisionSystem
負責這些圓的生成、移動,接著可以如下 setup
與 draw
繪圖,其餘程式碼不變:
let collisionSystem;
function setup() {
createCanvas(300, 300);
collisionSystem = new CollisionSystem(15, width, height);
}
function draw() {
background(220);
collisionSystem.update();
collisionSystem.circles.forEach(c => c.draw());
}
... 其餘程式碼不變
若要更進一步地,可以讓這些圓具有生命值,若跟另一個圓碰撞後生命值就會減一,直到生命值耗盡為止後將之移除,這麼一來,就成為一個粒子系統了,CollisionSystem
管理的粒子就是圓,負責粒子的生成、移動、轉化、消亡。
在我們的設計中,圓只是外形,Body
本身才有碰撞時相關的質量、速度等資訊,為了能知道發生碰撞的時機,來為 Body
設計個通知機制:
class Body {
constructor(coordinate, velocity, mass = 1) {
this.coordinate = coordinate;
this.velocity = velocity;
this.mass = mass;
// 使用 Set 來管理傾聽器
this.collisionListeners = new Set();
}
...
addCollisionListener(listener) {
this.collisionListeners.add(listener);
}
removeCollisionListener(listener) {
this.collisionListeners.delete(listener);
}
collideWith(body) {
const d = sub(this.coordinate, body.coordinate);
const m = body.mass / (this.mass + body.mass);
this.velocity = sub(
this.velocity,
d.mult(
2 * m / pow(d.mag(), 2) * p5.Vector.dot(
sub(this.velocity, body.velocity),
sub(this.coordinate, body.coordinate)
)
)
);
// 發生碰撞時逐一呼叫傾聽器
this.collisionListeners.forEach(listener => listener(this));
}
...
}
對圓的生命週期管理部份,可以使用 Map
,將 Circle
實例作為鍵(key),生命值作為值(value),對碰撞事件進行註冊,發生碰撞時減少生命值,而在 update
中,可以檢查粒子們的生命值,若小於等於 0 了,就將之移除:
class CollisionSystem {
constructor(number, width, height, lifespan = 255, losing = 10, minR = 5, maxR = 20, maxVx = 5, maxVy = 5) {
...
this.particles = new Map(); // 管理的粒子(也就是圓)
while(this.particles.size < number) {
const r = random(minR, maxR);
const coordinate = createVector(random(minX, maxX), random(minY, maxY));
if(Array.from(this.particles.keys()).every(c => p5.Vector.sub(c.body.coordinate, coordinate).mag() > (c.radius + r))) {
const body = new Body(
coordinate,
createVector(random(-maxVx, maxVx), random(-maxVy, maxVy)),
PI * r * r
);
// `Circle` 實例作為鍵(key),生命值作為值(value)
const circle = new Circle(body, r);
this.particles.set(circle, lifespan);
// 發生碰撞時,減少生命值
body.addCollisionListener(evt => {
this.particles.set(circle, this.particles.get(circle) - losing);
});
}
}
}
update() {
const circles = Array.from(this.particles.keys());
circles.forEach(c => c.update());
checkCollision(circles);
circles.forEach(c => checkEdges(c));
this.particles.forEach((lifespan, c) => {
if(lifespan <= 0) {
this.particles.delete(c);
}
});
}
}
為了視覺化粒子生命值的變化,使用生命值作為圓的灰階填充:
function draw() {
background(220);
collisionSystem.update();
collisionSystem.particles.forEach((lifespan, c) => {
fill(lifespan);
c.draw();
});
}
現在用這個互相傷害系統來模擬一下吧!這些粒子哪個可以存活下來呢?