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版本,簡單起見 沒有完全封裝為面向對象,等後面有時間再進行完善 目標是:全部用類進行分裝,然後拆分到多個模塊中