前一篇博客为游戏实现前所用的基础知识介绍
Python飞机大战项目前篇
此篇为飞机大战游戏项目的整个实现过程。从游戏框架的搭建、游戏背景的设置、英雄飞机和敌机的设定,再到飞机发生碰撞时的检测(子弹摧毁敌机,敌机撞毁英雄)等详细的笔记描述

游戏框架搭建

在这里插入图片描述游戏初始化 —— __init__() 会调用以下方法:

方法职责
__create_sprites(self)创建所有精灵和精灵组

游戏循环 —— start_game() 会调用以下方法:

方法职责
__event_handler(self)事件监听
__check_collide(self)碰撞检测 —— 子弹销毁敌机、敌机撞毁英雄
__update_sprites(self)精灵组更新和绘制
__game_over()游戏结束

新建plane_main.py文件和plane_sprites.py文件记得导入图片素材(如图)
在这里插入图片描述
plane_main

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

游戏背景滚动实现

背景图像的显示效果:

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

实现办法:

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

设计背景类

在这里插入图片描述* 初始化方法

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

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

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

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

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)

完整实现代码

plane_main.py部分

import pygame
from plane_sprites import *


class PlaneGame(object):
    """飞机大战主游戏"""

    def __init__(self):
        print('游戏初始化')

        # 1 创建游戏的窗口
        self.screen = pygame.display.set_mode(SCREEN_RECT.size)
        # 2 创建游戏的时钟
        self.clock = pygame.time.Clock()
        # 3 调用私有方法,精灵和精灵组的创建
        self.__create_spritea()

    def __create_spritea(self):
        # 创建背景精灵和精灵组
        bg1 = Background()
        bg2 = Background(True)
        # bg2.rect.y = -bg2.rect.height   # 定义初始位置

        self.back_group = pygame.sprite.Group(bg1, bg2)

    def start_game(self):
        print('游戏开始...')

        while True:
            # 1 设置刷新帧率
            self.clock.tick(FRAME_PER_SEC)
            # 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):

        self.back_group.update()
        self.back_group.draw(self.screen)


if __name__ == '__main__':
    # 创建游戏对象
    game = PlaneGame()
    # 启动游戏
    game.start_game()

plane_sprites.py部分

import pygame

# 屏幕大小的常量
SCREEN_RECT = pygame.Rect(0, 0, 480, 700)
# 刷新的帧率
FRAME_PER_SEC = 60
# 创建敌机的定时器常量(USEREVENT  是pygame提供的用户事件)
CREATE_ENEMY_EVENT = pygame.USEREVENT


class GameSprite(pygame.sprite.Sprite):  # 第一个是模块的名称 第二个是类的名称
    """ 飞机大战游戏精灵"""

    def __init__(self, image_name, speed=1):

        # 调用父类的初始化方法
        super().__init__()

        # 定义对象的属性
        self.image = pygame.image.load(image_name)
        self.rect = self.image.get_rect()
        self.speed = speed

    def update(self):

        # 在屏幕的垂直方向上移动
        self.rect.y += self.speed


class Background(GameSprite):
    """ 游戏背景精灵"""

    def __init__(self, is_alt=False):

        # 1 调用父类方法实现精灵组的创建(image/rect/speed)
        super().__init__('./images/background.png')

        # 2 判断是否是交替图像,如果是,需要设置初始位置
        if is_alt:
            self.rect.y = -self.rect.height

    def update(self):

        # 1 调用父类的方法实现
        super().update()

        # 2 判断是否移出屏幕,如果移出屏幕, 将图像设置到屏幕上方
        if self.rect.y >= SCREEN_RECT.height:
            self.rect.y = -self.rect.height

实现效果图

在这里插入图片描述

敌机部分实现

敌机的 出现规律

  1. 游戏启动后,每隔 1 秒 会出现一架敌机
  2. 每架敌机 向屏幕下方飞行,飞行速度各不相同
  3. 每架敌机出现的 水平位置 也不尽相同
  4. 当敌机 从屏幕下方飞出,不会再飞回到屏幕中

定义并监听创建敌机的定时器事件

  • pygame 中可以使用 pygame.time.set_timer() 来添加 定时器
  • 所谓 定时器,就是 每隔一段时间,去 执行一些动作
set_timer(eventid, milliseconds) -> None
  • set_timer 可以创建一个事件
  • 可以在游戏循环的事件监听方法中捕获到该事件
  • 第 1 个参数事件代号需要基于常量 pygame.USEREVENT 来指定
    • USEREVENT 是一个整数,再增加的事件可以使用 USEREVENT + 1 指定,依次类推…
  • 第 2 个参数是事件触发间隔的毫秒值
    pygame 的定时器使用套路非常固定:
  1. 定义定时器常量 —— eventid
  2. 在 初始化方法中,调用 set_timer 方法设置定时器事件
  3. 在 游戏循环 中,监听定时器事件

设计 Enemy

  1. 游戏启动后,每隔 1 秒会 出现一架敌机
  2. 每架敌机 向屏幕下方飞行,飞行速度各不相同
  3. 每架敌机出现的水平位置也不尽相同
  4. 当敌机从屏幕下方飞出,不会再飞回到屏幕中

敌机类的准备

  • plane_sprites 新建 Enemy 继承自 GameSprite
  • 重写 初始化方法,直接指定 图片名称
  • 暂时 不实现 随机速度 和 随机位置 的指定
  • 重写 update 方法,判断是否飞出屏幕

创建敌机

  1. __create_sprites,添加 敌机精灵组
    • 敌机是 定时被创建的,因此在初始化方法中,不需要创建敌机
  2. __event_handler,创建敌机,并且 添加到精灵组
    • 调用 精灵组 的 add 方法可以 向精灵组添加精灵
  3. __update_sprites,让 敌机精灵组 调用 updatedraw 方法

随机敌机位置和速度
修改 plane_sprites.py 增加 random 的导入

import random

随机位置:使用 pygame.Rect 提供的 bottom 属性,在指定敌机初始位置时,会比较方便

  • bottom = y + height
  • y = bottom - height

移出屏幕销毁敌机

  • 敌机移出屏幕之后,如果 没有撞到英雄,敌机的历史使命已经终结

  • 需要从 敌机组 删除,否则会造成 内存浪费

  • __del__ 内置方法会在对象被销毁前调用,在开发中,可以用于 判断对象是否被销毁

  • 判断敌机是否飞出屏幕,如果是,调用 kill() 方法从所有组中删除

完整实现代码

plane_main.py部分

import pygame
from plane_sprites import *


class PlaneGame(object):
    """飞机大战主游戏"""

    def __init__(self):
        print("游戏初始化")

        # 1. 创建游戏的窗口
        self.screen = pygame.display.set_mode(SCREEN_RECT.size)
        # 2. 创建游戏的时钟
        self.clock = pygame.time.Clock()
        # 3. 调用私有方法,精灵和精灵组的创建
        self.__create_sprites()

        # 4. 设置定时器事件 - 创建敌机 1s
        pygame.time.set_timer(CREATE_ENEMY_EVENT, 1000)

    def __create_sprites(self):

        # 创建背景精灵和精灵组
        bg1 = Background()
        bg2 = Background(True)

        self.back_group = pygame.sprite.Group(bg1, bg2)

        # 创建敌机的精灵组
        self.enemy_group = pygame.sprite.Group()

    def start_game(self):
        print("游戏开始...")

        while True:
            # 1. 设置刷新帧率
            self.clock.tick(FRAME_PER_SEC)
            # 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()
            elif event.type == CREATE_ENEMY_EVENT:
                print("敌机出场...")
                # 创建敌机精灵
                enemy = Enemy()

                # 将敌机精灵添加到敌机精灵组
                self.enemy_group.add(enemy)

    def __check_collide(self):
        pass

    def __update_sprites(self):

        self.back_group.update()
        self.back_group.draw(self.screen)

        self.enemy_group.update()
        self.enemy_group.draw(self.screen)

    @staticmethod
    def __game_over():
        print("游戏结束")

        pygame.quit()
        exit()

if __name__ == '__main__':

    # 创建游戏对象
    game = PlaneGame()

    # 启动游戏
    game.start_game()

plane_sprites.py部分

import random
import pygame

# 屏幕大小的常量
SCREEN_RECT = pygame.Rect(0, 0, 480, 700)
# 刷新的帧率
FRAME_PER_SEC = 60
# 创建敌机的定时器常量
CREATE_ENEMY_EVENT = pygame.USEREVENT


class GameSprite(pygame.sprite.Sprite):
    """飞机大战游戏精灵"""

    def __init__(self, image_name, speed=1):

        # 调用父类的初始化方法
        super().__init__()

        # 定义对象的属性
        self.image = pygame.image.load(image_name)
        self.rect = self.image.get_rect()
        self.speed = speed

    def update(self):

        # 在屏幕的垂直方向上移动
        self.rect.y += self.speed


class Background(GameSprite):
    """游戏背景精灵"""

    def __init__(self, is_alt=False):

        # 1. 调用父类方法实现精灵的创建(image/rect/speed)
        super().__init__("./images/background.png")

        # 2. 判断是否是交替图像,如果是,需要设置初始位置
        if is_alt:
            self.rect.y = -self.rect.height

    def update(self):

        # 1. 调用父类的方法实现
        super().update()

        # 2. 判断是否移出屏幕,如果移出屏幕,将图像设置到屏幕的上方
        if self.rect.y >= SCREEN_RECT.height:
            self.rect.y = -self.rect.height


class Enemy(GameSprite):
    """敌机精灵"""

    def __init__(self):

        # 1. 调用父类方法,创建敌机精灵,同时指定敌机图片
        super().__init__("./images/enemy1.png")

        # 2. 指定敌机的初始随机速度 1 ~ 3
        self.speed = random.randint(1, 3)

        # 3. 指定敌机的初始随机位置
        self.rect.bottom = 0

        max_x = SCREEN_RECT.width - self.rect.width
        self.rect.x = random.randint(0, max_x)

    def update(self):

        # 1. 调用父类方法,保持垂直方向的飞行
        super().update()

        # 2. 判断是否飞出屏幕,如果是,需要从精灵组删除敌机
        if self.rect.y >= SCREEN_RECT.height:
            print("飞出屏幕,需要从精灵组删除...")
            # kill方法可以将精灵从所有精灵组中移出,精灵就会被自动销毁
            self.kill()

    def __del__(self):
        print("敌机挂了 %s" % self.rect)

实现效果图

在这里插入图片描述

英雄部分实现

英雄需求

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

子弹需求

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

Hero —— 英雄

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

创建英雄

  • plane_sprites 新建 Hero
  • 重写 初始化方法,直接指定图片名称,并且将初始速度设置为 0
  • 设置 英雄的初始位置
  • centerx = x + 0.5 * width
  • centery = y + 0.5 * height
  • bottom = y + height
    绘制英雄
  1. __create_sprites,添加 英雄精灵 和 英雄精灵组
    • 后续要针对 英雄 做 碰撞检测 以及 发射子弹
    • 所以 英雄 需要 单独定义成属性
  2. __update_sprites,让 英雄精灵组 调用 updatedraw 方法
    移动英雄位置
  3. Hero 类中重写 update 方法
    • 用 速度 speed 和 英雄 rect.x 进行叠加
    • 不需要调用父类方法 —— 父类方法只是实现了单纯的垂直运动
  4. __event_handler 方法中根据 左右方向键 设置英雄的 速度
    • 向右 => speed = 2
    • 向左 => speed = -2
    • 其他 => speed = 0

Bullet —— 子弹

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

发射子弹
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()

定义子弹类

  • plane_sprites 新建 Bullet 继承自 GameSprite
  • 重写 初始化方法,直接指定 图片名称,并且设置 初始速度
  • 重写 update() 方法,判断子弹 飞出屏幕从精灵组删除

发射子弹

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

完整实现代码

plane_main.py部分

import pygame
from plane_sprites import *


class PlaneGame(object):
    """飞机大战主游戏"""

    def __init__(self):
        print("游戏初始化")

        # 1. 创建游戏的窗口
        self.screen = pygame.display.set_mode(SCREEN_RECT.size)
        # 2. 创建游戏的时钟
        self.clock = pygame.time.Clock()
        # 3. 调用私有方法,精灵和精灵组的创建
        self.__create_sprites()

        # 4. 设置定时器事件 - 创建敌机 1s
        pygame.time.set_timer(CREATE_ENEMY_EVENT, 1000)
        pygame.time.set_timer(HERO_FIRE_EVENT, 500)

    def __create_sprites(self):

        # 创建背景精灵和精灵组
        bg1 = Background()
        bg2 = Background(True)

        self.back_group = pygame.sprite.Group(bg1, bg2)

        # 创建敌机的精灵组
        self.enemy_group = pygame.sprite.Group()

        # 创建英雄的精灵和精灵组
        self.hero = Hero()
        self.hero_group = pygame.sprite.Group(self.hero)

    def start_game(self):
        print("游戏开始...")

        while True:
            # 1. 设置刷新帧率
            self.clock.tick(FRAME_PER_SEC)
            # 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()
            elif event.type == CREATE_ENEMY_EVENT:
                # print("敌机出场...")
                # 创建敌机精灵
                enemy = Enemy()

                # 将敌机精灵添加到敌机精灵组
                self.enemy_group.add(enemy)
            elif event.type == HERO_FIRE_EVENT:
                self.hero.fire()
            # elif event.type == pygame.KEYDOWN and event.key == pygame.K_RIGHT:
            #     print("向右移动...")

        # 使用键盘提供的方法获取键盘按键 - 按键元组
        keys_pressed = pygame.key.get_pressed()
        # 判断元组中对应的按键索引值 1
        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

    def __check_collide(self):
        pass

    def __update_sprites(self):

        self.back_group.update()
        self.back_group.draw(self.screen)

        self.enemy_group.update()
        self.enemy_group.draw(self.screen)

        self.hero_group.update()
        self.hero_group.draw(self.screen)

        self.hero.bullets.update()
        self.hero.bullets.draw(self.screen)

    @staticmethod
    def __game_over():
        print("游戏结束")

        pygame.quit()
        exit()

if __name__ == '__main__':

    # 创建游戏对象
    game = PlaneGame()

    # 启动游戏
    game.start_game()

plane_sprites.py部分

import random
import pygame

# 屏幕大小的常量
SCREEN_RECT = pygame.Rect(0, 0, 480, 700)
# 刷新的帧率
FRAME_PER_SEC = 60
# 创建敌机的定时器常量
CREATE_ENEMY_EVENT = pygame.USEREVENT
# 英雄发射子弹事件
HERO_FIRE_EVENT = pygame.USEREVENT + 1


class GameSprite(pygame.sprite.Sprite):
    """飞机大战游戏精灵"""

    def __init__(self, image_name, speed=1):

        # 调用父类的初始化方法
        super().__init__()

        # 定义对象的属性
        self.image = pygame.image.load(image_name)
        self.rect = self.image.get_rect()
        self.speed = speed

    def update(self):

        # 在屏幕的垂直方向上移动
        self.rect.y += self.speed


class Background(GameSprite):
    """游戏背景精灵"""

    def __init__(self, is_alt=False):

        # 1. 调用父类方法实现精灵的创建(image/rect/speed)
        super().__init__("./images/background.png")

        # 2. 判断是否是交替图像,如果是,需要设置初始位置
        if is_alt:
            self.rect.y = -self.rect.height

    def update(self):

        # 1. 调用父类的方法实现
        super().update()

        # 2. 判断是否移出屏幕,如果移出屏幕,将图像设置到屏幕的上方
        if self.rect.y >= SCREEN_RECT.height:
            self.rect.y = -self.rect.height


class Enemy(GameSprite):
    """敌机精灵"""

    def __init__(self):

        # 1. 调用父类方法,创建敌机精灵,同时指定敌机图片
        super().__init__("./images/enemy1.png")

        # 2. 指定敌机的初始随机速度 1 ~ 3
        self.speed = random.randint(1, 3)

        # 3. 指定敌机的初始随机位置
        self.rect.bottom = 0

        max_x = SCREEN_RECT.width - self.rect.width
        self.rect.x = random.randint(0, max_x)

    def update(self):

        # 1. 调用父类方法,保持垂直方向的飞行
        super().update()

        # 2. 判断是否飞出屏幕,如果是,需要从精灵组删除敌机
        if self.rect.y >= SCREEN_RECT.height:
            # print("飞出屏幕,需要从精灵组删除...")
            # kill方法可以将精灵从所有精灵组中移出,精灵就会被自动销毁
            self.kill()

    def __del__(self):
        # print("敌机挂了 %s" % self.rect)
        pass


class Hero(GameSprite):
    """英雄精灵"""

    def __init__(self):

        # 1. 调用父类方法,设置image&speed
        super().__init__("./images/me1.png", 0)

        # 2. 设置英雄的初始位置
        self.rect.centerx = SCREEN_RECT.centerx
        self.rect.bottom = SCREEN_RECT.bottom - 120

        # 3. 创建子弹的精灵组
        self.bullets = pygame.sprite.Group()

    def update(self):

        # 英雄在水平方向移动
        self.rect.x += self.speed

        # 控制英雄不能离开屏幕
        if self.rect.x < 0:
            self.rect.x = 0
        elif self.rect.right > SCREEN_RECT.right:
            self.rect.right = SCREEN_RECT.right

    def fire(self):
        print("发射子弹...")

        for i in (0, 1, 2):
            # 1. 创建子弹精灵
            bullet = Bullet()

            # 2. 设置精灵的位置
            bullet.rect.bottom = self.rect.y - i * 20
            bullet.rect.centerx = self.rect.centerx

            # 3. 将精灵添加到精灵组
            self.bullets.add(bullet)


class Bullet(GameSprite):
    """子弹精灵"""

    def __init__(self):

        # 调用父类方法,设置子弹图片,设置初始速度
        super().__init__("./images/bullet1.png", -2)

    def update(self):

        # 调用父类方法,让子弹沿垂直方向飞行
        super().update()

        # 判断子弹是否飞出屏幕
        if self.rect.bottom < 0:
            self.kill()

    def __del__(self):
        print("子弹被销毁...")

实现效果图

在这里插入图片描述

碰撞检测

pygame 提供了 两个非常方便 的方法可以实现碰撞检测:

pygame.sprite.groupcollide()

  • 两个精灵组 中 所有的精灵 的碰撞检测
groupcollide(group1, group2, dokill1, dokill2, collided = None) -> Sprite_dict
  • 如果将 dokill 设置为 True,则 发生碰撞的精灵将被自动移除
  • collided 参数是用于 计算碰撞的回调函数
    • 如果没有指定,则每个精灵必须有一个 rect 属性

pygame.sprite.spritecollide()

  • 判断 某个精灵 和 指定精灵组 中的精灵的碰撞
spritecollide(sprite, group, dokill, collided = None) -> Sprite_list
  • 如果将 dokill 设置为 True,则 指定精灵组 中 发生碰撞的精灵将被自动移除
  • collided 参数是用于 计算碰撞的回调函数
    • 如果没有指定,则每个精灵必须有一个 rect 属性
  • 返回 精灵组 中跟 精灵 发生碰撞的 精灵列表

完整实现代码

plane_main.py部分

import pygame
from plane_sprites import *

# 敌机出现事件
CREATE_ENEMY_EVENT = pygame.USEREVENT
# 发射子弹事件
HERO_FIRE_EVENT = pygame.USEREVENT + 1


class PlaneGame(object):
    """飞机大战游戏类"""

    def __init__(self):

        # 1. pygame 初始化
        pygame.init()

        # 2. 创建游戏屏幕
        self.screen = pygame.display.set_mode(SCREEN_RECT.size)

        # 3. 创建游戏时钟
        self.clock = pygame.time.Clock()

        # 4. 创建精灵组
        self.__create_sprites()

        # 5. 创建用户事件
        PlaneGame.__create_user_events()

    def __create_sprites(self):
        """创建精灵组"""

        # 背景组
        bg1 = Background()
        bg2 = Background(True)
        self.back_group = pygame.sprite.Group(bg1, bg2)

        # 敌机组
        enemy = Enemy()
        self.enemy_group = pygame.sprite.Group(enemy)

        # 英雄组
        self.hero = Hero()
        self.hero_group = pygame.sprite.Group(self.hero)

    @staticmethod
    def __create_user_events():
        """创建用户事件"""

        # 每秒添加一架敌机
        pygame.time.set_timer(CREATE_ENEMY_EVENT, 1 * 1000)

        # 每秒发射两次子弹
        pygame.time.set_timer(HERO_FIRE_EVENT, 500)

    def start_game(self):
        """开始游戏"""
        while True:
            # 1. 设置刷新帧率
            self.clock.tick(60)
            # 2. 事件监听
            self.__event_handler()
            # 3. 更新精灵组
            self.__update_sprites()
            # 碰撞检测
            self.__check_collide()
            # 4. 更新屏幕显示
            pygame.display.update()

    def __event_handler(self):
        """事件监听"""

        for event in pygame.event.get():

            if event.type == pygame.QUIT:
                print("退出游戏...")
                pygame.quit()
                exit()
            elif event.type == CREATE_ENEMY_EVENT:
                # 创建敌机,并且添加到敌机组
                self.enemy_group.add(Enemy())

                # 测试敌机精灵数量
                # enemy_count = len(self.enemy_group.sprites())
                # print("敌机精灵数量 %d" % enemy_count)
            elif event.type == HERO_FIRE_EVENT:
                # 英雄发射子弹
                self.hero.fire()

        # 通过 pygame.key 获取用户按键
        keys_pressed = pygame.key.get_pressed()
        dir = keys_pressed[pygame.K_RIGHT] - keys_pressed[pygame.K_LEFT]

        # 根据移动方向设置英雄的速度
        self.hero.speed = dir * 2

    def __update_sprites(self):
        """更新精灵组"""

        for group in [self.back_group, self.enemy_group,
                      self.hero_group, self.hero.bullets]:

            group.update()
            group.draw(self.screen)

    def __check_collide(self):
        """碰撞检测"""

        # 1. 子弹摧毁敌机
        pygame.sprite.groupcollide(self.hero.bullets, self.enemy_group, True, True)

        # 2. 英雄被撞毁
        collide_list = pygame.sprite.spritecollide(self.hero, self.enemy_group, False)

        if len(collide_list) > 0:
            self.hero.is_alive = False

            print("英雄牺牲...")

            pygame.quit()
            exit()

if __name__ == '__main__':
    # 1. 创建游戏对象
    game = PlaneGame()

    # 2. 开始游戏
    game.start_game()

plane_sprites.py部分

import random
import pygame


# 游戏屏幕大小
SCREEN_RECT = pygame.Rect(0, 0, 480, 700)


class GameSprite(pygame.sprite.Sprite):
    """游戏精灵基类"""

    def __init__(self, image_name, speed=1):

        # 调用父类的初始化方法
        super().__init__()

        # 加载图像
        self.image = pygame.image.load(image_name)
        # 设置尺寸
        self.rect = self.image.get_rect()
        # 记录速度
        self.speed = speed

    def update(self, *args):

        # 默认在垂直方向移动
        self.rect.top += self.speed


class Background(GameSprite):
    """背景精灵"""

    def __init__(self, is_alt=False):

        image_name = "./images/background.png"
        super().__init__(image_name)

        # 判断是否交替图片,如果是,将图片设置到屏幕顶部
        if is_alt:
            self.rect.bottom = 0

    def update(self, *args):

        # 调用父类方法
        super().update(args)

        # 判断是否超出屏幕
        if self.rect.top >= SCREEN_RECT.height:
            self.rect.bottom = 0


class Enemy(GameSprite):
    """敌机精灵"""

    def __init__(self):

        image_name = "./images/enemy1.png"
        super().__init__(image_name)

        # 随机敌机出现位置
        width = SCREEN_RECT.width - self.rect.width
        self.rect.left = random.randint(0, width)
        self.rect.bottom = 0

        # 随机速度
        self.speed = random.randint(1, 3)

    def update(self, *args):
        super().update(args)

        # 判断敌机是否移出屏幕
        if self.rect.top >= SCREEN_RECT.height:
            # 将精灵从所有组中删除
            self.kill()


class Hero(GameSprite):
    """英雄精灵"""

    def __init__(self):

        image_name = "./images/me1.png"
        super().__init__(image_name, 0)

        # 设置初始位置
        self.rect.centerx = SCREEN_RECT.centerx
        self.rect.bottom = SCREEN_RECT.bottom - 120

        # 创建子弹组
        self.bullets = pygame.sprite.Group()

    def update(self, *args):

        # 飞机水平移动
        self.rect.left += self.speed

        # 超出屏幕检测
        if self.rect.left < 0:
            self.rect.left = 0
        if self.rect.right > SCREEN_RECT.right:
            self.rect.right = SCREEN_RECT.right

    def fire(self):

        # bullet_count = len(self.bullets.sprites())
        # print("子弹数量 %d" % bullet_count)

        for i in range(0, 3):
            # 创建子弹精灵
            bullet = Bullet()

            # 设置子弹位置
            bullet.rect.bottom = self.rect.top - i * 20
            bullet.rect.centerx = self.rect.centerx

            # 将子弹添加到精灵组
            self.bullets.add(bullet)


class Bullet(GameSprite):
    """子弹精灵"""

    def __init__(self):

        image_name = "./images/bullet1.png"
        super().__init__(image_name, -2)

    def update(self, *args):

        super().update(args)

        # 判断是否超出屏幕
        if self.rect.bottom < 0:
            self.kill()

实现效果图

在这里插入图片描述

**简单的的一个飞机大战小游戏就实现了!
但是这太简略了
比如:游戏前没有设置开始页面,代码一运行就直接开始游戏
再比如:游戏中没有吸引人的爆炸效果,没有分数的统计,没有实现飞机的升级以及随着英雄的升级还有敌机的升级,直至游戏的难度升级。
再再比如:游戏后的结束页面,最高分的排名记录等功能。
有兴趣的同学可以尝试再整整!

又添加了一个爆炸效果的功能

更新2.0版

完整实现代码

plane_main.py部分

#! /usr/bin/python3

import pygame
from plane_sprites import *

# 敌机出现事件
CREATE_ENEMY_EVENT = pygame.USEREVENT
# 发射子弹事件
HERO_FIRE_EVENT = pygame.USEREVENT + 1


class PlaneGame:
    """飞机大战游戏类"""

    def __init__(self):
        print("游戏初始化...")

        pygame.init()

        self.screen = pygame.display.set_mode(SCREEN_RECT.size)
        self.clock = pygame.time.Clock()

        self.__create_sprites()
        PlaneGame.__create_user_events()

    def __create_sprites(self):
        """创建精灵组"""

        self.back_group = pygame.sprite.Group(Background(), Background(True))

        self.hero = Hero()
        self.hero_group = pygame.sprite.Group(self.hero)

        self.enemy_group = pygame.sprite.Group()
        self.destroy_group = pygame.sprite.Group()

    @staticmethod
    def __create_user_events():
        """创建用户事件"""

        # 每秒添加一架敌机
        pygame.time.set_timer(CREATE_ENEMY_EVENT, 1 * 1000)

        # 每秒发射两次子弹
        pygame.time.set_timer(HERO_FIRE_EVENT, 500)

    def start_game(self):
        """开启游戏循环"""

        while True:
            self.clock.tick(60)
            self.__event_handler()
            self.__update_sprites()
            self.__check_collide()
            pygame.display.update()

    def __check_collide(self):
        """碰撞检测"""

        # 子弹摧毁敌机
        enemies = pygame.sprite.groupcollide(self.enemy_group,
                                             self.hero.bullets,
                                             False,
                                             True).keys()
        for enemy in enemies:
            enemy.life -= 1

            if enemy.life <= 0:
                enemy.add(self.destroy_group)
                enemy.remove(self.enemy_group)

                enemy.destroied()

        # 敌机撞毁英雄
        for hero in pygame.sprite.spritecollide(self.hero,
                                                self.enemy_group,
                                                True):
            print("英雄牺牲了...")
            self.hero.destroied()

    def __event_handler(self):
        """事件处理"""

        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                PlaneGame.__finished_game()
            elif event.type == HERO_FIRE_EVENT:
                self.hero.fire()
            elif event.type == CREATE_ENEMY_EVENT:
                self.enemy_group.add(Enemy())
            # 按下 b 英雄自爆
            elif event.type == pygame.KEYDOWN and event.key == pygame.K_b:
                # self.hero.destroied()

                # 集体自爆
                for enemy in self.enemy_group.sprites():
                    enemy.destroied()

        # 判断英雄是否已经被销毁,如果是,游戏结束!
        if self.hero.can_destroied:
            PlaneGame.__finished_game()

        # 通过 pygame.key 获取用户按键
        keys_pressed = pygame.key.get_pressed()
        dir = keys_pressed[pygame.K_RIGHT] - keys_pressed[pygame.K_LEFT]

        # 根据移动方向设置英雄的速度
        self.hero.speed = dir * 2

    def __update_sprites(self):
        """更新/绘制精灵组"""

        for group in [self.back_group, self.hero_group,
                      self.hero.bullets, self.enemy_group,
                      self.destroy_group]:
            group.update()
            group.draw(self.screen)

    @staticmethod
    def __finished_game():
        """退出游戏"""

        print("退出游戏")
        pygame.quit()
        exit()


if __name__ == '__main__':
    PlaneGame().start_game()

plane_sprites.py部分

import random
import pygame

# 屏幕尺寸
SCREEN_RECT = pygame.Rect(0, 0, 480, 700)


class GameSprite(pygame.sprite.Sprite):
    """游戏精灵"""

    def __init__(self, image_name, speed=1):
        super().__init__()

        self.image = pygame.image.load(image_name)
        self.rect = self.image.get_rect()
        self.speed = speed

    def update(self, *args):
        self.rect.top += self.speed

    @staticmethod
    def image_names(prefix, count):
        names = []
        for i in range(1, count + 1):
            names.append("./images/" + prefix + str(i) + ".png")

        return names


class Background(GameSprite):
    """背景精灵"""

    def __init__(self, is_alt=False):
        super().__init__("./images/background.png")

        if is_alt:
            self.rect.bottom = 0

    def update(self, *args):
        super().update(args)

        if self.rect.top >= SCREEN_RECT.height:
            self.rect.bottom = 0


class PlaneSprite(GameSprite):
    """飞机精灵,包括敌机和英雄"""

    def __init__(self, image_names, destroy_names, life, speed):

        image_name = image_names[0]
        super().__init__(image_name, speed)

        # 生命值
        self.life = life

        # 正常图像列表
        self.__life_images = []
        for file_name in image_names:
            image = pygame.image.load(file_name)
            self.__life_images.append(image)

        # 被摧毁图像列表
        self.__destroy_images = []
        for file_name in destroy_names:
            image = pygame.image.load(file_name)
            self.__destroy_images.append(image)

        # 默认播放生存图片
        self.images = self.__life_images
        # 显示图像索引
        self.show_image_index = 0
        # 是否循环播放
        self.is_loop_show = True
        # 是否可以被删除
        self.can_destroied = False

    def update(self, *args):
        self.update_images()

        super().update(args)

    def update_images(self):
        """更新图像"""

        pre_index = int(self.show_image_index)
        self.show_image_index += 0.05
        count = len(self.images)

        # 判断是否循环播放
        if self.is_loop_show:
            self.show_image_index %= len(self.images)
        elif self.show_image_index > count - 1:
            self.show_image_index = count - 1
            self.can_destroied = True

        current_index = int(self.show_image_index)

        if pre_index != current_index:
            self.image = self.images[current_index]

    def destroied(self):
        """飞机被摧毁"""

        # 默认播放生存图片
        self.images = self.__destroy_images
        # 显示图像索引
        self.show_image_index = 0
        # 是否循环播放
        self.is_loop_show = False


class Hero(PlaneSprite):
    """英雄精灵"""

    def __init__(self):

        image_names = GameSprite.image_names("me", 2)
        destroy_names = GameSprite.image_names("me_destroy_", 4)

        super().__init__(image_names, destroy_names, 0, 0)

        # 设置初始位置
        self.rect.centerx = SCREEN_RECT.centerx
        self.rect.bottom = SCREEN_RECT.bottom - 120

        # 创建子弹组
        self.bullets = pygame.sprite.Group()

    def update(self, *args):
        self.update_images()

        # 飞机水平移动
        self.rect.left += self.speed

        # 超出屏幕检测
        if self.rect.left < 0:
            self.rect.left = 0
        if self.rect.right > SCREEN_RECT.right:
            self.rect.right = SCREEN_RECT.right

    def fire(self):
        """发射子弹"""

        # bullet_count = len(self.bullets.sprites())
        # print("子弹数量 %d" % bullet_count)

        for i in range(0, 3):
            # 创建子弹精灵
            bullet = Bullet()

            # 设置子弹位置
            bullet.rect.bottom = self.rect.top - i * 20
            bullet.rect.centerx = self.rect.centerx

            # 将子弹添加到精灵组
            self.bullets.add(bullet)


class Bullet(GameSprite):
    """子弹精灵"""

    def __init__(self):
        image_name = "./images/bullet1.png"
        super().__init__(image_name, -2)

    def update(self, *args):
        super().update(args)

        # 判断是否超出屏幕
        if self.rect.bottom < 0:
            self.kill()


class Enemy(PlaneSprite):
    """敌机精灵"""

    def __init__(self):
        image_names = ["./images/enemy1.png"]
        destroy_names = GameSprite.image_names("enemy1_down", 4)
        super().__init__(image_names, destroy_names, 2, 1)

        # 随机敌机出现位置
        width = SCREEN_RECT.width - self.rect.width
        self.rect.left = random.randint(0, width)
        self.rect.bottom = 0

        # 随机速度
        self.speed = random.randint(1, 3)

    def update(self, *args):
        super().update(args)

        # 判断敌机是否移出屏幕
        if self.rect.top >= SCREEN_RECT.height:
            # 将精灵从所有组中删除
            self.kill()

        # 判断敌机是否已经被销毁
        if self.can_destroied:
            self.kill()

实现效果图

在这里插入图片描述


图片素材:

链接:https://pan.baidu.com/s/1LKoVQCRApscxpgg4MYznyw
提取码:chui


此项目为学习python时笔记
来源于:黑马程序员python教程

Logo

华为开发者空间,是为全球开发者打造的专属开发空间,汇聚了华为优质开发资源及工具,致力于让每一位开发者拥有一台云主机,基于华为根生态开发、创新。

更多推荐