Python3+pygame實現的flappy bird遊戲,代碼完整,還有音樂

  • 2021 年 3 月 11 日
  • 筆記

之前一直在手機上玩flappy bird遊戲,閑暇時間就編寫了一個

是採用python3+pygame模塊製作而成的,運行效果非常流暢,會讓你大吃一驚哦😃哈哈

一、運行效果展示

下載遊戲之後,注意在自己的python環境中安裝pygame模塊,如果沒有安裝可以使用pip install pygame 進行安裝

然後使用使用命令運行起這個.py文件,運行之後的第一個界面效果如下,是不是很酷炫

當點擊上圖中的「Play」按鈕之後的效果如下:

運行之後是有音樂的,大家可以下載代碼的時候一起將素材下載,這樣就在運行時就能聽到音樂

 

二、完整代碼

下面代碼用到了素材(背景圖片,音樂等,下載地址 //www.itprojects.cn/detail.html?example_id=8af93ac601523a955f8280c95c2a9e0b

  1 import math
  2 import os
  3 import time
  4 from  random import randint
  5 from random import uniform
  6 import pygame
  7 from pygame.locals import * #導入一些常用的變量
  8 from collections import deque#加入了隊列
  9 
 10 FPS = 60
 11 BK_WIDTH = 900  #背景寬度
 12 BK_HEIGHT = 650  #背景高度
 13 PIPE_WIDTH = 80 #水管的寬度
 14 PIPE_HEIGHT = 10 #水管素材的高度
 15 PIPE_HEAD_HEIGHT = 32#管子頭的高度
 16 
 17 #初始化全局變量
 18 BK_MOVE_SPEED = 0.22#主柱子每毫秒移動的速度
 19 ADD_TIME = 2500##每隔多少毫秒就增加一個柱子 這種方法不會有漏洞嗎  就是當毫秒數和幀數不匹配啥的  #還需要仔細的思考
 20 TOTAL_PIPE_BODY  =  int(3/5 * BK_HEIGHT)  # 像素值必須為整數   占窗口的3/5
 21 PIPE_RATE =0.96
 22 a_i="bird-wingup"
 23 b_i="bird-wingmid"
 24 c_i="bird-wingdown"
 25 
 26 INITAL_SPEED = -0.37#鳥的Y軸初速度
 27 BIRD_WIDTH = 50
 28 BIRD_HEIGHT = 40
 29 BIRD_INIT_SCORE  = 7#鳥的初始通關分數
 30 
 31 STONE_ADD_TIME = 1000 #每隔多少毫秒就增加一個石頭
 32 STONE_WIDTH = 40
 33 STONE_HEIGHT = 30
 34 STONE_LEVEL = 4#石頭出現的等級
 35 
 36 BUTTON_WIDTH = 140
 37 BUTT0N_HEIGHT = 60
 38 
 39 BULLET_SPEED = 0.32#子彈的速度
 40 BULLET_WIETH = 50
 41 BULLET_HEIGHT = 30
 42 #設置全局變量 方便修改參數
 43 
 44 
 45 pygame.init()
 46 screen = pygame.display.set_mode((BK_WIDTH,BK_HEIGHT))
 47 pygame.mixer.init()
 48 
 49 music_lose = pygame.mixer.Sound("lose.wav")
 50 music1 = pygame.mixer.Sound("touch.wav")
 51 pygame.mixer.music.load("bkm.mp3")
 52 font = pygame.font.SysFont('comicsansms', 25)
 53 
 54 
 55 #用於設置鳥的種類
 56 def little_bird(list):
 57     global a_i
 58     global b_i
 59     global c_i
 60     a_i=list[0]
 61     b_i=list[1]
 62     c_i=list[2]
 63 
 64 
 65 #用於設置關卡難度
 66 def seteasy(list):
 67     global BK_MOVE_SPEED  # 背景每毫秒移動的速度   就是柱子移動的速度
 68     global ADD_TIME  # 每隔多少毫秒就增加一個柱子
 69     global TOTAL_PIPE_BODY  # 像素值必須為整數   占窗口的3/5
 70     global PIPE_RATE
 71     global STONE_LEVEL  # 鳥出現的等
 72     global BIRD_INIT_SCORE
 73 
 74     BK_MOVE_SPEED = list[0]  # 背景每毫秒移動的速度
 75     ADD_TIME = list[1]  # 每隔多少毫秒就增加一個柱子
 76     TOTAL_PIPE_BODY =list[2]  # 像素值必須為整數   占窗口的3/5
 77     PIPE_RATE = list[3]
 78     Pipe.add_time = list[1]
 79     BIRD_INIT_SCORE = list[4]
 80     STONE_LEVEL = list[5]
 81 
 82 
 83 #子彈類
 84 class Bullet(pygame.sprite.Sprite):
 85     speed = BULLET_SPEED
 86     width = BULLET_WIETH
 87     height = BULLET_HEIGHT
 88 
 89     def __init__(self,bird,images):
 90         super(Bullet,self).__init__() #d調用父類的初始函數 使用此方法 可以減少代碼的更改量 並且解決了多重繼承的問題
 91         self.x,self.y = bird.x,bird.y
 92         self.bullet = images #給鳥的圖片進行賦值
 93         self.mask_bullet = pygame.mask.from_surface(self.bullet)
 94     def update(self):#計算鳥在下一點的新坐標並更新
 95         self.x=self.x+self.speed*frames_to_msec(1)
 96     @property
 97     def image(self):
 98         return self.bullet
 99     @property
100     def mask(self):
101         return self.mask_bullet
102     @property
103     def rect(self):
104         return Rect(self.x,self.y,Bullet.width,Bullet.height)
105     def visible(self):
106         return 0<self.x<BK_WIDTH+Bullet.width
107 
108 
109 #小鳥做豎直上拋運動  當小鳥加速到一定狀態時 就不再加速了
110 class Bird(pygame.sprite.Sprite):
111 
112     width =BIRD_WIDTH #鳥寬
113     height = BIRD_HEIGHT #鳥長
114     sink_gravity = 0.001#鳥的下降重力
115 
116     def __init__(self,x,y,level,images):
117         super(Bird,self).__init__() #d調用父類的初始函數 使用此方法 可以減少代碼的更改量 並且解決了多重繼承的問題
118         self.x,self.y = x,y
119         self.wing_up,self.wing_mid,self.wing_down = images #給鳥的圖片進行賦值
120         self.mask_wing_up = pygame.mask.from_surface(self.wing_up)
121         self.mask_wing_mid = pygame.mask.from_surface(self.wing_mid)
122         self.mask_wing_down = pygame.mask.from_surface(self.wing_down)
123         self.inital_speed = 0 #鳥向上的初速度
124         self.level = level #鳥的初始等級
125         self.score = 0 #鳥的初始分數為 0
126 
127     def update(self,t):#計算鳥在下一點的新坐標並更新
128         y_ = self.inital_speed*t+0.5*self.sink_gravity*t*t
129         if self.inital_speed<=0.3:
130             self.inital_speed = self.inital_speed +self.sink_gravity*t
131         self.y+=y_   #在主函數里計算時間
132 
133     @property
134     def image(self):
135         if pygame.time.get_ticks()%400>=120:
136             return self.wing_up
137         elif pygame.time.get_ticks()%400>=280:
138             return self.wing_mid
139         else:
140             return self.wing_down
141     @property
142     def mask(self):
143         if pygame.time.get_ticks()%400>=120:
144             return self.mask_wing_up
145         elif pygame.time.get_ticks()%400>=280:
146             return self.mask_wing_mid
147         else:
148             return self.mask_wing_down
149 
150     @property
151     def rect(self):
152         return Rect(self.x,self.y,Bird.width,Bird.height)
153 
154 
155 
156 class Pipe(pygame.sprite.Sprite):
157     width = PIPE_WIDTH
158     pipe_head_height = PIPE_HEAD_HEIGHT
159     add_time = ADD_TIME
160 
161     def __init__(self,pipe_head_image,pipe_body_image):
162         super(Pipe, self).__init__()
163         self.x = float(BK_WIDTH-1)
164         self.score_count = False
165         self.image = pygame.Surface((Pipe.width,BK_HEIGHT),SRCALPHA)#創建一個surface 我理解為能畫到窗口上的對象
166         # #意為創建一個有ALPHA 通道的surface 如果需要透明就需要這個選項
167         self.image.convert()
168         self.image.fill((0,0,0,0))#前三位是顏色  最後一位是透明度
169         total_pipe_length = TOTAL_PIPE_BODY
170 
171         self.bottom_length = randint(int(0.1*total_pipe_length),int(0.8*total_pipe_length))#用於生成指定範圍內的整數
172         self.top_length = total_pipe_length-self.bottom_length
173 
174         for i in range(1,self.bottom_length+1):
175             pos = (0,BK_HEIGHT - i)
176             self.image.blit (pipe_body_image,pos)#用重疊的技術畫出來管子
177 
178         bottom_head_y = BK_HEIGHT - self.bottom_length-self.pipe_head_height  #求出管子頭的長度
179         bottom_head_pos = (0,bottom_head_y)
180         self.image.blit(pipe_head_image,bottom_head_pos)#畫管子
181 
182         for i in range(-PIPE_HEIGHT,self.top_length-PIPE_HEIGHT):
183             pos = (0,i)
184             self.image.blit(pipe_body_image,pos)
185         top_head_y = self.top_length
186         self.image.blit(pipe_head_image,(0,top_head_y))
187 
188         self.mask = pygame.mask.from_surface(self.image)
189     @property
190     def rect(self):
191         return Rect(self.x,0,Pipe.width,PIPE_HEIGHT)
192     @property
193     def visible(self):
194         return -Pipe.width<self.x<BK_WIDTH
195 
196     def update(self,delta_frames=1):
197         self.x-=BK_MOVE_SPEED*frames_to_msec(delta_frames)
198 
199     def collides(self,bird):
200         return pygame.sprite.collide_mask(self,bird)
201 
202 
203 def change_add_time():
204     Pipe.add_time= int( (Pipe.add_time*PIPE_RATE) /100)*100
205 #改變管子的增加時間
206 
207 
208 #石頭具有速度 位置等不同屬性
209 #起始的x屬性為固定值 y隨機 速度在一定範圍內隨機
210 class Stone(pygame.sprite.Sprite):
211     add_time  = STONE_ADD_TIME
212     width = STONE_WIDTH
213     height = STONE_HEIGHT
214     def __init__(self,image):
215         super(Stone, self).__init__()
216         self.x =BK_WIDTH-5
217         self.y = randint(1,int(0.95*BK_HEIGHT))
218         self.speed = uniform(0.1 ,0.5)
219         self.stone_image = image
220         self.mask_image = pygame.mask.from_surface(self.image)
221 
222     @property
223     def rect(self):
224         return Rect(self.x,self.y,self.width,self.height)
225     @property
226     def image(self):
227         return self.stone_image
228 
229     @property
230     def mask(self):
231         return self.mask_image
232 
233     def update(self,frame = 1):
234         self.x -= int(self.speed*frames_to_msec(frame))
235 
236     def collides(self, b):
237         return pygame.sprite.collide_mask(self, b)
238 
239     def visible(self):
240         return -self.width<self.x<BK_WIDTH
241 
242 
243 #返回每關需要達到的通關分數
244 def level_goal(bird):
245     return bird.level*BIRD_INIT_SCORE
246 
247 #載入圖片
248 def load_image(img_file_name):
249     file_name = os.path.join(".","images",img_file_name)#進行路徑字符串的合併
250     img = pygame.image.load(file_name)
251     img.convert()
252     return img
253 
254 #根據所在的等級返回需要的背景名
255 def search_bk(bird):
256     return "bk"+str(bird.level)
257 
258 img_x = load_image('backgroundx.png')#加載背景圖像
259 def load_images():
260     #加載所有遊戲需要用到的圖像
261         #上面寫了這個函數下面就用了起來  join用於分隔符和元組的拼接  os.path.join 用於路徑的順序拼接
262     return {'bk1': load_image('background.png'),
263              'bk2':load_image("background2.png"),
264             "bk3":load_image("background3.png"),
265             "bk4":load_image("background4.png"),
266             "bk5":load_image("background5.png"),
267             "bk6":load_image("background6.png"),
268             'stone':load_image('stone.png'),
269             'bullet': load_image('bullet.png'),
270             'pipe-end': load_image('pipe_end.png'),
271             'pipe-body': load_image('pipe_body.png'),
272             'f_u': load_image('fenghuang_up.png'),
273             'f_m': load_image('fenghuang_mid.png'),
274             'f_w': load_image('fenghuang_down.png'),
275             'bird-wingup': load_image('bird_wing_up.png'),
276             'bird-wingmid': load_image('bird_wing_mid.png'),
277             'bird-wingdown': load_image('bird_wing_down.png')}
278 
279 def frames_to_msec(frames,fps=FPS):
280     return 1000.0*frames/fps   #難道限制的意思就是我可以限制圖片出來的時間
281 
282 def msec_to_frames(milliseconds, fps=FPS):
283     return fps * milliseconds / 1000.0#轉化成對應的幀數
284     #轉化成每秒的相應的幀數
285 
286 
287 def game_loop():
288     pygame.mixer.music.play(-1)
289     pygame.display.set_caption("Flappy Bird")
290     clock = pygame.time.Clock()#創建一個時鐘對象
291     images = load_images()#建立所有需要的圖像字典
292 
293     bird = Bird(20,BK_HEIGHT//2,1,(images[a_i],images[b_i] ,images[c_i]))
294     score_font = pygame.font.SysFont(None,50,bold=True)#名字 大小  粗體  建立畫筆  用於記錄 分數
295     score_font2 = pygame.font.SysFont(None, 40, bold=True)  # 名字 大小  粗體  建立畫筆  用於記錄 分數
296     score_font3 = pygame.font.SysFont(None, 70, bold=True)  # 名字 大小  粗體  建立畫筆  用於記錄 分數
297     pipes = deque()
298 
299     stones =pygame.sprite.Group()#將石頭新建為一個精靈組
300     bullets =pygame.sprite.Group()#將子彈新建為一個精靈組
301 
302     pause = done = False
303     frames=0
304 
305     while not done :#當沒有按下中止鍵
306         clock.tick(FPS)
307         if not (pause or frames%msec_to_frames(Pipe.add_time)):#如果沒有按下暫停 或者滿足新生成柱子的條件
308             pp=Pipe(images['pipe-end'], images['pipe-body'])
309             pipes.append(pp)#生成新管子 並加入隊列
310 
311         if not (pause or frames%msec_to_frames(Stone.add_time)or bird.level<STONE_LEVEL):
312             ss = Stone(images["stone"])
313             stones.add(ss) #加入新生成的石頭
314 
315         #判斷發生了什麼事件進行相應的處理
316         for e in pygame.event.get():
317             if e.type == QUIT:
318                 done = True
319                 break
320             elif e.type == KEYUP :
321                 if  e.key == K_p:
322                     pause = not pause
323                 elif e.key ==K_d:#發射子彈
324                     bb=Bullet(bird,images["bullet"])
325                     bullets.add(bb)
326                 elif e.key ==K_s or e.key == K_SPACE:
327                     bird.inital_speed = INITAL_SPEED
328 
329 
330             elif e.type == MOUSEBUTTONUP:
331                 bird.inital_speed =INITAL_SPEED
332 
333             # 重新更新時間
334             # 使小鳥又進入相應的運動的開始
335         if pause:
336             continue  # 這個時段什麼都不做
337 
338         pygame.sprite.groupcollide(stones,bullets,True,True,pygame.sprite.collide_mask)
339         pipe_collision = any(p.collides(bird) for p in pipes)
340         stone_collision = any(s.collides(bird) for s in stones)
341 
342         if pipe_collision:
343             pygame.mixer.music.stop()
344             done = True
345             pygame.mixer.Sound.play(music_lose, -1)
346             time.sleep(3.5)
347             pygame.mixer.Sound.stop(music_lose)
348             time.sleep(0.1)
349 
350         if stone_collision:
351             pygame.mixer.music.stop()
352             pygame.mixer.Sound.play(music_lose, -1)
353             time.sleep(3.5)
354             pygame.mixer.Sound.stop(music_lose)
355             time.sleep(0.1)
356             done = True
357         if 0>=bird.y or bird.y>BK_HEIGHT-Bird.height:
358             done = True
359             pygame.mixer.music.stop()
360             pygame.mixer.Sound.play(music_lose, -1)
361             time.sleep(3.5)
362             pygame.mixer.Sound.stop(music_lose)
363             time.sleep(0.1)
364 
365 
366         screen.blit(images[search_bk(bird)], (0, 0))#畫背景牆 這種是分開兩張的
367 
368         while pipes and not pipes[0].visible:
369             pipes.popleft()#當隊列不為空  且管子 0 已經不可見的時候
370         for s in stones:#刪除看不見的石頭
371             if  not s.visible():
372                 del s
373         for b in bullets:#刪除看不見的子彈
374             if not b.visible():
375                 del b
376 
377 
378         for p in pipes:
379             p.update()
380             screen.blit(p.image,p.rect)#在指定的位置 畫柱子
381         for s in stones:
382             s.update()
383             screen.blit(s.image,s.rect)
384 
385         for b in bullets:
386             b.update()
387             screen.blit(b.bullet,b.rect)
388 
389         for p in pipes:
390             if bird.x>p.x+Pipe.width and not p.score_count:  #當柱子超過了鳥的位置並且柱子還沒有被計分
391                 bird.score+=1
392                 p.score_count = True
393 
394         sl = score_font.render("level:",True,(255,255,255))
395         sc = score_font.render("score:",True,(255,255,255))
396         sl2 = score_font2.render(str(bird.level),True,(255,255,255))
397         sc2 = score_font2.render(str(bird.score),True,(255,255,255))
398         screen.blit (sc,(BK_WIDTH-170,20))
399         screen.blit(sl, (BK_WIDTH - 320, 20))
400         screen.blit(sc2, (BK_WIDTH - 50, 27))
401         screen.blit(sl2, (BK_WIDTH - 210, 27))
402 
403         bird.update(frames_to_msec(1))#計算一幀所需要的時間
404         screen.blit(bird.image,bird.rect)
405 
406         pygame.display.flip()#繪製圖像到屏幕
407         if bird.score >= level_goal(bird):#如果已經達到了通關分數
408         #升入下一級  首先要初始化所有變量#清空柱子#改變等級
409             change_add_time()
410             pipes.clear()
411             stones.empty()
412             bullets.empty()
413             bird.level += 1  # 分數先暫不做清空後續再加入吧
414             if bird.level<=6:
415                 s3 = score_font3.render("Next   Level", True, (255, 255, 255))
416                 screen.blit(s3, (BK_WIDTH//2-150, BK_HEIGHT//2-50))
417                 pygame.display.flip()
418                 time.sleep(2)
419             if bird.level >6:
420                 s3 = score_font3.render("You   Win!", True, (255, 255, 255))
421                 screen.blit(s3, (BK_WIDTH // 2 - 150, BK_HEIGHT // 2 - 50))
422                 pygame.display.flip()
423                 time.sleep(2)
424                 exit()
425         frames+= 1
426     pygame.mixer.music.stop()
427 
428     Pipe.add_time = ADD_TIME#再次初始化柱子的速度
429     main()
430 
431 
432 def quit_but():
433     pygame.quit()
434     exit()
435 
436 
437 def buttons(x, y, w, h, color, color2, text,action,list=[]):
438     mouse_position = pygame.mouse.get_pos()
439     click = pygame.mouse.get_pressed()
440     if x+w > mouse_position[0] > x and y+h > mouse_position[1] > y:
441         color = color2
442         #get_pressed  只返回鼠標三個鍵是否被按過的狀態 不會分辨它是在哪裡被按的
443         if click[0]== 1 and action != None:
444             pygame.mixer.Sound.play(music1, -1)
445             time.sleep(0.215)
446             pygame.mixer.Sound.stop(music1)
447             if list:
448                 action(list)
449             else:
450                 action()
451 
452     pygame.draw.rect(screen, color, (x, y, w, h))
453     # font = pygame.font.SysFont('comicsansms', 25)
454     TextSurf = font.render(text, True, (0,0,0))
455     TextRect = TextSurf.get_rect()
456     TextRect.center = ((x + (w / 2)), (y + (h / 2)))
457     screen.blit(TextSurf, TextRect)
458     pygame.display.update()
459 
460 
461 def setting():
462     # img = load_image('backgroundx.png')
463     screen.blit(img_x, (0, 0))  # 畫背景牆 這種是分開兩張的
464     pygame.display.flip()
465     while True:
466         for event in pygame.event.get():
467             if event.type==pygame.QUIT:
468                 exit()
469 
470         buttons(100, 200, BUTTON_WIDTH, BUTT0N_HEIGHT,(255, 0, 0), (170, 0, 0), 'easy',seteasy,[0.19,2500,int(5 / 11 * BK_HEIGHT),0.97,5,6])  # 繪製圖標  進行事件
471         buttons(400, 200, BUTTON_WIDTH, BUTT0N_HEIGHT,(0, 255, 0), (0, 170, 0), 'normal', seteasy,[0.19,2500,int(3 / 5 * BK_HEIGHT),0.96,7,4])  # 繪製圖標  進行事件
472         buttons(700 ,200, BUTTON_WIDTH, BUTT0N_HEIGHT,(0, 0, 255), (0, 0, 160),'hard',seteasy,[0.21,1300,int(9 / 14 * BK_HEIGHT),0.96,2,1])  # 繪製圖標  進行事件
473         buttons(700, 550, BUTTON_WIDTH, BUTT0N_HEIGHT, (0, 0, 255), (0, 0, 160), 'back', main)  # 繪製圖標  進行事件
474         buttons(100, 400, BUTTON_WIDTH, BUTT0N_HEIGHT, (255, 0, 0), (170, 0, 0), 'huo lie niao',little_bird,["f_u","f_m","f_w"])  # 繪製圖標  進行事件
475         buttons(400, 400, BUTTON_WIDTH, BUTT0N_HEIGHT, (0, 255, 0), (0, 170, 0),  'xiao niao',little_bird,["bird-wingup","bird-wingmid","bird-wingdown"])  # 繪製圖標  進行事件
476         # buttons(700, 400, BUTTON_WIDTH, BUTT0N_HEIGHT, (0, 0, 255), (0, 0, 160), 'back', main)  # 繪製圖標  進行事件
477 
478 
479 def main():
480     screen.blit(img_x, (0, 0))  # 畫背景牆 這種是分開兩張的
481     pygame.display.flip()
482     while True:
483         for event in pygame.event.get():
484             if event.type==pygame.QUIT:
485                 exit()
486         buttons((BK_WIDTH-BUTTON_WIDTH)//2,(BK_HEIGHT-BUTT0N_HEIGHT-100)//2,BUTTON_WIDTH,BUTT0N_HEIGHT,(0,255,0),(0,170,0),'Play!',game_loop)#繪製圖標  進行事件
487         buttons((BK_WIDTH - BUTTON_WIDTH) // 2, (BK_HEIGHT - BUTT0N_HEIGHT + 100) // 2, BUTTON_WIDTH, BUTT0N_HEIGHT,(0, 0, 255), (0, 0, 160), 'setting', setting)  # 繪製圖標  進行事件
488         buttons((BK_WIDTH - BUTTON_WIDTH) // 2, (BK_HEIGHT - BUTT0N_HEIGHT + 300) // 2, BUTTON_WIDTH, BUTT0N_HEIGHT,(255, 0, 0), (170, 0, 0), 'Quit', quit_but)
489 
490 if __name__ =="__main__":
491    main()

 

上述代碼是第1版本,簡單起見 沒有完全封裝為面向對象,等後面有時間再進行完善 目標是:全部用類進行分裝,然後拆分到多個模塊中