• Pgzero飞机大战


    Pgzero飞机大战

    提示

    关于Pgzero更基础的东西可以参考我之前的一篇文章

    利用pgzero做一个接球的小游戏 - 菜缤的世界 CairBin’s Blog

    问题

    解决的问题&特点:

    • 在这个项目中我自己手写了gameObject基类代替了pgzero中的Actor类,并且该项目中的所有对象均继承此类

    • 之所以这样写,是因为我发现Actor无法对图片进行缩放,我利用pygame.transform.scale解决了这个问题

    待思考的问题:

    • 这个项目最没搞明白的地方就是精确碰撞。原本是想通过pygame的mask.overlap来实现精确碰撞,但无论我怎么改获取的offset坐标差值元组的纵横坐标都为0,如果有什么方法请大佬们在文章下放或私信告诉我一声
    • 我根据坐标判断两个矩形重叠来实现碰撞检测,但图片周围留白的地方也会触发,因此该方式不是一种精确的检测
    • 敌人爆炸没有爆炸特效,原因是我忘写了(doge)。要实现也很简单,我的思路是爆炸时修改为爆炸图片,最好是gif类型(我并不喜欢用pgzero自带的动画类, 感觉用起来都怪怪的),然后一定时间后效果消失

    代码

    资源

    资源来自网络 (非商业使用),我打包上传到百度云、CSDN资源了,或者直接从GitHub上获取

    百度云 提取码:6d1r
    CSDN资源 需要积分,如果你想“赞助”一下作者,可以通过该方式下载(doge)

    define.py

    文件define.py定义了一些常量(并非真正的常量),便于代码维护

    # 窗口参数
    WINDOW_TITLE = 'plane war'
    WINDOW_WIDTH = 800
    WINDOW_HEIGHT = 500
    
    BG_COLOR = 'white'
    
    # 方块尺寸 长/宽
    BLOCK_SIZE = 50
    
    # 飞机血量
    PLANE_HP = 3
    
    # 子弹参数
    BULLET_SPEED = 10
    BULLET_SIZE = (10, 10)
    BULLET_MOVE_LENGTH = -20
    BULLET_AGRES = 1
    
    # 敌人参数
    ENEMY_SPEEED = 5
    ENEMY_CREATE_TIME = 30
    
    # 血量大小
    HEART_SIZE = 30
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    gameObject.py

    文件gameObject.py用于创建所有对象都具有的共同属性和方法(父类)

    from typing import Tuple
    from numpy import rec
    import pygame
    from enum import Enum
    
    # 坐标锚点
    class posAnchor(Enum):
        TOP_LEFT = 0        # 左上方
        TOP = 1             # 正上方
        TOP_RIGHT = 2       # 右上方
        LEFT = 3            # 左
        CENTER = 4          # 中心
        RIGHT = 5           # 右
        BOTTOM_LEFT = 6     # 左下方
        BOTTOM = 7          # 正下方
        BOTTOM_RIGHT = 8    # 右下方
    
    # 游戏基类
    class gameObject:
        def __init__(self, img, init_pos:tuple, size:tuple, screen = None, pos_anchor=posAnchor.TOP_LEFT) -> None:
            self.img = img          # pgzero图片对象
            self.size = size        # 大小,元组(宽, 高)
            self.screen = screen    # screen
            self.destroy = False    # 用于检测是否被销毁,spiritGroup中的checkDestroy方法检测该属性,为True将从spiritGroup中被移除
    
            self.__anchor = pos_anchor                                  # 锚
            self.__pos = self.__getTruePos(init_pos, self.__anchor)     # 绘制坐标
            self.__userPos = init_pos                                   # 锚坐标
    
            # 图片容器对象,需导入pygame
            self.obj = pygame.transform.scale(self.img, self.size)
    
        # 初始化, 在pgzero update()中调用
        def update(self, screen):
            self.initScreen(screen)
            self.draw()
    
        # 初始化screen
        def initScreen(self, screen):
            self.screen = screen
    
        # 绘制
        def draw(self):
            self.screen.blit(self.obj, self.__pos)
    
        # 更改位置(要求锚点,防止坐标混乱)
        def changePos(self, pos: tuple, pos_anchor):
            self.__pos = self.__getTruePos(pos, pos_anchor)
            self.__userPos = pos
    
        # 获取宽度
        def width(self):
            return self.size[0]
    
        # 获取高度
        def height(self):
            return self.size[1]
    
        # 获取大小尺寸
        def getSize(self):
            return self.size
    
        # 获取横坐标(锚点坐标)
        def x(self):
            return self.__userPos[0]
    
        # 获取纵坐标(锚点坐标)
        def y(self):
            return self.__userPos[1]
    
        # 获取左上角坐标
        def getTopLeftPos(self):
            return self.__pos
    
        # 获取rect
        def getRect(self):
            return self.obj.get_rect()
    
        # 获取mask
        def getMask(self):
            return pygame.mask.from_surface(self.obj)
    
        # 获取锚点
        def getAnchor(self):
            return self.__anchor
    
        # 更改销毁属性
        def setDestroy(self, choose:bool):
            self.destroy = choose
    
        # 获得销毁属性
        def getDestroy(self)->bool:
            return self.destroy
    
        # 碰撞检测
        def collisionDetection(self, other):
            this_pos = self.getTopLeftPos()
            other_pos = other.getTopLeftPos()
            
            this_center_pos = (this_pos[0]+self.width()/2, this_pos[1]+self.height()/2)
            other_center_pos = (other_pos[0]+other.width()/2, other_pos[1]+other.height()/2)
    
            # 判断矩形相交
            if abs(this_center_pos[0] - other_center_pos[0]) <= (self.width() + other.width())/2 and abs(this_center_pos[1] - other_center_pos[1]) <= (self.height() + other.height())/2:
                return True
            else:
                return False
    
            def move(*args):
                pass
    
    
        # 根据锚和给出坐标,获取左上角坐标
        def __getTruePos(self, pos: tuple, pos_anchor):
            if pos_anchor == posAnchor.TOP_LEFT:
                return pos
            elif pos_anchor == posAnchor.TOP:
                return (pos[0]-self.size[0]/2, pos[1])
            elif pos_anchor == posAnchor.TOP_LEFT:
                return (pos[0]-self.size[0], pos[1])
            elif pos_anchor == posAnchor.LEFT:
                return (pos[0], pos[1]-self.size[1]/2)
            elif pos_anchor == posAnchor.CENTER:
                return (pos[0]-self.size[0]/2, pos[1]-self.size[1]/2)
            elif pos_anchor == posAnchor.RIGHT:
                return (pos[0]-self.size[0], pos[1]-self.size[1]/2)
            elif pos_anchor == posAnchor.BOTTOM_LEFT:
                return (pos[0], pos[1]-self.size[1])
            elif pos_anchor == posAnchor.BOTTOM:
                return (pos[0]-self.size[0]/2, pos[1]-self.size[1])
            elif pos_anchor == posAnchor.BOTTOM_RIGHT:
                return (pos[0]-self.size[0], pos[1]-self.size[1])
            else:
                return None
    
    # 精灵组
    class spiritGroup:
        def __init__(self) -> None:
            self.__spiritList = []
    
        # ----------------------列表操作
    
        # 追加
        def append(self, spirit: gameObject):
            self.__spiritList.append(spirit)
    
        # 索引
        def index(self, g_obj: gameObject):
            return self.__spiritList.index(g_obj)
    
        # 插入
        def insert(self, num):
            self.__spiritList.insert(num)
    
        # 移除
        def remove(self, g_obj: gameObject):
            self.__spiritList.remove(g_obj)
    
        # 根据索引移除
        def removeAsIndex(self, num):
            self.remove(self.index(num))
    
        # 元素个数
        def size(self):
            return len(self.__spiritList)
    
        # 清空
        def clear(self):
            self.__spiritList.clear()
    
        # ----------------------运算符重载
    
        # []
        def __getitem__(self, index) -> gameObject:
            return self.__spiritList[index]
    
        # in
        def __contains__(self, elem):
            return 
    
        # +
        def __add__(self, other):
            new_g_obj = spiritGroup()
            for i in self.__spiritList:
                new_g_obj.append(i)
            for i in other:
                new_g_obj.append(i)
    
            return new_g_obj
        
        # ----------------------基本操作
    
        # 初始化(draw)
        def update(self, screen):
            for i in self.__spiritList:
                i.update(screen)
    
        # 绘制
        def draw(self):
            for i in self.__spiritList:
                i.draw()
    
        # 碰撞检测
        def collisionDetection(self, other, collided = None, args=()):
            for e in self.__spiritList:
                for i in other:
                    if e.collisionDetection(i) == True:
                        if collided == None:
                            e.setDestroy(True)
                            i.setDestroy(True)
                        else:
                            collided(e, i, *args)
    
        # 移动
        def move(self, *args):
            for i in self.__spiritList:
                i.move(*args)
    
        # 检测元素是否应该被销毁(移除),并执行
        def checkDestroy(self):
            for i in self.__spiritList:
                if i.getDestroy():
                    temp = i
                    self.remove(i)
                    del i
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178
    • 179
    • 180
    • 181
    • 182
    • 183
    • 184
    • 185
    • 186
    • 187
    • 188
    • 189
    • 190
    • 191
    • 192
    • 193
    • 194
    • 195
    • 196
    • 197
    • 198
    • 199
    • 200
    • 201
    • 202
    • 203
    • 204
    • 205
    • 206
    • 207
    • 208
    • 209
    • 210
    • 211
    • 212
    • 213
    • 214
    • 215
    • 216
    • 217
    • 218
    • 219
    • 220
    • 221
    • 222
    • 223
    • 224
    • 225

    actors.py

    文件actors.py定义游戏内具体创建对象的类,例如飞机类,子弹类等

    import define
    import gameObject
    
    # 子弹类
    class bullet(gameObject.gameObject):
        def __init__(self, img, init_pos: tuple, size: tuple, screen=None, pos_anchor=...) -> None:
            super().__init__(img, init_pos, size, screen, pos_anchor)
            self.speed = define.BULLET_MOVE_LENGTH
            self.agres = define.BULLET_AGRES            #子弹威力
    
        def move(self, *args):
            self.changePos((self.x(), self.y()+self.speed), self.getAnchor())
            pos = self.getTopLeftPos()
            if pos[1]+self.height() < 0:
                self.setDestroy(True)
    
    
    # 飞机类
    class plane(gameObject.gameObject):
        def __init__(self, img, init_pos: tuple, size: tuple, screen=None, pos_anchor=...) -> None:
            super().__init__(img, init_pos, size, screen, pos_anchor)
            self.hp = define.PLANE_HP   # 玩家飞机血量
    
        def move(self, *args):
            # 边界判断
            pos = list((args[0][0]-self.width()/2, args[0][1]-self.height()/2))
    
            if pos[0] <0:
                pos[0] = 0
            if pos[0]+self.width() > define.WINDOW_WIDTH:
                pos[0] = define.WINDOW_WIDTH-self.width()
            
            if pos[1]<0:
                pos[1] = 0
            if pos[1]+self.height() > define.WINDOW_HEIGHT:
                pos[1] = define.WINDOW_HEIGHT-self.height()
    
            self.changePos(tuple(pos), gameObject.posAnchor.TOP_LEFT)
    
        # 开火,参数为子弹pgzero图片对象
        def fire(self, bullet_img):
            pos = self.getTopLeftPos()
            bullet_pos = (pos[0]+self.width()/2, pos[1])
            bull = bullet(bullet_img, bullet_pos, define.BULLET_SIZE, screen = self.screen, pos_anchor=gameObject.posAnchor.BOTTOM)
            return bull
    
    
    # 敌人类
    class enemy(gameObject.gameObject):
        def __init__(self, img, hp, init_pos: tuple, size: tuple, screen=None, pos_anchor=...) -> None:
            super().__init__(img, init_pos, size, screen, pos_anchor)
            self.hp = hp
            self.speed = define.ENEMY_SPEEED
            self.destroy = False
    
        def move(self, *args):
            if self.destroy == False:
                self.changePos((self.x(), self.y()+self.speed),
                            gameObject.posAnchor.CENTER)
                pos = self.getTopLeftPos()
                if pos[1] > define.WINDOW_HEIGHT:
                    self.setDestroy(True)
    
    # 血量类, 用于显示hp
    class heart(gameObject.gameObject):
        def __init__(self, img, init_pos: tuple, size: tuple, screen=None, pos_anchor=...) -> None:
            super().__init__(img, init_pos, size, screen, pos_anchor)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67

    main.py

    main.py程序的主要入口在此文件中

    import random
    import pgzrun
    import define
    import actors
    import pygame, sys
    
    # 窗口基本配置, pgzero会调用此处的变量来设置窗口
    TITLE = define.WINDOW_TITLE
    WIDTH = define.WINDOW_WIDTH
    HEIGHT = define.WINDOW_HEIGHT
    
    # 图片映射字典
    images_dict = {
        'plane': images.plane,
        'bullet': images.bullet,
        'enemy': images.enemy,
        'heart': images.heart
    }
    
    # 子弹组
    bullet_list = actors.gameObject.spiritGroup()
    # 敌人组
    enemy_list = actors.gameObject.spiritGroup()
    # 血量显示组
    heart_list = actors.gameObject.spiritGroup()
    
    # 角色飞机
    plane = actors.plane(images_dict['plane'], (define.WINDOW_WIDTH/2, define.WINDOW_HEIGHT*3/4),
                         (define.BLOCK_SIZE, define.BLOCK_SIZE), pos_anchor=actors.gameObject.posAnchor.CENTER)
    
    # 生成敌人时间间隔
    enemy_time = 0
    # 游戏得分
    player_scores = 0
    
    # -------------------------------------------------游戏控制----------------------------------- #
    # 随机生成敌人
    def creatEnemy():
        global enemy_time
        if enemy_time <= 0:
            # 此处血量随机1-2
            enemy_list.append(actors.enemy(images_dict['enemy'], random.randint(1,2),
                (random.randint(5, define.WINDOW_WIDTH-define.BLOCK_SIZE), 0), (define.BLOCK_SIZE, define.BLOCK_SIZE), 
                pos_anchor=actors.gameObject.posAnchor.TOP_LEFT))
            enemy_time = define.ENEMY_CREATE_TIME
        else:
            enemy_time -= 1
    
    # 显示血量
    def showHp():
        hp = plane.hp
        i = 0
        heart_list.clear()
        while i<hp:
            he = actors.heart(images_dict['heart'], (i*define.HEART_SIZE, 0), (define.HEART_SIZE,
                              define.HEART_SIZE), pos_anchor=actors.gameObject.posAnchor.TOP_LEFT)
            heart_list.append(he)
            i+=1
    
    # 得分
    def getScores():
        global player_scores
        player_scores += 1
    
    # 游戏结束
    def gameOver():
        global player_scores
        pygame.quit()
        print('Game Over')
        print('Your scores are {}'.format(player_scores))
        sys.exit()
    
    # 子弹碰撞敌机的回调函数
    # 默认的碰撞检测直接将对象删除,如果给敌人设置血量根据子弹威力来扣血则需要外部提供回调函数
    def bulletCollidedEnemy(bullet: actors.bullet, enemy: actors.enemy, getScore):
        enemy.hp -= bullet.agres
        bullet.setDestroy(True)         #销毁子弹
        # 根据血量判断敌人是否销毁
        if enemy.hp <= 0:
            enemy.setDestroy(True)
            getScore()                 # 得分
    
    # 敌机与玩家碰撞
    # 玩家飞机没有精灵组,所以直接写碰撞事件在Update()中调用
    def enemyCollidedPlane():
        # 遍历并检测碰撞
        for i in enemy_list:
            if i.collisionDetection(plane):
                plane.hp -= 1           #扣血
                i.setDestroy(True)
                # 检测是否血量归零以结束游戏
                if plane.hp == 0:
                    gameOver()
    
    # -------------------------------------------------pgzero内置----------------------------------- #
    
    def update():
        plane.update(screen)
    
        bullet_list.update(screen)
        bullet_list.move()
    
        creatEnemy()
        enemy_list.update(screen)
        enemy_list.move()
        enemy_list.checkDestroy()
    
    
        showHp()
        heart_list.update(screen)
    
        bullet_list.collisionDetection(enemy_list, collided=bulletCollidedEnemy, args=(getScores,))
        bullet_list.checkDestroy()
    
        enemyCollidedPlane()
    
    def draw():
        screen.fill(define.BG_COLOR)
    
        plane.draw()
        bullet_list.draw()
        enemy_list.draw()
        heart_list.draw()
    
    def on_mouse_move(pos):
        plane.move(pos)
    
    def on_key_down(key):
        # 空格飞机发射子弹
        if key == keys.SPACE:
            bullet_list.append(plane.fire(images_dict['bullet']))
    
    # -------------------------------------------------运行-----------------------------------#
    pgzrun.go()
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135

    开源

    Github:

    CairBin/PlaneWar: A game under python(pgzero) (github.com)

  • 相关阅读:
    Redis数据库角色:不只是缓存,还可以作为主数据库!
    武汉申报!2022年武汉经开区(汉南区) 技术合同登记奖励申报条件、材料申报流程
    【LTTng】核心概念精读
    翻译软件免费版下载-免费版翻译软件下载
    判断日期区间或季节等
    Go | 基本数据类型的相互转换
    我的Compose开源项目《出行防疫App》已发布
    【 OpenGauss源码学习 —— 列存储(analyze)(三)】
    【在线教育】POI入门
    tomcat nio2源码分析
  • 原文地址:https://blog.csdn.net/qq_42759112/article/details/126023149