Pygame球碰撞

发布于 2025-02-09 08:37:15 字数 2293 浏览 1 评论 0 原文

我正在尝试模拟两个球之间的弹性碰撞。问题是,这些球有时不会遵循适当的行为,有时会“振动”或“粘在”彼此之间,或者当发生碰撞时它们甚至只是彼此之间。我不知道这是否与我使用的方程式有关(应该是标准的,但我真的不知道)。这是我的代码:

import pygame
import random
import math

def checkcirclecollide(x1, y1, r1, x2, y2, r2):
    return (x1 - x2)**2 + (y1 - y2)**2 == (r1 + r2)**2

def ballcollision(m1, m2, v1, v2):
    v2f = (2*m1*v1+m2*v2-m1*v2)/(m1+m2)
    v1f = (m1*v1+m2*v2-m2*v2f)/m1
    return v1f, v2f

class Ball:
    def __init__(self, x, y, vx, vy, r, m):
        self.x = x
        self.y = y
        self.vx = vx
        self.vy = vy
        self.r = r
        self.m = m
    def change_attribute(self, x=None, y=None, vx=None, vy=None, r=None, m=None):
        if x!=None: self.x = x
        if y!=None: self.y = y
        if vx!=None: self.vx = vx
        if vy!=None: self.vy = vy
        if r!=None: self.r = r
        if m!=None: self.m = m

pygame.init()
screen = pygame.display.set_mode((500, 500))
x, y = random.randint(11, 489), random.randint(11, 489)
vx, vy = random.randint(-5, 5), random.randint(-5, 5)
animationTimer = pygame.time.Clock()
balls = []
num_balls = 2
for x in range(num_balls):
    balls.append(Ball(random.randint(11, 489), random.randint(11, 489), random.randint(-5, 5), random.randint(-5, 5), random.randint(30, 50), random.randint(1, 4)*5))
while True:
    for e in pygame.event.get():   
        if e.type == pygame.QUIT:
            break
    for ball in balls:
        pygame.draw.circle(screen, (200, 0, 0), (ball.x, ball.y), ball.r)
        if ball.x-ball.r<=0 or ball.x+ball.r>=500:
            ball.change_attribute(vx = -ball.vx)
        if ball.y-ball.r<=0 or ball.y+ball.r>=500:
            ball.change_attribute(vy = -ball.vy)
        for other in balls:
            if other != ball and checkcirclecollide(ball.x, ball.y, ball.r, other.x, other.y, other.r):
                new_v1x, new_v2x = ballcollision(ball.m, other.m, ball.vx, other.vx)
                new_v1y, new_v2y = ballcollision(ball.m, other.m, ball.vy, other.vy)
                ball.change_attribute(vx = new_v1x, vy = new_v1y)
                other.change_attribute(vx = new_v2x, vy = new_v2y)
        ball.change_attribute(x = ball.x+ball.vx, y = ball.y+ball.vy)
    animationTimer.tick(100)
    pygame.display.update()
    screen.fill((0, 0, 0))

I am trying to simulate an elastic collision in pygame between two balls. The problem is, the balls sometimes don't follow the proper behaviour and sometimes "vibrate" or "stick" to each other or they even just go through each other when a collision happens. I don't know whether this has something to do with the equation I used (should be pretty standard but I really don't know). Here is my code:

import pygame
import random
import math

def checkcirclecollide(x1, y1, r1, x2, y2, r2):
    return (x1 - x2)**2 + (y1 - y2)**2 == (r1 + r2)**2

def ballcollision(m1, m2, v1, v2):
    v2f = (2*m1*v1+m2*v2-m1*v2)/(m1+m2)
    v1f = (m1*v1+m2*v2-m2*v2f)/m1
    return v1f, v2f

class Ball:
    def __init__(self, x, y, vx, vy, r, m):
        self.x = x
        self.y = y
        self.vx = vx
        self.vy = vy
        self.r = r
        self.m = m
    def change_attribute(self, x=None, y=None, vx=None, vy=None, r=None, m=None):
        if x!=None: self.x = x
        if y!=None: self.y = y
        if vx!=None: self.vx = vx
        if vy!=None: self.vy = vy
        if r!=None: self.r = r
        if m!=None: self.m = m

pygame.init()
screen = pygame.display.set_mode((500, 500))
x, y = random.randint(11, 489), random.randint(11, 489)
vx, vy = random.randint(-5, 5), random.randint(-5, 5)
animationTimer = pygame.time.Clock()
balls = []
num_balls = 2
for x in range(num_balls):
    balls.append(Ball(random.randint(11, 489), random.randint(11, 489), random.randint(-5, 5), random.randint(-5, 5), random.randint(30, 50), random.randint(1, 4)*5))
while True:
    for e in pygame.event.get():   
        if e.type == pygame.QUIT:
            break
    for ball in balls:
        pygame.draw.circle(screen, (200, 0, 0), (ball.x, ball.y), ball.r)
        if ball.x-ball.r<=0 or ball.x+ball.r>=500:
            ball.change_attribute(vx = -ball.vx)
        if ball.y-ball.r<=0 or ball.y+ball.r>=500:
            ball.change_attribute(vy = -ball.vy)
        for other in balls:
            if other != ball and checkcirclecollide(ball.x, ball.y, ball.r, other.x, other.y, other.r):
                new_v1x, new_v2x = ballcollision(ball.m, other.m, ball.vx, other.vx)
                new_v1y, new_v2y = ballcollision(ball.m, other.m, ball.vy, other.vy)
                ball.change_attribute(vx = new_v1x, vy = new_v1y)
                other.change_attribute(vx = new_v2x, vy = new_v2y)
        ball.change_attribute(x = ball.x+ball.vx, y = ball.y+ball.vy)
    animationTimer.tick(100)
    pygame.display.update()
    screen.fill((0, 0, 0))

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

冷清清 2025-02-16 08:37:16

我对您的脚本进行了一些修改。我所做的修改在脚本本身上进行了解释。由于动能不在我的专业领域,因此我不知道它是否真的有效,也许仍然需要一些修改。但是,至少球不再融合或粘在墙壁上。

另外,我为每个球添加了自定义颜色。它们在测试中要好得多,并查看它们彼此相撞的位置以及如何相撞。

import pygame
import random
import math

# From: http://www.jeffreythompson.org/collision-detection/circle-circle.php
def checkcirclecollide(x1, y1, r1, x2, y2, r2):
    distX = x1 - x2
    distY = y1 - y2
    distance = math.sqrt( (distX * distX) + (distY * distY) )

    return (distance <= (r1 + r2))

# Kinectic Energy.. I'm sorry, I have no idea what this does..
def ballcollision(m1, m2, v1, v2):
    v2f = (2*m1*v1 + m2*v2 - m1*v2) / (m1+m2)
    v1f = (m1*v1 + m2*v2 - m2*v2f)/m1
    return v1f, v2f

# When two balls collide, there's no guarantee they are touching
# by their extremities.
#
# As they have different speeds, they might enter into one's
# space when they collide. So before/after applying the kinetic energy,
# we must firstly separate the balls so they do not merge to
# each other.
#
# Basically this happens:
#   __  __
#  /  /\  \
#  \__\/__/
#
# What does it do? It separates each ball once they merge:
#   ___   ___
#  /   \ /   \
#  \___/ \___/
#
# This function should only be called when both balls are
# colliding to each other.
def separate_balls(ball, other):
    # Get the Opposite direction
    angle = - math.atan2(ball.y - other.y, ball.x - other.x)

    # Calculate distance between both balls
    distX = ball.x - other.x
    distY = ball.y - other.y
    distance = math.sqrt( (distX * distX) + (distY * distY) )
    diffR = ball.r + other.r - distance

    diffR *= 0.5

    # Separate each ball by half of distance
    ball.x += math.cos(angle) * diffR
    ball.y += math.sin(angle) * diffR

    other.x -= math.cos(angle) * diffR
    other.y -= math.sin(angle) * diffR

class Ball:
    def __init__(self, x, y, vx, vy, r, m, color):
        self.x = x
        self.y = y
        self.vx = vx
        self.vy = vy
        self.r = r
        self.m = m

        # This is better for testing
        self.color = color

    def change_attribute(self, x=None, y=None, vx=None, vy=None, r=None, m=None):
        if x!=None: self.x = x
        if y!=None: self.y = y
        if vx!=None: self.vx = vx
        if vy!=None: self.vy = vy
        if r!=None: self.r = r
        if m!=None: self.m = m

pygame.init()
screen = pygame.display.set_mode((500, 500))

x, y = random.randint(11, 489), random.randint(11, 489)
vx, vy = random.randint(-5, 5), random.randint(-5, 5)

animationTimer = pygame.time.Clock()
balls = []
num_balls = 2

# Now I added random colors!
for x in range(num_balls):
    balls.append(Ball(random.randint(11, 489), random.randint(11, 489), random.randint(-5, 5), random.randint(-5, 5), random.randint(30, 50), random.randint(1, 4)*5, (random.randint(0, 255), random.randint(0, 255), random.randint(0, 255))))

while True:
    for e in pygame.event.get():   
        if e.type == pygame.QUIT:
            break
    
    for ball in balls:
        pygame.draw.circle(screen, ball.color, (ball.x, ball.y), ball.r)
        
        if ball.x-ball.r-ball.vx<=0 or ball.x+ball.r+ball.vx>=500:

            # Avoid sticking at walls
            if (ball.x<=ball.r):
                ball.x-=(ball.vx-1)
            elif (ball.x>=500-ball.r):
                ball.x+=(ball.vx-1)

            ball.change_attribute(vx = -ball.vx)
        
        if ball.y-ball.r-ball.vy<=0 or ball.y+ball.r+ball.vy>=500:

            # Avoid sticking at walls
            if (ball.y<=ball.r):
                ball.y-=(ball.vy-1)
            elif (ball.y>=500-ball.r):
                ball.y+=(ball.vy-1)

            ball.change_attribute(vy = -ball.vy)
        
        for other in balls:
            if other != ball and checkcirclecollide(ball.x, ball.y, ball.r, other.x, other.y, other.r):
                new_v1x, new_v2x = ballcollision(ball.m, other.m, ball.vx, other.vx)
                new_v1y, new_v2y = ballcollision(ball.m, other.m, ball.vy, other.vy)



                # Save current position
                originalX = ball.x
                originalY = ball.y

                # Calculate a hint of next position
                lookAheadX = ball.x + ball.vx
                lookAheadY = ball.y + ball.vy

                # Push hint position
                ball.change_attribute(x = lookAheadX, y = lookAheadY)

                # Separate both balls from each other
                separate_balls(ball, other)

                # Pop hint position to original one
                ball.change_attribute(x = originalX, y = originalY)



                ball.change_attribute(vx = new_v1x, vy = new_v1y)
                other.change_attribute(vx = new_v2x, vy = new_v2y)
        
        ball.change_attribute(x = ball.x+ball.vx, y = ball.y+ball.vy)
    
    animationTimer.tick(100)
    pygame.display.update()
    screen.fill((0, 0, 0))

另外,这是我在 andy_balls()上使用的想法。我的油漆不是很好,但是我们去了:

x 在脚本上为 diffr 。我使用此值将每个球融合后将每个球彼此移开。

I did some modifications to your script. The modifications I made are explained on the script itself. As Kinetic Energy is outside of my field of expertise, I have no idea to say if it's really working, maybe it still needs a few modifications. But at least the balls do not merge or stick to the walls anymore.

Also, I added custom colors for each ball. They are much better for testing and see where and how they collide to each other.

import pygame
import random
import math

# From: http://www.jeffreythompson.org/collision-detection/circle-circle.php
def checkcirclecollide(x1, y1, r1, x2, y2, r2):
    distX = x1 - x2
    distY = y1 - y2
    distance = math.sqrt( (distX * distX) + (distY * distY) )

    return (distance <= (r1 + r2))

# Kinectic Energy.. I'm sorry, I have no idea what this does..
def ballcollision(m1, m2, v1, v2):
    v2f = (2*m1*v1 + m2*v2 - m1*v2) / (m1+m2)
    v1f = (m1*v1 + m2*v2 - m2*v2f)/m1
    return v1f, v2f

# When two balls collide, there's no guarantee they are touching
# by their extremities.
#
# As they have different speeds, they might enter into one's
# space when they collide. So before/after applying the kinetic energy,
# we must firstly separate the balls so they do not merge to
# each other.
#
# Basically this happens:
#   __  __
#  /  /\  \
#  \__\/__/
#
# What does it do? It separates each ball once they merge:
#   ___   ___
#  /   \ /   \
#  \___/ \___/
#
# This function should only be called when both balls are
# colliding to each other.
def separate_balls(ball, other):
    # Get the Opposite direction
    angle = - math.atan2(ball.y - other.y, ball.x - other.x)

    # Calculate distance between both balls
    distX = ball.x - other.x
    distY = ball.y - other.y
    distance = math.sqrt( (distX * distX) + (distY * distY) )
    diffR = ball.r + other.r - distance

    diffR *= 0.5

    # Separate each ball by half of distance
    ball.x += math.cos(angle) * diffR
    ball.y += math.sin(angle) * diffR

    other.x -= math.cos(angle) * diffR
    other.y -= math.sin(angle) * diffR

class Ball:
    def __init__(self, x, y, vx, vy, r, m, color):
        self.x = x
        self.y = y
        self.vx = vx
        self.vy = vy
        self.r = r
        self.m = m

        # This is better for testing
        self.color = color

    def change_attribute(self, x=None, y=None, vx=None, vy=None, r=None, m=None):
        if x!=None: self.x = x
        if y!=None: self.y = y
        if vx!=None: self.vx = vx
        if vy!=None: self.vy = vy
        if r!=None: self.r = r
        if m!=None: self.m = m

pygame.init()
screen = pygame.display.set_mode((500, 500))

x, y = random.randint(11, 489), random.randint(11, 489)
vx, vy = random.randint(-5, 5), random.randint(-5, 5)

animationTimer = pygame.time.Clock()
balls = []
num_balls = 2

# Now I added random colors!
for x in range(num_balls):
    balls.append(Ball(random.randint(11, 489), random.randint(11, 489), random.randint(-5, 5), random.randint(-5, 5), random.randint(30, 50), random.randint(1, 4)*5, (random.randint(0, 255), random.randint(0, 255), random.randint(0, 255))))

while True:
    for e in pygame.event.get():   
        if e.type == pygame.QUIT:
            break
    
    for ball in balls:
        pygame.draw.circle(screen, ball.color, (ball.x, ball.y), ball.r)
        
        if ball.x-ball.r-ball.vx<=0 or ball.x+ball.r+ball.vx>=500:

            # Avoid sticking at walls
            if (ball.x<=ball.r):
                ball.x-=(ball.vx-1)
            elif (ball.x>=500-ball.r):
                ball.x+=(ball.vx-1)

            ball.change_attribute(vx = -ball.vx)
        
        if ball.y-ball.r-ball.vy<=0 or ball.y+ball.r+ball.vy>=500:

            # Avoid sticking at walls
            if (ball.y<=ball.r):
                ball.y-=(ball.vy-1)
            elif (ball.y>=500-ball.r):
                ball.y+=(ball.vy-1)

            ball.change_attribute(vy = -ball.vy)
        
        for other in balls:
            if other != ball and checkcirclecollide(ball.x, ball.y, ball.r, other.x, other.y, other.r):
                new_v1x, new_v2x = ballcollision(ball.m, other.m, ball.vx, other.vx)
                new_v1y, new_v2y = ballcollision(ball.m, other.m, ball.vy, other.vy)



                # Save current position
                originalX = ball.x
                originalY = ball.y

                # Calculate a hint of next position
                lookAheadX = ball.x + ball.vx
                lookAheadY = ball.y + ball.vy

                # Push hint position
                ball.change_attribute(x = lookAheadX, y = lookAheadY)

                # Separate both balls from each other
                separate_balls(ball, other)

                # Pop hint position to original one
                ball.change_attribute(x = originalX, y = originalY)



                ball.change_attribute(vx = new_v1x, vy = new_v1y)
                other.change_attribute(vx = new_v2x, vy = new_v2y)
        
        ball.change_attribute(x = ball.x+ball.vx, y = ball.y+ball.vy)
    
    animationTimer.tick(100)
    pygame.display.update()
    screen.fill((0, 0, 0))

Also, this is the idea I used on separate_balls(). I'm not very good with Paint, but here we go:
enter image description here

x on the picture is diffR on the script. I use this value to move each ball away from each other once they merge.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文