当前位置: 移动技术网 > IT编程>脚本编程>Python > python实现Flappy Bird源码

python实现Flappy Bird源码

2019年01月04日  | 移动技术网IT编程  | 我要评论

魔法留学生国语版,董舜文,北京缓解拥堵网摇号结果查询

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

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

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

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

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

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

注意:需要安装pygame模块

代码:

# -*- 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/yellowbird-upflap.png',
  'assets/sprites/yellowbird-midflap.png',
  'assets/sprites/yellowbird-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()

游戏截图:

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持移动技术网。

如对本文有疑问,请在下面进行留言讨论,广大热心网友会与你互动!! 点击进行留言回复

相关文章:

验证码:
移动技术网