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.
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ů.
x, y, což jsou čísla, a size, což je dvojice čísel (šířka, výška). V kódu vyjadřuje velikost obrazovky a pozici a velikost míče. Metoda Rect.move(dx, dy) navrací nový obdélník posunutý o dané hodnoty. Všechny zmíněné hodnoty budou celá čísla, ať do nich nastavíme cokoliv.ball, obrázek načtený z disku, druhak jako viditelná plocha okna screen a zatřetí jako plocha background, kterou jsme si vytvořili navíc a na kterou může uživatel kreslit. Užitečná je metoda Surface.blit(zdroj, kam, odkud), která z daného objektu zdroj typu Surface vezme obdélník z pozice odkud a vykreslí ho do obdélníku na pozici kam ve svém vlastním objektu. Druhý a třetí parametr můžeme vynechat.Clock.tick(fps), která zaručí, že hra nebude mít víc než daný počet FPS (frame per second). Podrobněji, funkce si pamatuje, kdy byla volaná naposledy, a její výpočet skončí teprve ve chvíli, kdy je mezi voláními 1/fps vteřin. Ta funkce ale samozřejmě nic nepočítá – slouží právě k tomu, abychom zbytečně nežhavili procesor.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()
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()
Zadání úkolů na poníky najdete na původní stránce z minula.