@yanglt7
2018-12-12T14:06:50.000000Z
字数 5376
阅读 1654
Pygame
Pygame 的 sprite 模块提供了一个动画精灵的基类,游戏中的小球就是通过继承它而创建出来的精灵。
例 9-1
import pygameimport sysfrom pygame.locals import *from random import *class Ball(pygame.sprite.Sprite): # 球类继承自 Sprite 类def __init__(self, image, position, speed):pygame.sprite.Sprite.__init__(self) # 初始化动画精灵self.image = pygame.image.load(image).convert_alpha()self.rect = self.image.get_rect()self.rect.left, self.rect_top = position # 将小球放在指定位置self.speed = speeddef main():pygame.init()ball_image = "grey_ball.png"bg_image = "bg.png"running = Truebg_size = width, height = 640, 480 # 根据背景图片指定游戏界面尺寸screen = pygame.display.set_mode(bg_size)pygame.display.set_caption("Play the ball - FishC Demo")background = pygame.image.load(bg_image).convert_alpha()balls = [] # 用来存放小球对象的列表# 创建 5 个小球BALL_NUM = 5for i in range(BALL_NUM):# 位置随机,速度随机position = randint(0, width - 100), randint(0, height - 100)speed = [randint(-10, 10), randint(-10, 10)]ball = Ball(ball_image, position, speed)balls.append(ball)clock = pygame.time.Clock()while running:for event in pygame.event.get():if event.type == QUIT:sys.exit()screen.blit(background, (0, 0))for each in balls:screen.blit(each.image, each.rect)pygame.display.flip()clock.tick(30)if __name__ == "__main__":main()

在 Ball 类中添加 move() 方法,然后在绘制每个小球前先调用一次 move() 移动到新的位置。
例 9-2
...def move(self):self.rect = self.rect.move(self.speed)...for each in balls:each.move()screen.blit(each.image, each.rect)...for each in balls:each.move()screen.blit(each.image, each.rect)...
如果小球从页面的上方穿过,会从下方出现,同样,如果小球从左边进入会从右边出来。
...class Ball(pygame.sprite.Sprite):# 增加一个背景尺寸的参数def __init__(self, image, position, speed, bg_size):...self.width, self.height = bg_size[0], bg_size[1]def move(self):self.rect = self.rect.move(self.speed)# 如果小球的左侧出了边界,那么将小球左侧的位置改为右侧的边界# 这样便实现了从左边进入,右边出来的效果if self.rect.right < 0:self.rect.left = self.widthelif self.rect.left > self.width:self.rect.right = 0elif self.rect.bottom < 0:self.rect.top = self.heightelif self.rect.top > self.height:self.rect.bottom = 0...

例 10-1 检测各小球之间是否发生碰撞,一旦发生便修改小球的移动方向。
...def collide_check(item, target):col_balls = []for each in target:distance = math.sqrt(\math.pow(item.rect.center[0] - each.rect.center[0], 2) + \math.pow(item.rect.center[1] - each.rect.center[1], 2))if distance <= (item.rect.width + each.rect.width) / 2:col_balls.append(each)return col_balls...# 先让所有小球移动一步for each in balls:each.move()screen.blit(each.image, each.rect)# 检测各个小球之间是否发生碰撞for i in range(BALL_NUM):# 先将要检测的小球拿出来item = balls.pop(i)# 与列表中的其他小球一一对比if collide_check(item, balls):item.speed[0] = -item.speed[0]item.speed[1] = -item.speed[1]# 将小球放回到列表中balls.insert(i, item)...

虽然实现了碰撞检测,但有时会出现小球卡住的现象。
原因是:当小球在诞生的位置恰好有其他小球,因此检测到两个小球发生碰撞,速度取反,但如果两个小球相互覆盖的范围大于移动一次的距离,那就会出现卡住的现象(反向移动后取反,又变成相向移动,速度不变的情况下是死循环)。
解决方案:在小球诞生的时候立刻检查该位置是否有其他小球,有点话修改新生小球的位置。
例 10-2
...# 创建 5 个小球BALL_NUM = 5for i in range(BALL_NUM):# 位置随机,速度随机position = randint(0, width - 100), randint(0, height - 100)speed = [randint(-10, 10), randint(-10, 10)]ball = Ball(ball_image, position, speed, bg_size)# 测试诞生小球的位置是否存在其他小球while collide_check(ball, balls):ball.rect.left, ball.rect_top = randint(0, width - 100), randint(0, height - 100)balls.append(ball)...

不过这个 collide_check 只适用于圆与圆间的碰撞检测,如果是其他多边形或不规则图形,那么就得不到相应的效果了。
Pygame 的 sprite 模块已经提供了碰撞检测的函数。
sprite 模块提供了一个 spritecollide() 函数,用于检测某个精灵是否与指定组中的其他精灵碰撞。
spritecollide(sprite, group, dokill, collided = None)
例 11-1
...# 用来存放小球对象的列表balls = []group = pygame.sprite.Group()# 创建 5 个小球BALL_NUM = 5for i in range(BALL_NUM):# 位置随机,速度随机position = randint(0, width - 100), randint(0, height - 100)speed = [randint(-10, 10), randint(-10, 10)]ball = Ball(ball_image, position, speed, bg_size)# 检测新诞生的球是否会卡住其他球while pygame.sprite.spritecollide(ball, group, False):ball.rect.left, ball.rect.top = randint(0, width-100), randint(0, height-100)balls.append(ball)group.add(ball)...for each in balls:each.move()screen.blit(each.image, each.rect)for each in group:# 先从组中移除当前球group.remove(each)# 判断当前球与其他球是否相撞if pygame.sprite.spritecollide(each, group, False):each.speed[0] = -each.speed[0]each.speed[1] = -each.speed[1]# 将当前球添加回组中group.add(each)...

有时候小球还没碰撞就弹开了。因为上面的代码没有设置 spritecollide() 函数的第四个参数,默认这个参数是 None,表示检测精灵的 rect 属性是否重叠。
由于小球的背景是透明的,所以看上去好像没有发生碰撞就弹开了(其实对应的 rect 已经重叠了)。因此,需要实现圆形的碰撞检测,需要指定 spritecollide() 函数的最后一个参数。
例 11-2
spritecollide() 函数的最后一个参数是指定一个回调函数,用于定制特殊的检测方法。而 sprite 模块中正好有一个 collide_circle() 函数用于检测两个圆之间是否发生碰撞。
注意:这个函数需要精灵对象中必须有一个 radius(半径)属性才行。
class Ball(pygame.sprite.Sprite):def __init__(self, image, position, speed, bg_size):...self.radius = self.rect.width / 2...# 创建 5 个小球BALL_NUM = 5for i in range(BALL_NUM):# 位置随机,速度随机position = randint(0, width - 100), randint(0, height - 100)speed = [randint(-10, 10), randint(-10, 10)]ball = Ball(ball_image, position, speed, bg_size)# 检测新诞生的球是否会卡住其他球while pygame.sprite.spritecollide(ball, group, False, pygame.sprite.collide_circle):ball.rect.left, ball.rect.top = randint(0, width-100), randint(0, height-100)balls.append(ball)group.add(ball)...for each in group:# 先从组中移除当前球group.remove(each)# 判断当前球与其他球是否相撞if pygame.sprite.spritecollide(each, group, False):each.speed[0] = -each.speed[0]each.speed[1] = -each.speed[1]# 将当前球添加回组中group.add(each)...

摘自《零基础入门学习Python》