Python与Pygame:球与圆内壁碰撞
我正在制作一个游戏,里面有小球在一个更大的圆圈的内部弹跳。这个大圆圈是静止不动的。
这是我目前用来处理这些碰撞的代码:
def collideCircle(circle, ball):
"""Check for collision between a ball and a circle"""
dx = circle.x - ball.x
dy = circle.y - ball.y
distance = math.hypot(dx, dy)
if distance >= circle.size + ball.size:
# We don't need to change anything about the circle, just the ball
tangent = math.atan2(dy, dx)
ball.angle = 2 * tangent - ball.angle
ball.speed *= elasticity + 0.251
angle = 0.5 * math.pi + tangent
ball.x -= math.sin(angle)
ball.y += math.cos(angle)
这个代码是基于Peter Collingridge的一个很棒的教程,你可以在这里找到。
圆圈和小球都是类,它们都有(x,y)坐标、半径、角度和速度。
不过,我在这个方法上遇到了两个问题:
- 小球从我猜测的“锚点”反弹,这个锚点似乎在圆圈的右上角。
- 当小球碰到圆圈的底部5%时,它的反弹高度不够,因此会“沉”出屏幕。我猜这可能是因为反弹的高度不足以让小球超过它(错误放置的)“锚点”?
我已经查看了一些可能的解决方案,比如“快速圆形碰撞检测”[链接因垃圾链接限制已删除],虽然这个是用Java写的,但方法是一样的,这些都处理的是外部碰撞,而我想要的是让小球在圆圈内部弹跳。
这里还有Ball()和Circle()的类定义:
class Ball():
def __init__(self, (x,y), size):
"""Setting up the new instance"""
self.x = x
self.y = y
self.size = size
self.colour = (0,128,255)
self.thickness = 0
self.speed = 0.01
self.angle = math.pi/2
def display(self):
"""Draw the ball"""
pygame.draw.circle(screen, self.colour, (int(self.x), int(self.y)), self.size, self.thickness)
def move(self):
"""Move the ball according to angle and speed"""
self.x += math.sin(self.angle) * self.speed
self.y -= math.cos(self.angle) * self.speed
(self.angle, self.speed) = addVectors((self.angle, self.speed), gravity)
self.speed *= drag
class Circle():
def __init__(self, (x,y), size):
"""Set up the new instance of the Circle class"""
self.x = x
self.y = y
self.size = size
self.colour = (236, 236, 236)
self.thickness = 0
self.angle = 0 # Needed for collision...
self.speed = 0 # detection against balls
def display(self):
"""Draw the circle"""
pygame.draw.circle(screen, self.colour, (int(self.x), int(self.y)), self.size, self.thickness
谢谢你,Nathan
3 个回答
大多数图形软件都是从左上角开始绘制的。你通常需要两组坐标,一组是用来检测碰撞、移动等的,另一组是用来绘制的(比如x半径和y半径)。
另外,虽然我没想太多,但检查是否相交的条件应该是 distance + ball.size >= circle.size
吗?也就是说,球心到中心的距离加上它的半径应该小于圆的半径,如果我理解得没错的话。
在不直接回答你问题的情况下,我想谈谈你的实现策略,并推荐一种新的方法。你用极坐标的方式来表示小球的速度,分别是 ball.angle
和 ball.speed
。
我觉得这样做会让你在后续的工作中感到不方便。比如,在你的碰撞代码中,你需要调用 atan2
来把向量(dx
,dy
)转换成一个角度,然后再用 sin
和 cos
把这个角度转换回向量。(而且,如果你将来想把代码扩展到三维,你会发现这会让你很头疼。)所以,除非你有特别的需求需要使用极坐标,我建议你像其他人一样,用笛卡尔坐标来表示小球的速度,使用向量(vx
,vy
)。
我还建议你把物理处理方式从静态的(“物体A现在是否与物体B碰撞?”)改为动态的(“物体A在下一步移动中会与物体B碰撞吗?”)。在静态物理系统中,你常常会发现物体在移动结束时相互重叠,然后你需要想办法让它们分开,这个过程很难处理好。
如果你同时做这两点,反弹小球就会变得简单,不需要用到三角函数。
第一步:使用 闵可夫斯基加法 将圆与圆的碰撞转化为点与圆的碰撞:
第二步:考虑一个时间段,在这个时间段内小球从p = (px,py) 开始,移动的速度是v = (vx,vy)。它是否与圆相交?你可以使用 标准的线段与圆的碰撞检测,不过这个检测的方向是反向的。
第三步:找到碰撞点c = (cx,cy)。小球会像碰到与圆在这个点切线t一样反弹。对于以原点为中心的圆,切线向量就是(−cy,cx),我相信你能算出其他圆的切线向量。
查看这个回答,了解如何根据摩擦系数和恢复系数计算小球的新路径。
第四步:别忘了小球可能还会沿着新的向量w继续移动。如果时间步长足够大或者速度足够快,它可能会在同一个时间段内再次碰撞。
我很高兴你喜欢我的教程。我也很喜欢你的改进版本,实际上应该更简单一些。
首先,我觉得你需要把碰撞检测的条件改成:
if distance >= circle.size - ball.size:
因为球的大小越大,它的中心和圆心之间的距离就可以越小。这应该能让球在正确的位置反弹(在圆内)。
然后,我觉得你只需要把x和y的符号互换一下,其他的应该就能正常工作了。
ball.x += math.sin(angle)
ball.y -= math.cos(angle)
要让球移动正确的距离,你可以计算重叠部分:
overlap = math.hypot(dx, dy) - (circle.size - ball.size)
if overlap >= 0:
tangent = math.atan2(dy, dx)
ball.angle = 2 * tangent - ball.angle
ball.speed *= elasticity
angle = 0.5 * math.pi + tangent
ball.x += math.sin(angle)*overlap
ball.y -= math.cos(angle)*overlap
祝你好运!