pygame.sprite
这个模块包含了几个简单的类,可以在游戏内部使用。有主要的Sprite类和几个包含Sprite的Group类。在使用pygame 时,这些类的使用完全是可选的。这些类相当轻量级,只提供了大多数游戏共有代码的起点。(这句话是官网翻译来的,看到这一句自谦的话,大家应该明白了吧:重点来了,该划重点了)
Sprite精灵类旨在用作游戏中不同类型对象的基类。还有一个用于简单存储精灵的基组Group。在游戏中我们可以创建新组类Group,用于操作它们包含的特定精灵实例。
Group类能把它包含的精灵(Sprite)画到图层(Surface)里。函数Group.draw()需要它里面的每一个精灵都具有Sprite.image和Sprite.rect属性。Group.clear()函数要求相同的属性,它能用背景来擦除这些精灵。这里还有一些更高级组:pygame.sprite.RenderUpdates()和pygame.sprite.OrderedUpdates()。
最后,这个模块还包含了几个碰撞函数。这能帮助我们找出在几个群组中发生了碰撞的精灵。当然,要找到这些碰撞,就必需要求他们中每一个精灵都有rect 属性。
这些组的设计旨在高效地添加和移除精灵,并且允许低成本的测试一个精灵是否已存在于某个组中。一个精灵可能存在于任意数量的组中。在游戏中,可以使用一些组来控制精灵的渲染,同时使用完全独立的另一些组来控制交互或玩家移动等。与其在派生的精灵类中添加类型属性或布尔值,不如考虑将精灵保持在有组织的组内。这将在游戏后期更容易地查找。
精灵和组通过add()和remove()方法来管理它们之间的关系。这些方法可以接受单个或多个目标来加入或移除成品。在组中反复添加和移除同一个精灵是安全的。
精灵不是线程安全的,因些,如果要使用线程,请自行锁定它们。
1. pygame.sprite.Sprite(*groups)->Sprite
- 可见游戏对象基类。它的派生类需要重写update()函数,并指定Sprite.image和Sprite.rect属性。能加入任意数量的组中。
- 当继承Sprite类时,确保在将Sprite添加到组Groups之前调用基类的初始化器(即父类的构造函数)。这样做是为了确保基类被正确初始化,以便派生类可以正常工作。
- update(*args,**kwargs)
* 用于在游戏循环中更新精灵的状态(如移动精灵、改变精灵状态或者检测精灵碰撞等)。
* *args:可以接受任意数量的位置参数
* *kwargs:可以接受任意数量的关键字参数。
* 这个方法默认是什么都不做,它只是一个方便的钩子(hook),我们需要时要重写它。
* 这个方法由Group.update()调用。
* 如果我们不使用Group类中的update()方法,就不需要在它的子类中实现这个方法。 - add(*groups)
* 把精灵添加到一个或多个组中。通过将精灵添加到组中,可以更方便地对它们进行批量操作,如更新、绘制或碰撞检测。 - remove(*groups)
* 把精灵从一个或多个组中移除。 - kill()
* 把精灵从所有包含它的组中移除。这不会改变精灵自身的属性和状态,即使调用了这个方法,精灵仍然可以继续使用,也可重新加入组中。这在游戏编程中很有用,特别是我们需要临时从屏幕上移除精灵,但并不希望完全销毁它,以以便之后再次使用它。 - alive()->bool
* 检查精灵是否包含在(任何)组中。 - groups()->group_list
* 返回包含这个精灵的所有组的列表。
2. pygame.sprite.WeakSprite(*groups)->WeakSprite
- 这是一个sprite的特殊子类,它以弱引用的方式引用其所属的组。这意味着它引用所属的组时不会阻止这些组被垃圾回收。也就是说如果一个组中只包含WeakSprite精灵的话,它会被程序的垃圾回收机制处理,从而释放内存。
3. pygame.sprite.DirtySprite(*groups)->DirtySprite
- 用于创建一个更多属性和特性的精灵。
- dirty默认值为1
- 当dirty为1时,表示精灵需要重绘,然后将值置为0。
- 当dirty为0时,表示精灵不需要重绘。
- 当dirty为2时,表示精灵每帧都要重绘。
- 更详细的信息以后用到再学。😃
4. pygame.sprite.Group(*sprites)->Group
用于存储和管理多个精灵的容器。Group是pygame中管理游戏对象的核心工具之一,它简化了对多个精灵的管理和操作,使得游戏更高效和有序。通过使用Group,我们可以轻松地组织和控制游戏中的多个对象,如敌人、子弹和玩家等。
这是一个精灵的容器,这个类设计为可以被继承,以便创建具有更具体行为的容器。构造函数接受任意数量的精灵添加到组中。Group支持以下python的基础操作
* in 检测精灵是否在组中
* len 返回组中精灵的数量
* bool 检测组中是否有精灵
* iter 遍历组中所有的精灵对象。
- sprites()->sprite_list
* 返回组中包含的精灵的列表。我们也可以通过这个列表来遍历这个组。但当组有修改时,不能用这个方法遍历。可用它的copy() - copy()->Group
* 复制当前的组,从而创建一个新组。 - add(*sprites)
* 将任意数量的精灵添加到组中。如果这个精灵已经是这个组的成员时,不会重复添加。参数sprite也可以是包含Sprite对象的迭代器。 - remove(*sprites)
* 从组中移除任意数量的精灵。参数sprite也可以是包含Sprite对象的迭代器。 - has(*sprites)->bool
* 测试组中是否包含给定的所有精灵。这个函数的功能类似于在Group上使用"in"操作符。 - update(*args,**kwargs)
* 对Group里的所有精灵的调用它们的update函数。这个函数不返回任何值。在实际应用中,update方法通常用于更新精灵的状态。 - draw(Surface,special_flags=0) ->List[Rect]
* 把组中包含的所有精灵画到指定的图层中。
* 这个函数使用精灵的image属性作为源图像,rect属性确定绘制位置。
* Group不维护精灵的顺序,因此绘制顺序是任意的。
*在实际应用中,draw方法通常用于在游戏或图形应用程序中渲染精灵。由于Group不保证精灵对象的绘制顺序,我们可能需要有Group外部控制精灵的顺序,以确保正确的绘制层次和效果。返回的矩形列表可以用于碰撞检测或其他需要知道精灵绘制区域的操作。 - clear(Surface_dest,background)
* 用背景覆盖精灵图像。这个函数通过在精灵位置上绘制背景来清除最后一次Group.draw()绘制的精灵。参数background通常是一个与目标大小一样的图层。然而有时也会是一个带有目标图层和一个用于清除它的区域的回调函数。
* 在实际应用中,clear方法通常用于在绘制新帧之前清除屏幕上的旧图像,以防止图像残留或闪烁。如果提供一个Surface作为背景,它会覆盖之前精灵位置。如果提供了一个回调函数,那么这个函数会在每次需要清除精灵时被调用,允许我们自定义清除逻辑,例如使用特定的图案或颜色来填充背景。
下面是一段有红色来填充指定区域的回调函数来清除精灵的代码
def clear_callback(surf,rect):
color = (255,0,0)
surf.fill(color,rect)
- empty()
* 移除组中所有的精灵。
5. pygame.sprite.GroupSingle(sprite=None)->GroupSingle
- 这个组只会保留一个精灵,每当有新的精灵加入时,会自动移除旧的精灵。
- GroupSingle.sprite 是它的一个属性。用于访问或设置容器中的精灵。当容器为空时,这个属性的值为None。
- 在实际应用中,当我们需要用一个容器来管理游戏中的单个重要对象(如玩家)时,使用GroupSingle可以确保我们不会意外地添加多个对象到同一个容器中。
6. pygame.sprite.spritecollide()
- spritecollide(sprite,group,dokill,collided=None)->Sprite_list
- 返回组中与另一个精灵发生碰撞的所有精灵的列表。
- 通过比较它们的rect属性来判断它们是否发生了碰撞。
- dokill参数是一个布尔值,用来表示当发生碰撞时,是否把发生碰撞的精灵从组中移除。
- collided参数是一个回调函数。用于自定义碰撞检测逻辑。collided能调用的有
- collide_rect collide_rect_ratio collide_circle collide_circle_ratio collide_mask
- 如果没有传递collided参数,那么它们将通过它们的rect属性来计算碰撞。
7. pygame.sprite.collide_rect(sprite1,sprite2)->bool
- 检测两个精灵之间是否发生碰撞。这个函数通常用作*collide函数的回调函数。这两个精灵必需有rect属性。
8. pygame.sprite.collide_rect_ratio(ratio)->collided_callable
- 根据比例因子来缩放精灵的矩形区域,然后检测两个缩放后的矩形碰撞。这个函数返回一个可调用对象,我们可以将这个对象作为回调函数传递给pygame的碰撞检测函数。
9. pygame.sprite.collide_circle(sprite1,sprite2)->bool
- 检测两个精灵之间是否发生碰撞.检测方式是通过两个精灵为中心的圆是否发生碰撞。如果精灵有radius属性,那么会用这个属性为半径画圆,否则将会画一个足以容纳精灵的rect的圆。
10. pygame.sprite.collide_circle_ratio(ratio)->collide_callable
- 检测两个精灵之间圆形是否发生碰撞.检测时要乘以比例因子。这个函数返回一个可调用对象,我们可以将这个对象作为回调函数传递给pygame的碰撞检测函数。
11. pygame.sprite.groupcollide(group1,group2,dokill1,dokill2,collided=None)
- 检测两个精灵组的碰撞情况。
- 函数返回是一个字典。键是group1中的每一个精灵,值是group2中与对应键发生碰撞的精灵列表。
- 当dokill1为True时,删除group1中发生碰撞的精灵。dokill2为True时,删除group2中发生碰撞的精灵。
12. pygame.sprite.spritecollideany(sprite,group,collided=None)
- 检测精灵与组中任意精灵发生碰撞。
- 如果发生了碰撞,返回第一个发生碰撞的精灵。如果都没有发生碰撞,则返回None.
'''
当玩家与怪物按比例因子扩大范围碰撞时,自动发出子弹,怪物减血,当血清空时,怪物死亡,生成新的怪物。
同时,怪物会向玩家冲来,当玩家与怪物真实碰撞时,玩家会减血。当玩家血清空时,游戏结束。
点击右上角商店,可以改变碰撞方式及碰撞范围。
'''
import pygame,sys
from pygame.sprite import Sprite
from pygame import font
import random
class Player:
'''
玩家资料
'''
def __init__(self,mg):
self.screen = mg.screen
#导入图形,根据需要改变大小
self.image = pygame.image.load("images/player1.jpg").convert()
self.image = pygame.transform.scale_by(self.image, 0.3)
#设置透明背景,及图片透明度
colorkey = self.image.get_at((0,0))
self.image.set_colorkey(colorkey)
self.image.set_alpha(120)
# 获取外接矩形,设置初始位置为显示窗口中心
self.rect = self.image.get_rect()
self.rect.center = mg.screen_rect.center
#设置x,y 更精准控制位置
self.x = float(self.rect.x)
self.y = float(self.rect.y)
#移动方向标志
self.moving_r = False
self.moving_l = False
self.moving_u = False
self.moving_d = False
#移动速度
self.speed = 2.5
# 设置圆形碰撞时的半径:
self.radius=30
#血条
self.health = 100
self.blood_height =3
self.blood_width = self.rect.width
# 根据方向键,位置更新
def update(self):
if self.moving_r:
self.x += self.speed
if self.moving_l:
self.x -= self.speed
if self.moving_u:
self.y -= self.speed
if self.moving_d:
self.y += self.speed
self.rect.x = self.x
self.rect.y = self.y
# 显示碰撞矩形范围,注:实际并不是以它为碰撞图形,只是为了直观观察加上的。实际无任何作用。
self.rect2 = self.rect.scale_by(mg.scale)
# 更新血条
self.blood_x =self.x
self.blood_y = self.y - self.blood_height -5
#把玩家画到图层上。
def draw(self):
self.screen.blit(self.image,self.rect)
# 为了直观观察碰撞情况,人为加的碰撞范围。实际游戏中,是不需要的。
if mg.flag == 1:
#当选择为矩形碰撞时,画出碰撞范围,以便我们观察。
pygame.draw.rect(self.screen,"green",self.rect2,1)
#当选择为圆形碰撞时,画出碰撞范围,以便我们观察。
elif mg.flag == 2:
pygame.draw.circle(self.screen,"green",self.rect.center,self.radius * mg.scale,1)
# 绘制血条
pygame.draw.rect(self.screen,"green",(self.blood_x,self.blood_y,self.blood_width,self.blood_height))
class Enemy(Sprite):
'''
敌人的数据
'''
def __init__(self,mg):
#Sprite 初始化
super().__init__()
self.screen = mg.screen
#导入敌人图片,并根据需要改变大小
self.image = pygame.image.load("images/alien1.gif").convert()
self.image = pygame.transform.scale_by(self.image,0.2)
#设置透明背景,及图片透明度
colorkey = self.image.get_at((0,0))
self.image.set_colorkey(colorkey)
self.image.set_alpha(100)
self.rect = self.image.get_rect()
#设置血量及速度
self.health = 100
self.speed = 2
#显示碰撞矩形范围,注:实际并不是以它为碰撞图形,只是为了直观观察加上的。实际无任何作用。
self.rect2 = self.rect.scale_by(mg.scale)
#设置圆形碰撞时的半径:
self.radius=25
#血条
self.height =3
self.blood_x =self.rect.x
self.blood_y = self.rect.top - self.height -5
self.width = self.rect.width
#计算目标位置,当与玩家发生碰撞时,玩家的位置
self.target_x = 0
self.target_y = 0
def update(self):
self.blood_x =self.rect.x
self.blood_y = self.rect.top - self.height -5
# 与玩家发生了碰撞
if self.target_x > 0:
#计算子弹的飞行向量
self.dx = self.target_x - self.rect.centerx
self.dy = self.target_y - self.rect.centery
#计算方向向量长度
dist = (self.dx**2 + self.dy**2)**0.5
#归一化方向向量
if dist != 0:
self.dx /= dist
self.dy /= dist
# 更新当前位置
self.rect.centerx += self.dx * self.speed
self.rect.centery += self.dy * self.speed
def draw(self):
if mg.flag == 1:
#当选择为矩形碰撞时,画出碰撞范围,以便我们观察。
pygame.draw.rect(self.screen,"green",self.rect2,1)
elif mg.flag == 2:
#当选择为圆形碰撞时,画出碰撞范围,以便我们观察。
pygame.draw.circle(self.screen,"green",self.rect.center,self.radius * mg.scale,1)
# 画血条
pygame.draw.rect(self.screen,"green",(self.blood_x,self.blood_y,self.width,self.height))
class Bullet(Sprite):
'''
子弹设置
'''
def __init__(self,mg,pos):
super().__init__()
self.screen = mg.screen
#子弹的半径及初始位置为玩家中心点,速度及伤害值
self.radius = 5
self.pos_x,self.pos_y = mg.player.rect.center
self.bullet_speed = 2
self.hurt =5
#因draw 返回的的Rect 没有rect属性,自己设置一个rect属性。用于组碰撞。
self.rect = pygame.Rect(self.pos_x-self.radius,self.pos_y-self.radius,self.radius*2,self.radius*2)
#计算子弹的目标位置,敌人的位置
self.target_x ,self.target_y= pos
#计算子弹的飞行向量
self.dx = self.target_x - self.pos_x
self.dy = self.target_y - self.pos_y
#计算子弹的方向向量长度
dist = (self.dx**2 + self.dy**2)**0.5
#归一化方向向量
if dist != 0:
self.dx /= dist
self.dy /= dist
def update(self):
#更新子弹位置
self.pos_x += self.dx * self.bullet_speed
self.pos_y += self.dy * self.bullet_speed
self.rect = pygame.Rect(self.pos_x-self.radius,self.pos_y-self.radius,self.radius*2,self.radius*2)
def draw(self):
pygame.draw.circle(self.screen,"red",(self.pos_x,self.pos_y),self.radius,1)
class Shop:
'''
商店。可改变碰撞方式及碰撞缩放倍率
'''
def __init__(self,mg):
self.screen = mg.screen
self.screen_rect = mg.screen_rect
# 商店渲染及把它放到显示窗口的右上角
self.font = pygame.font.SysFont("fangsong",36)
self.txt = "商店"
self.color =(100,100,100)
self.bgcolor = (30,30,30)
self.shop = self.font.render(self.txt,True,self.color,self.bgcolor)
self.shop_rect = self.shop.get_rect()
self.shop_rect.right = self.screen_rect.right-10
self.shop_rect.top = 10
# 商店打开标志,默认不打开
self.open = False
#矩形碰撞按钮渲染及把它放到商店下面
self.shop_r = self.font.render("矩形碰撞",True,self.color,self.bgcolor)
self.shop_r_rect=self.shop_r.get_rect()
self.shop_r_rect.top =self.shop_rect.bottom + 10
self.shop_r_rect.right = self.shop_rect.right
# 圆形碰撞按钮渲染及把它放到矩形碰撞按钮的下面
self.shop_c = self.font.render("圆形碰撞",True,self.color,self.bgcolor)
self.shop_c_rect = self.shop_c.get_rect()
self.shop_c_rect.top = self.shop_r_rect.bottom + 10
self.shop_c_rect.right = self.shop_rect.right
# 碰撞范围增加按钮渲染及把它放到圆形碰撞按钮的下面
self.shop_add = self.font.render("碰撞增加",True,self.color,self.bgcolor)
self.shop_add_rect = self.shop_add.get_rect()
self.shop_add_rect.top = self.shop_c_rect.bottom + 10
self.shop_add_rect.right = self.shop_rect.right
# 碰撞范围减小按钮渲染及把它放到碰撞范围增加按钮的下面
self.shop_decrease = self.font.render("碰撞减小",True,self.color,self.bgcolor)
self.shop_decrease_rect = self.shop_add.get_rect()
self.shop_decrease_rect.top = self.shop_add_rect.bottom + 10
self.shop_decrease_rect.right = self.shop_rect.right
def show_shop(self):
#把商店画到它的位置处。
self.screen.blit(self.shop,self.shop_rect)
# 如果商店打开了,显示商店里的几个按钮。
if self.open:
self.screen.blit(self.shop_r,self.shop_r_rect)
self.screen.blit(self.shop_c,self.shop_c_rect)
self.screen.blit(self.shop_add,self.shop_add_rect)
self.screen.blit(self.shop_decrease,self.shop_decrease_rect)
class MyGame:
def __init__(self):
font.init()
size = width,height = 1000,800
# 碰撞方式 1:矩形碰撞 2:圆形碰撞,默认为矩形碰撞
self.flag = 1
# 碰撞范围因子,默认为3,即原始大小的3倍。
self.scale = 3.0
self.screen = pygame.display.set_mode(size)
self.screen_rect =self.screen.get_rect()
self.clock = pygame.time.Clock()
#创建实例
self.shop =Shop(self)
self.player = Player(self)
#创建精灵组。用于存放精灵(敌人及子弹)
self.enemys = pygame.sprite.Group()
self.bullets = pygame.sprite.Group()
# 子弹发射频率
self.bullet_cooldown = 0
# 敌人攻击频率
self.enemy_cooldown = 0
# 游戏活动状态,默认为True,当玩家血量清空时,改为False.
self.active = True
def _create_enemy(self):
# 创建一个精灵,随机位置。再把它加入到精灵组。
enemy = Enemy(self)
enemy.rect.x = random.randint(10,950)
enemy.rect.y = random.randint(10,750)
self.enemys.add(enemy)
def run_game(self):
'''
主程序循环
'''
while True:
#事件处理
for event in pygame.event.get():
if event.type == pygame.QUIT:
sys.exit()
elif event.type == pygame.KEYDOWN:
self._event_keydown(event)
elif event.type == pygame.KEYUP:
self._event_keyup(event)
elif event.type == pygame.MOUSEBUTTONDOWN:
self._mouse_collide()
#确保有5个敌人
if len(self.enemys) < 5:
self._create_enemy()
# 状态更新
if self.active:
self.player.update()
self.enemys.update()
self._player_enemy_collide()
self.bullets.update()
for bullet in self.bullets:
#当子弹达到显示窗口之外时,从组中移除。
if bullet.pos_x <0 or bullet.pos_x>self.screen_rect.width or bullet.pos_y<0 or bullet.pos_y>self.screen_rect.height:
self.bullets.remove(bullet)
#子弹更新后马上检测子弹与敌人的碰撞
self._bullet_enemy_collide()
# 绘图
self.screen.fill((0, 0, 0))
self.enemys.draw(self.screen)
for enemy in self.enemys:
#用于显示敌人的碰撞范围。
enemy.rect2 = enemy.rect.scale_by(self.scale)
enemy.draw()
self.player.draw()
for bullet in self.bullets:
bullet.draw()
self.shop.show_shop()
pygame.display.flip()
self.clock.tick(60)
def _event_keydown(self,event):
# 键盘按下时的事件处理
if event.key == pygame.K_q:
sys.exit()
# 按下方向键时,玩家相应的移动状态设置为True
elif event.key == pygame.K_RIGHT:
self.player.moving_r = True
elif event.key == pygame.K_LEFT:
self.player.moving_l = True
elif event.key == pygame.K_UP:
self.player.moving_u = True
elif event.key == pygame.K_DOWN:
self.player.moving_d = True
def _event_keyup(self,event):
#键盘按键松开时事件处理。松开方向键时,玩家相应的移动状态设置为False
if event.key == pygame.K_RIGHT:
self.player.moving_r = False
elif event.key == pygame.K_LEFT:
self.player.moving_l = False
elif event.key == pygame.K_UP:
self.player.moving_u = False
elif event.key == pygame.K_DOWN:
self.player.moving_d = False
def _player_enemy_collide(self):
# 玩家与敌人发生碰撞事件检测
if self.flag == 1:
# 矩形碰撞检测
collide = pygame.sprite.spritecollide(self.player,self.enemys,False,pygame.sprite.collide_rect_ratio(self.scale) )
elif self.flag == 2:
# 圆形碰撞检测
collide = pygame.sprite.spritecollide(self.player,self.enemys,False,pygame.sprite.collide_circle_ratio(self.scale))
if collide:
# 当玩家与敌人按比例扩大范围碰撞时,创建一个子弹,并把它加入到子弹组中
for enemy in collide:
if self.bullet_cooldown <= 0:
new_bullet = Bullet(self,enemy.rect.center)
#设置敌人的中心点位置为子弹的攻击目标点
new_bullet.target_x,new_bullet.target_y = enemy.rect.center
self.bullets.add(new_bullet)
#当发出子弹后,设置子弹的频率为30,即循环30次后再次发射子弹。在这里为0.5秒发射一次。
self.bullet_cooldown = 30
self.bullet_cooldown -= 1
#设置玩家的中心点位置为敌人的攻击目标点,敌人向这个点移动。
enemy.target_x,enemy.target_y = self.player.rect.center
if pygame.sprite.spritecollide(self.player,self.enemys,False):
# 检测玩家与敌人的真实碰撞。发生真实碰撞时,敌人会攻击玩家
if self.enemy_cooldown ==0 :
self.player.health -= 5
#更改玩家的血条长度
self.player.blood_width = self.player.rect.width * self.player.health/100
# 设置敌人攻击玩家的频率。在此为0.5秒攻击一次
self.enemy_cooldown = 30
self.enemy_cooldown -=1
if self.player.health <= 0:
#当玩家的血量清空里,设置游戏活动状态为False
self.active = False
print(self.active)
def _bullet_enemy_collide(self):
# 子弹与敌人碰撞
collide = pygame.sprite.groupcollide(self.bullets,self.enemys,True,False)
for values in collide.values():
# 遍历字典的值(注:这个值是一个列表,一个与子弹发生碰撞的敌人的列表)
for value in values:
# 遍历敌人列表
value.health -= 20
# 敌人受伤后,改变它的血条长度
value.width *= value.health/100
#敌人血量清空时,从敌人组中移除
if value.health <= 0:
self.enemys.remove(value)
def _mouse_collide(self):
# 精灵与点(鼠标位置)的碰撞检测
mouse_pos = pygame.mouse.get_pos()
if self.shop.shop_rect.collidepoint(mouse_pos):
# 更改商店打开状态,打开或关闭商店
self.shop.open = not self.shop.open
if self.shop.shop_r_rect.collidepoint(mouse_pos):
# 碰撞方式改为矩形碰撞
self.flag = 1
if self.shop.shop_c_rect.collidepoint(mouse_pos):
# 碰撞方式改为圆形碰撞
self.flag = 2
if self.shop.shop_add_rect.collidepoint(mouse_pos):
# 增加碰撞范围
self.scale += 0.5
if self.shop.shop_decrease_rect.collidepoint(mouse_pos):
# 减小碰撞范围
if self.scale > 0.5:
self.scale -= 0.5
if __name__ == "__main__":
mg=MyGame()
mg.run_game()