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: 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 funkcia move_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 funkciu set_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().


 

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ísal Adam Bureš
Avatar
Užívateľské hodnotenie:
Ešte nikto nehodnotil, buď prvý!
Učím se programovat v jazyku Pythonu a nějaký ten web development.
Aktivity