Obsah
1. Detekce kolize spritů v knihovně Pygame Zero
2. První demonstrační příklad – zobrazení trojice nepohyblivých spritů
3. Nastavení, popř. změna pozice spritu na obrazovce
4. Zobrazení aktuální pozice spritu
5. Změna pozice spritu s využitím klávesnice
7. Automatický pohyb posledního spritu, odrazy od okrajů okna
8. Úplný zdrojový kód příkladu s odrážejícím se spritem
10. Využití metody colliderect pro detekci kolizí spritů
11. Úplný zdrojový kód příkladu s detekcí kolize spritů
12. Využití metody collidelist pro detekci kolizí spritů
13. Úplný zdrojový kód druhé varianty příkladu s detekcí kolize spritů
15. Reakce herního „engine“ na kolizi spritu
16. Naplánování činnosti s využitím objektu clock
17. Obnovení barvy spritu sekundu po kolizi
18. Relevantní funkce z knihovny Pygame
19. Repositář s demonstračními příklady
1. Detekce kolize spritů v knihovně Pygame Zero
V předchozích částech miniseriálu o nástroji Pygame Zero [1] [2] [3] [4] jsme se seznámili mj. i s tím, jakým způsobem lze pracovat s takzvanými sprity, tj. s rastrovými obrázky, s nimiž je možné pohybovat po celé dvourozměrné scéně. Sprity samozřejmě mohou úplně či částečně překreslit pozadí vytvářené scény, mohou se v případě potřeby navzájem překrývat, při překrytí lze aplikovat již dříve zmíněnou a použitou průhlednost či průsvitnost apod.
Obrázek 1: Sprity použité ve známé herní sérii Sonic the Hedgehog.
V poslední verzi knihovny Pygame Zero je dokonce možné provést i otočení spritů o libovolný úhel, což je téma, kterému jsme se věnovali minule (viz základ jednoduché hry typu Asteroids).
Obrázek 2: Snímek ze „hry“ naprogramované minule.
Ovšem zbývá nám vyřešit ještě jeden relativně závažný problém úzce související se sprity – v mnoha aplikacích (především v počítačových hrách) je nutné nějakým způsobem zareagovat ve chvíli, kdy dojde ke kolizi či k překryvu dvou spritů. Představme si například klasickou hru Pac-Man, která musí adekvátně reagovat při srážce hlavního hrdiny s duchem, zareagovat na sežrání bodu (což je taktéž srážka spritu/spritů) apod. I tyto případy, které byly v minulosti řešeny už na úrovni příslušného hardware (video čipu), jsou samozřejmě v nástroji Pygame Zero relativně jednoduše vyřešitelné, což ostatně uvidíme v navazujících kapitolách. Již na tomto místě je vhodné poznamenat, že detekce kolize spritů není dokonalá a vlastně je horší, než tomu bylo v dobách osmibitových domácích mikropočítačů – kolizi dokážeme vypočítat pouze pro sprite jako celek (čtverec či obdélník) a navíc ve chvíli, kdy nedochází k natočení spritu. Pochopitelně by bylo možné kolizi detekovat až na úroveň pixelů (per-pixel collision detection), ovšem tuto funkcionalitu Pygame Zero (alespoň prozatím) nenabízí.
Obrázek 3: Starší varianta hry Pac-Man, zde pro osmibitovou herní konzoli Atari 2600. I v této hře (a speciálně na hardware Atari 2600) byly masivně používány sprity.
2. První demonstrační příklad – zobrazení trojice nepohyblivých spritů
Nejprve si připravíme kostru demonstračního příkladu, kterou budeme v rámci dalších kapitol rozšiřovat a doplňovat. Po jejím spuštění se na pozadí zobrazí trojice spritů, z nichž každý je představován čtvercem o rozměrech 32×32 pixelů. Pro jednoduchost je tento čtverec vyplněn konstantní barvou (mj. i proto je velikost příslušných rastrových obrázků nepatrná – 63 bajtů; použili jsme navíc pro takto malé obrázky výhodnější formát GIF). Zdrojový kód tohoto demonstračního příkladu vypadá následovně:
WIDTH = 480 HEIGHT = 480 BACKGROUND_COLOR = (0, 0x40, 0x40) red_sprite = Actor("red.gif") red_sprite.pos = (120, 240) blue_sprite = Actor("blue.gif") blue_sprite.pos = (240, 240) yellow_sprite = Actor("yellow.gif") yellow_sprite.pos = (360, 240) def draw(): screen.fill(BACKGROUND_COLOR) red_sprite.draw() blue_sprite.draw() yellow_sprite.draw()
Obrázek 4: Dnešní první demonstrační příklad po svém spuštění.
Alternativně je pochopitelně možné namísto pozadí vyplněného konstantní barvou použít nějaký podkladový obrázek, například z již výše zmíněné hry Pac-Man (použit je výřez z emulovaného pozadí reálného herního automatu):
WIDTH = 480 HEIGHT = 480 red_sprite = Actor("red.gif") red_sprite.pos = (160, 220) blue_sprite = Actor("blue.gif") blue_sprite.pos = (240, 220) yellow_sprite = Actor("yellow.gif") yellow_sprite.pos = (320, 220) def draw(): screen.blit(images.pacman, (0, 0)) red_sprite.draw() blue_sprite.draw() yellow_sprite.draw()
Obrázek 5: Změna pozadí (rastrový obrázek) a nepatrné posunutí počáteční pozice spritů na obrazovce.
3. Nastavení, popř. změna pozice spritu na obrazovce
Sprite si můžeme představit jako čtverec nebo obdélník, který je vyplněný rastrovým obrázkem (nebo chcete-li, je otexturovaný). Pokud nebudeme brát v úvahu možnou rotaci spritu, lze pozici vyjádřit dvojicí souřadnic x, y. Ovšem význam těchto souřadnic se liší podle toho, který bod spritu budeme používat. K dispozici je celkem devět významných bodů, k nimž je možné souřadnice x a y vztáhnout:
Obrázek 6: Devět bodů, k nimž lze vztáhnout souřadnice x, y udávající pozici spritu na obrazovce.
Těmto bodům odpovídá i pojmenovaný (nepovinný) parametr předávaný konstruktoru Actor:
# | Keyword |
---|---|
1 | topleft |
2 | midtop |
3 | topright |
4 | midleft |
5 | center |
6 | midright |
7 | bottomleft |
8 | midbottom |
9 | bottomright |
Souřadnice spritu lze nastavit změnou následujících atributů (skutečná pozice spritu se vypočte ihned po nastavení těchto atributů):
# | Atribut | Souřadnice |
---|---|---|
1 | left | horizontální |
2 | right | horizontální |
3 | top | vertikální |
4 | bottom | vertikální |
5 | center | horizontální či vertikální |
6 | middle | horizontální či vertikální |
4. Zobrazení aktuální pozice spritu
Pozici spritu lze kdykoli přečíst a nějakým způsobem využít. V dnešním druhém demonstračním příkladu tuto pozici (přesněji řečeno souřadnice x a y) zobrazíme. Tato operace probíhá ve funkci nazvané draw_sprite_pos:
WIDTH = 480 HEIGHT = 480 BACKGROUND_COLOR = (0, 0x40, 0x40) red_sprite = Actor("red.gif") red_sprite.pos = (120, 240) blue_sprite = Actor("blue.gif") blue_sprite.pos = (240, 240) yellow_sprite = Actor("yellow.gif") yellow_sprite.pos = (360, 240) def draw_sprite_pos(which, sprite, pos_x, pos_y, color): screen.draw.text(which + ":", (pos_x, pos_y), color=color) screen.draw.text(str(int(sprite.x)), (pos_x+70, pos_y), color=color) screen.draw.text(str(int(sprite.y)), (pos_x+110, pos_y), color=color) def draw(): screen.fill(BACKGROUND_COLOR) red_sprite.draw() blue_sprite.draw() yellow_sprite.draw() draw_sprite_pos("Red", red_sprite, 10, 10, "#db0000") draw_sprite_pos("Blue", blue_sprite, 10, 30, "#00a2db") draw_sprite_pos("Yellow", yellow_sprite, 10, 50, "#dbc500")
Obrázek 7: Výchozí pozice spritů.
5. Změna pozice spritu s využitím klávesnice
Pohyb spritu je možné ovládat klávesnicí, což je ostatně způsob používaný v mnoha klasických 2D hrách. Tento způsob je v tom nejjednodušším případě založen na trojici callback funkcí nazvaných on_key_down, on_key_up a update. První callback funkce je zavolána ve chvíli, kdy je stisknuta nějaká klávesa. Reagovat budeme na kurzorové šipky a taktéž na klávesu Esc:
def on_key_down(key, mod, unicode): global dx, dy if key == keys.ESCAPE: exit() if key == keys.UP: dy = -1 if key == keys.DOWN: dy = 1 if key == keys.LEFT: dx = -1 if key == keys.RIGHT: dx = 1
Druhá callback funkce je zavolána po puštění klávesy (ukončení stisku). Reagovat budeme na ukončení stisku kurzorových šipek:
def on_key_up(key, mod): global dx, dy if key == keys.ESCAPE: exit() if key == keys.UP or key == keys.DOWN: dy = 0 if key == keys.LEFT or key == keys.RIGHT: dx = 0
A konečně třetí callback funkce je volána pravidelně šedesátkrát za sekundu a můžeme v ní realizovat pohyb spritu na základě nastavených hodnot globálních proměnných dx a dy:
def update(): blue_sprite.left += dx blue_sprite.top += dy
Úplný zdrojový kód tohoto příkladu vypadá následovně:
WIDTH = 480 HEIGHT = 480 BACKGROUND_COLOR = (0, 0x40, 0x40) red_sprite = Actor("red.gif") red_sprite.pos = (120, 240) blue_sprite = Actor("blue.gif") blue_sprite.pos = (240, 240) yellow_sprite = Actor("yellow.gif") yellow_sprite.pos = (360, 240) dx = 0 dy = 0 def draw_sprite_pos(which, sprite, pos_x, pos_y, color): screen.draw.text(which + ":", (pos_x, pos_y), color=color) screen.draw.text(str(int(sprite.x)), (pos_x+70, pos_y), color=color) screen.draw.text(str(int(sprite.y)), (pos_x+110, pos_y), color=color) def draw(): screen.fill(BACKGROUND_COLOR) red_sprite.draw() blue_sprite.draw() yellow_sprite.draw() draw_sprite_pos("Red", red_sprite, 10, 10, "#db0000") draw_sprite_pos("Blue", blue_sprite, 10, 30, "#00a2db") draw_sprite_pos("Yellow", yellow_sprite, 10, 50, "#dbc500") def update(): blue_sprite.left += dx blue_sprite.top += dy def on_key_down(key, mod, unicode): global dx, dy if key == keys.ESCAPE: exit() if key == keys.UP: dy = -1 if key == keys.DOWN: dy = 1 if key == keys.LEFT: dx = -1 if key == keys.RIGHT: dx = 1 def on_key_up(key, mod): global dx, dy if key == keys.ESCAPE: exit() if key == keys.UP or key == keys.DOWN: dy = 0 if key == keys.LEFT or key == keys.RIGHT: dx = 0
Obrázek 8: Pohyb spritu s využitím kurzorových kláves.
Obrázek 9: Překrytí dvou spritů.
6. Souběžný posun dvou spritů
Předchozí demonstrační příklad můžeme snadno rozšířit tak, aby bylo možné s využitím další čtveřice kláves (W, A, S, D) posunovat i dalším spritem. Ovšem ideální by bylo, aby nám nerostl počet globálních proměnných. Z tohoto důvodu přidáme další atributy k existujícím spritům (což je možné), což v našem konkrétním případě znamená:
blue_sprite = Actor("blue.gif") blue_sprite.pos = (240, 240) blue_sprite.dx = 0 blue_sprite.dy = 0
a:
yellow_sprite = Actor("yellow.gif") yellow_sprite.pos = (360, 240) yellow_sprite.dx = 0 yellow_sprite.dy = 0
Upravený zdrojový kód bude vypadat následovně:
WIDTH = 480 HEIGHT = 480 BACKGROUND_COLOR = (0, 0x40, 0x40) red_sprite = Actor("red.gif") red_sprite.pos = (120, 240) blue_sprite = Actor("blue.gif") blue_sprite.pos = (240, 240) blue_sprite.dx = 0 blue_sprite.dy = 0 yellow_sprite = Actor("yellow.gif") yellow_sprite.pos = (360, 240) yellow_sprite.dx = 0 yellow_sprite.dy = 0 def draw_sprite_pos(which, sprite, pos_x, pos_y, color): screen.draw.text(which + ":", (pos_x, pos_y), color=color) screen.draw.text(str(int(sprite.x)), (pos_x+70, pos_y), color=color) screen.draw.text(str(int(sprite.y)), (pos_x+110, pos_y), color=color) def draw(): screen.fill(BACKGROUND_COLOR) red_sprite.draw() blue_sprite.draw() yellow_sprite.draw() draw_sprite_pos("Red", red_sprite, 10, 10, "#db0000") draw_sprite_pos("Blue", blue_sprite, 10, 30, "#00a2db") draw_sprite_pos("Yellow", yellow_sprite, 10, 50, "#dbc500") def update(): blue_sprite.left += blue_sprite.dx blue_sprite.top += blue_sprite.dy yellow_sprite.left += yellow_sprite.dx yellow_sprite.top += yellow_sprite.dy def on_key_down(key, mod, unicode): global dx, dy if key == keys.ESCAPE: exit() if key == keys.UP: blue_sprite.dy = -1 if key == keys.DOWN: blue_sprite.dy = 1 if key == keys.LEFT: blue_sprite.dx = -1 if key == keys.RIGHT: blue_sprite.dx = 1 if key == keys.W: yellow_sprite.dy = -1 if key == keys.S: yellow_sprite.dy = 1 if key == keys.A: yellow_sprite.dx = -1 if key == keys.D: yellow_sprite.dx = 1 def on_key_up(key, mod): global dx, dy if key == keys.ESCAPE: exit() if key == keys.UP or key == keys.DOWN: blue_sprite.dy = 0 if key == keys.LEFT or key == keys.RIGHT: blue_sprite.dx = 0 if key == keys.W or key == keys.S: yellow_sprite.dy = 0 if key == keys.A or key == keys.D: yellow_sprite.dx = 0
Dvojice atributů left+right a top+bottom je svázaná, tj. změnou jednoho atributu dojde i ke změně atributu druhého. To si můžeme snadno ověřit spuštěním dalšího demonstračního příkladu a posunem spritu po obrazovce:
WIDTH = 480 HEIGHT = 480 BACKGROUND_COLOR = (0, 0x40, 0x40) red_sprite = Actor("red.gif") red_sprite.pos = (120, 240) blue_sprite = Actor("blue.gif") blue_sprite.pos = (240, 240) yellow_sprite = Actor("yellow.gif") yellow_sprite.pos = (360, 240) dx = 0 dy = 0 def draw_sprite_pos(which, sprite, pos_x, pos_y, color): screen.draw.text(which + ":", (pos_x, pos_y), color=color) msg = "left: {} right: {} top: {} bottom: {}".format(int(sprite.left), int(sprite.right), int(sprite.top), int(sprite.bottom)) screen.draw.text(msg, (pos_x+100, pos_y), color=color) def draw(): screen.fill(BACKGROUND_COLOR) red_sprite.draw() blue_sprite.draw() yellow_sprite.draw() draw_sprite_pos("Red", red_sprite, 10, 10, "#db0000") draw_sprite_pos("Blue", blue_sprite, 10, 30, "#00a2db") draw_sprite_pos("Yellow", yellow_sprite, 10, 50, "#dbc500") def update(): blue_sprite.left += dx blue_sprite.top += dy def on_key_down(key, mod, unicode): global dx, dy if key == keys.ESCAPE: exit() if key == keys.UP: dy = -1 if key == keys.DOWN: dy = 1 if key == keys.LEFT: dx = -1 if key == keys.RIGHT: dx = 1 def on_key_up(key, mod): global dx, dy if key == keys.ESCAPE: exit() if key == keys.UP or key == keys.DOWN: dy = 0 if key == keys.LEFT or key == keys.RIGHT: dx = 0
Obrázek 10: Výchozí pozice spritů.
Obrázek 11: Horizontální posun modrého spritu doprava.
Obrázek 12: Vertikální posun modrého spritu dolů.
7. Automatický pohyb posledního spritu, odrazy od okrajů okna
Poslední sprite se bude ve scéně pohybovat zcela automaticky. Jeho atributy dx a dy nastavíme na shodnou nenulovou hodnotu, čímž zajistíme jeho úhlopříčný pohyb:
red_sprite = Actor("red.gif") red_sprite.pos = (120, 240) red_sprite.dx = 1 red_sprite.dy = 1
Na okrajích okna se bude sprite odrážet, tj. jeden z jeho atributů dx či dy změní znaménko. Povšimněte si, jak je možné využít všech čtyř atributů se souřadnicemi spritu left, right, top a bottom takovým způsobem, aby se kód zajišťující odrazy co nejvíce zjednodušil:
def bounce(sprite): if red_sprite.left < 0: red_sprite.left = 0 red_sprite.dx = -red_sprite.dx if red_sprite.right > WIDTH: red_sprite.right = WIDTH red_sprite.dx = -red_sprite.dx if red_sprite.top < 0: red_sprite.top = 0 red_sprite.dy = -red_sprite.dy if red_sprite.bottom > HEIGHT: red_sprite.bottom = HEIGHT red_sprite.dy = -red_sprite.dy
8. Úplný zdrojový kód příkladu s odrážejícím se spritem
WIDTH = 480 HEIGHT = 480 BACKGROUND_COLOR = (0, 0x40, 0x40) red_sprite = Actor("red.gif") red_sprite.pos = (120, 240) red_sprite.dx = 1 red_sprite.dy = 1 blue_sprite = Actor("blue.gif") blue_sprite.pos = (240, 240) blue_sprite.dx = 0 blue_sprite.dy = 0 yellow_sprite = Actor("yellow.gif") yellow_sprite.pos = (360, 240) yellow_sprite.dx = 0 yellow_sprite.dy = 0 def draw_sprite_pos(which, sprite, pos_x, pos_y, color): screen.draw.text(which + ":", (pos_x, pos_y), color=color) screen.draw.text(str(int(sprite.x)), (pos_x+70, pos_y), color=color) screen.draw.text(str(int(sprite.y)), (pos_x+110, pos_y), color=color) def draw(): screen.fill(BACKGROUND_COLOR) red_sprite.draw() blue_sprite.draw() yellow_sprite.draw() draw_sprite_pos("Red", red_sprite, 10, 10, "#db0000") draw_sprite_pos("Blue", blue_sprite, 10, 30, "#00a2db") draw_sprite_pos("Yellow", yellow_sprite, 10, 50, "#dbc500") def bounce(sprite): if red_sprite.left < 0: red_sprite.left = 0 red_sprite.dx = -red_sprite.dx if red_sprite.right > WIDTH: red_sprite.right = WIDTH red_sprite.dx = -red_sprite.dx if red_sprite.top < 0: red_sprite.top = 0 red_sprite.dy = -red_sprite.dy if red_sprite.bottom > HEIGHT: red_sprite.bottom = HEIGHT red_sprite.dy = -red_sprite.dy def update(): blue_sprite.left += blue_sprite.dx blue_sprite.top += blue_sprite.dy yellow_sprite.left += yellow_sprite.dx yellow_sprite.top += yellow_sprite.dy red_sprite.left += red_sprite.dx red_sprite.top += red_sprite.dy bounce(red_sprite) def on_key_down(key, mod, unicode): global dx, dy if key == keys.ESCAPE: exit() if key == keys.UP: blue_sprite.dy = -1 if key == keys.DOWN: blue_sprite.dy = 1 if key == keys.LEFT: blue_sprite.dx = -1 if key == keys.RIGHT: blue_sprite.dx = 1 if key == keys.W: yellow_sprite.dy = -1 if key == keys.S: yellow_sprite.dy = 1 if key == keys.A: yellow_sprite.dx = -1 if key == keys.D: yellow_sprite.dx = 1 def on_key_up(key, mod): global dx, dy if key == keys.ESCAPE: exit() if key == keys.UP or key == keys.DOWN: blue_sprite.dy = 0 if key == keys.LEFT or key == keys.RIGHT: blue_sprite.dx = 0 if key == keys.W or key == keys.S: yellow_sprite.dy = 0 if key == keys.A or key == keys.D: yellow_sprite.dx = 0
9. Kolize spritů
Nyní se začneme zabývat kolizí spritů. V „herní“ scéně se nyní nachází tři sprity, z nichž jeden se pohybuje zcela autonomně (odráží se od stěn atd.) a další dva můžeme posunovat s využitím klávesnice – kurzorových kláves a taktéž známou kombinací WASD. Jakým způsobem je však možné detekovat, že se dva nebo dokonce všechny tři sprity setkaly? K tomuto účelu lze využít několika metod, které jsou deklarovány přímo pro instance třídy Actor (interně je ovšem situace složitější, protože jsou tyto metody deklarovány ve třídě ZRect odvozené od třídy Rect knihovny Pygame). Jedná se o tyto metody:
# | Metoda | Stručný popis metody |
---|---|---|
1 | collidepoint | test na kolizi spritu s bodem |
2 | colliderect | test na kolizi spritu s jiným spritem |
3 | collidelist | test na kolizi spritu s více sprity (ze sekvence) |
4 | collidelistall | dtto, ovšem vrací seznam kolidujících spritů |
5 | collidedict | jako collidelist, ovšem pro slovník spritů |
6 | collidedictall | jako collidelistall, ovšem pro slovník spritů |
V následujících kapitolách se zaměříme na metody colliderect a collidelist.
10. Využití metody colliderect pro detekci kolizí spritů
Metoda colliderect slouží ke zjištění, zda se jeden objekt typu Rect protíná s jiným objektem stejného typu. Vzhledem k tomu, že sprite, tedy objekt typu Actor, získal vlastnosti Rect, můžeme přímo detekovat kolize (protnutí, dotyk) dvou spritů následujícím testem:
if sprite.colliderect(other_sprite): ... ... ...
V našem konkrétním případě potřebujeme zjistit kolizi spritu sprite s ostatními sprity realizovanými sekvencí other_sprites:
collision = False for other_sprite in other_sprites: if sprite.colliderect(other_sprite): collision = True break if collision: ... ... ...
Příklad použití tak, jak je tomu i v následujícím demonstračním příkladu:
def draw_sprite_col(sprite, other_sprites, pos_x, pos_y, color): collision = False for other_sprite in other_sprites: if sprite.colliderect(other_sprite): collision = True break if collision: screen.draw.text("x", (pos_x, pos_y), color=color)
11. Úplný zdrojový kód příkladu s detekcí kolize spritů
Výše uvedenou funkci draw_sprite_col můžeme snadno zakomponovat do ucelenějšího demonstračního příkladu, který u každého spritu vypisuje i příslušné kolize:
def draw(): screen.fill(BACKGROUND_COLOR) ... ... ... draw_sprite_col(red_sprite, (blue_sprite, yellow_sprite), 200, 10, "#db0000") draw_sprite_col(blue_sprite, (red_sprite, yellow_sprite), 200, 30, "#00a2db") draw_sprite_col(yellow_sprite, (red_sprite, blue_sprite), 200, 50, "#dbc500")
Obrázek 13: Nekolidující sprity.
Obrázek 14: Kolize dvou spritů.
Obrázek 15: Kolize všech tří spritů.
Následuje výpis úplného zdrojového kódu tohoto příkladu:
WIDTH = 480 HEIGHT = 480 BACKGROUND_COLOR = (0, 0x40, 0x40) red_sprite = Actor("red.gif") red_sprite.pos = (120, 240) red_sprite.dx = 1 red_sprite.dy = 1 blue_sprite = Actor("blue.gif") blue_sprite.pos = (240, 240) blue_sprite.dx = 0 blue_sprite.dy = 0 yellow_sprite = Actor("yellow.gif") yellow_sprite.pos = (360, 240) yellow_sprite.dx = 0 yellow_sprite.dy = 0 def draw_sprite_pos(which, sprite, pos_x, pos_y, color): screen.draw.text(which + ":", (pos_x, pos_y), color=color) screen.draw.text(str(int(sprite.x)), (pos_x+70, pos_y), color=color) screen.draw.text(str(int(sprite.y)), (pos_x+110, pos_y), color=color) def draw_sprite_col(sprite, other_sprites, pos_x, pos_y, color): collision = False for other_sprite in other_sprites: if sprite.colliderect(other_sprite): collision = True break if collision: screen.draw.text("x", (pos_x, pos_y), color=color) def draw(): screen.fill(BACKGROUND_COLOR) red_sprite.draw() blue_sprite.draw() yellow_sprite.draw() draw_sprite_pos("Red", red_sprite, 10, 10, "#db0000") draw_sprite_pos("Blue", blue_sprite, 10, 30, "#00a2db") draw_sprite_pos("Yellow", yellow_sprite, 10, 50, "#dbc500") draw_sprite_col(red_sprite, (blue_sprite, yellow_sprite), 200, 10, "#db0000") draw_sprite_col(blue_sprite, (red_sprite, yellow_sprite), 200, 30, "#00a2db") draw_sprite_col(yellow_sprite, (red_sprite, blue_sprite), 200, 50, "#dbc500") def bounce(sprite): if red_sprite.left < 0: red_sprite.left = 0 red_sprite.dx = -red_sprite.dx if red_sprite.right > WIDTH: red_sprite.right = WIDTH red_sprite.dx = -red_sprite.dx if red_sprite.top < 0: red_sprite.top = 0 red_sprite.dy = -red_sprite.dy if red_sprite.bottom > HEIGHT: red_sprite.bottom = HEIGHT red_sprite.dy = -red_sprite.dy def update(): blue_sprite.left += blue_sprite.dx blue_sprite.top += blue_sprite.dy yellow_sprite.left += yellow_sprite.dx yellow_sprite.top += yellow_sprite.dy red_sprite.left += red_sprite.dx red_sprite.top += red_sprite.dy bounce(red_sprite) def on_key_down(key, mod, unicode): global dx, dy if key == keys.ESCAPE: exit() if key == keys.UP: blue_sprite.dy = -1 if key == keys.DOWN: blue_sprite.dy = 1 if key == keys.LEFT: blue_sprite.dx = -1 if key == keys.RIGHT: blue_sprite.dx = 1 if key == keys.W: yellow_sprite.dy = -1 if key == keys.S: yellow_sprite.dy = 1 if key == keys.A: yellow_sprite.dx = -1 if key == keys.D: yellow_sprite.dx = 1 def on_key_up(key, mod): global dx, dy if key == keys.ESCAPE: exit() if key == keys.UP or key == keys.DOWN: blue_sprite.dy = 0 if key == keys.LEFT or key == keys.RIGHT: blue_sprite.dx = 0 if key == keys.W or key == keys.S: yellow_sprite.dy = 0 if key == keys.A or key == keys.D: yellow_sprite.dx = 0
12. Využití metody collidelist pro detekci kolizí spritů
V některých případech, zejména když se má detekovat kolize většího množství spritů (a je nám jedno, o který konkrétní sprite se jedná), je výhodnější použít metodu nazvanou collidelist. Této metodě se předá seznam nebo n-tice spritů a výsledkem je index prvního spritu, který koliduje se spritem výchozím. Pokud k žádné kolizi nedochází, vrátí se namísto indexu hodnota –1, což je chování podobné například hledání znaku v řetězci atd.:
if sprite.collidelist(other_sprites) >= 0: ... ... ...
Konkrétní příklad použití:
def draw_sprite_col(sprite, other_sprites, pos_x, pos_y, color): if sprite.collidelist(other_sprites) >= 0: screen.draw.text("x", (pos_x, pos_y), color=color)
Při volání této funkce musíme v prvním parametru předat sprite, pro který kolizi hledáme a následně seznam či n-tici všech ostatních spritů:
draw_sprite_col(red_sprite, (blue_sprite, yellow_sprite), 200, 10, "#db0000") draw_sprite_col(blue_sprite, (red_sprite, yellow_sprite), 200, 30, "#00a2db") draw_sprite_col(yellow_sprite, (red_sprite, blue_sprite), 200, 50, "#dbc500")
13. Úplný zdrojový kód druhé varianty příkladu s detekcí kolize spritů
Podobně jako jedenácté kapitole si i nyní zobrazíme celý zdrojový kód příkladu, který detekuje kolize jednotlivých spritů mezi sebou:
WIDTH = 480 HEIGHT = 480 BACKGROUND_COLOR = (0, 0x40, 0x40) red_sprite = Actor("red.gif") red_sprite.pos = (120, 240) red_sprite.dx = 1 red_sprite.dy = 1 blue_sprite = Actor("blue.gif") blue_sprite.pos = (240, 240) blue_sprite.dx = 0 blue_sprite.dy = 0 yellow_sprite = Actor("yellow.gif") yellow_sprite.pos = (360, 240) yellow_sprite.dx = 0 yellow_sprite.dy = 0 def draw_sprite_pos(which, sprite, pos_x, pos_y, color): screen.draw.text(which + ":", (pos_x, pos_y), color=color) screen.draw.text(str(int(sprite.x)), (pos_x+70, pos_y), color=color) screen.draw.text(str(int(sprite.y)), (pos_x+110, pos_y), color=color) def draw_sprite_col(sprite, other_sprites, pos_x, pos_y, color): if sprite.collidelist(other_sprites) >= 0: screen.draw.text("x", (pos_x, pos_y), color=color) def draw(): screen.fill(BACKGROUND_COLOR) red_sprite.draw() blue_sprite.draw() yellow_sprite.draw() draw_sprite_pos("Red", red_sprite, 10, 10, "#db0000") draw_sprite_pos("Blue", blue_sprite, 10, 30, "#00a2db") draw_sprite_pos("Yellow", yellow_sprite, 10, 50, "#dbc500") draw_sprite_col(red_sprite, (blue_sprite, yellow_sprite), 200, 10, "#db0000") draw_sprite_col(blue_sprite, (red_sprite, yellow_sprite), 200, 30, "#00a2db") draw_sprite_col(yellow_sprite, (red_sprite, blue_sprite), 200, 50, "#dbc500") def bounce(sprite): if red_sprite.left < 0: red_sprite.left = 0 red_sprite.dx = -red_sprite.dx if red_sprite.right > WIDTH: red_sprite.right = WIDTH red_sprite.dx = -red_sprite.dx if red_sprite.top < 0: red_sprite.top = 0 red_sprite.dy = -red_sprite.dy if red_sprite.bottom > HEIGHT: red_sprite.bottom = HEIGHT red_sprite.dy = -red_sprite.dy def update(): blue_sprite.left += blue_sprite.dx blue_sprite.top += blue_sprite.dy yellow_sprite.left += yellow_sprite.dx yellow_sprite.top += yellow_sprite.dy red_sprite.left += red_sprite.dx red_sprite.top += red_sprite.dy bounce(red_sprite) def on_key_down(key, mod, unicode): global dx, dy if key == keys.ESCAPE: exit() if key == keys.UP: blue_sprite.dy = -1 if key == keys.DOWN: blue_sprite.dy = 1 if key == keys.LEFT: blue_sprite.dx = -1 if key == keys.RIGHT: blue_sprite.dx = 1 if key == keys.W: yellow_sprite.dy = -1 if key == keys.S: yellow_sprite.dy = 1 if key == keys.A: yellow_sprite.dx = -1 if key == keys.D: yellow_sprite.dx = 1 def on_key_up(key, mod): global dx, dy if key == keys.ESCAPE: exit() if key == keys.UP or key == keys.DOWN: blue_sprite.dy = 0 if key == keys.LEFT or key == keys.RIGHT: blue_sprite.dx = 0 if key == keys.W or key == keys.S: yellow_sprite.dy = 0 if key == keys.A or key == keys.D: yellow_sprite.dx = 0
14. Změna stavu hry po kolizi
Kolizi spritů, tedy například střetnutí hráče s protihráčem, můžeme zjišťovat buď po posunu každého spritu nebo kontinuálně ve funkci update, která je volána periodicky šedesátkrát za minutu:
def update(): collide(red_sprite, blue_sprite) collide(red_sprite, yellow_sprite)
Po zjištění kolize samozřejmě může „engine“ hry nějak zareagovat. V našem případě pouze změníme barvu spritu, což znamená, že spritu přiřadíme jiný obrázek („white.gif“):
def collide(sprite1, sprite2): if sprite1.colliderect(sprite2): sprite2.image = "white"
Obrázek 16: Adventure ve verzi pro herní konzoli Atari 5200.
15. Reakce herního „engine“ na kolizi spritu
Nyní se podívejme na výsledek:
WIDTH = 480 HEIGHT = 480 BACKGROUND_COLOR = (0, 0x40, 0x40) red_sprite = Actor("red.gif") red_sprite.pos = (120, 240) red_sprite.dx = 1 red_sprite.dy = 1 blue_sprite = Actor("blue.gif") blue_sprite.pos = (240, 240) blue_sprite.dx = 0 blue_sprite.dy = 0 yellow_sprite = Actor("yellow.gif") yellow_sprite.pos = (360, 240) yellow_sprite.dx = 0 yellow_sprite.dy = 0 def draw_sprite_pos(which, sprite, pos_x, pos_y, color): screen.draw.text(which + ":", (pos_x, pos_y), color=color) screen.draw.text(str(int(sprite.x)), (pos_x+70, pos_y), color=color) screen.draw.text(str(int(sprite.y)), (pos_x+110, pos_y), color=color) def draw_sprite_col(sprite, other_sprites, pos_x, pos_y, color): if sprite.collidelist(other_sprites) >= 0: screen.draw.text("x", (pos_x, pos_y), color=color) def draw(): screen.fill(BACKGROUND_COLOR) red_sprite.draw() blue_sprite.draw() yellow_sprite.draw() draw_sprite_pos("Red", red_sprite, 10, 10, "#db0000") draw_sprite_pos("Blue", blue_sprite, 10, 30, "#00a2db") draw_sprite_pos("Yellow", yellow_sprite, 10, 50, "#dbc500") draw_sprite_col(red_sprite, (blue_sprite, yellow_sprite), 200, 10, "#db0000") draw_sprite_col(blue_sprite, (red_sprite, yellow_sprite), 200, 30, "#00a2db") draw_sprite_col(yellow_sprite, (red_sprite, blue_sprite), 200, 50, "#dbc500") def bounce(sprite): if red_sprite.left < 0: red_sprite.left = 0 red_sprite.dx = -red_sprite.dx if red_sprite.right > WIDTH: red_sprite.right = WIDTH red_sprite.dx = -red_sprite.dx if red_sprite.top < 0: red_sprite.top = 0 red_sprite.dy = -red_sprite.dy if red_sprite.bottom > HEIGHT: red_sprite.bottom = HEIGHT red_sprite.dy = -red_sprite.dy def collide(sprite1, sprite2): if sprite1.colliderect(sprite2): sprite2.image = "white" def update(): blue_sprite.left += blue_sprite.dx blue_sprite.top += blue_sprite.dy yellow_sprite.left += yellow_sprite.dx yellow_sprite.top += yellow_sprite.dy red_sprite.left += red_sprite.dx red_sprite.top += red_sprite.dy bounce(red_sprite) collide(red_sprite, blue_sprite) collide(red_sprite, yellow_sprite) def on_key_down(key, mod, unicode): global dx, dy if key == keys.ESCAPE: exit() if key == keys.UP: blue_sprite.dy = -1 if key == keys.DOWN: blue_sprite.dy = 1 if key == keys.LEFT: blue_sprite.dx = -1 if key == keys.RIGHT: blue_sprite.dx = 1 if key == keys.W: yellow_sprite.dy = -1 if key == keys.S: yellow_sprite.dy = 1 if key == keys.A: yellow_sprite.dx = -1 if key == keys.D: yellow_sprite.dx = 1 def on_key_up(key, mod): global dx, dy if key == keys.ESCAPE: exit() if key == keys.UP or key == keys.DOWN: blue_sprite.dy = 0 if key == keys.LEFT or key == keys.RIGHT: blue_sprite.dx = 0 if key == keys.W or key == keys.S: yellow_sprite.dy = 0 if key == keys.A or key == keys.D: yellow_sprite.dx = 0
Obrázek 17: Stav před kolizí spritů.
Obrázek 18: Stav po kolizi spritů.
16. Naplánování činnosti s využitím objektu clock
Poslední vlastností nástroje Pygame Zero, s níž se v tomto miniseriálu setkáme, je schopnost naplánovat určitou činnost tak, aby proběhla až po uplynutí nějakého časového intervalu. K tomuto účelu se používá objekt clock s metodami schedule a schedule_unique. Těmto metodám se předává volaná funkce a taktéž časový interval, po jehož uplynutí je funkce skutečně zavolaná:
schedule(funkce, 1.00) # interval jedné sekundy schedule_unique(funkce, 1.00) # interval jedné sekundy
Rozdíl mezi schedule a schedule_unique spočívá v tom, že pokud je naplánována stejná úloha (resp. přesněji řečeno úloha představovaná stejnou funkcí), je v případě schedule_unique původní plán zahozen. Představme si například naprogramování pozdní reakce na kliknutí na objekt ve scéně – i když budeme klikat vícekrát, budeme (většinou) požadovat pouze jediné vykonání příslušné akce.
17. Obnovení barvy spritu sekundu po kolizi
Zkusme si nyní naprogramovat změnu barvy spritu po nárazu do zdi (a odražení od ní). Po jedné sekundě by se měla barva vrátit na původní hodnotu (tedy použije se původní obrázek). Současně se přehraje zvuk s nárazem:
if bounced: red_sprite.image = "gray.gif" clock.schedule_unique(reset_sprite, 0.4) sounds.ping.play()
Po jedné sekundě od nárazu se zavolá tato funkce:
def reset_sprite(): red_sprite.image = "red.gif"
Úplný zdrojový kód demonstračního příkladu vypadá následovně:
WIDTH = 480 HEIGHT = 480 BACKGROUND_COLOR = (0, 0x40, 0x40) red_sprite = Actor("red.gif") red_sprite.pos = (120, 240) red_sprite.dx = 1 red_sprite.dy = 1 blue_sprite = Actor("blue.gif") blue_sprite.pos = (240, 240) blue_sprite.dx = 0 blue_sprite.dy = 0 yellow_sprite = Actor("yellow.gif") yellow_sprite.pos = (360, 240) yellow_sprite.dx = 0 yellow_sprite.dy = 0 def draw_sprite_pos(which, sprite, pos_x, pos_y, color): screen.draw.text(which + ":", (pos_x, pos_y), color=color) screen.draw.text(str(int(sprite.x)), (pos_x+70, pos_y), color=color) screen.draw.text(str(int(sprite.y)), (pos_x+110, pos_y), color=color) def draw_sprite_col(sprite, other_sprites, pos_x, pos_y, color): if sprite.collidelist(other_sprites) >= 0: screen.draw.text("x", (pos_x, pos_y), color=color) def draw(): screen.fill(BACKGROUND_COLOR) red_sprite.draw() blue_sprite.draw() yellow_sprite.draw() draw_sprite_pos("Red", red_sprite, 10, 10, "#db0000") draw_sprite_pos("Blue", blue_sprite, 10, 30, "#00a2db") draw_sprite_pos("Yellow", yellow_sprite, 10, 50, "#dbc500") draw_sprite_col(red_sprite, (blue_sprite, yellow_sprite), 200, 10, "#db0000") draw_sprite_col(blue_sprite, (red_sprite, yellow_sprite), 200, 30, "#00a2db") draw_sprite_col(yellow_sprite, (red_sprite, blue_sprite), 200, 50, "#dbc500") def reset_sprite(): red_sprite.image = "red.gif" def bounce(sprite): bounced = False if red_sprite.left < 0: red_sprite.left = 0 red_sprite.dx = -red_sprite.dx bounced = True if red_sprite.right > WIDTH: red_sprite.right = WIDTH red_sprite.dx = -red_sprite.dx bounced = True if red_sprite.top < 0: red_sprite.top = 0 red_sprite.dy = -red_sprite.dy bounced = True if red_sprite.bottom > HEIGHT: red_sprite.bottom = HEIGHT red_sprite.dy = -red_sprite.dy bounced = True if bounced: red_sprite.image = "gray.gif" clock.schedule_unique(reset_sprite, 0.4) sounds.ping.play() def collide(sprite1, sprite2): if sprite1.colliderect(sprite2): sprite1.image = "white.gif" clock.schedule_unique(reset_sprite, 1.0) def update(): blue_sprite.left += blue_sprite.dx blue_sprite.top += blue_sprite.dy yellow_sprite.left += yellow_sprite.dx yellow_sprite.top += yellow_sprite.dy red_sprite.left += red_sprite.dx red_sprite.top += red_sprite.dy bounce(red_sprite) collide(red_sprite, blue_sprite) collide(red_sprite, yellow_sprite) def on_key_down(key, mod, unicode): global dx, dy if key == keys.ESCAPE: exit() if key == keys.UP: blue_sprite.dy = -1 if key == keys.DOWN: blue_sprite.dy = 1 if key == keys.LEFT: blue_sprite.dx = -1 if key == keys.RIGHT: blue_sprite.dx = 1 if key == keys.W: yellow_sprite.dy = -1 if key == keys.S: yellow_sprite.dy = 1 if key == keys.A: yellow_sprite.dx = -1 if key == keys.D: yellow_sprite.dx = 1 def on_key_up(key, mod): global dx, dy if key == keys.ESCAPE: exit() if key == keys.UP or key == keys.DOWN: blue_sprite.dy = 0 if key == keys.LEFT or key == keys.RIGHT: blue_sprite.dx = 0 if key == keys.W or key == keys.S: yellow_sprite.dy = 0 if key == keys.A or key == keys.D: yellow_sprite.dx = 0
18. Relevantní funkce z knihovny Pygame
V této kapitole jsou uvedeny odkazy na dokumentaci k funkcím knihovny Pygame, které jsou interně používány i projektem Pygame Zero. Tyto funkce jsou vidět v některých chybových hlášeních, protože i přes snahy autorů Pygame Zero není odstínění od knihovny Pygame úplné (což je možná jeden z největších současných nedostatků tohoto projektu):
- pygame.init()
http://www.pygame.org/docs/ref/pygame.html#pygame.init - pygame.quit()
http://www.pygame.org/docs/ref/pygame.html#pygame.quit - pygame.display.set_mode()
http://www.pygame.org/docs/ref/display.html#pygame.display.set_mode - pygame.display.set_caption()
http://www.pygame.org/docs/ref/display.html#pygame.display.set_caption - pygame.display.update()
http://www.pygame.org/docs/ref/display.html#pygame.display.update - pygame.event.get()
http://www.pygame.org/docs/ref/event.html#pygame.event.get - pygame.time.wait()
http://www.pygame.org/docs/ref/time.html#pygame.time.wait - pygame.time.Clock.tick()
http://www.pygame.org/docs/ref/time.html#pygame.time.Clock.tick - pygame.draw.line()
http://www.pygame.org/docs/ref/draw.html#pygame.draw.line - pygame.draw.circle()
http://www.pygame.org/docs/ref/draw.html#pygame.draw.circle - pygame.draw.rect()
http://www.pygame.org/docs/ref/draw.html#pygame.draw.rect - pygame.draw.ellipse()
http://www.pygame.org/docs/ref/draw.html#pygame.draw.ellipse - pygame.key.get_pressed
https://www.pygame.org/docs/ref/key.html#pygame.key.get_pressed - pygame.key.get_mods
https://www.pygame.org/docs/ref/key.html#pygame.key.get_mods - pygame.mouse.get_pos
https://www.pygame.org/docs/ref/mouse.html#pygame.mouse.get_pos - pygame.mouse.get_rel
https://www.pygame.org/docs/ref/mouse.html#pygame.mouse.get_rel - pygame.mouse.get_pressed
https://www.pygame.org/docs/ref/mouse.html#pygame.mouse.get_pressed - pygame.mixer
https://www.pygame.org/docs/ref/mixer.html - pygame.mixer.music
https://www.pygame.org/docs/ref/music.html - pygame.mixer.Sound
https://www.pygame.org/docs/ref/mixer.html#pygame.mixer.Sound - pygame.mixer.Channel
https://www.pygame.org/docs/ref/mixer.html#pygame.mixer.Channel
19. Repositář s demonstračními příklady
Zdrojové kódy všech popsaných demonstračních příkladů určených pro Python 3 a současně i pro nástroj Pygame Zero byly uloženy do Git repositáře dostupného na adrese https://github.com/tisnik/most-popular-python-libs. V případě, že nebudete chtít klonovat celý repositář (ten je ovšem stále velmi malý, dnes má velikost zhruba několik desítek kilobajtů), můžete namísto toho použít odkazy na jednotlivé příklady, které naleznete v následující čtveřici tabulek (ovšem vzhledem k tomu, že se datové soubory hledají v konkrétních podadresářích, je výhodnější provést klon celého repositáře):
20. Odkazy na Internetu
- Welcome to Pygame Zero
https://pygame-zero.readthedocs.io/en/stable/index.html - Other libraries like Pygame Zero
https://pygame-zero.readthedocs.io/en/stable/other-libs.html - Principles of Pygame Zero
https://pygame-zero.readthedocs.io/en/stable/principles.html - Built-in Objects (in Pygame Zero)
https://pygame-zero.readthedocs.io/en/stable/builtins.html - Pygame
https://www.pygame.org/news - Kniha: Coding Games With Pygame Zero & Python: Student workbook
https://bookerystore.com/downloads/coding-games-with-pygame-zero-python-student-workbook/ - Projekty založené na Pygame
https://www.pygame.org/tags/all - Domovská stránka projektu LÖVE
https://love2d.org/ - PyWeek, a bi-annual game jam to write games in Python
https://pyweek.org/ - Teaching a kid to code with Pygame Zero
https://www.mattlayman.com/blog/2019/teach-kid-code-pygame-zero/ - Games with PyGame Zero
https://codewith.mu/en/tutorials/1.0/pgzero - Coding Games With Pygame Zero & Python: Student workbook (2nd edition)
https://electronstudio.github.io/pygame-zero-book/ - Historie vývoje počítačových her (116. část – vývoj her v současnosti: od assembleru k PyGame)
https://www.root.cz/clanky/historie-vyvoje-pocitacovych-her-116-cast-vyvoj-her-v-soucasnosti-od-assembleru-k-pygame/ - Lua + LÖVE: vytvořte si vlastní hru
https://www.root.cz/clanky/lua-love-vytvorte-si-vlastni-hru/ - Hrátky se systémem LÖVE
https://www.root.cz/clanky/hratky-se-systemem-love/ - Vytváříme hru v systému LÖVE
https://www.root.cz/clanky/vytvarime-hru-v-systemu-love/ - Hrátky se systémem LÖVE – částicové systémy
https://www.root.cz/clanky/hratky-se-systemem-love-casticove-systemy/ - Hrátky se systémem LÖVE – kolize a odrazy těles
https://www.root.cz/clanky/hratky-se-systemem-love-ndash-kolize-a-odrazy-teles/ - Hrátky se systémem LÖVE – kolize a odrazy těles II
https://www.root.cz/clanky/hratky-se-systemem-love-kolize-a-odrazy-teles-ii/ - Hrátky se systémem LÖVE – pružné vazby mezi tělesy
https://www.root.cz/clanky/hratky-se-systemem-love-pruzne-vazby-mezi-telesy/ - Hrátky se systémem LÖVE – dokončení
https://www.root.cz/clanky/hratky-se-systemem-love-dokonceni/ - Seriál Letní škola programovacího jazyka Logo
http://www.root.cz/serialy/letni-skola-programovaciho-jazyka-logo/ - Scratch: oficiální stránka projektu
http://scratch.mit.edu/ - Scratch: galerie projektů vytvořených ve Scratchi
http://scratch.mit.edu/galleries/browse/newest - Scratch: nápověda
file:///usr/share/scratch/Help/en/index.html - Scratch: obrazovky nápovědy
file:///usr/share/scratch/Help/en/allscreens.html - Scratch (Wikipedie CZ)
http://cs.wikipedia.org/wiki/Scratch - Scratch (programming language)
http://en.wikipedia.org/wiki/Scratch_(programming_language) - Scratch Modification
http://wiki.scratch.mit.edu/wiki/Scratch_Modification - Scratch Lowers Resistance to Programming
http://www.wired.com/gadgetlab/2009/03/scratch-lowers/ - Snap!
http://snap.berkeley.edu/ - Prostředí Snap!
http://snap.berkeley.edu/snapsource/snap.html - Alternatives to Scratch
http://wiki.scratch.mit.edu/wiki/Alternatives_to_Scratch - Snap! (programming language)
https://en.wikipedia.org/wiki/Snap!_(programming_language) - Kniha o Basicu-256
http://www.basicbook.org/files/syw2l2p_b256.pdf/ - Basic-256 home page
http://www.basic256.org/index_en - Basic-256 Language Documentation
http://doc.basic256.org/doku.php - Basic-256 Art Gallery
http://www.basic256.org/artgallery - Basic-256 Tutorial
http://www.basic256.org/tutorials - Why BASIC?
http://www.basic256.org/whybasic - A book to teach ANYBODY how to program a computer (using BASIC)
http://www.basicbook.org/ - Sprite ve Scratchi
https://en.scratch-wiki.info/wiki/Sprite - Scratch Modification
https://en.scratch-wiki.info/wiki/Scratch_Modification - 3D Programming in Python – Part 1
https://greendalecs.wordpress.com/2012/04/21/3d-programming-in-python-part-1/ - A very basic Pyglet tutorial
http://www.natan.termitnjak.net/tutorials/pyglet_basic.html - Alpha blending
https://en.wikipedia.org/wiki/Alpha_compositing#Alpha_blending - Pygame Colors
https://pygame-zero.readthedocs.io/en/latest/colors_ref.html - Python Color Constants Module
https://www.webucator.com/blog/2015/03/python-color-constants-module/ - Inbetweening
https://en.wikipedia.org/wiki/Inbetweening - MP3 (Wikipedia)
https://en.wikipedia.org/wiki/MP3 - Ogg (Wikipedia)
https://en.wikipedia.org/wiki/Ogg - Vorbis audio compression
https://xiph.org/vorbis/ - Vorbis (Wikipedia)
https://en.wikipedia.org/wiki/Vorbis - Tweens
https://helpx.adobe.com/animate/using/Tweens.html