Python 设计真实反弹球算法及原理分析 (使用物理定律)

文章结合动量守恒定律和能量守恒定律, 并使用向量进行计算, 模拟真实世界中的碰撞。

简单的反弹

在开始正式讲解之前, 先看这两行简单的反弹代码:

# 把球掉头
ball.speed[0] = -ball.speed[0]
ball.speed[1] = -ball.speed[1]

可以看到, 这个代码直接把球的速度取反了一下, 比较粗糙。
这是提升的版本 (真实世界中两个球质量相同时,的确是这个样子):

# 这里用 speed表示速度
self.speed,other.speed = other.speed,self.speed # 把两个球进行速度交换

运行效果:
球

应用物理定律的算法

前面的算法, 都还不能很好地模拟真实世界中的碰撞。
由于我们模拟的是弹性碰撞, 碰撞前后两球的总动量守恒,总动能也守恒。
根据动量p=mv, 动能E=mv2 / 2,
可以推导出以下公式:(v1, v2是两小球的速度, m1,m2是质量, v1’ , v2’ 是碰撞后的速度)


根据这个公式, 就可以设计相应的程序。
使用pygame设计的完整代码如下:

import sys, pygame,math,time
from random import *

__version__='1.1'
class Ball(pygame.sprite.Sprite):
    def __init__(self, image, location, speed,mass=1):
        pygame.sprite.Sprite.__init__(self)
        self.image = image
        self.rect = self.image.get_rect()
        self.rect.left, self.rect.top = location
        self.pos = location
        self.speed = speed
        self.m = mass

    def move(self):
        self.pos = (self.pos[0]+self.speed[0],self.pos[1]+self.speed[1])
        self.rect.left,self.rect.top = self.pos

        # 检查有无撞到窗口
        if self.rect.left <= 0:
            self.speed[0] = abs(self.speed[0])
        elif self.rect.right >= width:
            self.speed[0] = -abs(self.speed[0])
        if self.rect.top <= 0:
            self.speed[1] = abs(self.speed[1])
        elif self.rect.bottom >= height:
            self.speed[1] = -abs(self.speed[1])
    def distance(self,other):
        dx=other.rect.left-self.rect.left
        dy=other.rect.top-self.rect.top
        return math.hypot(dx,dy)
    def collide(self,other):
        # 反弹球算法部分
        m1=self.m;m2=other.m
        dx1 = (m1-m2)/(m1+m2)*self.speed[0] + 2*m2/(m2+m1)*other.speed[0]
        dy1 = (m1-m2)/(m1+m2)*self.speed[1] + 2*m2/(m2+m1)*other.speed[1]
        dx2 = (m2-m1)/(m1+m2)*other.speed[0] + 2*m1/(m2+m1)*self.speed[0]
        dy2 = (m2-m1)/(m1+m2)*other.speed[1] + 2*m1/(m2+m1)*self.speed[1]
        self.speed=[dx1, dy1]
        other.speed=[dx2, dy2]

animate()函数更新一次小球在屏幕上的位置,
state用于记录球的碰撞信息, 避免球重复碰撞。

state = {}
def animate(group):
    rect=pygame.rect.Rect((0,0),(width,height))
    screen.fill((255,255,255),rect)
    for i in range(len(group)):
        ball=group[i]
        for j in range(i):
            other=group[j]
            collided = ball.distance(other) < ball.rect.width
            # 避免球下次重复碰撞。碰撞之后,两个球可能暂时仍然是重叠的
            if i!=j:
                if collided and not state.get((i,j),0):
                    ball.collide(other)
                    state[(i,j)] = 1
                elif not collided:
                    state[(i,j)] = 0
    # 更新球位置并绘制球
    for ball in group:
        ball.move()
        screen.blit(ball.image, ball.rect)
    pygame.display.flip()

使用pygame的主程序的实现:

size = width,height = 640, 480
screen = pygame.display.set_mode(size,pygame.RESIZABLE)
screen.fill((255, 255, 255))
image = pygame.image.load("beach_ball.png")
clock = pygame.time.Clock()
group = []
for row in range(3):
    for column in range(3):
        location = [column * 180 + 50, row * 120 + 50]
        speed = [randrange(-4,5), randrange(-4,5)]
        ball = Ball(image,location, speed)
        group.append(ball)

主事件循环, 每循环一次就调用一次(也可以是多次) animate()函数, 刷新一次小球位置:

running = True
while running:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            frame_rate = clock.get_fps()
            print("frame rate = ", frame_rate)
            running = False
        elif event.type == pygame.VIDEORESIZE:
            width,height = event.size
            screen = pygame.display.set_mode(event.size, pygame.RESIZABLE)
    animate(group)
    clock.tick(60)
pygame.quit()

使用向量的算法,实现了斜碰

下面的代码使用了向量,用于球体碰撞后速度的计算。
首先定义向量类:

class Vec2D(list):
    """A 2 dimensional vector class, used as a helper class
    for implementing turtle graphics.
    May be useful for turtle graphics programs also.
    Derived from list, so a vector is a list!
    """
    def __init__(self,*args):
        if len(args)==1:
            list.__init__(self,args[0])
        else:
            list.__init__(self,args)
    def __add__(self, other):
        return Vec2D(self[0] + other[0], self[1] + other[1])
    def __mul__(self, other):
        if isinstance(other, Vec2D):
            return self[0] * other[0] + self[1] * other[1]
        return Vec2D(self[0] * other, self[1] * other)

    def __rmul__(self, other):
        if isinstance(other, (int, float)):
            return Vec2D(self[0] * other, self[1] * other)

    def __sub__(self, other):
        return Vec2D(self[0] - other[0], self[1] - other[1])

    def __neg__(self):
        return Vec2D(-self[0], -self[1])

    def __abs__(self):
        return (self[0]**2 + self[1]**2)**0.5

    def rotate(self, angle):
        """rotate self counterclockwise by angle"""
        perp = Vec2D(-self[1], self[0])
        angle = angle * math.pi / 180.0
        c, s = math.cos(angle), math.sin(angle)
        return Vec2D(self[0] * c + perp[0] * s, self[1] * c + perp[1] * s)

新的碰撞检测算法,使用了向量之间的运算:

class Ball(pygame.sprite.Sprite):
    def __init__(self, image, location, speed,mass=1):
        pygame.sprite.Sprite.__init__(self)
        self.image = image
        self.rect = self.image.get_rect()
        self.rect.left, self.rect.top = location
        self.pos = location
        self.speed = speed
        self.m = mass

    def move(self):
        self.pos = (self.pos[0]+self.speed[0],self.pos[1]+self.speed[1])
        self.rect.left,self.rect.top = self.pos

        # 检查有无撞到窗口
        if self.rect.left <= 0:
            self.speed[0] = abs(self.speed[0])
        elif self.rect.right >= width:
            self.speed[0] = -abs(self.speed[0])
        if self.rect.top <= 0:
            self.speed[1] = abs(self.speed[1])
        elif self.rect.bottom >= height:
            self.speed[1] = -abs(self.speed[1])
        #else:
            #self.speed[1] += 0.2 # 重力
    def distance(self,other):
        dx=other.rect.left-self.rect.left
        dy=other.rect.top-self.rect.top
        return math.hypot(dx,dy)
    def collide(self,other):
        m1=self.m;m2=other.m
        x1, y1 = self.pos
        x2, y2 = other.pos
        v1, v2 = self.speed, other.speed

        # s向量是球心连线上的
        s = Vec2D(x2 - x1, y2 - y1)
        s_length = abs(s)
        s = Vec2D(s[0]/s_length, s[1]/s_length)  # 单位化s向量

        # t向量是s的垂直线上的
        t = s.rotate(90)

        # 计算v1(v1x, v1y)在s和t轴的投影值
        v1s = v1 * s  # v1在s轴的分量
        v1t = v1 * t  # v1在t轴的分量
        v2s = v2 * s  # v2在s轴的分量
        v2t = v2 * t  # v2在t轴的分量

        # 交换速度分量
        v1s, v2s = v2s, v1s

        # 将分量转换回向量
        v1s_vector = s * v1s
        v1t_vector = t * v1t
        v2s_vector = s * v2s
        v2t_vector = t * v2t

        # 计算新的速度
        new_v1 = v1s_vector + v1t_vector
        new_v2 = v2s_vector + v2t_vector

        self.speed=new_v1
        other.speed=new_v2

state = {}
def animate(group):
    rect=pygame.rect.Rect((0,0),(width,height))
    screen.fill((255,255,255),rect)
    for i in range(len(group)):
        ball=group[i]
        for j in range(i):
            other=group[j]
            collided = ball.distance(other) < ball.rect.width
            # 避免球下次重复碰撞。碰撞之后,两个球可能暂时仍然是重叠的
            if i!=j:
                if collided and not state.get((i,j),0):
                    ball.collide(other)
                    state[(i,j)] = 1
                elif not collided:
                    state[(i,j)] = 0
    # 更新球位置并绘制球
    for ball in group:
        ball.move()
        screen.blit(ball.image, ball.rect)
    pygame.display.flip()

主程序:

size = width,height = 640, 480
screen = pygame.display.set_mode(size,pygame.RESIZABLE)
screen.fill((255, 255, 255))
image = pygame.image.load("beach_ball.png")
clock = pygame.time.Clock()
group = []
for row in range(3):
    for column in range(3):
        location = (column * 180 + 50, row * 120 + 50)
        speed = (randrange(-4,5), randrange(-4,5))
        ball = Ball(image, Vec2D(location), Vec2D(speed))
        group.append(ball)

running = True
while running:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            frame_rate = clock.get_fps()
            print("frame rate = ", frame_rate)
            running = False
        elif event.type == pygame.VIDEORESIZE:
            width,height = event.size
            screen = pygame.display.set_mode(event.size, pygame.RESIZABLE)
    animate(group)
    clock.tick(60)
pygame.quit()

程序运行效果:
球

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

qfcy_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值