文章简单地使用物理定律, 编写程序模拟真实世界中的碰撞。
在开始正式讲解之前, 先看这两个代码:
# 把球掉头
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)
global ball
for ball in group:
ball.move()
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
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()