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.