蟒蛇 & 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
I'm making a game in which balls bounce around the inside of a much larger circle. The larger circle doesn't move.
Here's the code that I'm currently using for these collisions:
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)
It is based on the wonderful tutorial by Peter Collingridge over here.
The circle and ball objects are both classes, with (x,y), radius, angle and speed.
I am having two problems with this method, however:
- The ball bounces from (what I suspect) is its "anchor point", which seems to be in the top right corner of the circle.
- When colliding with the bottom 5% of the circle, is fails to bounce high enough and therefore "sinks" out of the screen. I am guessing that this is because the bounce is not high enough to move the ball above its (incorrectly placed) "anchor point"?
Having looked at possible solutions already on here, notably "Fast circle collision detection" [Link deleted due to spam link limit], which, although in Java is using the same method, these all deal with external collisions, whereas I am looking at bouncing a ball around the interior of a circle.
Here is also the class definitions of the Ball() and the 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
Thanks in advance, Nathan
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
在不回答您的问题的情况下,我想评论一下您的实施策略并推荐一种新方法。您可以用极坐标形式表示球的速度,如
ball.angle
和ball.speed
。我认为这通常会给您带来不便。例如,在碰撞代码中,您最终调用
atan2
将矢量 (dx
,dy
) 转换为角度,然后调用sin
和cos
将角度再次转换回矢量。 (此外,如果您尝试将代码推广到三维,您会发现自己陷入了痛苦的世界。)因此,除非您有需要极坐标的特殊要求,否则我建议您做其他人所做的事情,即表示笛卡尔坐标系中球的速度作为向量 (vx
,vy
)。我还建议将您的物理方法从静态(“对象 A 当前是否与对象 B 碰撞?”)更改为动态(“对象 A 是否会与对象 B 碰撞”) B 在其下一个移动步骤中?”)。在静态物理系统中,您通常会在运动步骤结束时看到对象相互交叉,然后您必须找出使它们再次分离的最佳方法,而这很难做到正确。
如果您同时执行这两项操作,则无需任何三角学即可轻松弹跳球。
步骤 1. 使用 Minkowski 加法 将圆/圆碰撞转换为点/圆碰撞:
步骤 2. 考虑一个时间段,其中球从 p = (px,py) 开始,移动 v = (vx ,维)。它与圆相交吗?您可以使用标准线段/圆测试,除了测试的意义颠倒了。
步骤 3. 找到碰撞点 c = (cx,cy)。球从圆上弹起的方式与从此时与圆相切的线 t 上弹起的方式相同。对于以原点为中心的圆,切向量就是 (−cy,cx),我相信您可以弄清楚如何计算其他圆的切向量。
请参阅此答案,了解如何根据摩擦系数和恢复系数计算球的新路径。
步骤 4. 不要忘记球可能仍然有一段距离可以沿着新矢量 w 移动。如果时间步长足够大或者速度足够高,它可能会在同一时间段内再次发生碰撞。
Without answering your question, I'd like to comment on your implementation strategy and recommend a new approach. You represent the velocity of the ball in polar coordinate form, as
ball.angle
andball.speed
.I think that this is going to be generally inconvenient for you. For example, in your collision code you end up calling
atan2
to turn the vector (dx
,dy
) into an angle, and then you callsin
andcos
to turn the angle back into a vector again. (Also, should you ever try to generalize your code to three dimensions, you will find yourself in a world of pain.) So, unless you have particular requirements that necessitate polar coordinates, I recommend that you do what everyone else does, namely represent the velocity of the ball in Cartesian coordinates as the vector (vx
,vy
).I also recommend changing your physics approach from a static one ("is object A currently colliding with object B?") to a dynamic one ("will object A collide with object B during its next movement step?"). In a static physics system you often end up with objects intersecting each other at the end of a movement step, and then you have to figure out the best way to get them to separate again, which is hard to get right.
If you do both of these, it is straightforward to bounce the ball without any trigonometry.
Step 1. Transform circle/circle collision into point/circle collision using Minkowski addition:
Step 2. Consider a time segment in which the ball starts at p = (px,py) and moves by v = (vx,vy). Does it intersect with the circle? You can use a standard line segment/circle test for this except that the sense of the test is reversed.
Step 3. Find the point of collision c = (cx,cy). The ball bounces off the circle in the same way as it would bounce off the line t tangent to the circle at this point. For a circle centred at the origin, the tangent vector is just (−cy,cx) and I'm sure you can work out how to compute it for other circles.
See this answer for how to calculate the new path of the ball based on coefficients of friction and restitution.
Step 4. Don't forget that the ball may still have some distance to move along the new vector w. If the time step is large enough or the velocity high enough it may collide again during the same time segment.
我很高兴你喜欢我的教程。我喜欢你的变体,它实际上应该更简单。
首先,我认为你需要将碰撞测试更改为:
因为球的尺寸越大,其中心与圆心之间的距离可以越小。这应该使球在正确的位置(圆圈内)弹跳。
然后我认为你只需要交换 x 和 y 的符号,一切就应该可以了。
要将球移动正确的距离,您可以计算重叠:
祝你好运
I'm glad you liked my tutorial. I like your variation, it should actually be simpler.
First, I think you need change the test for collision to:
Because the larger the ball size, the smaller the distance between its centre and the centre of the circle can be. This should make the balls bounce at the right place (inside the circle).
Then I think you just need to swap the signs for the x and y and everything should work.
To move the ball by the correct distance you can calculate the overlap:
Good luck
大多数图形包使用左上角作为绘制代码的开始。您很可能需要 2 组坐标,一组用于碰撞/移动等,一组用于绘图(x 半径、y 半径)。
另外,没有考虑太多,交集检查是否应该是距离+ ball.size >=circle.size ?如果我正确理解了设置,球到中心的距离加上其半径应该小于圆的半径。
Most graphics packages use upper-left as start for drawing code. You most likely want 2 sets of coordinates, the one's you collide/move/etc with and the one's for drawing (x-radius, y-radius).
Also, without having thought about it too much, should the check for intersection be
distance + ball.size >= circle.size
? The balls distance from the center plus its radius should be less than the circle's radius, if I understood the setup correctly.