python实现Flappy Bird源码

时间:2022-09-23 16:26:07

Flappy Bird是前段时间(好像一年or两年前....)特别火的有一个小游戏,相信大家都玩过。

Flappy Bird操作简单,通过点击手机屏幕使Bird上升,穿过柱状障碍物之后得分,碰到则游戏结束。由于障碍物高低不等,控制Bird上升和下降需要反应快并且灵活,要得到较高的分数并不容易。作为一个游戏渣,我最高纪录是8分......

我记得当时还想,是谁发明了这个小游戏,逼死强迫症,记得当时本科时好多人在玩....

无意间在GitHub上看到了python实现的代码,所以拿来学习了一番。代码思路比较简洁。

因为第一次接触pygame,所以代码注释写的比较详细,也算是一次新体验。

玩法:空格键进入游戏,↑控制小鸟飞行

注意:需要安装pygame模块

代码:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
# -*- coding: utf8 -*-
 
from itertools import cycle
import random
import sys
 
import pygame #将pygame库导入到python程序中
from pygame.locals import * #需要引入pygame中的所有常量。
 
 
FPS = 30
SCREENWIDTH = 288 #屏幕宽度
SCREENHEIGHT = 512 #屏幕高度
# amount by which base can maximum shift to left
PIPEGAPSIZE = 100 # gap between upper and lower part of pipe 管道上下之间的间隙
BASEY  = SCREENHEIGHT * 0.79 #base那个条条所在的高度 注意以左上角为坐标起始点 所以这个高度是往下为正
# image, sound and hitmask dicts
IMAGES, SOUNDS, HITMASKS = {}, {}, {} #图像,声音,撞击的文件
 
# list of all possible players (tuple of 3 positions of flap) #三种小鸟造型
PLAYERS_LIST = (
 # red bird
 (
  'assets/sprites/redbird-upflap.png',
  'assets/sprites/redbird-midflap.png',
  'assets/sprites/redbird-downflap.png',
 ),
 # blue bird
 (
  # amount by which base can maximum shift to left
  'assets/sprites/bluebird-upflap.png',
  'assets/sprites/bluebird-midflap.png',
  'assets/sprites/bluebird-downflap.png',
 ),
 # yellow bird
 (
  'assets/sprites/*-upflap.png',
  'assets/sprites/*-midflap.png',
  'assets/sprites/*-downflap.png',
 ),
)
 
# list of backgrounds 两种背景,一种白天,一种黑夜
BACKGROUNDS_LIST = (
 'assets/sprites/background-day.png',
 'assets/sprites/background-night.png',
)
 
# list of pipes 管道的两种颜色,一种绿色,一种红色
PIPES_LIST = (
 'assets/sprites/pipe-green.png',
 'assets/sprites/pipe-red.png',
)
 
 
try:
 xrange
except NameError:
 xrange = range
 
 
def main():
 global SCREEN, FPSCLOCK
 pygame.init() #经过初始化以后我们就可以尽情地使用pygame了。
 
 #使用Pygame时钟之前,必须先创建Clock对象的一个实例,
 FPSCLOCK = pygame.time.Clock()#控制每个循环多长时间运行一次。这就像一个定时器在控制时间进程,指出“现在开始下一个循环”!现在开始下一个循环!……
 
 SCREEN = pygame.display.set_mode((SCREENWIDTH, SCREENHEIGHT))#通常来说我们需要先创建一个窗口,方便我们与程序的交互。
 pygame.display.set_caption('Flappy Bird')#设置窗口标题
 
 # numbers sprites for score display #加载并转换图像
 #在pygame中可以使用pygame.image.load()函数来加载位图 (支持jpg,png,gif,bmp,pcx,tif,tga等多种图片格式)。
 #convert_alpha()方法会使用透明的方法绘制前景对象。
 # 因此在加载一个有alpha通道的素材时(比如PNG TGA),需要使用convert_alpha()方法,当然普通的图片也是可以使用这个方法的,用了也不会有什么副作用。
 IMAGES['numbers'] = (
  pygame.image.load('assets/sprites/0.png').convert_alpha(),
  pygame.image.load('assets/sprites/1.png').convert_alpha(),
  pygame.image.load('assets/sprites/2.png').convert_alpha(),
  pygame.image.load('assets/sprites/3.png').convert_alpha(),
  pygame.image.load('assets/sprites/4.png').convert_alpha(),
  pygame.image.load('assets/sprites/5.png').convert_alpha(),
  pygame.image.load('assets/sprites/6.png').convert_alpha(),
  pygame.image.load('assets/sprites/7.png').convert_alpha(),
  pygame.image.load('assets/sprites/8.png').convert_alpha(),
  pygame.image.load('assets/sprites/9.png').convert_alpha()
 )
 
 # game over sprite 游戏结束显示的图像
 IMAGES['gameover'] = pygame.image.load('assets/sprites/gameover.png').convert_alpha()
 # message sprite for welcome screen 欢迎界面显示的图像
 IMAGES['message'] = pygame.image.load('assets/sprites/message.png').convert_alpha()
 # base (ground) sprite 始终显示的base图像
 IMAGES['base'] = pygame.image.load('assets/sprites/base.png').convert_alpha()
 
 # sounds
 # WAV版 OGG版是指游戏的音频格式
 # WAV版是属于游戏原版
 # OGG是大大们通过转换器把音频格式的WAV改成OGG,这样游戏的配置提高要求使游戏本身的体积而缩小节省了空间。
 #可以看一下同一个音频 ogg版的是比wav版的文件小很多
 if 'win' in sys.platform: #判断当前系统平台 来设置声音文件后缀
  soundExt = '.wav'
 else:
  soundExt = '.ogg'
 
 # 音效:pygame.mixer
 # sound = pygame.mixer.Sound('/home/liumin/love.wav')使用指定文件名载入一个音频文件,并创建一个Sound对象。 音频文件可以是wav,ogg等格式。
 # 音频文件的内容会被全部载入到内存中。
 SOUNDS['die'] = pygame.mixer.Sound('assets/audio/die' + soundExt)
 SOUNDS['hit'] = pygame.mixer.Sound('assets/audio/hit' + soundExt)
 SOUNDS['point'] = pygame.mixer.Sound('assets/audio/point' + soundExt)
 SOUNDS['swoosh'] = pygame.mixer.Sound('assets/audio/swoosh' + soundExt)
 SOUNDS['wing'] = pygame.mixer.Sound('assets/audio/wing' + soundExt)
 
 while True:
  # select random background sprites 加载随机背景 (白天或者黑夜)
  randBg = random.randint(0, len(BACKGROUNDS_LIST) - 1)#随机选择0或者1
  IMAGES['background'] = pygame.image.load(BACKGROUNDS_LIST[randBg]).convert()#加载随机背景
 
  # select random player sprites 加载随机角色 (红色、蓝色、黄色小鸟)
  randPlayer = random.randint(0, len(PLAYERS_LIST) - 1)
  IMAGES['player'] = (
   pygame.image.load(PLAYERS_LIST[randPlayer][0]).convert_alpha(),
   pygame.image.load(PLAYERS_LIST[randPlayer][1]).convert_alpha(),
   pygame.image.load(PLAYERS_LIST[randPlayer][2]).convert_alpha(),
  )
 
  # select random pipe sprites 加载随机管道样式
  pipeindex = random.randint(0, len(PIPES_LIST) - 1)
  IMAGES['pipe'] = (
   pygame.transform.rotate(
    pygame.image.load(PIPES_LIST[pipeindex]).convert_alpha(), 180),#旋转180度
   pygame.image.load(PIPES_LIST[pipeindex]).convert_alpha(),
  )#一个上面的管道 一个下面的管道
 
  # hismask for pipes #得到管道的边界mask
  HITMASKS['pipe'] = (
   getHitmask(IMAGES['pipe'][0]),
   getHitmask(IMAGES['pipe'][1]),
  )
 
  # hitmask for player #得到player的边界mask
  HITMASKS['player'] = (
   getHitmask(IMAGES['player'][0]),
   getHitmask(IMAGES['player'][1]),
   getHitmask(IMAGES['player'][2]),
  )
 
  movementInfo = showWelcomeAnimation()#返回'playery'(player所在位置),'basex'(base图像所在位置) 'playerIndexGen'(飞行姿势index)
  crashInfo = mainGame(movementInfo)
  showGameOverScreen(crashInfo)
 
 
def showWelcomeAnimation():
 """Shows welcome screen animation of flappy bird"""
 # index of player to blit on screen
 playerIndex = 0
 playerIndexGen = cycle([0, 1, 2, 1])
 # iterator used to change playerIndex after every 5th iteration
 loopIter = 0
 
 #player所在位置
 playerx = int(SCREENWIDTH * 0.2)
 playery = int((SCREENHEIGHT - IMAGES['player'][0].get_height()) / 2)
 #欢迎图像所在位置
 messagex = int((SCREENWIDTH - IMAGES['message'].get_width()) / 2)
 messagey = int(SCREENHEIGHT * 0.12)
 
 basex = 0
 # amount by which base can maximum shift to left 可以最大限度地向左移动的距离
 baseShift = IMAGES['base'].get_width() - IMAGES['background'].get_width()
 
 # player shm for up-down motion on welcome screen 角色在欢迎屏幕上进行上下移动
 playerShmVals = {'val': 0, 'dir': 1}
 
 while True:
  for event in pygame.event.get():#使用pygame.event.get()来处理所有的事件,
   if event.type == QUIT or (event.type == KEYDOWN and event.key == K_ESCAPE):#如果 quit 或者 按键之后又按下esc,就结束游戏
    pygame.quit()
    sys.exit()
   if event.type == KEYDOWN and (event.key == K_SPACE or event.key == K_UP):#如果按键之后点击或者按下↑
    # make first flap sound and return values for mainGame
    SOUNDS['wing'].play()#播放飞的特效声音
    return {#返回初始位置 进入maingame
     'playery': playery + playerShmVals['val'],
     'basex': basex,
     'playerIndexGen': playerIndexGen,
    }
 
  # adjust playery, playerIndex, basex
  if (loopIter + 1) % 5 == 0:
   playerIndex = next(playerIndexGen)#获得匹配元素集合中每个元素紧邻的同胞元素 调整飞行姿势图片
  loopIter = (loopIter + 1) % 30
  basex = -((-basex + 4) % baseShift)
  playerShm(playerShmVals)
 
  # draw sprites
  #screen.blit(space, (0,0))可以绘制位图 第一个参数是加载完成的位图,第二个参数是绘制的起始坐标。
  SCREEN.blit(IMAGES['background'], (0,0))
  SCREEN.blit(IMAGES['player'][playerIndex],
     (playerx, playery + playerShmVals['val']))
  SCREEN.blit(IMAGES['message'], (messagex, messagey))
  SCREEN.blit(IMAGES['base'], (basex, BASEY))
 
  pygame.display.update()#更新整个窗口
  FPSCLOCK.tick(FPS)#循环应该多长时间运行一次
 
 
def mainGame(movementInfo):
 score = playerIndex = loopIter = 0#初始得分以及初始player的姿态以及迭代次数都为0
 playerIndexGen = movementInfo['playerIndexGen']#得到飞行姿势
 playerx, playery = int(SCREENWIDTH * 0.2), movementInfo['playery']#player所在位置
 
 basex = movementInfo['basex']#base图像所在位置
 baseShift = IMAGES['base'].get_width() - IMAGES['background'].get_width()
 
 # get 2 new pipes to add to upperPipes lowerPipes list
 newPipe1 = getRandomPipe()
 newPipe2 = getRandomPipe()
 
 # list of upper pipes
 upperPipes = [
  {'x': SCREENWIDTH + 200, 'y': newPipe1[0]['y']},
  {'x': SCREENWIDTH + 200 + (SCREENWIDTH / 2), 'y': newPipe2[0]['y']},
 ]
 
 # list of lowerpipe
 lowerPipes = [
  {'x': SCREENWIDTH + 200, 'y': newPipe1[1]['y']},
  {'x': SCREENWIDTH + 200 + (SCREENWIDTH / 2), 'y': newPipe2[1]['y']},
 ]
 
 pipeVelX = -4
 
 # player velocity, max velocity, downward accleration, accleration on flap 角色速度,最大速度,向下加速度,襟翼加速度
 playerVelY = -9 # player's velocity along Y, default same as playerFlapped
 playerMaxVelY = 10 # max vel along Y, max descend speed
 playerMinVelY = -8 # min vel along Y, max ascend speed
 playerAccY = 1 # players downward accleration
 playerRot  = 45 # player's rotation
 playerVelRot = 3 # angular speed
 playerRotThr = 20 # rotation threshold
 playerFlapAcc = -9 # players speed on flapping
 playerFlapped = False # True when player flaps
 
 
 while True:
  for event in pygame.event.get():
   if event.type == QUIT or (event.type == KEYDOWN and event.key == K_ESCAPE):
    pygame.quit()
    sys.exit()
   if event.type == KEYDOWN and (event.key == K_SPACE or event.key == K_UP):
    if playery > -2 * IMAGES['player'][0].get_height():#如果点击
     playerVelY = playerFlapAcc#上升
     playerFlapped = True
     SOUNDS['wing'].play()#并播放飞行音效
 
  # check for crash here
  crashTest = checkCrash({'x': playerx, 'y': playery, 'index': playerIndex},
        upperPipes, lowerPipes)
  if crashTest[0]:#如果掉在地上或者撞击到了管道,就返回结束游戏
   return {
    'y': playery,
    'groundCrash': crashTest[1],
    'basex': basex,
    'upperPipes': upperPipes,
    'lowerPipes': lowerPipes,
    'score': score,
    'playerVelY': playerVelY,
    'playerRot': playerRot
   }
 
  # check for score
  playerMidPos = playerx + IMAGES['player'][0].get_width() / 2
  for pipe in upperPipes:
   pipeMidPos = pipe['x'] + IMAGES['pipe'][0].get_width() / 2
   if pipeMidPos <= playerMidPos < pipeMidPos + 4:#当角色达到管道缝隙的中间+4时,score+1,并且在此时播放得分音效
    score += 1
    SOUNDS['point'].play()
 
  # playerIndex basex change
  if (loopIter + 1) % 3 == 0:
   playerIndex = next(playerIndexGen)
  loopIter = (loopIter + 1) % 30
  basex = -((-basex + 100) % baseShift)
 
  # rotate the player
  if playerRot > -90:
   playerRot -= playerVelRot
 
  # player's movement
  if playerVelY < playerMaxVelY and not playerFlapped:
   playerVelY += playerAccY
  if playerFlapped:
   playerFlapped = False
 
   # more rotation to cover the threshold (calculated in visible rotation)
   playerRot = 45
 
  playerHeight = IMAGES['player'][playerIndex].get_height()
  playery += min(playerVelY, BASEY - playery - playerHeight)
 
  # move pipes to left
  for uPipe, lPipe in zip(upperPipes, lowerPipes):
   uPipe['x'] += pipeVelX #管道移动
   lPipe['x'] += pipeVelX
 
  # add new pipe when first pipe is about to touch left of screen
  if 0 < upperPipes[0]['x'] < 5:#当第一个管道移动到屏幕左侧边缘时,生成下一个管道
   newPipe = getRandomPipe()
   upperPipes.append(newPipe[0])
   lowerPipes.append(newPipe[1])
 
  # remove first pipe if its out of the screen
  if upperPipes[0]['x'] < -IMAGES['pipe'][0].get_width(): #当管道移动到屏幕外侧后,删除它
   upperPipes.pop(0)
   lowerPipes.pop(0)
 
  # draw sprites
  SCREEN.blit(IMAGES['background'], (0,0))
 
  for uPipe, lPipe in zip(upperPipes, lowerPipes):
   SCREEN.blit(IMAGES['pipe'][0], (uPipe['x'], uPipe['y']))
   SCREEN.blit(IMAGES['pipe'][1], (lPipe['x'], lPipe['y']))
 
  SCREEN.blit(IMAGES['base'], (basex, BASEY))
  # print score so player overlaps the score
  showScore(score) #显示得分
 
  # Player rotation has a threshold
  visibleRot = playerRotThr
  if playerRot <= playerRotThr:
   visibleRot = playerRot
  
  playerSurface = pygame.transform.rotate(IMAGES['player'][playerIndex], visibleRot)#旋转角色
  SCREEN.blit(playerSurface, (playerx, playery))#显示旋转后的角色
 
  pygame.display.update()#更新窗口
  FPSCLOCK.tick(FPS)#循环应该多长时间运行一次
 
 
def showGameOverScreen(crashInfo):
 """crashes the player down ans shows gameover image"""
 score = crashInfo['score']#获取得分
 playerx = SCREENWIDTH * 0.2
 playery = crashInfo['y']
 playerHeight = IMAGES['player'][0].get_height()
 playerVelY = crashInfo['playerVelY']
 playerAccY = 2
 playerRot = crashInfo['playerRot']
 playerVelRot = 7
 
 basex = crashInfo['basex']
 
 upperPipes, lowerPipes = crashInfo['upperPipes'], crashInfo['lowerPipes']
 
 # play hit and die sounds
 SOUNDS['hit'].play()
 if not crashInfo['groundCrash']:#如果没有撞击到地面,就播放die音效就可以了
  SOUNDS['die'].play()
 
 while True:
  for event in pygame.event.get():
   if event.type == QUIT or (event.type == KEYDOWN and event.key == K_ESCAPE):
    pygame.quit()
    sys.exit()
   if event.type == KEYDOWN and (event.key == K_SPACE or event.key == K_UP):
    if playery + playerHeight >= BASEY - 1:
     return
 
  # player y shift
  if playery + playerHeight < BASEY - 1:
   playery += min(playerVelY, BASEY - playery - playerHeight)
 
  # player velocity change
  if playerVelY < 15:
   playerVelY += playerAccY
 
  # rotate only when it's a pipe crash
  if not crashInfo['groundCrash']:
   if playerRot > -90:
    playerRot -= playerVelRot
 
  # draw sprites
  SCREEN.blit(IMAGES['background'], (0,0))
 
  for uPipe, lPipe in zip(upperPipes, lowerPipes):
   SCREEN.blit(IMAGES['pipe'][0], (uPipe['x'], uPipe['y']))
   SCREEN.blit(IMAGES['pipe'][1], (lPipe['x'], lPipe['y']))
 
  SCREEN.blit(IMAGES['base'], (basex, BASEY))
  showScore(score)
 
  playerSurface = pygame.transform.rotate(IMAGES['player'][1], playerRot)
  SCREEN.blit(playerSurface, (playerx,playery))
 
  FPSCLOCK.tick(FPS)
  pygame.display.update()
 
 
def playerShm(playerShm):
 """oscillates the value of playerShm['val'] between 8 and -8"""
 if abs(playerShm['val']) == 8:
  playerShm['dir'] *= -1
 
 if playerShm['dir'] == 1:
   playerShm['val'] += 1
 else:
  playerShm['val'] -= 1
 
 
def getRandomPipe():#随机生成随机高度的管道 ????????还需要看细节
 """returns a randomly generated pipe"""
 # y of gap between upper and lower pipe
 gapY = random.randrange(0, int(BASEY * 0.6 - PIPEGAPSIZE))
 gapY += int(BASEY * 0.2)
 pipeHeight = IMAGES['pipe'][0].get_height()
 pipeX = SCREENWIDTH + 10
 
 return [
  {'x': pipeX, 'y': gapY - pipeHeight}, # upper pipe
  {'x': pipeX, 'y': gapY + PIPEGAPSIZE}, # lower pipe
 ]
 
 
def showScore(score):
 """displays score in center of screen"""
 scoreDigits = [int(x) for x in list(str(score))]
 totalWidth = 0 # total width of all numbers to be printed
 
 for digit in scoreDigits:
  totalWidth += IMAGES['numbers'][digit].get_width()
 
 Xoffset = (SCREENWIDTH - totalWidth) / 2
 
 for digit in scoreDigits:
  SCREEN.blit(IMAGES['numbers'][digit], (Xoffset, SCREENHEIGHT * 0.1))#显示得分
  Xoffset += IMAGES['numbers'][digit].get_width()
 
 
def checkCrash(player, upperPipes, lowerPipes):
 """returns True if player collders with base or pipes."""
 pi = player['index']#飞行姿势
 player['w'] = IMAGES['player'][0].get_width()
 player['h'] = IMAGES['player'][0].get_height()
 
 # if player crashes into ground 掉在地上
 if player['y'] + player['h'] >= BASEY - 1:
  return [True, True] #返回
 else:
 
  playerRect = pygame.Rect(player['x'], player['y'],
      player['w'], player['h'])
  pipeW = IMAGES['pipe'][0].get_width()
  pipeH = IMAGES['pipe'][0].get_height()
 
  for uPipe, lPipe in zip(upperPipes, lowerPipes):
   # upper and lower pipe rects
   uPipeRect = pygame.Rect(uPipe['x'], uPipe['y'], pipeW, pipeH)
   lPipeRect = pygame.Rect(lPipe['x'], lPipe['y'], pipeW, pipeH)
 
   # player and upper/lower pipe hitmasks
   pHitMask = HITMASKS['player'][pi]
   uHitmask = HITMASKS['pipe'][0]
   lHitmask = HITMASKS['pipe'][1]
 
   # if bird collided with upipe or lpipe
   uCollide = pixelCollision(playerRect, uPipeRect, pHitMask, uHitmask)
   lCollide = pixelCollision(playerRect, lPipeRect, pHitMask, lHitmask)
 
   if uCollide or lCollide:#如果撞击到了上管道或者下管道 返回
    return [True, False]
 
 return [False, False]
 
def pixelCollision(rect1, rect2, hitmask1, hitmask2):
 """Checks if two objects collide and not just their rects"""
 rect = rect1.clip(rect2)#角色和管道之间重合的情况
 
 if rect.width == 0 or rect.height == 0:#没重合就是没撞击到
  return False
 
 x1, y1 = rect.x - rect1.x, rect.y - rect1.y
 x2, y2 = rect.x - rect2.x, rect.y - rect2.y
 
 for x in xrange(rect.width):
  for y in xrange(rect.height):
   if hitmask1[x1+x][y1+y] and hitmask2[x2+x][y2+y]:#撞击到了
    return True
 return False
 
def getHitmask(image):
 """returns a hitmask using an image's alpha."""
 #得到撞击mask
 mask = []
 for x in xrange(image.get_width()):
  mask.append([])
  for y in xrange(image.get_height()):
   mask[x].append(bool(image.get_at((x,y))[3]))
 return mask
 
if __name__ == '__main__':
 main()

游戏截图:

python实现Flappy Bird源码

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持服务器之家。

原文链接:https://blog.csdn.net/sgfmby1994/article/details/77981621