在pygame中好好玩玩精灵,滚雪球学Python游

「这是我参与11月更文挑战的第6天,活动详情查看:2021最后一次更文挑战

本系列专栏将通过不断编写游戏的方式,带你夯实 Python 知识。

本专栏追求迅速掌握 pygame 的同时,夯实 python 知识,所以一起来吧。

Sprite 模块、Sprite 对象

精灵类与精灵对象。首先要看的是精灵类中提供了哪些属性与方法。

print(dir(pygame.sprite))
复制代码
['AbstractGroup', 'DirtySprite', 'Group', 'GroupSingle', 'LayeredDirty', 'LayeredUpdates', 'OrderedUpdates', 'Rect', 'RenderClear', 'RenderPlain', 'RenderUpdates', 'Sprite', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'collide_circle', 'collide_circle_ratio', 'collide_mask', 'collide_rect', 'collide_rect_ratio', 'from_surface', 'get_ticks', 'groupcollide', 'pygame', 'spritecollide', 'spritecollideany', 'truth']
复制代码

提供的内容并不多,下文要重点学习的就是 Sprite 类。

查阅该类的属性与方法,清单如下:

['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'add', 'add_internal', 'alive', 'groups', 'kill', 'layer', 'remove', 'remove_internal', 'update']
复制代码

列举一下重点方法(默认提供的方法确实不多,在使用中更多的需要自行扩展)

  • update():适用于控制 sprite 对象行为的方法。基类中该方法没有任何实现,需要重写该方法;
  • add():将该 sprite 对象添加到 group 中;
  • remove():sprite 对象会从 group 中删掉;
  • kill():将该 sprite 对象从所有 groups 中删掉;
  • groups():返回 sprite 对象所在的所有组;
  • alive():判断 sprite 方法是否还在组中。

完成精灵图动态切换

先看最终效果,在进行代码学习。

在 pygame 中好好玩玩精灵,滚雪球学 Python 游戏番

使用的素材图片如下,你可以直接保存到本地,或者自行去互联网检索精灵图资源。
精灵图是将多张图片合成到一张图片上,编写代码时不用在逐个加载图片,优点是加载图片速度快。
精灵图资源,可以在如下网址下载:

www.aigei.com/game2d/
www.6pea.com/
www.6m5m.com/
www.spriters-resource.com/
opengameart.org/
openclipart.org/

在 pygame 中好好玩玩精灵,滚雪球学 Python 游戏番

精灵图在使用的时候,可以进行部分区域的显示,代码如下:

import sys

import pygame

pygame.init()
pygame.display.set_caption("精灵图局部展示")
screen = pygame.display.set_mode([500, 500])
clock = pygame.time.Clock()
fps = 60
my_sprite = pygame.image.load('./imgs/guofurong.png').convert_alpha()

while True:
    clock.tick(fps)
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            sys.exit()
    screen.fill((12, 200, 180))
    screen.blit(my_sprite, (100, 100))
    screen.blit(my_sprite, (100, 100), (0, 0, 128, 192))
    screen.blit(my_sprite, (300, 100), (0, 0, 32, 48))
    screen.blit(my_sprite, (300, 200), (0, 48, 32, 48))
    pygame.display.update()
复制代码

screen.blit(my_sprite, (100, 100), (0, 0, 128, 192))
blit 存在三个参数,分别是图像,绘制的坐标点,绘制的截面。这个截面相当于拿着一个透明的玻璃在图片上滑动,透明部分可以展示出来。

pygame.display.set_mode()
该函数的原型如下,默认情况该建议只传递第一个参数,即宽和高即可,第三个参数操作系统会自动选择最优的参数,一般不进行设置。第二个参数表示的是显示类型,不设置的情况下会展示默认窗口,可设置的在原型后面描述。

set_mode(size=(0, 0), flags=0, depth=0, display=0, vsync=0) -> Surface
复制代码

flags 参数的值
该参数有如下选型,多个值的时候使用 | 分割。

  • pygame.FULLSCREEN:全屏展示;
  • pygame.DOUBLEBUF:双缓冲模式,推荐和 HWSURFACE 或者 OPENGL 一起使用;
  • pygame.OPENGL:创建一个 OPENGL 渲染窗口;
  • pygame.HWSURFACE:硬件加速,在全屏时有效;
  • pygame.RESIZABLE:窗口可调整尺寸;
  • pygame.NOFRAME:窗口没有边框和控制按钮(最大化,最小化,关闭)。

利用精灵图让精灵动起来

核心代码如下,重点部分用注释进行解释。

import pygame

# 声明一个类继承自 Sprite
class MySprite(pygame.sprite.Sprite):
    def __init__(self):
        # 调用基类的构造方法
        pygame.sprite.Sprite.__init__(self)
        self.image = None
        self.master_image = None
        self.rect = None
        self.frame = 0
        self.frame_width = 1
        self.frame_height = 1
        self.first_frame = 0
        self.last_frame = 0
        self.columns = 1
        self.last_time = 0

    def load(self, filename, width, height, columns):
        # 加载图片
        self.master_image = pygame.image.load(filename).convert_alpha()
        # 裁切图片,获取最上面的一行图片
        self.master_image = self.master_image.subsurface((0, 0, 128, 48))
        # 获取目标区域的宽度和高度
        self.frame_width = width
        self.frame_height = height

        # 获取矩形区域
        self.rect = 0, 0, width, height
        # 合计 4 张小图
        self.columns = columns

        rect = self.master_image.get_rect()
        print(rect)
        # 获取最后一帧,这里得到的是 3
        # with 传递进来的是 32,合计宽度是 128 / 32
        # height 传递进来的是 48,合计高度是 48
        self.last_frame = rect.width // width - 1

    def update(self, current_time, rate=60):
        # 如果当前时间大于 60,进行计算
        if current_time > self.last_time + rate:
            self.frame += 1
            # 大于最后一帧,帧数重置
            if self.frame > self.last_frame:
                self.frame = self.first_frame
            # 计算时间
            self.last_time = current_time

        # 获取 x 坐标,x 坐标变化规律为 0,3,64,96
        frame_x = (self.frame % self.columns) * self.frame_width
        # 获取 y 坐标,y 坐标在该案例中变化规律为 0
        frame_y = (self.frame // self.columns) * self.frame_height
        rect = (frame_x, frame_y, self.frame_width, self.frame_height)
        # print(rect)
        # 继续裁剪图片
        self.image = self.master_image.subsurface(rect)

pygame.init()
screen = pygame.display.set_mode((300, 300))
pygame.display.set_caption("My Sprite")
rate = pygame.time.Clock()

my_sprite = MySprite()
my_sprite.load("./imgs/guofurong.png", 32, 48, 4)
groups = pygame.sprite.Group()
groups.add(my_sprite)

while True:
    rate.tick(60)
    ticks = pygame.time.get_ticks()

    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            pygame.quit()
            exit()

    screen.fill((44, 169, 240))
    groups.update(ticks)
    groups.draw(screen)
    pygame.display.update()
复制代码

上述案例比较长,核心代码就是在计算每个小图所在的坐标位置。还有一点,在本案例中,我并未使用 blit() 进行绘制,而是采用的 subsurface,这两个方法本质上的区别在于blit() 直接把图像绘制在 surface 对象表面,subsurface() 先将图像提取子表面,然后通过Sprite.draw, 或者 Group.draw 将子表面绘制出来。

所以在本文精灵组进行绘制的使用,调用了 groups.draw(screen) 方法。

本系列专栏属于番外篇,希望你能学到新知识。

有任何疑问,都可以联系橡皮擦进行解决,一起做游戏吧
本专栏每天的练习量大概在 1 小时左右,整篇博客节奏会比较快,毕竟咱们是有基础的人。