当前位置 博文首页 > 文章内容

    pygame面向对象的飞行小鸟实现(Flappy bird)

    作者:shunshunshun18 栏目:未分类 时间:2021-04-01 14:42:25

    本站于2023年9月4日。收到“大连君*****咨询有限公司”通知
    说我们IIS7站长博客,有一篇博文用了他们的图片。
    要求我们给他们一张图片6000元。要不然法院告我们

    为避免不必要的麻烦,IIS7站长博客,全站内容图片下架、并积极应诉
    博文内容全部不再显示,请需要相关资讯的站长朋友到必应搜索。谢谢!

    另祝:版权碰瓷诈骗团伙,早日弃暗投明。

    相关新闻:借版权之名、行诈骗之实,周某因犯诈骗罪被判处有期徒刑十一年六个月

    叹!百花齐放的时代,渐行渐远!



    一些想法

    自学python已经有快三个月了 最近这段时间没有怎么写过python 很多东西反而又遗忘了 准备翻以前的笔记复习一下在博客上记录下来 自己也没能够做出什么厉害的东西 小鸟游戏算是目前自己写的最好的一个代码了

    基本游戏界面就是这样

    在这里插入图片描述

    分析需要的功能

    我的构思是将游戏分成三个部分

    • 初始游戏菜单界面
    • 游戏进行界面
    • 游戏结束界面

    游戏里的角色和道具则使用类

    • 小鸟类
    • 管道类

    因为是使用pygame模块 我对这个模块也很不熟悉 很多功能都是论坛参考其他大神的 比如

    pygame.transform 里面的各种变化功能
    pygame.sprite 精灵模块里面的方法
    

    构建整体框架

    1.导入pygame和random

    pygame拥有丰富的制作游戏的功能

    random是随机模块 游戏里各种随机事件就是通过这个模块功能实现

    import pygame
    import random

    2.我们写一个小的项目之前 需要将每个功能分成不同的代码块

    定义的变量都写到最上面

    MAP_WIDTH = 288 # 地图大小
    MAP_HEIGHT = 512
    FPS = 30 # 刷新率
    PIPE_GAPS = [110, 120, 130, 140, 150, 160] # 缺口的距离 有这6个随机距离
    
    # 写的途中的全局变量都可以写在最上面 
    

    全局变量我一般喜欢使用大写来区分

    3.游戏窗口的设置

    pygame.init() # 进行初始化
    SCREEN = pygame.display.set_mode((MAP_WIDTH, MAP_HEIGHT)) # 屏幕大小
    pygame.display.set_caption('飞行小鸟') # 标题
    CLOCK = pygame.time.Clock() 
    

    4.加载素材
    加载游戏图片和音乐

    SPRITE_FILE = './images'
    IMAGES = {}
    IMAGES['guide'] = pygame.image.load(SPRITE_FILE + 'guide.png')
    IMAGES['gameover'] = pygame.image.load(SPRITE_FILE + 'gameover.png')
    IMAGES['floor'] = pygame.image.load(SPRITE_FILE + 'floor.png')
    
    SPRITE_SOUND = './audio/'
    SOUNDS = {} 
    SOUNDS['start'] = pygame.mixer.Sound(SPRITE_SOUND + 'start.wav')
    SOUNDS['die'] = pygame.mixer.Sound(SPRITE_SOUND + 'die.wav')
    SOUNDS['hit'] = pygame.mixer.Sound(SPRITE_SOUND + 'hit.wav')
    SOUNDS['score'] = pygame.mixer.Sound(SPRITE_SOUND + 'score.wav')
    

    5.执行函数
    就是执行程序的函数

    def main():
    		menu_window()
        result = game_window()
        end_window(result)
    

    6.程序入口

    if __name__ == '__main__':
      main()
    

    7.我将游戏分成了三个界面

    • 初始游戏菜单界面
    • 游戏进行界面
    • 游戏结束界面
    def menu_window():
    	pass
    
    def game_window():
    	pass
    
    def end_window(result):
    	pass
    
    # 这里就是写运行三种游戏界面的代码
    

    8.因为要显示游戏得分

    所以专门写一个方法在游戏主界面代码里面直接调用这个方法 让代码不会显得冗余

    9.最后就是我们游戏角色和道具的类方法

    • 小鸟类
    • 管道类
    class Bird(pygame.sprite.Sprite):
      def __init__(self, x, y):
        # super(Bird, self).__init__(x, y)
        pygame.sprite.Sprite.__init__(self)
        pass
    
      def update(self, flap=False):
        pass
    
      def go_die(self):
        pass
    
    class Pipe(pygame.sprite.Sprite):
      def __init__(self, x, y, upwards=True):
        pygame.sprite.Sprite.__init__(self)
        pass
    
      def update(self):
        pass
    

    我们把整体框架搭建好之后 就可以着手完善代码

    着手完整代码

    """
    Project: pygame
    Creator: stan Z
    Create time: 2021-03-08 19:37
    IDE: PyCharm
    Introduction:
    """
    import pygame
    import random
    
    ######################################## 定义变量
    MAP_WIDTH = 288 # 地图大小
    MAP_HEIGHT = 512
    FPS = 30 # 刷新率
    PIPE_GAPS = [90, 100, 110, 120, 130, 140] # 缺口的距离 有这6个随机距离
    # PIPE_GAPS1 = []
    PIPE_HEIGHT_RANGE = [int(MAP_HEIGHT * 0.3), int(MAP_HEIGHT * 0.7)] # 管道长度范围
    PIPE_DISTANCE = 120 # 管道之间距离
    
    ######################################## 游戏基本设置
    pygame.init() # 进行初始化
    SCREEN = pygame.display.set_mode((MAP_WIDTH, MAP_HEIGHT)) # 调用窗口设置屏幕大小
    pygame.display.set_caption('飞行小鸟byStanZ') # 标题
    CLOCK = pygame.time.Clock() # 建立时钟
    
    ######################################## 加载素材
    SPRITE_FILE = './images'
    # 列表推导式 获得三种不同的鸟和三种状态
    BIRDS = [[f'{SPRITE_FILE}{bird}-{move}.png' for move in ['up', 'mid', 'down']] for bird in ['red', 'blue', 'yellow']]
    BGPICS = [SPRITE_FILE + 'day.png', SPRITE_FILE + 'night.png']
    PIPES = [SPRITE_FILE + 'green-pipe.png', SPRITE_FILE + 'red-pipe.png']
    NUMBERS = [f'{SPRITE_FILE}{n}.png' for n in range(10)]
    
    # 将图片设置成一个大字典 里面通过key-value存不同的场景图
    IMAGES = {}
    IMAGES['numbers'] = [pygame.image.load(number) for number in NUMBERS] # 数字素材有10张 因此遍历
    IMAGES['guide'] = pygame.image.load(SPRITE_FILE + 'guide.png')
    IMAGES['gameover'] = pygame.image.load(SPRITE_FILE + 'gameover.png')
    IMAGES['floor'] = pygame.image.load(SPRITE_FILE + 'floor.png')
    
    # 地板的高是一个很常用的变量 因此我们专门拿出来
    FLOOR_H = MAP_HEIGHT - IMAGES['floor'].get_height() # 屏幕高减去floor图片的高 就是他在屏幕里的位置
    
    SPRITE_SOUND = './sound'
    SOUNDS = {} # 同理声音素材也这样做
    SOUNDS['start'] = pygame.mixer.Sound(SPRITE_SOUND + 'start.wav')
    SOUNDS['die'] = pygame.mixer.Sound(SPRITE_SOUND + 'die.wav')
    SOUNDS['hit'] = pygame.mixer.Sound(SPRITE_SOUND + 'hit.wav')
    SOUNDS['score'] = pygame.mixer.Sound(SPRITE_SOUND + 'score.wav')
    SOUNDS['flap'] = pygame.mixer.Sound(SPRITE_SOUND + 'flap.wav')
    SOUNDS['death'] = pygame.mixer.Sound(SPRITE_SOUND + 'death.wav')
    SOUNDS['main'] = pygame.mixer.Sound(SPRITE_SOUND + 'main_theme.ogg')
    SOUNDS['world_clear'] = pygame.mixer.Sound(SPRITE_SOUND + 'world_clear.wav')
    
    
    # 执行函数
    def main():
      while True:
        IMAGES['bgpic'] = pygame.image.load(random.choice(BGPICS)) # random的choice方法可以随机从列表里返回一个元素 白天或者黑夜
        IMAGES['bird'] = [pygame.image.load(frame) for frame in random.choice(BIRDS)] # 列表推导式 鸟也是随机
        pipe = pygame.image.load(random.choice(PIPES))
        IMAGES['pipe'] = [pipe, pygame.transform.flip(pipe, False, True)] # flip是翻转 将管道放下面和上面 Flase水平不动,True上下翻转
        SOUNDS['start'].play()
        # SOUNDS['main'].play()
        menu_window()
        result = game_window()
        end_window(result)
    
    
    def menu_window():
      SOUNDS['world_clear'].play()
      floor_gap = IMAGES['floor'].get_width() - MAP_WIDTH # 地板间隙 336 - 288 = 48
      floor_x = 0
    
      # 标题位置
      guide_x = (MAP_WIDTH - IMAGES['guide'].get_width()) / 2
      guide_y = MAP_HEIGHT * 0.12
    
      # 小鸟位置
      bird_x = MAP_WIDTH * 0.2
      bird_y = MAP_HEIGHT * 0.5 - IMAGES['bird'][0].get_height() / 2
      bird_y_vel = 1 # 小鸟飞行的速率 按y坐标向下
      max_y_shift = 50 # 小鸟飞行的最大幅度
      y_shift = 0 # 小鸟起始幅度为0
    
      idx = 0 # 小鸟翅膀煽动频率
      frame_seq = [0] * 5 + [1] * 5 + [2] * 5 + [1] * 5 # 控制小鸟翅膀运动上中下
    
      while True:
        for event in pygame.event.get(): # 监控行为
          if event.type == pygame.QUIT:
            quit()
          elif event.type == pygame.KEYDOWN and event.key == pygame.K_SPACE:
            return
    
        if floor_x <= -floor_gap: # 当地板跑到最大间隔的时候
          floor_x = floor_x + floor_gap # 刷新地板的x轴
        else:
          floor_x -= 4 # 地板 x轴的移动速度
    
        if abs(y_shift) == max_y_shift: # 如果y_shift的绝对值 = 最大幅度
          bird_y_vel *= -1 # 调转方向飞 同时飞行速度为1
        else:
          bird_y += bird_y_vel
        y_shift += bird_y_vel # 小鸟y轴正负交替 上下飞
    
        # 小鸟翅膀
        idx += 1 # 翅膀煽动频率
        idx %= len(frame_seq) # 通过取余得到 0 1 2
        frame_index = frame_seq[idx] # 小鸟图片的下标 就是翅膀的状态
    
        SCREEN.blit(IMAGES['bgpic'], (0, 0))
        SCREEN.blit(IMAGES['floor'], (floor_x, FLOOR_H))
        SCREEN.blit(IMAGES['guide'], (guide_x, guide_y))
        SCREEN.blit(IMAGES['bird'][frame_index], (bird_x, bird_y))
    
        pygame.display.update()
        CLOCK.tick(FPS) # 以每秒30帧刷新屏幕
    
    
    def game_window():
      SOUNDS['world_clear'].stop()
      SOUNDS['main'].play()
      score = 0
    
      floor_gap = IMAGES['floor'].get_width() - MAP_WIDTH # 地板间隙 336 - 288 = 48
      floor_x = 0
    
      # 小鸟位置
      bird_x = MAP_WIDTH * 0.2
      bird_y = MAP_HEIGHT * 0.5 - IMAGES['bird'][0].get_height() / 2
      bird = Bird(bird_x, bird_y)
    
      n_pair = round(MAP_WIDTH / PIPE_DISTANCE) # 四舍五入取整数 屏幕宽度/两个管道之间的距离 这个距离时候刷新第二个管道 2.4
      pipe_group = pygame.sprite.Group() # 是一个集合
    
      # 生成前面的管道
      pipe_x = MAP_WIDTH
      pipe_y = random.randint(PIPE_HEIGHT_RANGE[0], PIPE_HEIGHT_RANGE[1]) # 管道长度随机从153.6 到 358.4
      pipe1 = Pipe(pipe_x, pipe_y, upwards=True) # 创建一个管道对象
      pipe_group.add(pipe1) # 将对象添加到这个精灵集合里面
      pipe2 = Pipe(pipe_x, pipe_y - random.choice(PIPE_GAPS), upwards=False) # 翻转的管道
      pipe_group.add(pipe2)
    
      SOUNDS['flap'].play()
    
      while True:
        flap = False
    
        for event in pygame.event.get():
          if event.type == pygame.QUIT:
            quit()
          elif event.type == pygame.KEYDOWN and event.key == pygame.K_SPACE: # 空格拍翅膀
            SOUNDS['flap'].play()
            flap = True
    
        bird.update(flap)
    
        if floor_x <= -floor_gap: # 当地板跑到最大间隔的时候
          floor_x = floor_x + floor_gap # 刷新地板的x轴
        else:
          floor_x -= 4 # 地板 x轴的移动速度
    
        # 生成最后一个管道
        if len(pipe_group) / 2 < n_pair: # 当管道组长度<2.4 时 意思就是两个半管道的时候
          # sprites()将管道组返回成列表
          last_pipe = pipe_group.sprites()[-1]
          pipe_x = last_pipe.rect.right + PIPE_DISTANCE
          pipe_y = random.randint(PIPE_HEIGHT_RANGE[0], PIPE_HEIGHT_RANGE[1])
          pipe1 = Pipe(pipe_x, pipe_y, upwards=True)
          pipe_group.add(pipe1)
          pipe2 = Pipe(pipe_x, pipe_y - random.choice(PIPE_GAPS), upwards=False)
          pipe_group.add(pipe2)
    
        pipe_group.update()
        # 鸟的矩形y坐标如果大于地板的高度 就死亡
        # pygame.sprite.spritecollideany 碰撞函数 如果bird和pipe_group碰撞了 就死亡
        if bird.rect.y > FLOOR_H or bird.rect.y < 0 or pygame.sprite.spritecollideany(bird, pipe_group):
          SOUNDS['score'].stop()
          SOUNDS['main'].stop()
          SOUNDS['hit'].play()
          SOUNDS['die'].play()
          SOUNDS['death'].play()
          # 保存死亡时的鸟儿 分数 管道 继续显示在结束窗口
          result = {'bird': bird, 'score': score, 'pipe_group': pipe_group}
          return result
    
        # 当小鸟左边大于 管道右边就得分
        if pipe_group.sprites()[0].rect.left == 0:
          SOUNDS['score'].play()
          score += 1
    
        SCREEN.blit(IMAGES['bgpic'], (0, 0))
        pipe_group.draw(SCREEN)
        SCREEN.blit(IMAGES['floor'], (floor_x, FLOOR_H))
        SCREEN.blit(bird.image, bird.rect)
        show_score(score)
        pygame.display.update()
        CLOCK.tick(FPS)
    
    
    def end_window(result):
      # 显示gameover的图片
      gameover_x = MAP_WIDTH * 0.5 - IMAGES['gameover'].get_width() / 2
      gameover_y = MAP_HEIGHT * 0.4
      bird = result['bird']
      pipe_group = result['pipe_group']
    
      while True:
        for event in pygame.event.get():
          if event.type == pygame.QUIT:
            quit()
          elif event.type == pygame.KEYDOWN and event.key == pygame.K_SPACE and bird.rect.y > FLOOR_H:
            SOUNDS['death'].stop()
            return
    
        # 使用类go_die方法 鸟儿撞墙后 旋转往下
        bird.go_die()
        SCREEN.blit(IMAGES['bgpic'], (0, 0))
        pipe_group.draw(SCREEN)
        SCREEN.blit(IMAGES['floor'], (0, FLOOR_H))
        SCREEN.blit(IMAGES['gameover'], (gameover_x, gameover_y))
        show_score(result['score'])
        SCREEN.blit(bird.image, bird.rect)
        pygame.display.update()
        CLOCK.tick(FPS)
    
    
    # 显示得分
    def show_score(score):
      score_str = str(score)
      w = IMAGES['numbers'][0].get_width()
      x = MAP_WIDTH / 2 - 2 * w / 2
      y = MAP_HEIGHT * 0.1
      for number in score_str: # IMAGES['numbers'] = [pygame.image.load(number) for number in NUMBERS]
        SCREEN.blit(IMAGES['numbers'][int(number)], (x, y))
        x += w
    
    
    class Bird(pygame.sprite.Sprite):
      def __init__(self, x, y):
        # super(Bird, self).__init__(x, y)
        pygame.sprite.Sprite.__init__(self)
        self.frames = IMAGES['bird'] # 鸟儿框架
        self.frame_list = [0] * 5 + [1] * 5 + [2] * 5 + [1] * 5 # 控制小鸟翅膀运动上中下
        self.frame_index = 0
        self.image = self.frames[self.frame_list[self.frame_index]] # 和菜单界面小鸟扇翅膀一个原理
        self.rect = self.image.get_rect() # 鸟儿的矩形
        self.rect.x = x
        self.rect.y = y
        self.gravity = 1 # 重力
        self.flap_acc = -10 # 翅膀拍打往上飞 y坐标-10
        self.y_vel = -10 # y坐标的速度
        self.max_y_vel = 15 # y轴下落最大速度
        self.rotate = 0 # 脑袋朝向
        self.rotate_vel = -3 # 转向速度
        self.max_rotate = -30 # 最大转向速度
        self.flap_rotate = 45 # 按了空格只会脑袋朝向上30度
    
      def update(self, flap=False):
        if flap:
          self.y_vel = self.flap_acc # 拍打翅膀 则y速度-10向上
          self.rotate = self.flap_rotate
        else:
          self.rotate = self.rotate + self.rotate_vel
    
        self.y_vel = min(self.y_vel + self.gravity, self.max_y_vel)
        self.rect.y += self.y_vel # 小鸟向上移动的距离
        self.rorate = max(self.rotate + self.rotate_vel, self.max_rotate)
    
        self.frame_index += 1 # 扇翅膀的速率
        self.frame_index %= len(self.frame_list) # 0~20
        self.image = self.frames[self.frame_list[self.frame_index]]
        self.image = pygame.transform.rotate(self.image, self.rotate) # transform变形方法 旋转
    
      def go_die(self):
        if self.rect.y < FLOOR_H:
          self.y_vel = self.max_y_vel
          self.rect.y += self.y_vel
          self.rotate = -90
          self.image = self.frames[self.frame_list[self.frame_index]]
          self.image = pygame.transform.rotate(self.image, self.rotate)
    
    
    # 管道类
    class Pipe(pygame.sprite.Sprite):
      def __init__(self, x, y, upwards=True):
        pygame.sprite.Sprite.__init__(self)
        self.x_vel = -4 # 管道移动速度
        # 默认属性为真 则是正向管道
        if upwards:
          self.image = IMAGES['pipe'][0]
          self.rect = self.image.get_rect()
          self.rect.x = x
          self.rect.top = y
        # 利用flip方法 旋转管道成为反向管道
        else:
          self.image = IMAGES['pipe'][1]
          self.rect = self.image.get_rect()
          self.rect.x = x
          self.rect.bottom = y
    
      def update(self):
        self.rect.x += self.x_vel # 管道x轴加移动速度
        if self.rect.right < 0:
          self.kill()
    
    
    if __name__ == '__main__':
      main()