2020年9月12日星期六

86 《零基礎入門學習Python》筆記 第086講:Pygame:碰撞檢測

《零基礎入門學習Python》第086講:Pygame:碰撞檢測



上節課我們介紹了動畫精靈,這節課我們把重點放在碰撞檢測上,大部分遊戲都是需要做碰撞檢測的,因為你需要知道小球是否發生了碰撞,子彈是否擊中了目標,主角是否踩到了狗屎。
那應該如何實現呢?
說白了,它這個原理很簡單,就是檢測兩個精靈之間是否存在重疊的部分,像我們上節課的小球,在圖1的情況下,它們就沒有產生重疊,也就是沒有發生碰撞。

圖1

當碰撞發生的那一剎那,width = r1 + r2,如圖2所示。

圖2

當它們產生重疊,產生交集的時候,width < r1 + r2,如圖3所示:

圖3

所以我們判斷兩個小球是否發生碰撞,我們只要檢測兩個小球的圓心距是否小於等於它們兩個半徑之和。
我們先來嘗試自己寫碰撞檢測的函數:
(我當然知道sprite 模塊裡面有提供現成的,但是我提議大家學習任何東西之前我們要搞懂它的原理,懂得了它的原理,以後你學習別的語言、別的知識就會事半功倍了)
函數名叫做collide_check()。
  1. import pygame
  2. import sys
  3. import math
  4. from pygame.locals import *
  5. from random import *
  6. # 球类继承自Spirte类
  7. class Ball(pygame.sprite.Sprite):
  8. def __init__(self, image, position, speed, bg_size):
  9. # 初始化动画精灵
  10. pygame.sprite.Sprite.__init__(self)
  11. self.image = pygame.image.load(image).convert_alpha()
  12. self.rect = self.image.get_rect()
  13. # 将小球放在指定位置
  14. self.rect.left, self.rect.top = position
  15. self.speed = speed
  16. self.width, self.height = bg_size[0], bg_size[1]
  17. def move(self):
  18. self.rect = self.rect.move(self.speed)
  19. # 如果小球的左侧出了边界,那么将小球左侧的位置改为右侧的边界
  20. # 这样便实现了从左边进入,右边出来的效果
  21. if self.rect.right < 0:
  22. self.rect.left = self.width
  23. elif self.rect.left > self.width:
  24. self.rect.right = 0
  25. elif self.rect.bottom < 0:
  26. self.rect.top = self.height
  27. elif self.rect.top > self.height:
  28. self.rect.bottom = 0
  29. def collide_check(item, target): #碰撞检测
  30. col_balls = []
  31. for each in target:
  32. distance = math.sqrt(\
  33. math.pow((item.rect.center[0] - each.rect.center[0]), 2) + \
  34. math.pow((item.rect.center[1] - each.rect.center[1]), 2))
  35. if distance <= (item.rect.width + each.rect.width) / 2:
  36. col_balls.append(each)
  37. return col_balls #返回碰撞的小球
  38. def main():
  39. pygame.init()
  40. ball_image = "gray_ball.png"
  41. bg_image = "background.png"
  42. running = True
  43. # 根据背景图片指定游戏界面尺寸
  44. bg_size = width, height = 1024, 681
  45. screen = pygame.display.set_mode(bg_size)
  46. pygame.display.set_caption("Play the ball - Python Demo")
  47. background = pygame.image.load(bg_image).convert_alpha()
  48. # 用来存放小球对象的列表
  49. balls = []
  50. # 创建五个小球
  51. BALL_NUM = 5
  52. for i in range(BALL_NUM):
  53. # 位置随机,速度随机
  54. position = randint(0, width-100), randint(0, height-100)
  55. speed = [randint(-10, 10), randint(-10, 10)]
  56. ball = Ball(ball_image, position, speed, bg_size)
  57. while collide_check(ball, balls):#在创建小球的时候也要进行一次碰撞检测,防止一生成就与别的小球重叠了
  58. ball.rect.left, ball.rect.top = randint(0, width-100), randint(0, height-100)
  59. balls.append(ball)
  60. clock = pygame.time.Clock()
  61. while running:
  62. for event in pygame.event.get():
  63. if event.type == QUIT:
  64. sys.exit()
  65. screen.blit(background, (0, 0))
  66. for each in balls:
  67. each.move()
  68. screen.blit(each.image, each.rect)
  69. for i in range(BALL_NUM):
  70. item = balls.pop(i) #把自身拿出来
  71. if collide_check(item, balls): #把自己和别的球进行碰撞检测
  72. item.speed[0] = -item.speed[0]
  73. item.speed[1] = -item.speed[1]
  74. balls.insert(i, item) #还要把自己放进去
  75. pygame.display.flip()
  76. clock.tick(30)
  77. if __name__ == "__main__":
  78. main()
這就已經很完美了。

上面是我們自己寫的碰撞檢測。接下來我們來談一下現成的。
為什麼我們要談一下sprite 提供的現成的碰撞檢測函數呢?不知道大家有沒有註意到我們寫的collide_check() 函數只是適用於圓與圓之間的碰撞檢測,其它的多邊形(如矩形、三角形)以及一些不規則的多邊形,那怎麼辦,我們就不會得到相應的結果了,我們可以試圖去為每一種情況寫一個碰撞檢測函數,也不是不可以。
但其實Pygame 的sprite 模塊事實上已經提供了一個成熟的碰撞檢測的函數供我們使用,這也是我們要將我們的類繼承sprite 模塊的Sprite 基類的原因。
sprite 模塊提供了spritecollide() 方法用於檢測某個精靈是否與指定的組中的其它精靈發生碰撞。(和我們寫的collide_check() 原理基本上類似)
spritecollide(sprite, group, dokill, collided = None)
第一個參數sprite 是指定被檢測的精靈;(就是我們寫的里面的item)
第二個參數group 是指定一個組(就是我們寫的里面的target 列表),它是sprite 的組,因此要使用sprite.Group() 來生成;
第三個參數dokill 是設置是否從組中刪除檢測到碰撞的精靈,設置為True,則刪除;
第四個參數collided 是指定一個回調函數,用於定制特殊的檢測方法,如果第四個參數忽略的話,默認是檢測精靈之間的rect 屬性。
(我們這裡不能使用默認的檢測方法,看下圖,圖中的情況會被檢測為碰撞,但其實兩小球還沒碰撞)
collide_circle(left, right) 方法適用於檢測兩個圓之間是否發生碰撞,left 和right 參數分別是兩個精靈,所以我們直接使用
collided = pygame.sprite.collide_circle
另外,collide_circle()方法需要精靈有一個半徑的屬性,所以我們給Ball 類增加一個radius 屬性。
代碼如下:
  1. import pygame
  2. import sys
  3. from pygame.locals import *
  4. from random import *
  5. # 球类继承自Spirte类
  6. class Ball(pygame.sprite.Sprite):
  7. def __init__(self, image, position, speed, bg_size):
  8. # 初始化动画精灵
  9. pygame.sprite.Sprite.__init__(self)
  10. self.image = pygame.image.load(image).convert_alpha()
  11. self.rect = self.image.get_rect()
  12. # 将小球放在指定位置
  13. self.rect.left, self.rect.top = position
  14. self.speed = speed
  15. self.width, self.height = bg_size[0], bg_size[1]
  16. self.radius = self.rect.width / 2 #增加半径属性
  17. def move(self):
  18. self.rect = self.rect.move(self.speed)
  19. # 如果小球的左侧出了边界,那么将小球左侧的位置改为右侧的边界
  20. # 这样便实现了从左边进入,右边出来的效果
  21. if self.rect.right < 0:
  22. self.rect.left = self.width
  23. elif self.rect.left > self.width:
  24. self.rect.right = 0
  25. elif self.rect.bottom < 0:
  26. self.rect.top = self.height
  27. elif self.rect.top > self.height:
  28. self.rect.bottom = 0
  29. def main():
  30. pygame.init()
  31. ball_image = "gray_ball.png"
  32. bg_image = "background.png"
  33. running = True
  34. # 根据背景图片指定游戏界面尺寸
  35. bg_size = width, height = 1024, 681
  36. screen = pygame.display.set_mode(bg_size)
  37. pygame.display.set_caption("Play the ball - Python Demo")
  38. background = pygame.image.load(bg_image).convert_alpha()
  39. # 用来存放小球对象的列表
  40. balls = []
  41. group = pygame.sprite.Group()
  42. # 创建五个小球
  43. for i in range(5):
  44. # 位置随机,速度随机
  45. position = randint(0, width-100), randint(0, height-100)
  46. speed = [randint(-10, 10), randint(-10, 10)]
  47. ball = Ball(ball_image, position, speed, bg_size)
  48. while pygame.sprite.spritecollide(ball, group, False, pygame.sprite.collide_circle):#在创建小球这里必须进行一下碰撞检测
  49. ball.rect.left, ball.rect.top = randint(0, width-100), randint(0, height-100)
  50. balls.append(ball)
  51. group.add(ball)
  52. clock = pygame.time.Clock()
  53. while running:
  54. for event in pygame.event.get():
  55. if event.type == QUIT:
  56. sys.exit()
  57. screen.blit(background, (0, 0))
  58. for each in balls:
  59. each.move()
  60. screen.blit(each.image, each.rect)
  61. for each in group:
  62. group.remove(each) #把自身拿出来
  63. if pygame.sprite.spritecollide(each, group, False, pygame.sprite.collide_circle):#把自己和别的球进行碰撞检测
  64. each.speed[0] = -each.speed[0]
  65. each.speed[1] = -each.speed[1]
  66. group.add(each)#还要把自己放进去
  67. pygame.display.flip()
  68. clock.tick(30)
  69. if __name__ == "__main__":
  70. main()

0 留言:

發佈留言