Máte-li možnost, instalujte Pygame na svůj počítač z oficiálního webu. Na školních počítačích taková možnost není, protože nemáme administrátorksá práva, a proto používáme knihovnu Pygame tak, jak ji pod Windows zkompiloval jistý pan Gohlke. Příslušný archiv můžete stáhnout zde, dejte přitom pozor, abyste správně vybrali verzi Pythonu (ve škole je 3.3) a architekturu procesoru (ve škole je 32-bitová, tedy win32).

Kód všech níže uvedených ukázek můžete stáhnout jako jeden archiv zde, spolu s ostatními potřebnými soubory.

Prográmek s míčem

Již několikrát opakovaná ukázka s míčem, tentokrát lépe komentovaná a doplněná o odrážení od stěn.

Komentovaný kód.
# nezbytné formality
import pygame
pygame.init()

# vytvoříme okno velikosti 640x480 pixelů
screenrect = pygame.Rect(0, 0, 640, 480)
screen = pygame.display.set_mode(screenrect.size)
pygame.display.set_caption('Míček v Pygame')
# připravíme si základní barvy, aby byl zbytek kódu přehlednější
black = pygame.Color(0, 0, 0) # trojčíslí znamená hodnoty červená, zelená, modrá
white = pygame.Color(255, 255, 255)
# otevřeme obrázek
ball = pygame.image.load('ball.png')
# připravíme si bílé pozadí (půjde na něj kreslit)
background = pygame.Surface(screenrect.size)
background.fill(white)
screen.blit(background, (0,0))

# zapamatujeme si pozici míče a jeho rychlost
#  narozdíl od dřívějších ukázek si pozici zapamatujeme ještě zvlášť, protože
#  typ pygame.Rect zvládá jen celá čísla; změny rychlosti by byly trhané
# poloha a velikost míče na obrazovce
ballrect = ball.get_rect()
# poloha míče přesněji
pos_x, pos_y = 0, 0
speed_x, speed_y = 0, 0
# zrychlení při stisku klávesy; za běhu programu ho už nebudeme měnit
acceleration = 300

clock = pygame.time.Clock()
alive = True
# následující cyklus (až do předposledního řádku programu) se bude opakovat
# až do úmoru a pokaždé se okno překreslí.
# Abychom netrápili procesor, je na konci volání clock.tick(30), které zajistí,
# že cyklus proběhne nejvýš třicetkrát za vteřinu
while alive:
    # zpracujeme události, které nastaly od poslední kontroly
    # mohlo jich být víc najednou, proto potřebujeme for cyklus
    for event in pygame.event.get():
        # pokud uživatel kliknul na křížek v záhlaví, aby zavřel okno
        if event.type == pygame.QUIT:
            alive = False
        
        # pokud uživatel pohnul myší přes plochu okna
        elif event.type == pygame.MOUSEMOTION:
            # kreslení, když je zmáčknuté levé tlačítko, mazání, když pravé
            left, middle, right = pygame.mouse.get_pressed()
            # kód je trochu ošklivý; potřebujeme kreslit na pozadí, aby změny zůstaly,
            # ale zároveň i na okno, aby byly vidět hned
            if left:
                pygame.draw.circle(background, black, event.pos, 5, 0)
                pygame.draw.circle(screen, black, event.pos, 5, 0)
            elif right:
                pygame.draw.circle(background, white, event.pos, 5, 0)
                pygame.draw.circle(screen, white, event.pos, 5, 0)
    
    # zeptáme se časomíry na dobu od minulého clock.tick() a převedeme na vteřiny
    time = clock.get_time() / 1000.0
    
    # změníme rychlost podle toho, jaké klávesy jsou stisknuté
    keystate = pygame.key.get_pressed()
    horz = keystate[pygame.K_RIGHT] - keystate[pygame.K_LEFT]
    vert = keystate[pygame.K_DOWN] - keystate[pygame.K_UP]
    speed_x += horz * time * acceleration
    speed_y += vert * time * acceleration
    
    # pokud míč narazí na okraj plochy, převrátíme mu rychlost
    if pos_x < 0 or pos_x + ballrect.width > screenrect.width:
        speed_x = -speed_x
    if pos_y < 0 or pos_y + ballrect.height > screenrect.height:
        speed_y = -speed_y
    
    # vymažeme plochu, kde byl míč posledně
    screen.blit(background, ballrect, ballrect)
    # posuneme míč
    pos_x += speed_x * time
    pos_y += speed_y * time
    # přesně spočítanou pozici překopírujeme do proměnné pro vykreslování
    ballrect.x, ballrect.y = pos_x, pos_y
    # vykreslíme míč na nové pozici
    screen.blit(ball, ballrect)
    # poprosíme, aby změny okna opravdu ukázaly na obrazovce
    pygame.display.update()
    clock.tick(30) # 30 FPS

pygame.quit()

Typy použitých objektů.

Míč jako Sprite

Tahle ukázka sice už neobsahuje odrážení od stěn, ale zato je míč pověšený pružinkou na kurzoru myši. Pružinka zastupuje sílu směrem k danému středu, která roste se vzdáleností.

Komentovaný kód.
# coding: utf-8
# tentokrát importujeme navíc modul random pro generování náhodných čísel
import pygame, random

# v tomto skriptu už je míč zařízený jako třída, dědí od typu pygame Sprite
class Ball(pygame.sprite.Sprite):
    def __init__(self, filename, screenrect):
        # zavoláme konstruktor svého předka, pygame.sprite.Sprite
        pygame.sprite.Sprite.__init__(self) # jde taky zapsat jako: super().__init__(self)
        # vlastnosti self.image, self.rect jsou potřeba pro funkce clear(screen, background) a draw(screen)
        # všimněte si: obrázek se znovu načte pokaždé, když vyrobíme nový míč
        self.image = pygame.image.load('ball.png')
        self.rect = self.image.get_rect()
        # posuneme míč na náhodnou pozici na obrazovce
        self.rect.x = random.randint(0, screenrect.width - self.rect.width)
        self.rect.y = random.randint(0, screenrect.height - self.rect.height)
        # navíc si zavedeme několik vlastních proměnných pro fyziku
        self.speed_x, self.speed_y = 0, 0
        self.hook_x, self.hook_y = self.rect.x, self.rect.y
        self.spring_stiffness = random.uniform(2, 5)
    
    def update(self, time):
        # spočítáme působící sílu a podle toho změníme rychlost
        center_x = self.rect.x + 0.5 * self.rect.width
        center_y = self.rect.y + 0.5 * self.rect.height
        self.speed_x += (self.hook_x - center_x) * time * self.spring_stiffness 
        self.speed_y += (self.hook_y - center_y) * time * self.spring_stiffness
        # zbrzdíme míč, jakoby třením o vzduch
        self.speed_x *= 0.9
        self.speed_y *= 0.9
        # posuneme míč správnou rychlostí (tentokrát zaokrouhlování ignorujeme)
        self.rect = self.rect.move(self.speed_x*time*30, self.speed_y*time*30)
    
# nachystáme si všechny rekvizity
pygame.init()
clock = pygame.time.Clock()

screenrect = pygame.Rect(0, 0, 640, 480)
screen = pygame.display.set_mode(screenrect.size)
pygame.display.set_caption('Míček na provázku')

# vyrábět takhle výslovně bílý obrázek na pozadí je trochu hloupé, ale šetří to práci
background = pygame.Surface(screenrect.size)
background.fill(pygame.Color(255, 255, 255))
screen.blit(background, (0,0))
pygame.display.update()

# vyrobíme skupinu s názvem everything a postupně do ní přidáme deset míčů
everything = pygame.sprite.RenderUpdates()
for i in range(10):
    ball = Ball('ball.png', screenrect)
    everything.add(ball)

alive = True
while alive:
    for event in pygame.event.get():
        if event.type == pygame.QUIT or (event.type == pygame.KEYDOWN and event.key == pygame.K_ESCAPE):
            alive = False
        
        elif event.type == pygame.MOUSEMOTION:
            # všem míčům převěsíme konec pružinky na současnou pozici myši
            for ball in everything:
                # všimněte si: list unpacking (dvě hodnoty z dvouprvkového seznamu event.pos)
                ball.hook_x, ball.hook_y = event.pos
    
    # skupinu everything vymažeme z obrazovky pomocí obrázku background
    everything.clear(screen, background)
    # volání update na skupinu způsobí, že se tatáž funkce zavolá na každý objekt ve skupině
    everything.update(clock.get_time() / 1000.0)
    # vykreslíme všechno na obrazovku a poprosíme o zobrazení změněných oblastí (dirty) v okně
    # všimněte si, že změněné oblasti musí být také ty, odkud objekty mazala funkce clear()
    dirty = everything.draw(screen)
    pygame.display.update(dirty)
    # počkáme 1/30 vteřiny od posledního cyklu
    clock.tick(30)

pygame.quit()

Prográmek s poníky

Hříčka s poníky chodícími po bludišti z čar na místo, kam kliknete. Jeden poník je navíc zabiják a maže všechny, koho se dotkne.

Komentovaný kód.
# coding: utf-8
import pygame
from random import randint

# všimněte si, že třída níže volá přímo funkci pygame.image.load
# (o něco obyčejnější by bylo volat ji v konstruktoru, ale chceme šetřit čas)
# proto je potřeba zavolat pygame.init() před samotnou definicí té třídy
pygame.init()
screenrect = pygame.Rect(0, 0, 640, 480)
screen = pygame.display.set_mode(screenrect.size)
pygame.display.set_caption('Poníci')

black = pygame.Color(0, 0, 0)
white = pygame.Color(255, 255, 255)

class Pony(pygame.sprite.Sprite):
    # rychlost pohybu v pixelech za sekundu
    speed = 50
    # všechny obrázky poníků, co budou potřeba, načteme jednou během spouštění skriptu
    # obrázek PNG s patřičným názvem musíte stáhnout a uložit do stejné složky jako tento skript
    spritesheet = pygame.image.load('spritesheet.png').convert_alpha()
    sprite_size = 32 # velikost jednoho čtverečku ve spritesheetu, v pixelech
    
    def __init__(self, maze):
        """maze: pygame.Mask; určuje, kam poník smí a kam ne"""
        pygame.sprite.Sprite.__init__(self)
        # barva. Číslo v rozsahu 0..7, protože tolik barev máme ve spritesheetu
        self.color = randint(0, 7)
        # směr chůze. 0: dolů, 1: doleva, 2: doprava, 3: nahoru, protože tak je to ve spritesheetu
        self.direction = 0
        # poloha na mapě
        self.border_right = maze.get_size()[0] - self.sprite_size
        self.border_bottom = maze.get_size()[1] - self.sprite_size
        self.x, self.y = randint(0, self.border_right), randint(0, self.border_bottom)
        self.rect = pygame.Rect(self.x, self.y, self.sprite_size, self.sprite_size)
        # na začátku cíl chůze necháme na správném místě
        self.target = self.rect.x, self.rect.y
        # načteme obrázek ze spritesheetu
        self.update_image()
        # zapamatujeme si bludiště pro pozdější kontroly nárazu
        self.maze = maze
        # do masky zaznamenáme pixely obrázku, co nejsou průhledné
        self.mask = pygame.mask.from_surface(self.image)
    
    def update(self, time):
        """Posune poníka směrem k cíli, pokud to jde
        time: float; udává čas od posledního volání"""
        distance = time * self.speed
        # pokaždé zkoušíme, jestli poník chce jít tímhle směrem a jestli může
        if self.target[0] > self.rect.x + distance and self.can_move(distance, 0):
            self.x += distance
            self.direction = 2
        elif self.target[0] < self.rect.x - distance and self.can_move(-distance, 0):
            self.x -= distance
            self.direction = 1
        elif self.target[1] > self.rect.y + distance and self.can_move(0, distance):
            self.y += distance
            self.direction = 0
        elif self.target[1] < self.rect.y - distance and self.can_move(0, -distance):
            self.y -= distance
            self.direction = 3
        # skutečnou polohu překopírujeme do celočíselného self.rect
        self.rect.x, self.rect.y = self.x, self.y
        self.update_image()
    
    def update_image(self):
        """Vybere aktuální obrázek ze spritesheetu, pro animaci"""
        # začneme v levém horním rohu tabulky obsahující poníky příslušné barvy
        cell_x, cell_y = 3 * (self.color % 4), 4 * (self.color // 4)
        # najdeme buňku podle směru chůze a aktuálního času (animace 6 snímků za vteřinu)
        cell_y += self.direction
        time = pygame.time.get_ticks() / 1000.0 # čas od spuštění hry, původně v milisekundách
        cell_x += int(6 * time) % 3
        # přepočítáme pozici v tabulce na skutečné souřadnice v pixelech a zkopírujeme obrázek
        x = cell_x * self.sprite_size
        y = cell_y * self.sprite_size
        self.image = self.spritesheet.subsurface(pygame.Rect((x, y), self.rect.size))
    
    def can_move(self, horz, vert):
        """Ověří, jestli se maska poníka nepřekrývá s maskou překážek
        horz, vert: int; posuv oproti současné pozici poníka"""
        overlap = self.maze.overlap_area(self.mask, (int(self.x + horz), int(self.y + vert)))
        return overlap == 0
    
    def set_target(self, x, y):
        """Změní cíl chůze na dané souřadnice"""
        self.target = [x, y]

def draw_map(image, filename):
    """Vykreslí na daný obrázek černě mapu z textového souboru daného názvu"""
    f = open(filename, "r")
    for line in f.readlines():
        # každý řádek souboru rozdělíme na čtyři hodnoty a ty převedeme na čísla
        x1, y1, x2, y2 = line.split()
        pygame.draw.line(image, black, (int(x1), int(y1)), (int(x2), int(y2)))
    f.close()

background = pygame.Surface(screenrect.size)
background.fill(white)
draw_map(background, "map.txt")
screen.blit(background, (0,0))
pygame.display.update()

# vyrobíme z vykreslené mapy masku: překážka je všechno, co není bílé
# (set_colorkey je malý úskok, kterým nastavíme dočasně bílou jako průhlednost)
background.set_colorkey(white)
maze = pygame.mask.from_surface(background)
background.set_colorkey(None)

ponies = pygame.sprite.RenderUpdates()
peaceful = pygame.sprite.Group()
for i in range(10):
    pony = Pony(maze)
    pony.add(ponies)
    pony.add(peaceful)
killer = pony
peaceful.remove(killer)

clock = pygame.time.Clock()

alive = True
while alive:
    for event in pygame.event.get():
        if event.type == pygame.QUIT or (event.type == pygame.KEYDOWN and event.key == pygame.K_ESCAPE):
            alive = False
        
        elif event.type == pygame.MOUSEBUTTONDOWN:
            for pony in ponies:
                pony.set_target(event.pos[0], event.pos[1])
    
    ponies.clear(screen, background)
    ponies.update(clock.get_time() / 1000.0)
    for pony in pygame.sprite.spritecollide(killer, peaceful, dokill=False, collided=pygame.sprite.collide_mask):
        pony.kill() # poníci ve skutečnosti neumřou, jen je odebereme ze všech skupin
    dirty = ponies.draw(screen)
    pygame.display.update(dirty)
    
    clock.tick(30) # 30 FPS

pygame.quit()

Zpátky k poníkům

Zadání úkolů na poníky najdete na původní stránce z minula.