[关闭]
@yanglt7 2018-12-12T14:06:50.000000Z 字数 5376 阅读 1584

Pygame05_动画精灵(碰撞检测)

Pygame


游戏背景

5.1 创建精灵

Pygame 的 sprite 模块提供了一个动画精灵的基类,游戏中的小球就是通过继承它而创建出来的精灵。

例 9-1

  1. import pygame
  2. import sys
  3. from pygame.locals import *
  4. from random import *
  5. class Ball(pygame.sprite.Sprite): # 球类继承自 Sprite 类
  6. def __init__(self, image, position, speed):
  7. pygame.sprite.Sprite.__init__(self) # 初始化动画精灵
  8. self.image = pygame.image.load(image).convert_alpha()
  9. self.rect = self.image.get_rect()
  10. self.rect.left, self.rect_top = position # 将小球放在指定位置
  11. self.speed = speed
  12. def main():
  13. pygame.init()
  14. ball_image = "grey_ball.png"
  15. bg_image = "bg.png"
  16. running = True
  17. bg_size = width, height = 640, 480 # 根据背景图片指定游戏界面尺寸
  18. screen = pygame.display.set_mode(bg_size)
  19. pygame.display.set_caption("Play the ball - FishC Demo")
  20. background = pygame.image.load(bg_image).convert_alpha()
  21. balls = [] # 用来存放小球对象的列表
  22. # 创建 5 个小球
  23. BALL_NUM = 5
  24. for i in range(BALL_NUM):
  25. # 位置随机,速度随机
  26. position = randint(0, width - 100), randint(0, height - 100)
  27. speed = [randint(-10, 10), randint(-10, 10)]
  28. ball = Ball(ball_image, position, speed)
  29. balls.append(ball)
  30. clock = pygame.time.Clock()
  31. while running:
  32. for event in pygame.event.get():
  33. if event.type == QUIT:
  34. sys.exit()
  35. screen.blit(background, (0, 0))
  36. for each in balls:
  37. screen.blit(each.image, each.rect)
  38. pygame.display.flip()
  39. clock.tick(30)
  40. if __name__ == "__main__":
  41. main()

此处输入图片的描述

5.2 移动精灵

在 Ball 类中添加 move() 方法,然后在绘制每个小球前先调用一次 move() 移动到新的位置。

例 9-2

  1. ...
  2. def move(self):
  3. self.rect = self.rect.move(self.speed)
  4. ...
  5. for each in balls:
  6. each.move()
  7. screen.blit(each.image, each.rect)
  8. ...
  9. for each in balls:
  10. each.move()
  11. screen.blit(each.image, each.rect)
  12. ...

如果小球从页面的上方穿过,会从下方出现,同样,如果小球从左边进入会从右边出来。

  1. ...
  2. class Ball(pygame.sprite.Sprite):
  3. # 增加一个背景尺寸的参数
  4. def __init__(self, image, position, speed, bg_size):
  5. ...
  6. self.width, self.height = bg_size[0], bg_size[1]
  7. def move(self):
  8. self.rect = self.rect.move(self.speed)
  9. # 如果小球的左侧出了边界,那么将小球左侧的位置改为右侧的边界
  10. # 这样便实现了从左边进入,右边出来的效果
  11. if self.rect.right < 0:
  12. self.rect.left = self.width
  13. elif self.rect.left > self.width:
  14. self.rect.right = 0
  15. elif self.rect.bottom < 0:
  16. self.rect.top = self.height
  17. elif self.rect.top > self.height:
  18. self.rect.bottom = 0
  19. ...

此处输入图片的描述

5.3 碰撞检测

collide_check 函数

例 10-1 检测各小球之间是否发生碰撞,一旦发生便修改小球的移动方向。

  1. ...
  2. def collide_check(item, target):
  3. col_balls = []
  4. for each in target:
  5. distance = math.sqrt(\
  6. math.pow(item.rect.center[0] - each.rect.center[0], 2) + \
  7. math.pow(item.rect.center[1] - each.rect.center[1], 2))
  8. if distance <= (item.rect.width + each.rect.width) / 2:
  9. col_balls.append(each)
  10. return col_balls
  11. ...
  12. # 先让所有小球移动一步
  13. for each in balls:
  14. each.move()
  15. screen.blit(each.image, each.rect)
  16. # 检测各个小球之间是否发生碰撞
  17. for i in range(BALL_NUM):
  18. # 先将要检测的小球拿出来
  19. item = balls.pop(i)
  20. # 与列表中的其他小球一一对比
  21. if collide_check(item, balls):
  22. item.speed[0] = -item.speed[0]
  23. item.speed[1] = -item.speed[1]
  24. # 将小球放回到列表中
  25. balls.insert(i, item)
  26. ...

此处输入图片的描述

虽然实现了碰撞检测,但有时会出现小球卡住的现象。

原因是:当小球在诞生的位置恰好有其他小球,因此检测到两个小球发生碰撞,速度取反,但如果两个小球相互覆盖的范围大于移动一次的距离,那就会出现卡住的现象(反向移动后取反,又变成相向移动,速度不变的情况下是死循环)。

解决方案:在小球诞生的时候立刻检查该位置是否有其他小球,有点话修改新生小球的位置。

例 10-2

  1. ...
  2. # 创建 5 个小球
  3. BALL_NUM = 5
  4. for i in range(BALL_NUM):
  5. # 位置随机,速度随机
  6. position = randint(0, width - 100), randint(0, height - 100)
  7. speed = [randint(-10, 10), randint(-10, 10)]
  8. ball = Ball(ball_image, position, speed, bg_size)
  9. # 测试诞生小球的位置是否存在其他小球
  10. while collide_check(ball, balls):
  11. ball.rect.left, ball.rect_top = randint(0, width - 100), randint(0, height - 100)
  12. balls.append(ball)
  13. ...

此处输入图片的描述

不过这个 collide_check 只适用于圆与圆间的碰撞检测,如果是其他多边形或不规则图形,那么就得不到相应的效果了。

Pygame 的 sprite 模块已经提供了碰撞检测的函数。

sprite 模块提供的碰撞检测函数

sprite 模块提供了一个 spritecollide() 函数,用于检测某个精灵是否与指定组中的其他精灵碰撞。

  1. spritecollide(sprite, group, dokill, collided = None)

例 11-1

  1. ...
  2. # 用来存放小球对象的列表
  3. balls = []
  4. group = pygame.sprite.Group()
  5. # 创建 5 个小球
  6. BALL_NUM = 5
  7. for i in range(BALL_NUM):
  8. # 位置随机,速度随机
  9. position = randint(0, width - 100), randint(0, height - 100)
  10. speed = [randint(-10, 10), randint(-10, 10)]
  11. ball = Ball(ball_image, position, speed, bg_size)
  12. # 检测新诞生的球是否会卡住其他球
  13. while pygame.sprite.spritecollide(ball, group, False):
  14. ball.rect.left, ball.rect.top = randint(0, width-100), randint(0, height-100)
  15. balls.append(ball)
  16. group.add(ball)
  17. ...
  18. for each in balls:
  19. each.move()
  20. screen.blit(each.image, each.rect)
  21. for each in group:
  22. # 先从组中移除当前球
  23. group.remove(each)
  24. # 判断当前球与其他球是否相撞
  25. if pygame.sprite.spritecollide(each, group, False):
  26. each.speed[0] = -each.speed[0]
  27. each.speed[1] = -each.speed[1]
  28. # 将当前球添加回组中
  29. group.add(each)
  30. ...

此处输入图片的描述

有时候小球还没碰撞就弹开了。因为上面的代码没有设置 spritecollide() 函数的第四个参数,默认这个参数是 None,表示检测精灵的 rect 属性是否重叠。

由于小球的背景是透明的,所以看上去好像没有发生碰撞就弹开了(其实对应的 rect 已经重叠了)。因此,需要实现圆形的碰撞检测,需要指定 spritecollide() 函数的最后一个参数。

例 11-2

spritecollide() 函数的最后一个参数是指定一个回调函数,用于定制特殊的检测方法。而 sprite 模块中正好有一个 collide_circle() 函数用于检测两个圆之间是否发生碰撞。

注意:这个函数需要精灵对象中必须有一个 radius(半径)属性才行。

  1. class Ball(pygame.sprite.Sprite):
  2. def __init__(self, image, position, speed, bg_size):
  3. ...
  4. self.radius = self.rect.width / 2
  5. ...
  6. # 创建 5 个小球
  7. BALL_NUM = 5
  8. for i in range(BALL_NUM):
  9. # 位置随机,速度随机
  10. position = randint(0, width - 100), randint(0, height - 100)
  11. speed = [randint(-10, 10), randint(-10, 10)]
  12. ball = Ball(ball_image, position, speed, bg_size)
  13. # 检测新诞生的球是否会卡住其他球
  14. while pygame.sprite.spritecollide(ball, group, False, pygame.sprite.collide_circle):
  15. ball.rect.left, ball.rect.top = randint(0, width-100), randint(0, height-100)
  16. balls.append(ball)
  17. group.add(ball)
  18. ...
  19. for each in group:
  20. # 先从组中移除当前球
  21. group.remove(each)
  22. # 判断当前球与其他球是否相撞
  23. if pygame.sprite.spritecollide(each, group, False):
  24. each.speed[0] = -each.speed[0]
  25. each.speed[1] = -each.speed[1]
  26. # 将当前球添加回组中
  27. group.add(each)
  28. ...

此处输入图片的描述

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

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注