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
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
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)
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()
开源
Github: