Создание поверхностей (Surface), их анимация, метод blit

До сих пор мы с вами выполняли рисование в клиентской области окна, используя переменную sc:

import pygame
 
pygame.init()
 
W = 600
H = 400
 
sc = pygame.display.set_mode((W, H))
pygame.display.set_caption("Класс Surface")
pygame.display.set_icon(pygame.image.load("app.bmp"))
 
WHITE = (255, 255, 255)
BLUE = (0, 0, 255)
GREEN = (0, 255, 0)
RED = (255, 0, 0)
 
FPS = 60        # число кадров в секунду
clock = pygame.time.Clock()
 
while 1:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            exit()
 
    clock.tick(FPS)

Эта переменная ссылается на класс Surface, который связан с окном всего приложения. Однако, при необходимости, мы можем создать дополнительные поверхности рисования, используя конструктор класса Surface, например, так:

surf = pygame.Surface((200, 200))
surf.fill(RED)
sc.blit(surf, (50, 50))
pygame.display.update()

В результате на поверхности sc будет отображена еще одна поверхность surf со всем своим содержимым:

Спрашивается: зачем создавать дополнительный объект surf, когда можно все рисовать непосредственно на основной поверхности sc? Это сделано главным образом для удобства: мы можем на дополнительной поверхности нарисовать сложную графическую информацию, а затем, переносить ее в область главного окна, просто, используя операцию:

sc.blit(surf, (x, y))

Здесь x, y – координаты верхнего левого угла, от которого позиционируется вторая поверхность surf. Затем, достаточно менять в цикле координаты x, y для создания простой анимации нашего графического образа. Перерисовывать каждый раз сложную графическую информацию уже не придется. В этом основное достоинство таких поверхностей.

Класс Surface имеет множество полезных методов. Подробно все их можно посмотреть на странице официальной документации:

https://www.pygame.org/docs/ref/surface.html

Один из них мы уже использовали – это метод:

Surface.blit(source, pos, …)

который отображает содержимое поверхности source на поверхности Surface. Другой весьма полезный метод:

Surface.set_alpha(alpha)

задает степень прозрачности поверхности:

  • alpha = 0 – полностью прозрачная;
  • alpha = 255 – полностью непрозрачная;

Например, можно сделать так:

surf = pygame.Surface((200, 200))
surf.fill(RED)
pygame.draw.circle(surf, GREEN, (100, 100), 80)
 
surf_alpha = pygame.Surface((W, 100))
pygame.draw.rect(surf_alpha, BLUE, (0, 0, W, 100))
surf_alpha.set_alpha(128)
 
surf.blit(surf_alpha, (0, 50))
sc.blit(surf, (50, 50))
pygame.display.update()

Мы здесь определяем две дополнительные поверхности: surf и surf_alpha. Причем, surf_alpha полупрозрачная и располагается на surf. В итоге, имеем такую иерархию поверхностей:

И обратите внимание на такой эффект: поверхность surf_alpha по ширине больше, чем surf. Но, так как surf является родителем для surf_alpha, то все, что выходит за пределы surf – обрезается. В результате, мы видим следующее отображение:

Вот так можно создавать иерархию объектов Surface в PyGame. Причем, если поменять порядок следования слоев:

surf_alpha.blit(surf, (0, 0))
sc.blit(surf_alpha, (50, 50))

То поверхность surf также будет отображаться как полупрозрачная – она унаследует это свойство от родителя surf_alpha:

В заключение этого занятия я приведу небольшую программу простой анимации поверхностей (слоев):

import pygame
 
pygame.init()
 
W = 600
H = 400
 
sc = pygame.display.set_mode((W, H))
pygame.display.set_caption("Класс Surface")
pygame.display.set_icon(pygame.image.load("app.bmp"))
 
WHITE = (255, 255, 255)
BLUE = (0, 0, 255)
GREEN = (0, 255, 0)
RED = (255, 0, 0)
 
FPS = 60        # число кадров в секунду
clock = pygame.time.Clock()
 
surf = pygame.Surface((W, 200))
bita = pygame.Surface((50, 10))
 
surf.fill(BLUE)
bita.fill(RED)
 
bx, by = 0, 150
x, y = 0, 0
 
while 1:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            exit()
 
    surf.fill(BLUE)
    surf.blit(bita, (bx, by))
    if bx < W:
        bx += 5
    else:
        bx = 0
 
    if y < H:
        y += 1
    else:
        y = 0
 
    sc.fill(WHITE)
    sc.blit(surf, (x, y))
    pygame.display.update()
 
    clock.tick(FPS)

Здесь создается два слоя: surf и bita, причем bita рисуется на surf, а surf уже на основной поверхности sc. Далее, в цикле слой surf смещается вниз на 1 пиксель за итерацию, а слой bita на 5 пикселей по горизонтали. После запуска программы мы видим, что слой bita смещается относительно surf, а слой surf движется относительно основного слоя sc. Этот простой пример показывает как можно создавать более сложную анимацию с использованием поверхностей.