8. diel - Skákačka v Pygame: Vytváranie herného enginu
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.
Zatiaľ máme postavičku, ktorá sa hýbe zľava doprava. V tejto lekcii si pridáme do hry herný engine, ktorý sa nám bude starať o to, čo sa deje v pozadí hry.
GameObject
Začneme tým, že si už budeme nejako štruktúrovať zložky. Vytvoríme si zložku nazvanúengine
, v ktorej si vytvoríme súbor
game_object
a presunieme si tam súbor game.py z minulej
lekcie. Teraz si otvoríme súbor game_object
a naimportujeme si
doň knižnice, ktoré budeme potrebovať. Najprv importujeme knižnicou
math
, a potom knižnicu typing
. Z tej si naimportujeme
potrebné funkcie. Teraz keď máme všetky potrebné knižnice, vytvoríme si
triedu GameObject
, ktorá bude dediť z
pygame.sprite.Sprite
:
import math from typing import Dict, Set, Optional, Tuple, Type, Union, Callable, List import pygame import engine Key = int class GameObject(pygame.sprite.Sprite): def __init__(self): super().__init__() # základní proměnné využívané ve všech objektech self.hspeed: float = 0 self.vspeed: float = 0 self.gravity: float = 0 self.z_index = 0 self.collision_side_mask: Optional[Tuple[int, int, int, int]] = None self._solids: Set[Type["GameObject"]] = set() self._draw_boundaries = False self.boundaries_color = (255, 255, 255) self.rect = pygame.Rect((0, 0), (0, 0)) self.rect_prev = self.rect.copy() self._colliding_now: Dict[Union[type, "GameObject"], pygame.sprite.Group] = {} self._self_group = pygame.sprite.GroupSingle(self) self._game: Optional[engine.Game] = None
V tejto triede si vytvoríme metódu __init__
. V tejto metóde
budeme mať niekoľko premenných. V budúcnosti do tejto metódy budeme
vkladať poslucháčov a premenných, ktoré budú kontrolovať, či dochádza k
nejakej kolízii postavičky s objektom v hre.
Funkcie
Pohybové funkcie
Teraz si začneme vytvárať funkcie, ktoré budú v tejto triede. Prvá bude funkciamove_x
, ktorá berie argument delta
. Funkcia
nám umožní pohybovať postavičkou po osi x v dĺžke
argumentu delta. Následne si k nej pridáme ďalšiu funkciu, zvanú
move_y
. Tá tiež berie argument delta a bude fungovať
rovnako, akurát umožní pohybovať sa po osi y. Môžeme si
všimnúť, že v oboch týchto funkciách používame self.rect
s
funkciou move_ip
, ktorá nám pohybuje s objektom. Funguje to na
rovnakom princípe ako funkcia move
, s tým rozdielom, že táto
funkcia sa pohybuje v mieste, a nie voľne ako jej náprotivok:
def move_x(self, delta: float) -> None: self.rect.move_ip(delta, 0) def move_y(self, delta: float) -> None: self.rect.move_ip(0, delta)
Pomocné funkcie
Ešte než sa vrhneme na ďalšie funkcie, budeme si musieť vložiť isté „pomocné“ funkcie:@property def pygame(self): return pygame @property def screen(self) -> Optional["pygame.Surface"]: if self.game is None: return None return self.game.screen @property def game(self): return self._game def get_image(self): return self._image
Môžeme si tu všimnúť dekorátor @property
. Viac o ňom
nájdete v OOP
lekcii o getteroch a setteroch.
Zavedieme si ešte pár esenciálnych funkcií, ktoré nám poslúžia na nastavovanie a volanie premenných. Prvý budeme vracať rýchlosť obrázku. Táto rýchlosť bude aplikovaná na obrázky nepriateľov, postavičky alebo pozadia:
@property def image_speed(self) -> int: return self._image_speed
Ďalej budeme potrebovať nastavovať rýchlosť obrázku:
@image_speed.setter def image_speed(self, val: int) -> None: self._image_speed = val img = self.get_image() if img is not None: img.speed = self.image_speed
Teraz prejdeme k smerom. Najprv budeme znovu smer vypisovať, a potom ho budeme nastavovať:
@property def direction(self) -> float: return self.game.count_angle(self.hspeed, -self.vspeed) @direction.setter def direction(self, value: float) -> None: speed = self.speed direction = math.radians(value) self.hspeed = speed * math.cos(direction) self.vspeed = speed * -math.sin(direction)
A teraz Prejdeme k rýchlosti. Tentokrát k rýchlosti postáv v hre:
@property def speed(self) -> float: return math.sqrt(self.vspeed ** 2 + self.hspeed ** 2) @speed.setter def speed(self, value: float): direction = math.radians(self.direction) self.hspeed = value * math.cos(direction) self.vspeed = value * -math.sin(direction)
Ďalej budeme počítať smery v hre. Pred tým ako si definujeme túto
funkciu musíme si definovať inú funkciu nad triedou GameObject
.
Táto funkcia sa bude nazývať sign
a bude vyzerať
nasledovne:
def sign(x: float) -> int: return 1 if x > 0 else -1 if x < 0 else 0
Po tejto definícii prejdeme k funkcii __count_direction
, ktorá
vychádza z matematiky z jednotkovej kružnice:
@staticmethod def __count_direction(value: float) -> float: if abs(value) >= 360: value %= 360 * sign(value) elif value < 0: value = 360 + value return value
Zvyšok funkcií je už samovysvetľujúci:
@property def width(self) -> float: return self.rect.width @width.setter def width(self, val: float) -> None: self.rect.width = val @property def height(self) -> float: return self.rect.height @height.setter def height(self, val: float) -> None: self.rect.height = val @property def x(self) -> float: return self.rect.x @x.setter def x(self, val: float) -> None: self.rect.x = val @property def y(self) -> float: return self.rect.y @y.setter def y(self, val: float) -> None: self.rect.y = val
Nastavenie rýchlosti a smeru
Teraz si vytvoríme funkciuset_speed
. Táto funkcia berie dva
argumenty, hsped
a vspeed
. Funkcia
set_speed
bude nastavovať rýchlosť všetkým objektom v hre, či
už to bude naša postavička, alebo jej nepriatelia. Môže sa nám zdať
zbytočné definovať takto premenné, ale verte, že to zbytočné nie je.
Tento spôsob úpravy premennej nám zaistí, že budeme mať vždy desatinné
číslo na stotiny, bez zaokrúhlenia.
Premenná self.speed
vychádza z Pytagrove vety. Tiež si
môžeme všimnúť, že v premennej self.direction
voláme funkciu
z game.py
, ktorou je count_angle
. Túto funkciu
nemáme ešte definovanú, ale za chvíľu si ju pridáme do súboru. Ako
posledný si ešte definujeme funkciu set_direction
. Táto funkcia
berie argument value
a nastavuje ho k smerovej premennej:
def set_speed(self, hspeed: float, vspeed: float) -> None: hspeed = int(hspeed * 100) / 100 vspeed = int(vspeed * 100) / 100 self.speed = math.sqrt(hspeed ** 2 + vspeed ** 2) self.direction = self.game.count_angle(hspeed, vspeed) def set_direction(self, value: float) -> None: self.direction = value
Teraz si otvoríme náš súbor game.py
Do triedy, ktorú máme
v súbore, si pridáme funkciu count_angle
. Táto funkcia berie
argumenty dx
a dy
:
@staticmethod def count_angle(dx: float, dy: float) -> float: if dx == 0 and dy == 0: angle = 0 elif dx == 0: angle = 90 if dy > 0 else 270 elif dy == 0: angle = 0 if dx > 0 else 180 else: if dx > 0 and dy > 0: angle = math.degrees(math.atan(abs(dy) / abs(dx))) elif dx < 0 < dy: angle = math.degrees(math.atan(abs(dx) / abs(dy))) angle += 90 elif dx < 0 and dy < 0: angle = math.degrees(math.atan(abs(dy) / abs(dx))) angle += 180 else: angle = math.degrees(math.atan(abs(dx) / abs(dy))) angle += 270 return angle
Funkcia nám vypočítava uhol, ktorý bude fungovať ako vzdialenosť. Uhol
budeme počítať za pomoci goniometrických funkcií. Pomocou funkcie
math.degrees
si výsledok z radiánov prevedieme na stupne, ktoré
sú nám určite príjemnejšie ako radiány 😃. Pokiaľ niekto nevie čo
znamená dekorátor @ @staticmethod
, viac sa o ňom dozvie v
OOP lekcii o statike.
Teraz si pridáme ďalšie metódy, ktoré pracujú s uhlami. Prvé si
vytvoríme triednu metódu count_angle_points
, ktorá berie päť
parametrov. Prvý parameter je odkaz na triedu. Viac sa o ňom opäť dozviete
v
OOP lekcii o statike. Ďalšie parametre sú x1, x2, y1, y2
,
ktoré sú všetky premenné s dátovým typom float:
@classmethod def count_angle_points(cls, x1: float, y1: float, x2: float, y2: float) -> float: dx = x2 - x1 dy = y2 - y1 return cls.count_angle(dx, dy)
Posledná metóda, ktorú si ukážeme v tejto lekcii, je
count_angle_rects
. Táto metóda berie tri parametre. Prvá je
opäť odkaz na triedu, a zvyšné dva sú rect1, rect2
. Metóda
nám vypočíta uhly obdĺžnikov, alebo objektov v hre:
@classmethod def count_angle_rects(cls, rect1, rect2): return cls.count_angle_points(rect1.centerx, rect1.centery, rect2.centerx, rect2.centery)
To už je pre tento diel skutočne všetko. Vytvorili sme si tu z väčšiny celý herný engine. Na funkčnosti hry sa nám zatiaľ nič nezmenilo, ale uvidíte, že v budúcnosti tieto funkcie budú skutočne kľúčové.
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()
.