IT rekvalifikácia. Seniorní programátori zarábajú až 6 000 €/mesiac a rekvalifikácia je prvým krokom. Zisti, ako na to!

8. diel - Skákačka v Pygame - Obrázky

V minulej lekcii, Skákačka v Pygame - Tvorba okna a pohyb postavy , sme sa dozvedeli o tvorbe okna, vytvorení a pohybe postavy, alebo aj ako vkladať obrázky v Pygame.

V tomto tutoriále Pygame v Pythone v našom projekte Skákačky pokročíme k práci s obrázkami. Vytvoríme si pre nich triedu Image.

Súbor pre obrázky a zvuky

Aby sa nám s obrázkami dobre pracovalo, vytvoríme si pre nich vlastnú triedu. Pretože ale z rozboru vieme, že podobným spôsobom budeme chcieť pracovať aj so zvukmi, vytvoríme pre tieto dve triedy jeden súbor, aby boli pohromade. Súbor pomenujeme media.py a vytvoríme ho v priečinku engine/. Budeme tu používať knižnice pygame a logging, takže ich naimportujeme:

import pygame
import logging

Teraz krátko odbočíme na logovanie. Chceme vedieť, či nám hra hlási niečo o priebehu z pohľadu hráča (napríklad posun jeho figúrky) alebo z pohľadu programu (naimportovanie obrázku). Vytvoríme si preto novú inštanciu triedy Logger, ktorú budeme používať na tieto programové udalosti. Na začiatok súboru game.py (kde sa bude odohrávať väčšina týchto udalostí) hneď pod import pridáme:

import logging
GameEngineLogger = logging.getLogger("Engine")

Aby sme sa k tomuto objektu mohli dostať aj v media.py, musíme ho tam tiež naimportovať:

from engine.game import GameEngineLogger

Trieda Image

Teraz sa konečne môžeme vrhnúť na samotnú triedu Image. Napíšeme hlavičku class Image:, než sa však vrhneme na konštruktor, pridáme ešte premenné. Tieto budú prístupné všetkým inštanciám triedy Image a pôjdu zmeniť iba cez meno triedy.

Premenné triedy Image

Ako prvé pridáme premennú default_dir, z ktorej sa budú načítavať obrázky. Uložíme do nej ., čiže zložku, v ktorej sa momentálne nachádzame. Ďalšou bude _accepted_extensions, kam uložíme zoznam možných prípon obrázkov. Poslednú premennú pomenujeme _images_cache. Do nej budeme ukladať už načítané obrázky. Lenže ako chceme obrázky vlastne ukladať?

Aby naša hra vyzerala o niečo živšie, budeme chcieť, aby sa nám obrázky menili. Vlajka bude viať vo vetre, lietajúci hmyz bude mávať krídlami, nepriatelia nebudú pri pohybe tam a späť cúvať, ale otočia sa a pôjdu iným smerom. Ale ako to urobiť? Použijeme na to ten najjednoduchší (aj keď nie práve algoritmický) spôsob. Jednoducho si uložíme viac obrázkov a budeme ich periodicky meniť. Budeme si však musieť pamätať, ktoré skupinky obrázkov patria k sebe. Preto bude fajn mať ich všetky v rámci jedného Image objektu. V _images_cache bude teda uložený slovník. Jeho kľúče budú mená objektov v hre a hodnoty budú zoznamy všetkých pygame verzií obrázkov (budú to teda objekty typu pygame.surface). Aby sme na toto nezabudli, radšej si to poznamenáme pomocou knižnice typing. Predpripravíme si u nej aj iné dátové typy, než potrebujeme, aby sme sa sem neskôr nemuseli vracať. Celý súbor media.py teraz teda vyzerá 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]] = {}

Konštruktor

A teraz už konečne prejdeme ku konštruktoru. Na to, aby sme mohli obrázok správne načítať, budeme potrebovať:

  • meno obrázku,
  • veľkosť, na ktorú ho chceme zmeniť,
  • info, či chceme obrázok vôbec načítať.
Povinné bude iba meno obrázku. Ďalšie dve premenné môžeme nastaviť defaultne. Formát konštruktora __init__() teda bude vyzerať takto:
def __init__(self, image_name: str, convert_size: (int, int) = None, load: bool = True):

Teraz spíšeme atribúty inštancie triedy Image:

  • self.name – meno obrázku z __init__,
  • self.speed - koľko iterácií hernej slučky musí prebehnúť, aby sa obrázok premenil na svoju ďalšiu „fázu“. Ako základná hodnota tu bude nula, ktorú použijeme ako deaktiváciu zmeny obrázku,
  • self._image_index – index obrázku, ktorý sa práve zobrazuje,
  • self._image_tick – koľko slučiek ubehlo od poslednej zmeny,
  • self._size – veľkosť obrázku vo formáte (šírka, výška), zatiaľ nastavená na nulu, pretože nemáme žiadny obrázok načítaný.
Na neskôr si sem tiež pripravíme ešte self._game (konkrétne inštancie práve bežiacej hry) a self._subimages (zoznam obrázkov v rámci jedného 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

Ďalšou vecou, ktorú musíme urobiť, je nevytvárať znovu obrázok, pokiaľ už je v našej cache pamäti. Pokiaľ tam teda je, do self._subimages príde hodnota cache pod menom obrázku. Zároveň ohlásime načítanie z pamäte cez logging:

if image_name in self._images_cache:
    GameEngineLogger.debug(f"Image {image_name} is in cache")
    self._subimages = self._images_cache[image_name]

Teraz však už máme načítané obrázky av atribúte self_size tak nemôže zostať hodnota (0,0). Budeme ju musieť zmeniť a aby sa nám nestalo, že bude niektorý z obrázkov väčší ako uložená hodnota, prejdeme ich všetky. Do atribútu self_size uložíme najväčšiu šírku a najväčšiu dĺžku. Vytvoríme na to metódu self._get_size(), ktorá bude vyzerať 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

Túto metódu zavoláme v našej momentálnej vetve konštruktora __init__() a potom zavoláme return, aby sme konštruktor v tejto vetve ukončili a neprepísali si načítané obrázky ďalším kódom:

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

Ďalšia okrajová podmienka sa týka našej premennej load z hlavičky __init__(). Ak by totiž bola False, nemalo by sa nič načítať. Navyše by sme mali z cache pamäte odstrániť podobrázky, pokiaľ by tam boli. Táto vetva teda bude vyzerať 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 tejto lekcii sme síce nijako nezmenili vizuál hry, avšak pripravili sme si podstatnú časť kódu pre budúcu podobu Skákačky. Kompletný zdrojový kód je na stiahnutie pod lekciou.

V ďalšej lekcii, Skákačka v Pygame - Obrázky - Načítanie, dokončíme načítanie obrázkov pre Skákačku. Zameriame sa tiež na funkcie map() a lambda().


 

Mal si s čímkoľvek problém? Stiahni si vzorovú aplikáciu nižšie a porovnaj ju so svojím projektom, chybu tak ľahko nájdeš.

Stiahnuť

Stiahnutím nasledujúceho súboru súhlasíš s licenčnými podmienkami

Stiahnuté 10x (9.14 kB)
Aplikácia je vrátane zdrojových kódov v jazyku Python

 

Predchádzajúci článok
Skákačka v Pygame - Tvorba okna a pohyb postavy
Všetky články v sekcii
Pygame
Preskočiť článok
(neodporúčame)
Skákačka v Pygame - Obrázky - Načítanie
Článok pre vás napísala Lucie Flídrová
Avatar
Užívateľské hodnotenie:
Ešte nikto nehodnotil, buď prvý!
Autor se věnuje pygame a pythonu obecně.
Aktivity