2020年9月12日星期六

89A 《零基礎入門學習Python》筆記 第089講:Pygame:遊戲勝利 上

《零基礎入門學習Python》第089講:Pygame:遊戲勝利


通過摩擦摩擦,可以使得小球變綠色,並不再隨機移動:
玩家此時可以通過鍵盤上的WSAD 按鍵來上下左右的移動小球,我們現在就來寫響應相關按鍵事件的代碼:
  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, grayball_image, greenball_image, position, speed, bg_size, target):
  8. # 初始化动画精灵
  9. pygame.sprite.Sprite.__init__(self)
  10. self.grayball_image = pygame.image.load(grayball_image).convert_alpha() #加载灰色小球
  11. self.greenball_image = pygame.image.load(greenball_image).convert_alpha() #加载绿色小球
  12. self.rect = self.grayball_image.get_rect() #两颜色小球矩形位置是一样的
  13. # 将小球放在指定位置
  14. self.rect.left, self.rect.top = position
  15. self.speed = speed
  16. #1、为每个小球设定一个不同的目标;
  17. self.target = target
  18. #5、小球应该添加一个 control 属性,用于记录当前的状态(绿色 -> 玩家控制 or 灰色 -> 随机移动)。
  19. self.control = False
  20. self.width, self.height = bg_size[0], bg_size[1]
  21. self.radius = self.rect.width / 2 #增加半径属性
  22. def move(self):
  23. self.rect = self.rect.move(self.speed)
  24. # 如果小球的左侧出了边界,那么将小球左侧的位置改为右侧的边界
  25. # 这样便实现了从左边进入,右边出来的效果
  26. if self.rect.right < 0:
  27. self.rect.left = self.width
  28. elif self.rect.left > self.width:
  29. self.rect.right = 0
  30. elif self.rect.bottom < 0:
  31. self.rect.top = self.height
  32. elif self.rect.top > self.height:
  33. self.rect.bottom = 0
  34. #3、为小球添加一个 check() 方法,用于判断鼠标在1秒钟内产生的事件数量是否匹配此目标;
  35. def check(self, motion):
  36. if self.target < motion < self.target + 5:
  37. return True
  38. else:
  39. return False
  40. class Glass(pygame.sprite.Sprite):
  41. def __init__(self, glass_image, mouse_image, bg_size):
  42. #初始化动画精灵
  43. pygame.sprite.Sprite.__init__(self)
  44. self.glass_image = pygame.image.load(glass_image).convert_alpha()
  45. self.glass_rect = self.glass_image.get_rect()
  46. self.glass_rect.left, self.glass_rect.top = \
  47. (bg_size[0] - self.glass_rect.width) // 2, \
  48. bg_size[1] - self.glass_rect.height
  49. self.mouse_image = pygame.image.load(mouse_image).convert_alpha()#加载小手光标
  50. self.mouse_rect = self.mouse_image.get_rect()#获取小手光标矩形位置
  51. self.mouse_rect.left, self.mouse_rect.top = \
  52. self.glass_rect.left, self.glass_rect.top #小手光标初始在玻璃面板左上角
  53. pygame.mouse.set_visible(False)#设置Pygame 光标不可见
  54. def main():
  55. pygame.init()
  56. grayball_image = "gray_ball.png"
  57. greenball_image = "green_ball.png"
  58. bg_image = "background.png"
  59. glass_image = "glass.png"
  60. mouse_image = "hand.png"
  61. running = True
  62. #添加背景音乐
  63. pygame.mixer.music.load('bg_music.ogg')
  64. pygame.mixer.music.set_volume(0.2)#设置音量
  65. pygame.mixer.music.play()#播放
  66. #加载音效
  67. winner_sound = pygame.mixer.Sound("winner.wav")
  68. winner_sound.set_volume(0.2)
  69. loser_sound = pygame.mixer.Sound("loser.wav")
  70. loser_sound.set_volume(0.2)
  71. laugh_sound = pygame.mixer.Sound("laugh.wav")
  72. laugh_sound.set_volume(0.2)
  73. hole_sound = pygame.mixer.Sound("hole.wav")
  74. hole_sound.set_volume(0.2)
  75. #背景音乐会贯穿游戏的始终,背景音乐完整播放一次我们视为游戏的时间,
  76. #音乐播放完时,游戏结束
  77. GAMEOVER = USEREVENT
  78. pygame.mixer.music.set_endevent(GAMEOVER)
  79. # 根据背景图片指定游戏界面尺寸
  80. bg_size = width, height = 1024, 681
  81. screen = pygame.display.set_mode(bg_size)
  82. pygame.display.set_caption("Play the ball - Python Demo")
  83. background = pygame.image.load(bg_image).convert_alpha()
  84. # 用来存放小球对象的列表
  85. balls = []
  86. group = pygame.sprite.Group()
  87. # 创建五个小球
  88. for i in range(5):
  89. # 位置随机,速度随机
  90. position = randint(0, width-100), randint(0, height-100)
  91. speed = [randint(-10, 10), randint(-10, 10)]
  92. ball = Ball(grayball_image, greenball_image, position, speed, bg_size, 5 * (i + 1)) #target 设为5-30,比较适中
  93. while pygame.sprite.spritecollide(ball, group, False, pygame.sprite.collide_circle):#在创建小球这里必须进行一下碰撞检测
  94. ball.rect.left, ball.rect.top = randint(0, width-100), randint(0, height-100)
  95. balls.append(ball)
  96. group.add(ball)
  97. glass = Glass(glass_image, mouse_image, bg_size)
  98. #2、创建一个motion 变量来记录每一秒钟产生事件数量;
  99. motion = 0
  100. #########################################
  101. #4.1、添加一个自定义事件,每一秒钟触发一次。
  102. MYTIMER = USEREVENT + 1 #自定义事件的知识点可以查看上一节课的末尾注解
  103. pygame.time.set_timer(MYTIMER, 1000)
  104. pygame.key.set_repeat(100, 100)
  105. clock = pygame.time.Clock()
  106. while running:
  107. for event in pygame.event.get():
  108. if event.type == QUIT:
  109. sys.exit()
  110. elif event.type == GAMEOVER: #判断事件是否为我们自定义的GAMEOVER事件
  111. loser_sound.play()
  112. pygame.time.delay(2000)#暂停2秒
  113. laugh_sound.play()
  114. running = False
  115. #4.2、 调用每个小球的 check() 检测 motion 的值是否匹配某一个小球的目标,并将motion重新初始化,以便记录下1秒鼠标事件数量;
  116. elif event.type == MYTIMER:
  117. if motion:
  118. for each in group:
  119. if each.check(motion):
  120. each.speed = [0, 0]
  121. each.control = True
  122. motion = 0
  123. #需要计算一下motion
  124. elif event.type == MOUSEMOTION:
  125. motion += 1
  126. #用户通过WSAD按键移动绿色小球,使用+= 和 -= 而不是 +1 或者 -1,是为了体验加速度的感觉
  127. elif event.type == KEYDOWN:
  128. if event.key == K_w: #W,上
  129. for each in group:
  130. if each.control:
  131. each.speed[1] -= 1
  132. if event.key == K_a: #a,左
  133. for each in group:
  134. if each.control:
  135. each.speed[0] -= 1
  136. if event.key == K_s: #s,下
  137. for each in group:
  138. if each.control:
  139. each.speed[1] += 1
  140. if event.key == K_d: #d, 右
  141. for each in group:
  142. if each.control:
  143. each.speed[0] += 1
  144. #在默认我们是不能感受到加速度的感觉的,
  145. #因为在默认情况下,无论你是简单的按一下按键或是按住按键不放,
  146. #Pygame都只为你发送一个键盘按下的事件
  147. #不过我们可以通过 key 模块的 set_repeat() 方法来设置是否重复响应按下某个按键
  148. # pygame.key.set_repeat(delay, interval)
  149. #--delay 参数制动第一次发送事件的延迟时间
  150. #--interval 参数指定重复发送事件的时间间隔
  151. #--如果不带任何参数,表示取消重复发送事件
  152. #为了使小球获得加速度的快感,我们设置按键的重复间隔为100毫秒,在while 循环前面设置。
  153. screen.blit(background, (0, 0))
  154. screen.blit(glass.glass_image, glass.glass_rect)
  155. #将小手光标画在Pygame 默认光标位置上
  156. glass.mouse_rect.left, glass.mouse_rect.top = pygame.mouse.get_pos()
  157. #限制光标只能在玻璃面板范围内移动(摩擦摩擦)
  158. if glass.mouse_rect.left < glass.glass_rect.left:
  159. glass.mouse_rect.left = glass.glass_rect.left
  160. if glass.mouse_rect.left > glass.glass_rect.right - glass.mouse_rect.width:
  161. glass.mouse_rect.left = glass.glass_rect.right - glass.mouse_rect.width
  162. if glass.mouse_rect.top < glass.glass_rect.top:
  163. glass.mouse_rect.top = glass.glass_rect.top
  164. if glass.mouse_rect.top > glass.mouse_rect.bottom - glass.mouse_rect.height:
  165. glass.mouse_rect.top = glass.mouse_rect.bottom - glass.mouse_rect.height
  166. screen.blit(glass.mouse_image, glass.mouse_rect)
  167. for each in balls:
  168. each.move()
  169. #如果小球的 control 属性为 True,就画绿球
  170. if each.control:
  171. #画绿色小球
  172. screen.blit(each.greenball_image, each.rect)
  173. else:
  174. #画灰色小球
  175. screen.blit(each.grayball_image, each.rect)
  176. #碰撞检测
  177. for each in group:
  178. group.remove(each) #把自身拿出来
  179. if pygame.sprite.spritecollide(each, group, False, pygame.sprite.collide_circle):#把自己和别的球进行碰撞检测
  180. each.speed[0] = -each.speed[0]
  181. each.speed[1] = -each.speed[1]
  182. group.add(each)#还要把自己放进去
  183. pygame.display.flip()
  184. clock.tick(30)
  185. if __name__ == "__main__":
  186. main()
這一部分需要特別講解的就是:
在默認情況下,無論你是簡單的按一下按鍵或是按住按鍵不放,
Pygame都只為你發送一個鍵盤按下的事件
不過我們可以通過key 模塊的set_repeat() 方法來設置是否重複響應按下某個按鍵
pygame.key.set_repeat(delay, interval)
--delay 參數制動第一次發送事件的延遲時間
--interval 參數指定重複發送事件的時間間隔
--如果不帶任何參數,表示取消重複發送事件
這一部分成功了,接下來就是解決當小球發生碰撞時,無論你是綠色還是灰色,都會失控並脫離控制狀態,這就只需要在檢測到碰撞時把control 屬性設置為False。
  1. #碰撞检测
  2. for each in group:
  3. group.remove(each) #把自身拿出来
  4. if pygame.sprite.spritecollide(each, group, False, pygame.sprite.collide_circle):#把自己和别的球进行碰撞检测
  5. each.speed[0] = -each.speed[0]
  6. each.speed[1] = -each.speed[1]
  7. #发生碰撞,绿球就会失控
  8. each.control = False
  9. group.add(each)#还要把自己放进去
這個很好搞定,接下來我們要做的就是讓小球在碰撞的時候獲得一個新的隨機速度,這將大大的加大遊戲的難度,因為均勻的速度是可以測算出來的,但是如果是以很慢的速度碰撞,卻以新的速度反彈,你就會躲閃不及。
所以我們需要改一下碰撞檢測這一部分的代碼:
如果我們這樣子改:
  1. #碰撞检测
  2. for each in group:
  3. group.remove(each) #把自身拿出来
  4. if pygame.sprite.spritecollide(each, group, False, pygame.sprite.collide_circle):#把自己和别的球进行碰撞检测
  5. each.speed = [randint(-10, 10), randint(-10, 10)]
  6. #发生碰撞,绿球就会失控
  7. each.control = False
  8. group.add(each)#还要把自己放进去
就會出現有的兩個小球碰撞之後如膠似漆的不願意分開,如下圖紅框所示:
這就會讓用戶感覺這個遊戲好卡,我們又該怎麼解決呢?
首先分析產生這種情況的原因:
因為碰撞之後,我們隨機給一個新的速度,這個速度是帶方向的速度,如果兩個小球碰撞之後得到的新的隨機速度的方向是相像的,它們就會再次發生碰撞,然後速度再相像,再檢測到碰撞。然後。就一直糾纏不清了。直到獲得的速度是反向的,並且速度的大小能夠脫離彼此,才會分開。
那怎麼解決呢?
解決這個問題的方法就是將方向和速度這兩個概念獨立開來,大家注意到了,問題的根源在於我們得到的隨機速度是帶方向的,它可能獲取一個向左的,也可能獲取一個向右的,所以我們把問題細分,把方向和速度獨立開,速度永遠為正,只描述書速度的大小,而方向為-1表示向左,+1 表示向右。把方向乘以速度得到帶方向的速度。然後在每一次撞擊的時候,它以撞擊的速度反向移動一格,這兩個小球就分開了,再獲得一個隨機的速度就可以了。
我們來嘗試是否能夠實現:
  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, grayball_image, greenball_image, position, speed, bg_size, target):
  8. # 初始化动画精灵
  9. pygame.sprite.Sprite.__init__(self)
  10. self.grayball_image = pygame.image.load(grayball_image).convert_alpha() #加载灰色小球
  11. self.greenball_image = pygame.image.load(greenball_image).convert_alpha() #加载绿色小球
  12. self.rect = self.grayball_image.get_rect() #两颜色小球矩形位置是一样的
  13. # 将小球放在指定位置
  14. self.rect.left, self.rect.top = position
  15. #加多一个side表示速度的方向
  16. self.side = [choice([-1, 1]), choice([-1, 1])]#第一个元素表示水平方向,第二个元素表示垂直方向,使用random 的choice() 方法随机选择-1和1
  17. self.speed = speed
  18. #加多一个属性表示是否碰撞了
  19. self.collide = False
  20. #1、为每个小球设定一个不同的目标;
  21. self.target = target
  22. #5、小球应该添加一个 control 属性,用于记录当前的状态(绿色 -> 玩家控制 or 灰色 -> 随机移动)。
  23. self.control = False
  24. self.width, self.height = bg_size[0], bg_size[1]
  25. self.radius = self.rect.width / 2 #增加半径属性
  26. def move(self):
  27. self.rect = self.rect.move((self.side[0] * self.speed[0], \
  28. self.side[1] * self.speed[1])) #移动的速度就变为速度的大小乘以方向了
  29. # 如果小球的左侧出了边界,那么将小球左侧的位置改为右侧的边界
  30. # 这样便实现了从左边进入,右边出来的效果
  31. if self.rect.right < 0:
  32. self.rect.left = self.width
  33. elif self.rect.left > self.width:
  34. self.rect.right = 0
  35. elif self.rect.bottom < 0:
  36. self.rect.top = self.height
  37. elif self.rect.top > self.height:
  38. self.rect.bottom = 0
  39. #3、为小球添加一个 check() 方法,用于判断鼠标在1秒钟内产生的事件数量是否匹配此目标;
  40. def check(self, motion):
  41. if self.target < motion < self.target + 5:
  42. return True
  43. else:
  44. return False
  45. class Glass(pygame.sprite.Sprite):
  46. def __init__(self, glass_image, mouse_image, bg_size):
  47. #初始化动画精灵
  48. pygame.sprite.Sprite.__init__(self)
  49. self.glass_image = pygame.image.load(glass_image).convert_alpha()
  50. self.glass_rect = self.glass_image.get_rect()
  51. self.glass_rect.left, self.glass_rect.top = \
  52. (bg_size[0] - self.glass_rect.width) // 2, \
  53. bg_size[1] - self.glass_rect.height
  54. self.mouse_image = pygame.image.load(mouse_image).convert_alpha()#加载小手光标
  55. self.mouse_rect = self.mouse_image.get_rect()#获取小手光标矩形位置
  56. self.mouse_rect.left, self.mouse_rect.top = \
  57. self.glass_rect.left, self.glass_rect.top #小手光标初始在玻璃面板左上角
  58. pygame.mouse.set_visible(False)#设置Pygame 光标不可见
  59. def main():
  60. pygame.init()
  61. grayball_image = "gray_ball.png"
  62. greenball_image = "green_ball.png"
  63. bg_image = "background.png"
  64. glass_image = "glass.png"
  65. mouse_image = "hand.png"
  66. running = True
  67. #添加背景音乐
  68. pygame.mixer.music.load('bg_music.ogg')
  69. pygame.mixer.music.set_volume(0.2)#设置音量
  70. pygame.mixer.music.play()#播放
  71. #加载音效
  72. winner_sound = pygame.mixer.Sound("winner.wav")
  73. winner_sound.set_volume(0.2)
  74. loser_sound = pygame.mixer.Sound("loser.wav")
  75. loser_sound.set_volume(0.2)
  76. laugh_sound = pygame.mixer.Sound("laugh.wav")
  77. laugh_sound.set_volume(0.2)
  78. hole_sound = pygame.mixer.Sound("hole.wav")
  79. hole_sound.set_volume(0.2)
  80. #背景音乐会贯穿游戏的始终,背景音乐完整播放一次我们视为游戏的时间,
  81. #音乐播放完时,游戏结束
  82. GAMEOVER = USEREVENT
  83. pygame.mixer.music.set_endevent(GAMEOVER)
  84. # 根据背景图片指定游戏界面尺寸
  85. bg_size = width, height = 1024, 681
  86. screen = pygame.display.set_mode(bg_size)
  87. pygame.display.set_caption("Play the ball - Python Demo")
  88. background = pygame.image.load(bg_image).convert_alpha()
  89. # 用来存放小球对象的列表
  90. balls = []
  91. group = pygame.sprite.Group()
  92. # 创建五个小球
  93. for i in range(5):
  94. # 位置随机,速度随机
  95. position = randint(0, width-100), randint(0, height-100)
  96. #speed 初始化就只为正数了
  97. speed = [randint(1, 10), randint(1, 10)]
  98. ball = Ball(grayball_image, greenball_image, position, speed, bg_size, 5 * (i + 1)) #target 设为5-30,比较适中
  99. while pygame.sprite.spritecollide(ball, group, False, pygame.sprite.collide_circle):#在创建小球这里必须进行一下碰撞检测
  100. ball.rect.left, ball.rect.top = randint(0, width-100), randint(0, height-100)
  101. balls.append(ball)
  102. group.add(ball)
  103. glass = Glass(glass_image, mouse_image, bg_size)
  104. #2、创建一个motion 变量来记录每一秒钟产生事件数量;
  105. motion = 0
  106. #########################################
  107. #4.1、添加一个自定义事件,每一秒钟触发一次。
  108. MYTIMER = USEREVENT + 1 #自定义事件的知识点可以查看上一节课的末尾注解
  109. pygame.time.set_timer(MYTIMER, 1000)
  110. pygame.key.set_repeat(100, 100)
  111. clock = pygame.time.Clock()
  112. while running:
  113. for event in pygame.event.get():
  114. if event.type == QUIT:
  115. sys.exit()
  116. elif event.type == GAMEOVER: #判断事件是否为我们自定义的GAMEOVER事件
  117. loser_sound.play()
  118. pygame.time.delay(2000)#暂停2秒
  119. laugh_sound.play()
  120. running = False
  121. #4.2、 调用每个小球的 check() 检测 motion 的值是否匹配某一个小球的目标,并将motion重新初始化,以便记录下1秒鼠标事件数量;
  122. elif event.type == MYTIMER:
  123. if motion:
  124. for each in group:
  125. if each.check(motion):
  126. each.speed = [0, 0]
  127. each.control = True
  128. motion = 0
  129. #需要计算一下motion
  130. elif event.type == MOUSEMOTION:
  131. motion += 1
  132. #用户通过WSAD按键移动绿色小球,使用+= 和 -= 而不是 +1 或者 -1,是为了体验加速度的感觉
  133. elif event.type == KEYDOWN:
  134. if event.key == K_w: #W,上
  135. for each in group:
  136. if each.control:
  137. each.speed[1] -= 1
  138. if event.key == K_a: #a,左
  139. for each in group:
  140. if each.control:
  141. each.speed[0] -= 1
  142. if event.key == K_s: #s,下
  143. for each in group:
  144. if each.control:
  145. each.speed[1] += 1
  146. if event.key == K_d: #d, 右
  147. for each in group:
  148. if each.control:
  149. each.speed[0] += 1
  150. #在默认我们是不能感受到加速度的感觉的,
  151. #因为在默认情况下,无论你是简单的按一下按键或是按住按键不放,
  152. #Pygame都只为你发送一个键盘按下的事件
  153. #不过我们可以通过 key 模块的 set_repeat() 方法来设置是否重复响应按下某个按键
  154. # pygame.key.set_repeat(delay, interval)
  155. #--delay 参数制动第一次发送事件的延迟时间
  156. #--interval 参数指定重复发送事件的时间间隔
  157. #--如果不带任何参数,表示取消重复发送事件
  158. #为了使小球获得加速度的快感,我们设置按键的重复间隔为100毫秒,在while 循环前面设置。
  159. screen.blit(background, (0, 0))
  160. screen.blit(glass.glass_image, glass.glass_rect)
  161. #将小手光标画在Pygame 默认光标位置上
  162. glass.mouse_rect.left, glass.mouse_rect.top = pygame.mouse.get_pos()
  163. #限制光标只能在玻璃面板范围内移动(摩擦摩擦)
  164. if glass.mouse_rect.left < glass.glass_rect.left:
  165. glass.mouse_rect.left = glass.glass_rect.left
  166. if glass.mouse_rect.left > glass.glass_rect.right - glass.mouse_rect.width:
  167. glass.mouse_rect.left = glass.glass_rect.right - glass.mouse_rect.width
  168. if glass.mouse_rect.top < glass.glass_rect.top:
  169. glass.mouse_rect.top = glass.glass_rect.top
  170. if glass.mouse_rect.top > glass.mouse_rect.bottom - glass.mouse_rect.height:
  171. glass.mouse_rect.top = glass.mouse_rect.bottom - glass.mouse_rect.height
  172. screen.blit(glass.mouse_image, glass.mouse_rect)
  173. for each in balls:
  174. each.move()
  175. #
  176. if each.collide:
  177. each.speed = [randint(1, 10), randint(1, 10)]
  178. each.collide = False
  179. #如果小球的 control 属性为 True,就画绿球
  180. if each.control:
  181. #画绿色小球
  182. screen.blit(each.greenball_image, each.rect)
  183. else:
  184. #画灰色小球
  185. screen.blit(each.grayball_image, each.rect)
  186. #碰撞检测
  187. for each in group:
  188. group.remove(each) #把自身拿出来
  189. if pygame.sprite.spritecollide(each, group, False, pygame.sprite.collide_circle):#把自己和别的球进行碰撞检测
  190. #发生碰撞,方向取反,这样就会以多大速度相撞,就以相反速度相离
  191. each.side[0] = -each.side[0]
  192. each.side[1] = -each.side[1]
  193. each.collide = True
  194. #发生碰撞,绿球就会失控
  195. each.control = False
  196. group.add(each)#还要把自己放进去
  197. pygame.display.flip()
  198. clock.tick(30)
  199. if __name__ == "__main__":
  200. main()

0 留言:

發佈留言