Python和Pygame:球与圆心的碰撞

7
我将制作一个游戏,其中球在一个更大的圆圈内弹跳。这个更大的圆圈不会移动。
以下是我目前用于处理碰撞的代码:
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), 半径, 角度和速度。

然而,我遇到了两个问题:

  1. 球从其“锚点”(我认为)反弹,该点似乎位于圆的右上角。
  2. 当与圆的底部5%相撞时,它未能反弹高到足以将球移出屏幕,我猜测这是因为反弹不够高,无法将球移动到其(放置不正确的)“锚点”上方?

已经在这里查看了可能的解决方案,特别是“快速圆形碰撞检测”[由于垃圾邮件链接限制已删除链接],虽然使用相同的方法,但这些都处理外部碰撞,而我正在考虑将球弹在圆的内部。

以下是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个回答

14
不回答您的问题,我想评论一下您的实现策略,并推荐一种新方法。您用极坐标形式表示球的速度,如ball.angleball.speed,我认为这对您来说通常会很不方便。例如,在碰撞代码中,您最终会调用atan2将向量(dx, dy)转换为角度,然后再调用sincos将角度转换回向量。(此外,如果您尝试将代码推广到三维空间,您将发现自己陷入了痛苦之中。)因此,除非您有特殊要求需要使用极坐标,否则我建议您像其他人一样,用笛卡尔坐标系表示球的速度,即向量(vx, vy)。
我还建议将物理方法从静态方法(“A对象当前是否与B对象相撞?”)改为动态方法(“A对象在其下一个运动步骤中是否会与B对象相撞?”)。在静态物理系统中,您经常会发现物体在运动步骤结束时互相交叉,然后您必须找出让它们再次分离的最佳方法,这很难做到。
如果您这样做,就可以直接反弹球,而无需使用任何三角函数。
步骤1.使用Minkowski加法将圆/圆碰撞转换为点/圆碰撞:

Original problem at left shows small circle moving inside a large circle. Transformed problem at right shows point moving in circle whose radius is the difference between the radii of the circles in the original problem.

步骤2. 考虑一个时间段,在该时间段内,球从p = (px,py)开始移动,移动速度为v = (vx,vy)。它是否与圆相交?您可以使用标准线段/圆测试进行测试,但测试的方向是相反的。

步骤3. 找到碰撞点c = (cx,cy)。球以与其在此点处切线t反弹,反弹方式与其在直线上反弹相同。对于以原点为中心的圆,切向量就是(−cy,cx),我相信您可以计算出其他圆的切向量。

Figure described above

查看此答案以了解如何根据摩擦和弹性系数计算球的新路径。

步骤4. 不要忘记,球可能仍然需要沿着新向量w移动一定距离。如果时间步长足够大或速度足够高,它可能在同一时间段内再次碰撞。


虽然这不是我在寻找的东西,但非常感谢!我正在研究它。 - nchpmn

12

很高兴你喜欢我的教程。我喜欢你的变化,它应该更简单。

首先,我认为你需要改变检测碰撞的测试为:

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

祝你好运


X轴不是由角度的余弦定义,Y轴也不是由正弦定义吗?还是我学错了?:X - jonathancardoso
我认为你说得对,那是定义事物的标准方式,但只要你保持一致,实际上并没有任何影响。显然,如果你将所有东西旋转90度,这不应该影响到物体弹跳的方式(假设重力也发生了改变)。 - Peter Collingridge
@PeterCollingridge,你的文章真是太棒了!优雅的解决方案,Pythonic风格,恭喜... 谢谢! - MestreLion
@PeterCollingridge:顺便说一下,我认为你的文章中有一个物理错误:如果一个球与一个静止的球碰撞(没有重力、阻力和弹性=1),你的模拟将总是以踢另一个球并使第一个球静止的方式结束。在正确的物理学中,只有在正面碰撞(速度和接触法线平行)时才会发生这种情况。 - MestreLion

2

大多数图形包使用左上角作为绘图代码的起点。您很可能需要两组坐标,一组用于碰撞/移动等操作,另一组用于绘制(x半径,y半径)。

另外,没有仔细考虑,检查交集的方法应该是distance + ball.size >= circle.size吗?如果我正确理解了设置,则球到中心的距离加上其半径应小于圆的半径。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接