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 18x (9.14 kB)
Aplikace je včetně zdrojových kódů v jazyce Python


