Lekce 8 - Skákačka v Pygame - Obrázky
V předchozí lekci, Skákačka v Pygame - Logging, jsme se naučili značit informace o
průběhu projektu naší Skákačky pomocí knihovny logging
.
V tomto tutoriálu Pygame v Pythonu v našem projektu
Skákačky pokročíme k práci s obrázky. Vytvoříme si pro
ně třídu Image
.
Soubor pro obrázky a zvuky
Aby se nám s obrázky dobře pracovalo, vytvoříme si pro ně vlastní
třídu. Protože ale z rozboru víme, že podobným způsobem budeme chtít
pracovat i se zvuky, vytvoříme pro tyto dvě třídy jeden soubor, aby byly
pohromadě. Soubor pojmenujeme media.py
a vytvoříme ho ve složce
engine/
. Budeme zde používat knihovny pygame
a
logging
, takže je naimportujeme:
import pygame import logging
Nyní krátce odbočíme k logování. Chceme vědět, jestli nám hra
hlásí něco o průběhu z pohledu hráče (například posun jeho figurky)
nebo z pohledu programu (naimportování obrázku). Vytvoříme si proto novou
instanci třídy Logger
, kterou budeme používat pro tyto
programové události. Na začátek souboru game.py
(kde se bude
odehrávat většina těchto událostí) hned pod import přidáme:
import logging GameEngineLogger = logging.getLogger("Engine")
Abychom se k tomuto objektu mohli dostat i v media.py
, musíme
ho tam také naimportovat:
from engine.game import GameEngineLogger
Třída Image
Nyní se konečně můžeme vrhnout na samotnou třídu Image
.
Napíšeme hlavičku class Image:
, než se však vrhneme na
konstruktor, přidáme ještě proměnné. Tyto budou přístupné všem
instancím třídy Image
a půjdou změnit pouze přes jméno
třídy.
Proměnné třídy Image
Jako první přidáme proměnnou default_dir
, ze které se se
budou načítat obrázky. Uložíme do ní .
, neboli složku, ve
které se momentálně nacházíme. Další bude
_accepted_extensions
, kam uložíme seznam možných přípon
obrázků. Poslední proměnnou pojmenujeme _images_cache
. Do ní
budeme ukládat již načtené obrázky. Jenže jak chceme obrázky vlastně
ukládat?
Aby naše hra vypadala o něco živěji, budeme chtít, aby se nám obrázky
měnily. Vlajka bude vlát ve větru, létající hmyz bude mávat křídly,
nepřátelé nebudou při pohybu tam a zpět couvat, ale otočí se a půjdou
jiným směrem. Ale jak to udělat? Použijeme k tomu ten nejjednodušší (i
když ne zrovna algoritmický) způsob. Prostě si uložíme více obrázků a
budeme je periodicky měnit. Budeme si ale muset pamatovat, které skupinky
obrázků patří k sobě. Proto bude fajn mít je všechny v rámci jednoho
Image
objektu. V _images_cache
bude tedy uložen
slovník. Jeho klíče budou jména objektů ve hře a hodnoty budou seznamy
všech pygame
verzí obrázků (budou to tedy objekty typu
pygame.surface
). Abychom na toto nezapomněli, raději si to
poznamenáme pomocí knihovny typing
. Předpřipravíme si u ní i
jiné datové typy, než potřebujeme, abychom se sem později nemuseli vracet.
Celý soubor media.py
teď tedy vypadá takto:
import pygame import logging from typing import Dict, Set, Optional, List, Callable from engine.game import GameEngineLogger class Image: default_dir = '.' _accepted_extensions = ['.png', '.jpg', '.gif', '.bmp'] _images_cache: Dict[str, List[pygame.Surface]] = {}
Konstruktor
A teď už konečně přejdeme ke konstruktoru. K tomu, abychom mohli obrázek správně načíst, budeme potřebovat:
- jméno obrázku,
- velikost, na kterou ho chceme změnit,
- info, jestli chceme obrázek vůbec načíst.
Povinné bude pouze jméno obrázku. Další dvě proměnné můžeme
nastavit defaultně. Formát konstruktoru __init__()
tedy bude
vypadat takto:
def __init__(self, image_name: str, convert_size: (int, int) = None, load: bool = True):
Nyní sepíšeme atributy instance třídy Image
:
self.name
– jméno obrázku z__init__
,self.speed
- kolik iterací herní smyčky musí proběhnout, aby se obrázek proměnil na svoji další „fázi“. Jako základní hodnota zde bude nula, kterou použijeme jako deaktivaci změny obrázku,self._image_index
– index obrázku, který se právě zobrazuje,self._image_tick
– kolik smyček uběhlo od poslední změny,self._size
– velikost obrázku ve formátu (šířka, výška), prozatím nastavená na nulu, protože nemáme žádný obrázek načtený.
Na později si sem také připravíme ještě self._game
(konkrétní instance právě běžící hry) a self._subimages
(seznam obrázků v rámci jednoho objektu):
self.name = image_name self.speed = 0 self._image_index = 0 self._image_tick = 0 self._size = (0, 0) self._game = None self._subimages = None
Další věcí, kterou musíme udělat, je nevytvářet znovu obrázek, pokud
už je v naší cache paměti. Pokud tam tedy je, do
self._subimages
přijde hodnota cache pod jménem obrázku.
Zároveň ohlásíme načítání z paměti přes logging
:
if image_name in self._images_cache: GameEngineLogger.debug(f"Image {image_name} is in cache") self._subimages = self._images_cache[image_name]
Nyní však už máme načteny obrázky a v atributu self_size
tak nemůže zůstat hodnota (0,0)
. Budeme ji muset změnit a aby
se nám nestalo, že bude některý z obrázků větší než uložená hodnota,
projdeme je všechny. Do atributu self_size
uložíme největší
šířku a největší délku. Vytvoříme na to metodu
self._get_size()
, která bude vypadat takto:
def _count_size(self) -> (int, int): max_w = 0 max_h = 0 for sub in self._subimages: w, h = sub.get_size() if w > max_w: max_w = w if h > max_h: max_h = h self._size = (max_w, max_h) return self._size
Tuto metodu zavoláme v naší momentální větvi konstruktoru
__init__()
a pak zavoláme return
, abychom konstruktor
v této větvi ukončili a nepřepsali si načtené obrázky dalším
kódem:
if image_name in self._images_cache: GameEngineLogger.debug(f"Image {image_name} is in cache") self._subimages = self._images_cache[image_name] self._count_size() return
Další okrajová podmínka se týká naší proměnné load
z
hlavičky __init__()
. Pokud by totiž byla False
,
nemělo by se nic načíst. Navíc bychom měli z cache paměti odstranit
podobrázky, pokud by tam byly. Tato větev tedy bude vypadat takto:
if not load: GameEngineLogger.debug(f"Image {image_name} is not due to load") if not self._subimages: GameEngineLogger.debug(f"Image {image_name} has not been loaded, emptying all subimages") self._images_cache[image_name] = self._subimages = [] return
V této lekci jsme sice nijak nezměnili vizuál hry, nicméně připravili jsme si podstatnou část kódu pro budoucí podobu Skákačky. Kompletní zdrojový kód je ke stažení pod lekcí.
V další lekci, Skákačka v Pygame - Obrázky - Načítání, dokončíme načítání obrázků pro Skákačku.
Zaměříme se také na funkce map()
a lambda()
.
Měl jsi s čímkoli problém? Stáhni si vzorovou aplikaci níže a porovnej ji se svým projektem, chybu tak snadno najdeš.
Stáhnout
Stažením následujícího souboru souhlasíš s licenčními podmínkami
Staženo 14x (9.14 kB)
Aplikace je včetně zdrojových kódů v jazyce Python