携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第3天
前言
最近太忙了,拖了很久才產出一篇,我看我是拿不到7天獎勵了o(╥﹏╥)o
思路
小球(勳章)之間相互碰撞,比邊界碰撞要複雜一點,因為這些球是有速度的,不像牆壁是固定在四個邊靜止不動。
速度具有方向性,因此我這裡將碰撞分為反向碰撞與同向碰撞。
- 碰撞判定
- 反向碰撞
- 同向碰撞
碰撞判定
通過每個球與其他球距離,可以判斷球與球是否接觸,如此一來會有雙重迴圈O(n²)感覺不是那麼漂亮,若是大佬們有其他更好的方法歡迎分享。
- 由於邊界碰撞的優先度比較高,邊界碰撞部分放在後邊。
- 使用勾股定理計算距離
- 當距離小於等於兩球半徑之和即為碰撞
for (var ball in balls) {
ball.speedX -= phoneXY.value[0] / 20;
ball.speedY += phoneXY.value[1] / 20;
double dx = ball.speedX;
double dy = ball.speedY;
for (var ball2 in balls) {
if (ball != ball2) {
double distanceX = ball.position.dx - ball2.position.dx;
double distanceY = ball.position.dy - ball2.position.dy;
double distance = sqrt(distanceX * distanceX + distanceY * distanceY);
if (distance <= ball.radius + ball2.radius) {
。
。
。
}
}
}
//
//
// 邊界碰撞部分
//
//
ball.position += Offset(dx, dy);
_paint.color = ball.color;
canvas.drawCircle(ball.position, ball.radius, _paint);
}
反向碰撞
兩球的速度方向相反(速度相乘小於0),有兩種情況,一是相互靠近,二是相互遠離,我們只需要相互靠近的情況。
以水平方向X為例,兩球相互靠近時,右側的distanceX為負值速度為正值,左側正好相反。因此水平方向X的反向判斷就是👇
ball.speedX * ball2.speedX < 0 && distanceX * ball.speedX < 0
碰撞後的速度變化處理和邊界碰撞一樣(本來我為此去複習初高中的動量守恆公式,但是轉念一想不符合公式的部分就當做能量損失,真是天才😁)。同樣的,速度變化與停球判斷自行取值決定效果。
//反向
if (ball.speedY * ball2.speedY < 0 && distanceY * ball.speedY < 0) {
ball.speedY = -ball.speedY * 0.8;
dy = ball.speedY;
if (ball.speedY.abs() < 1) dy = 0;
}
if (ball.speedX * ball2.speedX < 0 && distanceX * ball.speedX < 0) {
ball.speedX = -ball.speedX * 0.8;
dx = ball.speedX;
if (ball.speedX.abs() < 1) dx = 0;
}
同向碰撞
兩球的速度方向相同(速度相乘大於0),也可以分為兩種情況如下圖,右側追撞球、左側被追撞球。
- 右側追撞球 判斷條件:
distanceX * ball.speedX < 0 - 左側被追撞球 判斷條件:
distanceX * ball.speedX >= 0
右側追撞球碰撞後反彈並減少,左側被追撞球碰撞後會加速。
我這裡直接忽略了左側被追撞球的加速,大家可以試情況加上。
//同向
if (ball.speedY * ball2.speedY >= 0) {
if (distanceY * ball.speedY < 0) {
ball.speedY = -ball.speedY * 0.5;
}
dy = ball.speedY;
if (ball.speedY.abs() < 1) dy = 0;
}
if (ball.speedX * ball2.speedX >= 0) {
if (distanceX * ball.speedX < 0) {
ball.speedX = -ball.speedX * 0.5;
}
dx = ball.speedX;
if (ball.speedX.abs() < 1) dx = 0;
}
Code與效果
@override
void paint(Canvas canvas, Size size) {
for (var ball in balls) {
ball.speedX -= phoneXY.value[0] / 20;
ball.speedY += phoneXY.value[1] / 20;
double dx = ball.speedX;
double dy = ball.speedY;
for (var ball2 in balls) {
if (ball != ball2) {
double distanceX = ball.position.dx - ball2.position.dx;
double distanceY = ball.position.dy - ball2.position.dy;
double distance = sqrt(distanceX * distanceX + distanceY * distanceY);
if (distance <= ball.radius + ball2.radius) {
//反向
if (ball.speedY * ball2.speedY < 0 && distanceY * ball.speedY < 0) {
ball.speedY = -ball.speedY * 0.8;
dy = ball.speedY;
if (ball.speedY.abs() < 1) dy = 0;
}
if (ball.speedX * ball2.speedX < 0 && distanceX * ball.speedX < 0) {
ball.speedX = -ball.speedX * 0.8;
dx = ball.speedX;
if (ball.speedX.abs() < 1) dx = 0;
}
//同向
if (ball.speedY * ball2.speedY >= 0) {
if (distanceY * ball.speedY < 0) {
ball.speedY = -ball.speedY * 0.5;
}
dy = ball.speedY;
if (ball.speedY.abs() < 1) dy = 0;
}
if (ball.speedX * ball2.speedX >= 0) {
if (distanceX * ball.speedX < 0) {
ball.speedX = -ball.speedX * 0.5;
}
dx = ball.speedX;
if (ball.speedX.abs() < 1) dx = 0;
}
}
}
}
if (ball.position.dy >= size.height - ball.radius && ball.speedY > 0) {
ball.speedY = -ball.speedY * 0.8;
dy = ball.speedY;
if (ball.speedY.abs() < 2) dy = 0;
}
if (ball.position.dy <= ball.radius && ball.speedY < 0) {
ball.speedY = -ball.speedY * 0.8;
dy = ball.speedY;
if (ball.speedY.abs() < 2) dy = 0;
}
if (ball.position.dx >= size.width - ball.radius && ball.speedX > 0) {
ball.speedX = -ball.speedX * 0.8;
dx = ball.speedX;
if (ball.speedX.abs() < 2) dx = 0;
}
if (ball.position.dx <= ball.radius && ball.speedX < 0) {
ball.speedX = -ball.speedX * 0.8;
dx = ball.speedX;
if (ball.speedX.abs() < 2) dx = 0;
}
ball.position += Offset(dx, dy);
_paint.color = ball.color;
canvas.drawCircle(ball.position, ball.radius, _paint);
}