Pygame | 9 – 精灵的初始化与帧更新

Sprite(精灵类)的作用是派生子类,

子类必须在初始化方法中定义图像(image)和位置(rect)的属性。

也就是说,使用Sprite方法是继承这个类,然后重写各种方法。

可以通过复制以下代码,感受一下精灵类如何运行

精灵的初始化

import pygame
from pygame.locals import QUIT

class Plat(pygame.sprite.Sprite):
    def __init__(self, color, initial_position):
        pygame.sprite.Sprite.__init__(self)

        # 这里直接加载一个图片文件
        self.image = pygame.image.load('background.jpg').convert_alpha()
        # 以下数据会保存为一个元组,前两个是绘制的坐标,后两个是图像长与高
        self.rect = 0, 0, 100, 100

# 这样就算基本完成一个精灵类了,之后把它放到屏幕里

pygame.init()
screen = pygame.display.set_mode([800, 600])

b = pygame.sprite.Group()
a = Plat((255,0,0), (100,100))
# 通过组的办法管理精灵,可以直接进行定位和更新
b.add(a)

while True:

    if pygame.event.poll().type == QUIT:
        pygame.quit()
        exit()

    screen.fill((0, 0, 0))
    current_time = pygame.time.get_ticks()
    b.draw(screen)

    pygame.display.update()

帧的更新

精灵序列图

将要加载的动画帧放在一个精灵序列图里面,然后在程序里面调用它。pygame会自动更新动画帧,这样一个动态的图像就会展现在我们面前了。

下面是一个典型的精灵序列图:行和列的索引都是从0开始的。

值得一提的是这不是列表,而是整张图片就是这样的,通过截取不同的区域来实现图片的变化,这就是比较传统的帧数更新方法,也可以使用一系列图片来实现帧更新,不过这可能让缓存比较慢

加载精灵图序列:

在加载一个精灵图序列的时候,我们需要告知程序一帧的大小,(传入帧的宽度和高度,文件名)。

除此之外,还需要告诉精灵类,精灵序列图里面有多少列。load函数可以加载一个精灵序列图。

def load(self, filename, width, height, columns):
        self.master_image = pygame.image.load(filename).convert_alpha()
        self.frame_width = width
        self.frame_height = height
        self.rect = 0,0,width,height
        self.columns = columns

更新帧

一个循环动画通常是这样工作的:从第一帧不断的加载直到最后一帧,然后在折返回第一帧,并不断重复这个操作。

self.frame += 1

        if self.frame > self.last_frame:

                self.frame = self.first_frame

        self.last_time = current_time

但是如果只是这样做的话,程序会一股脑地将动画播放完了,我们想让它根据时间间隔一张一张的播放,因此加入定时的代码。

pygame中的time模块有一个get_ticks()方法可以满足定时的需要。

ticks = pygame.time.get_ticks()

然后将ticks变量传递给sprite的update函数,这样就可以轻松让动画按照帧速率来播放了。哦,帧速率还没有设置,咱们现在设置一下帧速率。

启动一个定时器,然后调用tick(num)函数就可以让游戏以num帧来运行了。

framerate = pygame.time.Clock()

framerate.tick(60)

绘制帧

sprite.draw()方法是用来绘制帧的,但是这个函数是由精灵来自动调用的,我们没有办法重写它,因此需要在update函数里面做一些工作。

首先需要计算单个帧左上角的x,y位置值(x表示列编号,y表示行编号):

# 用帧数目除以行数,然后在乘上帧的高度,这就能完成索引了

frame_x = (self.frame % self.columns) * self.frame_width

frame_y = (self.frame // self.columns) * self.frame_height

# 然后将计算好的x,y值传递给位置rect属性。

rect = ( frame_x, frame_y, self.frame_width, self.frame_height )

# subsurface返回surface中的一部分,实现了区域截取的方法

self.image = self.master_image.subsurface(rect)

案例

使用图片

import pygame
from pygame.locals import *

class MySprite(pygame.sprite.Sprite):
    def __init__(self, target):
        pygame.sprite.Sprite.__init__(self)
        self.target_surface = target
        self.image = None
        self.master_image = None
        self.rect = None
        self.topleft = 0,0
        self.frame = 0
        self.old_frame = -1
        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.frame_width = width
        self.frame_height = height
        self.rect = 0,0,width,height
        self.columns = columns
        rect = self.master_image.get_rect()
        self.last_frame = (rect.width // width) * (rect.height // height) - 1

    def update(self, current_time, rate=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

        if self.frame != self.old_frame:
            frame_x = (self.frame % self.columns) * self.frame_width
            frame_y = (self.frame // self.columns) * self.frame_height
            rect = ( frame_x, frame_y, self.frame_width, self.frame_height )
            self.image = self.master_image.subsurface(rect)
            self.old_frame = self.frame

pygame.init()
screen = pygame.display.set_mode((800,600),0,32)
pygame.display.set_caption("精灵类测试")
font = pygame.font.Font(None, 18)
framerate = pygame.time.Clock()


cat = MySprite(screen)
cat.load("sprite.png", 100, 100, 4)
group = pygame.sprite.Group()
group.add(cat)

while True:
    framerate.tick(30)
    ticks = pygame.time.get_ticks()

    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            pygame.quit()
            exit()
    key = pygame.key.get_pressed()
    if key[pygame.K_ESCAPE]:
        exit()
        
    screen.fill((0,0,100))

    group.update(ticks)
    group.draw(screen)
    pygame.display.update()

可以深切体会一下作者的代码改写,我感觉十分精彩

原文地址

发表评论