Pygame Sprite

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

https://www.geeksforgeeks.org/pygame-creating-sprites/

https://coderslegacy.com/python/python-pygame-tutorial/

https://coderslegacy.com/python/pygame-sprite-collision-detection/

WPROWADZENIE

Moduł 'sprite' zawiera klasę Sprite i klasę Group, która zawiera sprites. W module są jeszcze inne specjalistyczne klasy oraz funkcje do wykrywania kolizji sprites. Użycie tych klas jest opcjonalne, ale są one przydatne w typowych grach.

Wykrywanie kolizji ciał poprzez sprawdzanie nakrywających się pikseli na ogół jest zbyt powolne, jak na potrzeby gier wideo. Lepiej jest stworzyć obiekty Rect trochę mniejsze od poruszających się ciał, a następnie użyć obiekty Rect do wykrycia kolizji.

KLASA SPRITE

Klasa Sprite jest pomyślana jako klasa bazowa dla różnych rodzajów obiektów w grze. Ważne jest nadpisanie atrybutów 'image' oraz 'rect'. Przydaje się nadpisanie metody update() do sterowania zachowaniem sprite, domyślna metoda nie robi nic.


class Block(pygame.sprite.Sprite):

    def __init__(self, color, width, height):
        #pygame.sprite.Sprite.__init__(self)   # działa konstruktor z klasy bazowej
        super().__init__()
        self.image = pygame.Surface([width, height])   # return Surface
        # Można załadować obrazek z dysku.
        self.image.fill(color)   # nie mamy obrazka, to będzie kolor
        self.rect = self.image.get_rect()   # return Rect

# Metody instancji klasy Sprite.

sprite.add(*groups)   # dodanie sprite do podanych grup
sprite.remove(*groups)   # usunięcie sprite z podanych grup
sprite.kill()   # usunięcie sprite ze wszystkich grup; sprite żyje dalej
sprite.alive()   # return bool; czy sprite należy do jakiejś grupy
sprite.groups()   # return group_list; lista grup, do których należy sprite

KLASA GROUP

Klasa Group jest zaprojektowana do przechowywania sprites, a w grze można tworzyć specjalizowane klasy potomne. Dodawanie i usuwanie sprites z grupy jest wydajne, podobnie jak sprawdzenie, czy sprite nateży do grupy. Jeden sprite może należeć jednocześnie do wielu grup. Lepiej korzystać z grup niż dodawać nowe atrubuty do klasy Sprite, bo to ułatwia wyszukiwanie w grze.

Metody Group.draw() i Group.clear() wymagają, aby każdy sprite miał atrybuty 'image' oraz 'rect'.

Metoda Group.update() wywołuje Sprite.update() dla wszystkich sprites należących do grupy.

Wykrywanie kolizji sprites wymaga, aby sprites posiadały atrybut 'rect'.


group = pygame.sprite.Group()
group = pygame.sprite.Group(*sprites)

# Standardowe operacje Pythona.

sprite in group   # test przynależności
len(group)   # liczebność grupy
bool(group)   # True, jeżeli len(group) != 0
for sprite in group: pass   # iteracja po grupie
# Nie można iterować po grupie i jednocześnie jej modyfikować!

# Metody instancji klasy Group.

group.sprites()   # return sprite_list
group2 = group1.copy()

group.add(*sprites)   # dodawanie podanych sprites do grupy
group1.add(group2)   # wstawiamy do group1 sprites należące do group2
group1.add(group2, group3, sprite4, sprite5)   # można łączyć

group.remove(*sprites)   # usuwanie podanych sprites z grupy
group1.remove(group2)   # usuwamy z group1 sprites należące do group2
group1.remove(group2, group3, sprite4, sprite5)   # można łączyć

group.empty()   # usuwanie wszystkich sprites z tej grupy

group.has(*sprites)   # return bool; True, jeżeli wszystkie sprites należą
group1.has(group2, group3, sprite4, sprite5)   # można łączyć
# Równoważny (prawie) kod:
# all(sprite in group for sprite in sprites)

# Wg dokumentacji dla add(), remove(), has():
# Each sprite argument can also be a iterator containing Sprites.

group.update(*arguments, **keywords)
# Równoważny kod:
# for sprite in group:
#     sprite.update(*arguments, **keywords)

group.draw(Surface)   # blit the Sprite images
# Równoważny (prawie) kod:
# for sprite in group:
#     Surface.blit(sprite.image, sprite.rect)

group.clear(Surface_dest, background)   # draw a background over the Sprites;
# Zamazuje pozycje sprites używając 'background'.
# Korzysta z pozycji (Rect) z ostatniego wywołania Group.draw().
# 'background' to jest zwykle Surface o taki rozmiarze jak Surface_dest.
# Może też być 'callback function' typu clear_callback(surface, rect).

WYKRYWANIE KOLIZJI


# Wykrywanie kolizji sprite1 i sprite2.

sprite1.rect.colliderect(sprite2.rect)   # return bool

pygame.sprite.collide_rect(sprite1, sprite2)   # return bool
# wewnętrznie używane są atrybuty 'rect'.

# Wykrywanie kolizji sprite z grupą innych sprites.
# Zwraca None, jeżeli nie ma kolizji.
# Zwraca pewien sprite2 z group, z którym jest przecięcie.
# Ta funkcja jest szybsza od spritecollide().

pygame.sprite.spritecollideany(sprite, group)   # return sprite2 lub None

# W wynikowej liście są wszystkie przecinane sprites z group.
# Jeżeli dokill=True, to te sprites są usuwane z group.
# Wewnętrznie do znalezienia przecięć używane są atrybuty 'rect'.

pygame.sprite.spritecollide(sprite, group, dokill)   # return sprite_list

# Wykrywanie kolizji między grupami sprites.
# W wynikowym słowniku sprites z group1 są kluczami, a wartościami
# są listy przecinanych sprites z group2.
# Jeżeli dokill1=True, to sprites są usuwane z group1.
# Jeżeli dokill2=True, to sprites są usuwane z group2.

pygame.sprite.groupcollide(group1, group2)   # return dict

pygame.sprite.groupcollide(group1, group2, dokill1, dokill2)   # return dict

PRZYKŁAD


# sprite1.py
import sys
import pygame

# COLORS
black = (0, 0, 0)
gray = (128, 128, 128)
white = (255, 255, 255)
red = (255, 0, 0)
green = (0, 255, 0)
blue = (0, 0, 255)
yellow = (255, 255, 0)

class Block(pygame.sprite.Sprite):

    def __init__(self, color, width, height):
        super().__init__()
        self.image = pygame.Surface([width, height])
        self.image.fill(color)
        self.rect = self.image.get_rect()

# INITIALIZE THE GAME
pygame.init()   # to zawsze na starcie
size = (width, height) = (500, 500)
screen = pygame.display.set_mode(size)   # display Surface
pygame.display.set_caption('Creating Sprite')

# CLOCK
FPS = 60   # frames per second setting
clock = pygame.time.Clock()

sprite_group = pygame.sprite.Group()

sprite_red = Block(red, 50, 100)
sprite_red.rect.topleft = (200, 300)
sprite_group.add(sprite_red)

sprite_blue = Block(blue, 100, 100)
sprite_blue.rect.topleft = (100, 100)
sprite_group.add(sprite_blue)

sprite_green = Block(green, 100, 50)
sprite_green.rect.topleft = (150, 100)
sprite_group.add(sprite_green)

# MAIN GAME LOOP
while True:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:   # QUIT Event, pygame.locals.QUIT
            pygame.quit()   # deactivates the Pygame library
            sys.exit(0)
        elif event.type == pygame.KEYDOWN:
            if event.key == pygame.K_t:
                print("test kolizji sprite_red i sprite_blue")
                print(pygame.sprite.collide_rect(sprite_red, sprite_blue))

                print("test kolizji sprite_blue i sprite_green")
                print(sprite_blue.rect.colliderect(sprite_green.rect))
        elif event.type == pygame.MOUSEBUTTONDOWN:
            for sprite in sprite_group:   # iteracja po grupie
                if sprite.rect.collidepoint(event.pos):
                    print("trafiony {}".format(event.pos))

    sprite_group.update()   # wywołuje update() dla sprites
    screen.fill(black)
    sprite_group.draw(screen)

    pygame.display.flip()
    clock.tick(FPS)