携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第2天
思路
現實中小球撞到牆壁或是掉到地上,一部分的能量會被吸收,剩餘的能量會使物體往反方向彈,於是球越彈越低直到停止。示意圖如下⬇️
於是可以列出幾個重點:
- 接觸:判斷小球與邊界位置
- 反彈:碰撞後速度減少並相反
- 停球:速度足夠小的時候停球
邊界碰撞
給出邊界
先設定好尺寸,把邊界的區域上色,用Stack疊在後方,比較方便觀察。
Size size = const Size(300, 400);
Stack(
children: [
SizedBox.fromSize(
size: size, child: const ColoredBox(color: Colors.blue)),
CustomPaint(
size: size,
painter: BallPainter(balls: balls, phoneXY: phoneXY),
),
]
)
接觸
回到Painer的paint裡,小球的圓心位置加上半徑,剛好接觸或是超過設定的邊界尺寸,即可視為是接觸碰撞。於是要針對四個邊分別判斷,當然你要是數學能力足夠強你可以利用三角函數統一判斷(有數學強的大佬來分享看看嗎😊)
還需要判斷速度的方向,不然小球反彈的可能會被卡在邊界無限反彈之類的。
@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;
if (ball.position.dy >= size.height - ball.radius && ball.speedY > 0) {
。
。
}
if (ball.position.dy <= ball.radius && ball.speedY < 0) {
。
。
}
if (ball.position.dx >= size.width - ball.radius && ball.speedX > 0) {
。
。
}
if (ball.position.dx <= ball.radius && ball.speedX < 0) {
。
。
}
ball.position += Offset(dx, dy);
_paint.color = ball.color;
canvas.drawCircle(ball.position, ball.radius, _paint);
}
}
反彈
以豎直方向下方的邊為例,竪直方向的速度speedY在碰撞之後速度會相反並且減弱,我這裡先假定每次碰撞速度會少兩成(可以自己調整決定要的效果。
if (ball.position.dy >= size.height - ball.radius && ball.speedY > 0) {
//速度反彈
ball.speedY = -ball.speedY * 0.8;
//覆蓋原來的位移
dy = ball.speedY;
}
停球
有了上述的反彈運動後,我們會發現小球下落後,會邊彈邊被削弱但是永遠不會停止,因為加速度計一直持續加上加速度,所以應該要給他一個停球的判斷,讓球⚽️休息一下。
可以試著print一下速度ball.speedY或ball.speedX,觀察小球在最小彈跳時其速度變化情況。我的手機在此時的值會在+-2內變化以此給停球判斷(不同平台或手機型號的值我不確定是否一致)。
if (ball.speedY.abs() < 2) dy = 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;
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) dy = 0;
}
if (ball.position.dx <= ball.radius && ball.speedX < 0) {
ball.speedX = -ball.speedX * 0.8;
dx = ball.speedX;
if (ball.speedX.abs() < 2) dy = 0;
}
ball.position += Offset(dx, dy);
_paint.color = ball.color;
canvas.drawCircle(ball.position, ball.radius, _paint);
}
}