python学习笔记第十三天------------飞机大战项目(二)

飞机大战项目(二)

  • 1.项目实战 —— 飞机大战
  • 2.英雄登场
  • 3.游戏背景
  • 4.游戏框架搭建


  • 1.项目实战 —— 飞机大战

    1.1 确认模块 —— pygame

    • pygame 就是一个 Python 模块,专为电子游戏设计
    • 官方网站:https://www.pygame.org/
      • 提示:要学习第三方模块,通常最好的参考资料就在官方网站
    网站栏目 内容
    GettingStarted 在各平台安装模块的说明
    Docs pygame 模块所有 子类 的参考手册

    安装 pygame

    $ sudo pip3 install pygame
    

    验证安装

    $ python3 -m pygame.examples.aliens
    

    2 英雄登场

    目标

    • 设计 英雄子弹
    • 使用 pygame.key.get_pressed() 移动英雄
    • 发射子弹

    2.1 设计 英雄子弹

    英雄需求

    1. 游戏启动后,英雄 出现在屏幕的 水平中间 位置,距离 屏幕底部 120 像素
    2. 英雄 每隔 0.5 秒发射一次子弹,每次 连发三枚子弹
    3. 英雄 默认不会移动,需要通过 左/右 方向键,控制 英雄 在水平方向移动

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6mpFg3CH-1580112219172)(media/15025349250200/017_%E8%8B%B1%E9%9B%84%E4%BD%8D%E7%BD%AE.png)]

    子弹需求

    1. 子弹英雄 的正上方发射 沿直线上方 飞行
    2. 飞出屏幕后,需要从 精灵组 中删除

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-B4s2Tfnw-1580112219173)(media/15025349250200/016_%E6%B4%BE%E7%94%9F%E8%8B%B1%E9%9B%84%E5%92%8C%E5%AD%90%E5%BC%B9%E5%AD%90%E7%B1%BB.png)]

    Hero —— 英雄

    • 初始化方法
      • 指定 英雄图片
      • 初始速度 = 0 —— 英雄默认静止不动
      • 定义 bullets 子弹精灵组 保存子弹精灵
    • 重写 update() 方法
      • 英雄需要 水平移动
      • 并且需要保证不能 移出屏幕
    • 增加 bullets 属性,记录所有 子弹精灵
    • 增加 fire 方法,用于发射子弹

    Bullet —— 子弹

    • 初始化方法
      • 指定 子弹图片
      • 初始速度 = -2 —— 子弹需要向上方飞行
    • 重写 update() 方法
      • 判断 是否飞出屏幕,如果是,从 精灵组 删除

    2.2 创建英雄

    2.2.1 准备英雄类

    • plane_sprites 新建 Hero
    • 重写 初始化方法,直接指定 图片名称,并且将初始速度设置为 0
    • 设置 英雄的初始位置

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MD5NCumL-1580112219176)(media/15025349250200/003_pygame.Rect.png)]

    • centerx = x + 0.5 * width
    • centery = y + 0.5 * height
    • bottom = y + height

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xcYgITZO-1580112219177)(media/15025349250200/017_%E8%8B%B1%E9%9B%84%E4%BD%8D%E7%BD%AE.png)]

    class Hero(GameSprite):
        """英雄精灵"""
    
        def __init__(self):
    
            super().__init__("./images/me1.png", 0)
    
            # 设置初始位置
            self.rect.centerx = SCREEN_RECT.centerx
            self.rect.bottom = SCREEN_RECT.bottom - 120
    

    2.3 绘制英雄

    1. __create_sprites,添加 英雄精灵英雄精灵组
      • 后续要针对 英雄碰撞检测 以及 发射子弹
      • 所以 英雄 需要 单独定义成属性
    2. __update_sprites,让 英雄精灵组 调用 updatedraw 方法

    代码实现

    • 修改 __create_sprites 方法如下:
    # 英雄组
    self.hero = Hero()
    self.hero_group = pygame.sprite.Group(self.hero)
    
    • 修改 __update_sprites 方法如下:
    self.hero_group.update()
    self.hero_group.draw(self.screen)
    

    2.4 移动英雄位置

    pygame 中针对 键盘按键的捕获,有 两种 方式

    • 第一种方式 判断 event.type == pygame.KEYDOWN
    • 第二种方式
      1. 首先使用 pygame.key.get_pressed() 返回 所有按键元组
      2. 通过 键盘常量,判断元组中 某一个键是否被按下 —— 如果被按下,对应数值为 1

    提问 这两种方式之间有什么区别呢?

    • 第一种方式
    elif event.type == pygame.KEYDOWN and event.key == pygame.K_RIGHT:
        print("向右移动...")
    
    • 第二种方式
    # 返回所有按键的元组,如果某个键被按下,对应的值会是1
    keys_pressed = pygame.key.get_pressed()
    # 判断是否按下了方向键
    if keys_pressed[pygame.K_RIGHT]:
        print("向右移动...")
    

    结论

    • 第一种方式 event.type 用户 必须要抬起按键 才算一次 按键事件操作灵活性会大打折扣
    • 第二种方式 用户可以按住方向键不放,就能够实现持续向某一个方向移动了,操作灵活性更好

    2.4.1 移动英雄位置

    演练步骤

    1. Hero 类中重写 update 方法
      • 速度 speed英雄 rect.x 进行叠加
      • 不需要调用父类方法 —— 父类方法只是实现了单纯的垂直运动
    2. __event_handler 方法中根据 左右方向键 设置英雄的 速度
      • 向右 => speed = 2
      • 向左 => speed = -2
      • 其他 => speed = 0

    代码演练

    • Hero 类,重写 update() 方法,根据速度水平移动 英雄的飞机
    def update(self):
    
        # 飞机水平移动
        self.rect.x += self.speed
    
    • 调整键盘按键代码
    # 获取用户按键
    keys_pressed = pygame.key.get_pressed()
    
    if keys_pressed[pygame.K_RIGHT]:
        self.hero.speed = 2
    elif keys_pressed[pygame.K_LEFT]:
        self.hero.speed = -2
    else:
        self.hero.speed = 0
    

    2.4.2 控制英雄运动边界

    • Hero 类的 update() 方法判断 英雄 是否超出 屏幕边界

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HZr89smI-1580112219177)(media/15025349250200/003_pygame.Rect.png)]

    • right = x + width 利用 right 属性可以非常容易的针对右侧设置精灵位置

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pWEKYU48-1580112219178)(media/15025349250200/017_%E8%8B%B1%E9%9B%84%E4%BD%8D%E7%BD%AEII.png)]

    def update(self):
    
        # 飞机水平移动
        self.rect.x += self.speed
        
        # 判断屏幕边界
        if self.rect.left < 0:
            self.rect.left = 0
        if self.rect.right > SCREEN_RECT.right:
            self.rect.right = SCREEN_RECT.right
    

    2.5 发射子弹

    需求回顾 —— 英雄需求

    1. 游戏启动后,英雄 出现在屏幕的 水平中间 位置,距离 屏幕底部 120 像素
    2. 英雄 每隔 0.5 秒发射一次子弹,每次 连发三枚子弹
    3. 英雄 默认不会移动,需要通过 左/右 方向键,控制 英雄 在水平方向移动

    2.5.1 添加发射子弹事件

    pygame定时器 使用套路非常固定:

    1. 定义 定时器常量 —— eventid
    2. 初始化方法 中,调用 set_timer 方法 设置定时器事件
    3. 游戏循环 中,监听定时器事件

    代码实现

    • Hero 中定义 fire 方法
    def fire(self):
        print("发射子弹...")
    
    • plane_main.py 的顶部定义 发射子弹 事件常量
    # 英雄发射子弹事件
    HERO_FIRE_EVENT = pygame.USEREVENT + 1
    
    • __init__ 方法末尾中添加 发射子弹 事件
    # 每隔 0.5 秒发射一次子弹
    pygame.time.set_timer(HERO_FIRE_EVENT, 500)
    
    • __event_handler 方法中让英雄发射子弹
    elif event.type == HERO_FIRE_EVENT:
        self.hero.fire()
    

    2.5.2 定义子弹类

    需求回顾 —— 子弹需求

    1. 子弹英雄 的正上方发射 沿直线上方 飞行
    2. 飞出屏幕后,需要从 精灵组 中删除

    Bullet —— 子弹

    • 初始化方法
      • 指定 子弹图片
      • 初始速度 = -2 —— 子弹需要向上方飞行
    • 重写 update() 方法
      • 判断 是否飞出屏幕,如果是,从 精灵组 删除

    定义子弹类

    • plane_sprites 新建 Bullet 继承自 GameSprite
    • 重写 初始化方法,直接指定 图片名称,并且设置 初始速度
    • 重写 update() 方法,判断子弹 飞出屏幕从精灵组删除
    class Bullet(GameSprite):
        """子弹精灵"""
        
        def __init__(self):
            
            super().__init__("./images/bullet1.png", -2)
     
        def update(self):
        
            super().update()
            
            # 判断是否超出屏幕,如果是,从精灵组删除
            if self.rect.bottom < 0:
                self.kill()
    

    2.6 发射子弹

    演练步骤

    1. Hero初始化方法 中创建 子弹精灵组 属性
    2. 修改 plane_main.py__update_sprites 方法,让 子弹精灵组 调用 updatedraw 方法
    3. 实现 fire() 方法
      • 创建子弹精灵
      • 设置初始位置 —— 在 英雄的正上方
      • 子弹 添加到精灵组

    代码实现

    • 初始化方法
    # 创建子弹的精灵组
    self.bullets = pygame.sprite.Group()
    
    • 修改 fire() 方法
    def fire(self):
    
        # 1. 创建子弹精灵
        bullet = Bullet()
        
        # 2. 设置精灵的位置
        bullet.rect.bottom = self.rect.y - 20
        bullet.rect.centerx = self.rect.centerx
        
        # 3. 将精灵添加到精灵组
        self.bullets.add(bullet)
    

    一次发射三枚子弹

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0FKa1J95-1580112219179)(media/15025349250200/017_%E8%8B%B1%E9%9B%84%E4%BD%8D%E7%BD%AEIII.png)]

    • 修改 fire() 方法,一次发射三枚子弹
    def fire(self):
    
        for i in (1, 2, 3):
            # 1. 创建子弹精灵
            bullet = Bullet()
            
            # 2. 设置精灵的位置
            bullet.rect.bottom = self.rect.y - i * 20
            bullet.rect.centerx = self.rect.centerx
            
            # 3. 将精灵添加到精灵组
            self.bullets.add(bullet)
    

    3.游戏背景

    3.1 背景交替滚动的思路确定

    运行 备课代码观察 背景图像的显示效果:

    • 游戏启动后,背景图像连续不断地 向下方 移动
    • 视觉上 产生英雄的飞机不断向上方飞行的 错觉 —— 在很多跑酷类游戏中常用的套路
      • 游戏的背景 不断变化
      • 游戏的主角 位置保持不变

    3.1.1 实现思路分析

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4XCWIai3-1580112245054)(media/15025262948537/013_%E8%83%8C%E6%99%AF%E5%9B%BE%E7%89%87%E4%BA%A4%E6%9B%BF%E6%BB%9A%E5%8A%A8.png)]

    解决办法

    1. 创建两张背景图像精灵
      • 1完全和屏幕重合
      • 2 张在 屏幕的正上方
    2. 两张图像 一起向下方运动
      • self.rect.y += self.speed
    3. 任意背景精灵rect.y >= 屏幕的高度 说明已经 移动到屏幕下方
    4. 移动到屏幕下方的这张图像 设置到 屏幕的正上方
      • rect.y = -rect.height

    3.1.2 设计背景类

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PHgJgXBx-1580112245054)(media/15025262948537/012_%E6%B4%BE%E7%94%9FBackground%E5%AD%90%E7%B1%BB.png)]

    • 初始化方法
      • 直接指定 背景图片
      • is_alt 判断是否是另一张图像
        • False 表示 第一张图像,需要与屏幕重合
        • True 表示 另一张图像,在屏幕的正上方
    • update() 方法
      • 判断 是否移动出屏幕,如果是,将图像设置到 屏幕的正上方,从而实现 交替滚动

    继承 如果父类提供的方法,不能满足子类的需求:

    • 派生一个子类
    • 在子类中针对特有的需求,重写父类方法,并且进行扩展

    3.2 显示游戏背景

    3.2.1 背景精灵的基本实现

    • plane_sprites 新建 Background 继承自 GameSprite
    class Background(GameSprite):
        """游戏背景精灵"""
    
        def update(self):
    
            # 1. 调用父类的方法实现
            super().update()
    
            # 2. 判断是否移出屏幕,如果移出屏幕,将图像设置到屏幕的上方
            if self.rect.y >= SCREEN_RECT.height:
                self.rect.y = -self.rect.height
    
    

    3.2.2 在 plane_main.py 中显示背景精灵

    1. __create_sprites 方法中创建 精灵精灵组
    2. __update_sprites 方法中,让 精灵组 调用 update()draw() 方法

    __create_sprites 方法

    def __create_sprites(self):
    
        # 创建背景精灵和精灵组
        bg1 = Background("./images/background.png")
        bg2 = Background("./images/background.png")
        bg2.rect.y = -bg2.rect.height
        
        self.back_group = pygame.sprite.Group(bg1, bg2)
    

    __update_sprites 方法

    def __update_sprites(self):
    
        self.back_group.update()
        self.back_group.draw(self.screen)
    

    3.2.3 利用初始化方法,简化背景精灵创建

    思考 —— 上一小结完成的代码存在什么样的问题?能否简化?

    • 在主程序中,创建的两个背景精灵传入了相同的图像文件路径
    • 创建 第二个 背景精灵 时,在主程序中,设置背景精灵的图像位置

    思考 —— 精灵 初始位置 的设置,应该 由主程序负责?还是 由精灵自己负责

    答案 —— 由精灵自己负责

    • 根据面向对象设计原则,应该将对象的职责,封装到类的代码内部
    • 尽量简化程序调用一方的代码调用

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oDBX3Jmh-1580112245055)(media/15025262948537/012_%E6%B4%BE%E7%94%9FBackground%E5%AD%90%E7%B1%BB.png)]

    • 初始化方法
      • 直接指定 背景图片
      • is_alt 判断是否是另一张图像
        • False 表示 第一张图像,需要与屏幕重合
        • True 表示 另一张图像,在屏幕的正上方

    plane_sprites.py 中实现 Background初始化方法

    def __init__(self, is_alt=False):
    
        image_name = "./images/background.png"
        super().__init__(image_name)
           
        # 判断是否交替图片,如果是,将图片设置到屏幕顶部
        if is_alt:
            self.rect.y = -self.rect.height     
    
    • 修改 plane_main__create_sprites 方法
    # 创建背景精灵和精灵组
    bg1 = Background()
    bg2 = Background(True)
    
    self.back_group = pygame.sprite.Group(bg1, bg2)
    

    4.游戏框架搭建

    目标 —— 使用 面相对象 设计 飞机大战游戏类

    • 明确主程序职责
    • 实现主程序类
    • 准备游戏精灵组

    4.1 明确主程序职责

    • 回顾 快速入门案例,一个游戏主程序的 职责 可以分为两个部分:
      • 游戏初始化
      • 游戏循环
    • 根据明确的职责,设计 PlaneGame 类如下:

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zVlVdgwC-1580112266734)(media/15025159832322/009_%E6%B8%B8%E6%88%8F%E4%B8%BB%E7%A8%8B%E5%BA%8F.png)]

    提示 根据 职责 封装私有方法,可以避免某一个方法的代码写得太过冗长

    • 游戏初始化 —— __init__() 会调用以下方法:
    方法 职责
    __create_sprites(self) 创建所有精灵和精灵组
    • 游戏循环 —— start_game() 会调用以下方法:
    方法 职责
    __event_handler(self) 事件监听
    __check_collide(self) 碰撞检测 —— 子弹销毁敌机、敌机撞毁英雄
    __update_sprites(self) 精灵组更新和绘制
    __game_over() 游戏结束

    4.2 实现飞机大战主游戏类

    4.2.1 明确文件职责

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mLDs5tZP-1580112266735)(media/15025159832322/011_%E7%A8%8B%E5%BA%8F%E6%96%87%E4%BB%B6%E8%81%8C%E8%B4%A3.png)]

    • plane_main
      1. 封装 主游戏类
      2. 创建 游戏对象
      3. 启动游戏
    • plane_sprites
      • 封装游戏中 所有 需要使用的 精灵子类
      • 提供游戏的 相关工具

    代码实现

    • 新建 plane_main.py 文件,并且设置为可执行
    • 编写 基础代码
    import pygame
    from plane_sprites import *
    
    
    class PlaneGame(object):
        """飞机大战主游戏"""
    
        def __init__(self):
            print("游戏初始化")
    
        def start_game(self):
            print("开始游戏...")
    
    
    if __name__ == '__main__':
        # 创建游戏对象
        game = PlaneGame()
    
        # 开始游戏
        game.start_game()
    
    

    4.2.2 游戏初始化部分

    • 完成 __init__() 代码如下:
    def __init__(self):
        print("游戏初始化")
        
        # 1. 创建游戏的窗口
        self.screen = pygame.display.set_mode((480, 700))
        # 2. 创建游戏的时钟
        self.clock = pygame.time.Clock()
        # 3. 调用私有方法,精灵和精灵组的创建
        self.__create_sprites()
    
    def __create_sprites(self):
        pass
    

    使用 常量 代替固定的数值

    • 常量 —— 不变化的量
    • 变量 —— 可以变化的量

    应用场景

    • 在开发时,可能会需要使用 固定的数值,例如 屏幕的高度700
    • 这个时候,建议 不要 直接使用固定数值,而应该使用 常量
    • 在开发时,为了保证代码的可维护性,尽量不要使用 魔法数字

    常量的定义

    • 定义 常量 和 定义 变量 的语法完全一样,都是使用 赋值语句
    • 常量命名 应该 所有字母都使用大写单词与单词之间使用下划线连接

    常量的好处

    • 阅读代码时,通过 常量名 见名之意,不需要猜测数字的含义
    • 如果需要 调整值,只需要 修改常量定义 就可以实现 统一修改

    代码调整

    • plane_sprites.py 中增加常量定义
    import pygame
    
    # 游戏屏幕大小
    SCREEN_RECT = pygame.Rect(0, 0, 480, 700)
    
    • 修改 plane_main.py 中的窗口大小
    self.screen = pygame.display.set_mode(SCREEN_RECT.size)
    

    4.2.3 游戏循环部分

    • 完成 start_game() 基础代码如下:
    def start_game(self):
        """开始游戏"""
        
        print("开始游戏...")
           
        while True:
        
            # 1. 设置刷新帧率
            self.clock.tick(60)
            
            # 2. 事件监听
            self.__event_handler()
            
            # 3. 碰撞检测
            self.__check_collide()
            
            # 4. 更新精灵组
            self.__update_sprites()
            
            # 5. 更新屏幕显示
            pygame.display.update()
    
    def __event_handler(self):
        """事件监听"""
        
        for event in pygame.event.get():
        
            if event.type == pygame.QUIT:
                PlaneGame.__game_over()
    
    def __check_collide(self):
        """碰撞检测"""
        pass
    
    def __update_sprites(self):
        """更新精灵组"""
        pass
        
    @staticmethod
    def __game_over():
       """游戏结束"""
    
       print("游戏结束")
       pygame.quit()
       exit()
    

    4.3 准备游戏精灵组

    4.3.1 确定精灵组

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5ZhqNmgI-1580112266735)(media/15025159832322/010_%E7%B2%BE%E7%81%B5%E7%BB%84%E7%A1%AE%E5%AE%9A.png)]

    4.3.2 代码实现

    • 创建精灵组方法
    def __create_sprites(self):
        """创建精灵组"""
        
        # 背景组
        self.back_group = pygame.sprite.Group()
        # 敌机组
        self.enemy_group = pygame.sprite.Group()
        # 英雄组
        self.hero_group = pygame.sprite.Group()
    
    
    • 更新精灵组方法
    def __update_sprites(self):
        """更新精灵组"""
        
        for group in [self.back_group, self.enemy_group, self.hero_group]:
        
            group.update()
            group.draw(self.screen)
    
    发布了25 篇原创文章 · 获赞 25 · 访问量 3993

    猜你喜欢

    转载自blog.csdn.net/zenghongju/article/details/104088261
    今日推荐