Vydělávej až 160.000 Kč měsíčně! Akreditované rekvalifikační kurzy s garancí práce od 0 Kč. Více informací.
Hledáme nové posily do ITnetwork týmu. Podívej se na volné pozice a přidej se do nejagilnější firmy na trhu - Více informací.

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

 

Předchozí článek
Skákačka v Pygame - Logging
Všechny články v sekci
Pygame - Tvorba her v Pythonu
Přeskočit článek
(nedoporučujeme)
Skákačka v Pygame - Obrázky - Načítání
Článek pro vás napsala Lucie Flídrová
Avatar
Uživatelské hodnocení:
4 hlasů
Autor se věnuje pygame a pythonu obecně.
Aktivity