22 сентября 2011

PyGame: Слои и группы

Привет. Вы наверное замечали что в некоторых играх (да почти во всех 2D играх) одни объекты находиться сверху, другие с низу. В большинстве случаев они находиться каждый на своем слое. И как ни странно, спрайты с разных слоев не перекрывают друг друга. По сути это достигается последовательным рисованием всех слоев.

В PyGame есть группа которая сама сортирует спрайты в зависимости от их слоя, и к тому же позволяет обновлять только часть экрана по средствам pygame.display.update, функции которая принимать массив прямоугольников(pygame.Rect) в качестве аргумента.

Речь пойдет о группе pygame.sprite.LayeredUpdates. Указать слой можно либо атрибутом _layer в pygame.sprite.Sprite. Либо указав в виде именованного параметра в конструкторе группы, например:

pygame.sprite.LayeredUpdates(sprite, sprite_other,sprite_other_other, layer=1)
Можно еще указать слой для спрайта при его добавлении методом add из класса группы, работает точно так же как конструктор.

Вот небольшой пример того как это может выглядеть:

# -*- coding: utf-8 -*-

import pygame

running = True

pygame.display.set_mode((140,140))
screen = pygame.display.get_surface()

sprite_one = pygame.sprite.Sprite()
sprite_two = pygame.sprite.Sprite()

sprite_one.rect = (30, 30)
sprite_two.rect = (60, 60)

sprite_one.image = pygame.Surface((50,50))
sprite_two.image = pygame.Surface((50,50))

sprite_one.image.fill((255,0,0))
sprite_two.image.fill((0,255,0))

# Только для чтения из LayeredUpdates
sprite_one._layer = 2;
sprite_two._layer = 1;

# Только для нашего чтения, значение выставляеться в LayeredUpdates
# Если даный атрибут в спрайте отсутствует, ошибки не будет.
sprite_one.layer = 2;
sprite_two.layer = 1;

group = pygame.sprite.LayeredUpdates(sprite_one, sprite_two)

clock = pygame.time.Clock()
dt = 0

while running:
    for e in pygame.event.get():
        if e.type == pygame.QUIT:
            running = False
        if e.type == pygame.KEYDOWN and e.key == pygame.K_SPACE:
            sprite_one._layer, sprite_two._layer \
                = sprite_two._layer, sprite_one._layer
            # Это не самая дешевая операция, лучше ей не злоупотреблять.
            # Метод бисекции находиться примерная точка вставки спрайта
            # после чего линейным поиском уточняеться точное его место.
            # Спрайты храняться в списке отсортированном по полю layer
            group.change_layer(sprite_one, sprite_one._layer)
            group.change_layer(sprite_two, sprite_two._layer)

    screen.fill((0,0,0))
    rects = group.draw(screen)
    pygame.display.update(rects)
    dt = clock.tick(40)

Если запустить данный код, то можно увидеть как меняются слоями красный и зеленый квадрат:

Вот так просто в PyGame добиться что бы спрайты перекрывали друг друга в строгом соответствии со слоем. Остальные возможности данной группы можно узнать из документации.