黑马python入门(7):Python基础(简单飞机大战实战)

官方标准源码素材来源

https://blog.csdn.net/gudada010/article/details/95527212


个人分析思路(和正统的有很大差距 非常粗糙 按照自己思路来写的 比较初级 仅供参考)

首先分析整个项目可能需要的对象 
飞机大战 有我机 有敌机  我机还要发射子弹
再精细一点就是下面
1.我机对象 2.敌机对象 3.子弹对象
4.背景对象 因为要做出飞机正在移动的错觉 需要背景也需要移动 所以也算是对象
5.游戏窗口对象


对象如何交互 才能让整个游戏正常运转下去
先要有一个游戏窗口  还要有一个游戏窗口背景 背景还要不断的向下滚动造成一种飞机在前进的假象
我机需要不断射出子弹 子弹打中敌机则敌机被摧毁 打不着直到子弹飞到最上面消失即可
敌机不断涌现向下飞 撞上了我机的话 我机敌机都被摧毁 游戏结束  没撞上则不管继续向下飞直到最下面消失即可
这样交互才能维持整个游戏的运行

由此我们进一步分析这几个对象可能需要具备的功能

我机:一开始出生在窗口的最下面的中间位置 可以通过键盘的上下左右来控制移动我机 但是不能超出边界 不断的发射子弹 一旦被敌机撞上就判定游戏结束

属性:
image 我机的图像来源
rect 坐标和宽高  初始位于最下面中间位置 还要限制飞机位置不能超出四方边界
方法 :
fire()发射子弹 按理说子弹这个是属于我机的 需要在该方法中生成子弹  然后才能发射子弹 
move() 移动(配合按键事件上下左右使用 )

class HeroPlaneClass(pygame.sprite.Sprite):
    """
    定义一个英雄飞机类 继承自pygame的精灵类
    属性 image rect  定义飞机的图片来源和初始坐标和宽高
    方法 update __init__(需要调用父类的init) fire() 发射子弹的方法
    支持 需要pygame和 常量 WIN_SIZE
    """
    def __init__(self, image, **kwargs):
        pygame.sprite.Sprite.__init__(self)
        self.image = pygame.image.load(image)  # image获取图像surface
        self.rect = self.image.get_rect()  # rect获取宽高
        # 英雄飞机初始位置位于最下面的中间
        self.rect.x = int((WIN_SIZE[0] - self.rect.w)/2)
        self.rect.y = WIN_SIZE[1] - self.rect.h

    def update(self):
        # 英雄飞机的行为:是用键盘事件上下左右键盘控制的 所以这里就不写具体行为了
        pass

    # 负责生成子弹实例 然后子弹按照自己的行为模式运动即可
    # 第二个参数是用于暂时存放子弹实例的子弹精灵组
    # 经过测试 发现类方法传递精灵组也是地址传递 在类方法内修改一样会提现到精灵组本身

    def fire(self, tmp_bullet_group):
        # 生成一个子弹
        tmp_bullet = BulletClass(BULLET_PATH, self.rect)
        tmp_bullet_group.add(tmp_bullet)  # 添加到子弹精灵组里面

    # 简单的移动我机 和按键触发事件配合使用
    def move(self, offset_x=0, offset_y=0):
        self.rect.x += offset_x
        self.rect.y += offset_y
        
        # 避免越界
        if self.rect.x < 0:
            self.rect.x = 0
        elif self.rect.x > WIN_SIZE[0] - self.rect.w:
            self.rect.x = WIN_SIZE[0] - self.rect.w

        if self.rect.y < 0:
            self.rect.y = 0
        elif self.rect.y > WIN_SIZE[1] - self.rect.h:
            self.rect.y = WIN_SIZE[1] - self.rect.h

敌机:游戏窗口最上面一行的随机位置生成 生成后无脑的向下移动 直到撞到我机或者到达游戏窗口最下面才消失  注意要源源不断的自动生成 而且移动速度是随机的

属性:
image 图像来源
rect 坐标和宽高  初始位于游戏窗口最上面一行的随机位置生成 
方法 :
update() 行为轨迹 直接写到update()方法里即可描绘其行为模式

源源不断的生成敌机只需要定时器定时生成敌机就好
class EnemyPlaneClass(pygame.sprite.Sprite):
    """
        定义一个敌人飞机类 继承自pygame的精灵类
        属性 image rect  定义飞机的图片来源和初始坐标和宽高
        方法 update __init__(需要调用父类的init)
        支持 需要pygame random 和 常量 WIN_SIZE
        """

    def __init__(self, image, **kwargs):
        pygame.sprite.Sprite.__init__(self)
        self.image = pygame.image.load(image)  # image获取图像surface
        self.rect = self.image.get_rect()  # rect获取宽高
        # 敌机要求在最上一行随机位置刷新
        self.rect.x = random.randint(1, WIN_SIZE[0] - self.rect.x)
        self.rect.y = 0

    def update(self, *args):
        # 敌机的行为:是在间隔一定时间自动在最上一行随机位置产生 然后无脑向下直走 超出最下边界 就自动销毁他
        # 后面也可以考虑远距离开始瞄准英雄飞机抛物线撞击 后面再说
        self.rect.y += 10
        if self.rect.y > WIN_SIZE[1]:
            self.kill()  # 敌机超出最下面边界了 自然是自动销毁

    def __del__(self):
        print("<敌机销毁>")


子弹:起始位置和当前的我机的一致 在我机的上部中间位置起始 无脑向上移动直到撞到敌机或者到达游戏窗口最上面才消失
属性 
image
rect  初始位于我机的上面中间发射 固定向上移动 到了最上面就消失 速度是固定的
方法 
update() 固定向上移动 到了最上面就消失 速度是固定的


源源不断的发射子弹只需要定时器定时触发我机的fire()就可以实现
class BulletClass(pygame.sprite.Sprite):
    """
         定义一个子弹类 继承自pygame的精灵类
         属性 image rect  定义子弹的图片来源和初始坐标和宽高
         方法 update __init__(需要调用父类的init)
         支持 需要pygame和 常量 WIN_SIZE
         """

    def __init__(self, image, hero_rect):
        pygame.sprite.Sprite.__init__(self)
        self.image = pygame.image.load(image)  # image获取图像surface
        self.rect = self.image.get_rect()  # rect获取宽高
        # 子弹的初始位置要求在英雄飞机的上部中间位置
        self.rect.x = hero_rect.x + int(hero_rect.w/2)
        self.rect.y = hero_rect.y

    def update(self, *args):
        # 子弹的行为:是在间隔一定时间自动在英雄飞机最上一行中间位置产生 然后无脑向上直走 超出最上边界 就自动销毁他
        self.rect.y -= 10
        if self.rect.y < 0 - self.rect.h:
            self.kill()  # 子弹超出最上边界 就自动销毁他

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

背景:只需要背景慢慢向下移动 无法遮住整个窗口的时候回归初始位置 重新向下移动即可
属性 
image
rect  初始背景左下和窗口左下对齐就行
方法 
update() 背景慢慢向下移动 无法遮住整个窗口的时候回归初始位置 重新向下移动即可 速度是固定的
class BgClass(pygame.sprite.Sprite):
    """
         定义一个背景类 继承自pygame的精灵类
         属性 image rect  定义背景的图片来源和初始坐标和宽高 初始位置是超出了游戏窗口 开始是左下和左下对齐 到头了是左上和左上对齐
         方法 update __init__(需要调用父类的init)
         支持 需要pygame和 常量 WIN_SIZE
         """

    def __init__(self, image):
        pygame.sprite.Sprite.__init__(self)
        self.image = pygame.image.load(image)  # image获取图像surface
        self.rect = self.image.get_rect()  # rect获取宽高
        self.rect.y = WIN_SIZE[1] - self.rect.h  # 设置背景的初始高度

    def update(self, *args):
        # 背景的行为:图左下和窗口左下对齐 然后慢慢的向下移动 当然了 我这里背景图片是1400高度 是2个背景图拼接在一起的
        # 背景图的左上对齐窗口左上或者超出 就自动回到初始位置重新来过
        self.rect.y += 10
        if self.rect.y > 0:
            self.rect.y = WIN_SIZE[1] - self.rect.h  # 让背景重新回到上面重新滚动

游戏窗口:没什么可说的 本质上其实就是一句代码的事情 但是为了归类方便 也把它作为一个对象来看待 没有移动 只有宽高
属性:wh元组 来规定窗口的宽高 其他没了 也没有什么动作

class WinClass(object):
    """
    经过测试用get_window返回一个窗口对象可以正常的使用
    这类其实就是生成一个游戏窗口对象并返回它
    需要pygame库的支持
    """
    def __init__(self, pos):
        self.x = pos[0]
        self.y = pos[1]

    def get_window(self):
        return pygame.display.set_mode((self.x, self.y))


整体代码:对象构建好了 我们要构建我们的整体代码 整体代码也可以是一个完整的类
因为这些对象我们需要指定规则让他们互动 需要一个神秘的god 很多东西需要god来负责 光靠这几个对象无法实现
整体代码写成一个类 其实也不难理解 平时我们常见的代码结构  变量 函数 可以轻轻松松的转化成 类实例属性 类实例方法 或者 静态方法 类属性 类方法等等 并且这些东西都在一个类内 类内调用就比较的方便省事 
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# author:albert time:2020/9/23
import random
import pygame

# 常量

WIN_SIZE = (480, 700)  # 游戏窗口宽高
FPS = 60  # 游戏帧数
HERO_PATH = "images/me1.png"  # 我方飞机图的路径
ENEMY_PATH = "images/enemy1.png"  # 敌机图的路径
BULLET_PATH = "images/bullet1.png"  # 子弹图的路径
BG_PATH = "images/background.png"  # 背景图的路径
CREATE_ENEMY_EVENT = pygame.USEREVENT  # 配合定时器定时生成敌机
CREATE_BULLET_EVENT = pygame.USEREVENT + 1  # 配合定时器定时生成子弹


class HeroPlaneClass(pygame.sprite.Sprite):
    """
    定义一个英雄飞机类 继承自pygame的精灵类
    属性 image rect  定义飞机的图片来源和初始坐标和宽高
    方法 update __init__(需要调用父类的init) fire() 发射子弹的方法
    支持 需要pygame和 常量 WIN_SIZE
    """
    def __init__(self, image, **kwargs):
        pygame.sprite.Sprite.__init__(self)
        self.image = pygame.image.load(image)  # image获取图像surface
        self.rect = self.image.get_rect()  # rect获取宽高
        # 英雄飞机初始位置位于最下面的中间
        self.rect.x = int((WIN_SIZE[0] - self.rect.w)/2)
        self.rect.y = WIN_SIZE[1] - self.rect.h

    def update(self):
        # 英雄飞机的行为:是用键盘事件上下左右键盘控制的 所以这里就不写具体行为了
        pass

    # 负责生成子弹实例 然后子弹按照自己的行为模式运动即可
    # 第二个参数是用于暂时存放子弹实例的子弹精灵组
    # 经过测试 发现类方法传递精灵组也是地址传递 在类方法内修改一样会提现到精灵组本身

    def fire(self, tmp_bullet_group):
        # 生成一个子弹
        tmp_bullet = BulletClass(BULLET_PATH, self.rect)
        tmp_bullet_group.add(tmp_bullet)  # 添加到子弹精灵组里面

    # 简单的移动我机 和按键触发事件配合使用
    def move(self, offset_x=0, offset_y=0):
        self.rect.x += offset_x
        self.rect.y += offset_y

        # 避免越界
        if self.rect.x < 0:
            self.rect.x = 0
        elif self.rect.x > WIN_SIZE[0] - self.rect.w:
            self.rect.x = WIN_SIZE[0] - self.rect.w

        if self.rect.y < 0:
            self.rect.y = 0
        elif self.rect.y > WIN_SIZE[1] - self.rect.h:
            self.rect.y = WIN_SIZE[1] - self.rect.h


class EnemyPlaneClass(pygame.sprite.Sprite):
    """
        定义一个敌人飞机类 继承自pygame的精灵类
        属性 image rect  定义飞机的图片来源和初始坐标和宽高
        方法 update __init__(需要调用父类的init)
        支持 需要pygame和 常量 WIN_SIZE
        """

    def __init__(self, image, **kwargs):
        pygame.sprite.Sprite.__init__(self)
        self.image = pygame.image.load(image)  # image获取图像surface
        self.rect = self.image.get_rect()  # rect获取宽高
        # 敌机要求在最上一行随机位置刷新
        self.rect.x = random.randint(1, WIN_SIZE[0] - self.rect.x)
        self.rect.y = 0

    def update(self, *args):
        # 敌机的行为:是在间隔一定时间自动在最上一行随机位置产生 然后无脑向下直走 超出最下边界 就自动销毁他
        # 后面也可以考虑远距离开始瞄准英雄飞机抛物线撞击 后面再说
        self.rect.y += 10
        if self.rect.y > WIN_SIZE[1]:
            self.kill()  # 敌机超出最下面边界了 自然是自动销毁

    def __del__(self):
        print("<敌机销毁>")


class BulletClass(pygame.sprite.Sprite):
    """
         定义一个子弹类 继承自pygame的精灵类
         属性 image rect  定义子弹的图片来源和初始坐标和宽高
         方法 update __init__(需要调用父类的init)
         支持 需要pygame和 常量 WIN_SIZE
         """

    def __init__(self, image, hero_rect):
        pygame.sprite.Sprite.__init__(self)
        self.image = pygame.image.load(image)  # image获取图像surface
        self.rect = self.image.get_rect()  # rect获取宽高
        # 子弹的初始位置要求在英雄飞机的上部中间位置
        self.rect.x = hero_rect.x + int(hero_rect.w/2)
        self.rect.y = hero_rect.y

    def update(self, *args):
        # 子弹的行为:是在间隔一定时间自动在英雄飞机最上一行中间位置产生 然后无脑向上直走 超出最上边界 就自动销毁他
        self.rect.y -= 10
        if self.rect.y < 0 - self.rect.h:
            self.kill()  # 子弹超出最上边界 就自动销毁他

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


class BgClass(pygame.sprite.Sprite):
    """
         定义一个背景类 继承自pygame的精灵类
         属性 image rect  定义背景的图片来源和初始坐标和宽高 初始位置是超出了游戏窗口 开始是左下和左下对齐 到头了是左上和左上对齐
         方法 update __init__(需要调用父类的init)
         支持 需要pygame和 常量 WIN_SIZE
         """

    def __init__(self, image):
        pygame.sprite.Sprite.__init__(self)
        self.image = pygame.image.load(image)  # image获取图像surface
        self.rect = self.image.get_rect()  # rect获取宽高
        self.rect.y = WIN_SIZE[1] - self.rect.h  # 设置背景的初始高度

    def update(self, *args):
        # 背景的行为:图左下和窗口左下对齐 然后慢慢的向下移动 当然了 我这里背景图片是1400高度 是2个背景图拼接在一起的
        # 背景图的左上对齐窗口左上或者超出 就自动回到初始位置重新来过
        self.rect.y += 10
        if self.rect.y > 0:
            self.rect.y = WIN_SIZE[1] - self.rect.h  # 让背景重新回到上面重新滚动


class WinClass(object):
    """
    经过测试用get_window返回一个窗口对象可以正常的使用
    这类其实就是生成一个游戏窗口对象并返回它
    需要pygame库的支持
    """
    def __init__(self, pos):
        self.x = pos[0]
        self.y = pos[1]

    def get_window(self):
        return pygame.display.set_mode((self.x, self.y))


class GameGod(object):
    def __init__(self):
        # 整体代码初始化 常用属性的声明
        pygame.init()

        # 窗口建立
        tmp_win = WinClass(WIN_SIZE)
        self.win1 = tmp_win.get_window()

        # 我机 敌机 的精灵实例和精灵组的创建 背景 子弹精灵组
        self.bg1 = BgClass(BG_PATH)
        self.bg_group = pygame.sprite.Group(self.bg1)
        self.hero1 = HeroPlaneClass(HERO_PATH)
        self.hero_group = pygame.sprite.Group(self.hero1)
        self.enemy_group = pygame.sprite.Group()
        self.bullet_group = pygame.sprite.Group()

        # clock的建立和定时器定时生成敌机和生成子弹
        self.clock = pygame.time.Clock()
        pygame.time.set_timer(CREATE_ENEMY_EVENT, 1000)
        pygame.time.set_timer(CREATE_BULLET_EVENT, 100)

    def game_start(self):

        # 正式循环的开始
        #  循环fps 事件触发 判断碰撞 绘图 刷新
        while True:
            # 1. 设置刷新帧率
            self.clock.tick(FPS)
            # 2. 事件监听
            # 开始遍历事件做针对处理 同时避免卡顿
            for event in pygame.event.get():
                if event.type == pygame.QUIT:
                    pygame.quit()
                if event.type == CREATE_ENEMY_EVENT:
                    # 定时创建敌机
                    self.enemy_group.add(EnemyPlaneClass(ENEMY_PATH))
                if event.type == CREATE_BULLET_EVENT:
                    # 定时开火 其实本质是定时生成子弹 子弹被生成后按照子弹类定义的行为移动
                    self.hero1.fire(self.bullet_group)

            # 配合按键事件来移动我机
            keys_pressed = pygame.key.get_pressed()
            if keys_pressed[pygame.K_UP]:
                print("向上")
                # hero1.rect.y -= 4
                self.hero1.move(0, -4)
            elif keys_pressed[pygame.K_DOWN]:
                print("向下")
                # hero1.rect.y += 4
                self.hero1.move(0, 4)
            elif keys_pressed[pygame.K_LEFT]:
                print("向左")
                # hero1.rect.x -= 4
                self.hero1.move(-4, 0)
            elif keys_pressed[pygame.K_RIGHT]:
                print("向右")
                # hero1.rect.x += 4
                self.hero1.move(4, 0)

            # 3. 碰撞检测
            #   子弹摧毁敌机(子弹组和敌机组的碰撞测试)
            pygame.sprite.groupcollide(self.bullet_group, self.enemy_group, True, True)

            #   敌机撞毁英雄(敌机组和英雄精英的碰撞测试)
            enemies = pygame.sprite.spritecollide(self.hero1, self.enemy_group, True)

            # 判断列表时候有内容 就是说我机被撞上了
            if len(enemies) > 0:
                # 让英雄牺牲
                self.hero1.kill()

                # 结束游戏
                pygame.quit()
                exit()

            # 4. 更新/绘制精灵组
            self.bg_group.update()  # 肯定是先更新背景
            self.bg_group.draw(self.win1)

            self.hero_group.update()
            self.hero_group.draw(self.win1)

            self.enemy_group.update()
            self.enemy_group.draw(self.win1)

            self.bullet_group.update()
            self.bullet_group.draw(self.win1)

            # 5. 更新显示
            pygame.display.update()

    def game_over(self):

        pygame.quit()
        exit()


if __name__ == '__main__':
    # 没事可以外面加个异常处理 懒得动弹了
    god1 = GameGod()
    god1.game_start()
    god1.game_over()

素材:另存为到py文件下的images文件夹下
images/background.png
background

images/me1.png
me1
images/enemy1.png
enemy1
images/bullet1.png
bullet1



posted @ 2020-09-25 12:21  点-滴  阅读(548)  评论(0编辑  收藏  举报