Labas, Pasauli!

Erdvėlaivio piešimas

VP
Vilius P.
2024-03-06

Šioje pamokoje, žaidimo lange piešime pagrindinius žaidimo objektus. Eisime nuo pavyzdino kodo į mūsų žaidimo kūrimą.

Pradžia

Susikurkime naują aplankalą pavadinimu src (source). Ten susikurkime failą spacecraft.py, kuriame talpinsime kodą susijusį su erdvėlaiviu. Parašykime erdvėlaivio klasės griaučius:

.py
src/spacecraft.py
import pygame

class Spacecraft: 
    def __init__(self):
        pass
        
    def draw(self, screen: pygame.Surface):
        pass

Erdvėlaivį piešime iš linijų, gal vėliau išbandysime ir kitokį paišymo būdą. Erdvėlaivis bus figūra panaši į trikampį. Įvairiuose mūsų kuriamo žaidimo klonuose vyrauja įvairios erdvėlaivių variacijos, pvz.:

Erdvėlaivio pavyzdys nr.1

Erdvėlaivio pavyzdys nr.2

Erdvėlaivio pavyzdys nr.3

Prieš pradedant piešti erdvėlaivį, turime išmokti naują sąvoką ir naują metodą - paviršius (arba surface) ir surface.blit()

Paviršius ir blit()

Anksčiau, analizuodami dokumentacijoje esančius pavyzdžius, mes apskritimus paišėme rašydami:

.py
asteroids.py
screen = pygame.display.set_mode((1280, 720))
player_pos = pygame.Vector2(screen.get_width() / 2, screen.get_height() / 2)
pygame.draw.circle(screen, "red", player_pos, 40)

Šiuo atveju turėjome lyg didelę, nustatyto dydžio drobę ant kurios paišėme apskritimą ir jį judinome. Kai žaidimas tampa vis komplikuotesnis, dėl vis daugėjančių objektų ir jų modifikacijų, efektų (screen = pygame.display.set_mode((1280, 720))), toks piešimo būdas tampa neefektyvus:

  • Atmintis ir našumas: didesni paviršiai sunaudoja daugiau atminties ir gali būti reiklesni grafiniam procesoriui, ypač kai jais manipuliuojama ar atnaujinama. Naudodami mažesnius paviršius galite optimizuoti atminties naudojimą ir našumą, atnaujindami ar perpiešdami tik tai, kas būtina;
  • Moduliarumas ir pakartotinis panaudojimas: mažesni paviršiai gali būti perpanaudojami. Tai leidžia juos lengviau valdyti, animuoti ir panaudoti skirtingose žaidimo ar programos vietose.
  • Specialieji efektai: Specialieji efektai naudojami tik mažiems paviršiams, nedarant poveikio visam ekranui. Pavyzdžiui, tam tikram žaidimo objektui galite iškraipyti arba pritaikyti filtrą. Tai daryti mažesniame paviršiuje yra efektyviau ir lengviau valdoma.
  • ir kt...

Šias teorines priežastis turėtų būti sunku įsisavinti, bet per patirtį, tai turėtų tapti suprantama. Toliau vis daugiau naudosime paviršius ir pajausime pieišimo tiesiai ant ekrano ribas.

Išbandykime pasinaudoti paviršiais ir nupiešti tą patį apskritimą. Tam panaudosime kodą esantį asteroids.py faile. Prieš while running: sukurkime paviršių circle_surface 80 ilgio ir pločio:

.py
asteroids.py
# ...
player_pos = pygame.Vector2(screen.get_width() / 2, screen.get_height() / 2)

speed_indicator = SpeedIndicator((screen.get_width()-20, 20), initial_speed_level=0)

circle_surface = pygame.Surface((80, 80))

while running:
    # poll for events
    # pygame.QUIT event means the user clicked X to close your window
    for event in pygame.event.get():
        # ...

Toliau iš karto po circle_surface, nustatysime šio paviršiaus spalvą (surface.fill() dokumentacija)

.py
asteroids.py
# ...
circle_surface = pygame.Surface((80, 80))
circle_surface.fill("yellow")
# ...

O dabar ištrinkime senąjį apskritimo piešimą ir įdėkime paviršiaus perkėlimą į ekraną su screen.blit() metodu (surface.blit()). Įrašykime screen.blit(circle_surface, (0, 0)) eilutę vietoje apskritimo paišymo eilutės. Jeigu atlikote viską gerai, turėjote pamatyti tokį vaizdą:

Geltonas paviršius perkeltas ant ekrano paviršiaus

Kol kas dar ne apskritimas. Paviršius yra keturkampė figūra, jos spalvą nustatėme geltona, o ją perkėleme į ekraną, kad jos kampas būtų ties taške. Pakeiskite (0, 0) reikšmę į kitą ir pamatysite, kaip keičiasi šio paviršiaus koordinatės. Dabar šitame paviršiuje nupieškime apskritimą.

.py
asteroids.py
# ...
circle_surface = pygame.Surface((80, 80))
circle_surface.fill("yellow")
pygame.draw.circle(circle_surface, "red", (circle_surface.get_width()/2, circle_surface.get_width()/2), 40)
# ...

Apskritimas paviršiuje

Dabar perkelkime paviršių į centrą. Šiuo atveju pats apskritimas nėra keliamas, jis yra paviršiuje. Įsivaizduokime, kaip paveikslo ant sienos perkelimas ant kitos vietos. Tai padarysime modifikavę (0, 0) reikšmę į kitą. Tuo pačiu pašalinkime geltoną paviršiaus spalvą ir padarykime jį permatomu. Modifikuotas kodas:

.py
asteroids.py
# Example file showing a circle moving on screen
import pygame

# pygame setup
pygame.init()
screen = pygame.display.set_mode((1280, 720))
clock = pygame.time.Clock()
running = True
dt = 0

player_pos = pygame.Vector2(screen.get_width() / 2, screen.get_height() / 2)

# Create a surface with the SRCALPHA flag for per-pixel alpha
circle_surface = pygame.Surface((80, 80), pygame.SRCALPHA)
# No need to fill circle_surface with a color here since we want it transparent

pygame.draw.circle(circle_surface, "red", (circle_surface.get_width()/2, circle_surface.get_width()/2), 40)

while running:
    # poll for events
    # pygame.QUIT event means the user clicked X to close your window
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            running = False

    # fill the screen with a color to wipe away anything from last frame
    screen.fill("purple")

    circle_pos = circle_surface.get_rect(center = (player_pos))
    screen.blit(circle_surface, circle_pos)

    keys = pygame.key.get_pressed()
    if keys[pygame.K_w]:
        player_pos.y -= 300 * dt
    if keys[pygame.K_s]:
        player_pos.y += 300 * dt
    if keys[pygame.K_a]:
        player_pos.x -= 300 * dt
    if keys[pygame.K_d]:
        player_pos.x += 300 * dt

    # flip() the display to put your work on screen
    pygame.display.flip()

    # limits FPS to 60
    # dt is delta time in seconds since last frame, used for framerate-
    # independent physics.
    dt = clock.tick(60) / 1000

pygame.quit()

Eilutė circle_pos = circle_surface.get_rect(center = (player_pos)) apskaičiuoja koordinates paviršiaus viršutinio kairio kampo, kad centras būtų (šiuo atveju) koordinatėje player_pos. Pradinio žaidimo momentu kintamasis player_pos yra lango centras.

Eilutėje circle_surface = pygame.Surface((80, 80), pygame.SRCALPHA) pridėjome argumentą pygame.SRCALPHA, kuris leidžia paviršiui būti permatomu. Jeigu šio nebūtų, viskas, kas nėra apskritimas paviršiuje, būtų juoda (galite išbandyti). Kaip visada - plačiau rasite dokumentacijoje.

Erdvėlaivis

Naujas žinias apie paviršius ir jų pritaikymą, panaudokime piešdami žaidimo erdvėlaivį. Grįžkime į spacecraft.py failą. Klasėje sukurkime naują metodą def create_spacecraft_surface(), o šiame sukurkime stačiakampį paviršių 20 ilgio, 30 pločio, kurio reikšmę ir grąžinsime:

.py
/src/spacecraft.py
class Spacecraft:
    def __init__(self):
        pass

    def draw(self, screen: pygame.Surface):
        pass

    def create_spacecraft_surface():
        spacecraft_surface = pygame.Surface((21, 31))
        return spacecraft_surface

Kol neiškvietėme create_spacecraft_surface() metodo ir nepanaudojome paviršiaus, tol pokyčių nematysime. Ištrinkimime asteroids.py faile visas eilutes susijusias su apskritimo piešimu, sukurkime erdvėlaivio kintmąjį/objektą su mūsų sukurta Spacecraft() klase ir iškvieskime šio objekto draw() metodą:

.py
asteroids.py
# Example file showing a circle moving on screen
import pygame
from src.spacecraft import Spacecraft

# pygame setup
pygame.init()
screen = pygame.display.set_mode((1280, 720))
clock = pygame.time.Clock()
running = True
dt = 0

player_pos = pygame.Vector2(screen.get_width() / 2, screen.get_height() / 2)

spacecraft = Spacecraft()

while running:
    # poll for events
    # pygame.QUIT event means the user clicked X to close your window
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            running = False

    screen.fill("black")

    spacecraft.draw(screen)

    keys = pygame.key.get_pressed()
    if keys[pygame.K_w]:
        player_pos.y -= 300 * dt
    if keys[pygame.K_s]:
        player_pos.y += 300 * dt
    if keys[pygame.K_a]:
        player_pos.x -= 300 * dt
    if keys[pygame.K_d]:
        player_pos.x += 300 * dt

    # flip() the display to put your work on screen
    pygame.display.flip()

    # limits FPS to 60
    # dt is delta time in seconds since last frame, used for framerate-
    # independent physics.
    dt = clock.tick(60) / 1000

pygame.quit()

Tuo pačiu pakeitėme, ekrano spalvą į juodą su screen.fill("black") eilute, kad atitiktų mūsų žaidimą. Kol kas neturėtume matyti pokyčių, kadangi mūsų erdvėlaivio def draw() metodas nedaro nieko - yra tuščias.

Grįžkime į spacecraft.py failą. Konstruktoriuje sukurkime naują klasės ypatybę, kuriame saugosime erdvėlaivio paviršių, kuriame jis ir bus nupieštas.

.py
/src/spacecraft.py
# ....
class Spacecraft:
    def __init__(self):
        self.spacecraft_surface = Spacecraft.create_spacecraft_surface()
# ...

Kadangi mūsų anksčiau sukurtas create_spacecraft_surface() metodas, neturi self argumento, jį galima pasiekti per klasės vardą. O šio argumento nerašėme, kadangi jo mums ir nereikia, nes nenaudosime.

Kadangi mūsų sukurti erdvėlaiviai, jau turi paviršių, dabar galima jį atvaizduoti def draw() metode:

.py
# ....
class Spacecraft:
    def draw(self, screen: pygame.Surface):
        screen.blit(self.spacecraft_surface, (0, 0))
# ...

Kol kas mūsų erdvėlaivio paviršiaus kairysis viršutinis kampas tegul būna taške - koncentruokimės tik į pačią erdvėlaivio formą. Jeigu bandysime pajungti žaidimą, šiame dar nieko nepamtysite. Nors mes ir jau perkeliame mūsų paviršių į ekraną, bet jis yra permatomas.

Atliekame paskutinį žingsnį - su linijomis nubraižykime erdvėlaivį. Naudosime pygame.draw.lines() metodą (dokumentacija):

Tai su šiuo metodu piešime keletą linijų sujungtų linijų. Panaudokime šį metodą savo kode ir pateikime paviršių, ant kurio braiyžsime, linijų spalvą, kintamąjį, kuris nurodys, ar pradžios ir galo taškus reikia sujungti, taškus, per kuriuos bus brėžiamos linijos, linijų plotį:

.py
/src/spacecraft.py
import pygame

class Spacecraft:
    def __init__(self):
        self.spacecraft_surface = Spacecraft.create_spacecraft_surface()

    def draw(self, screen: pygame.Surface):
        screen.blit(self.spacecraft_surface, (0, 0))

    def create_spacecraft_surface():
        spacecraft_surface = pygame.Surface((20, 30))
        pygame.draw.lines(spacecraft_surface, (255, 255, 255), True, [
                          (0, 28), (9, 0), (18, 28), (9, 21)], width=2)
        return spacecraft_surface

Dabar paleidus aplikaciją, turėtų būti nubrėžtos baltos 4 linijos. Šiek tiek sutvarkykime kodą, iškelkime spalvos ir linijų taškų reikšmes į kintamuosius:

.py
/src/spacecraft.py
import pygame

class Spacecraft:
    # ...

    def create_spacecraft_surface():
        spacecraft_surface = pygame.Surface((20, 31))
        spacecraft_color = pygame.Color(255, 255, 255)
        lines_points = [(0, 28), (9, 0), (18, 28), (9, 21)]

        pygame.draw.lines(spacecraft_surface, spacecraft_color,
                          True, lines_points, width=2)

        return spacecraft_surface

Jeigu padarėte viską gerai, kairiajame viršutiniame kampe turite pamatyti erdvėlaivį:

Mūsų sukurtas erdvėlaivis

Užduotys

Pradinės pozicijos nustatymas

Per klasės class Spacecraft konstruktorių suteikite erdvėlaiviui poziciją self.position. Panaudokite šią poziciją, kad erdvėlaivio centras būtų šios nurodytoje pradinėje pozicijoje.

Judėjimas vertikaliai

Kadangi jau turime klasės pozicijos kintamąjį, sukurkite metodą def update(), kuris pagal klaviatūros įvykius (event), paspaudimą WS. Kodo griaučiai:

.py
/src/spacecraft.py
import pygame

class Spacecraft:
    def __init__(self):
        # ...

    def update(self, dt: float, events: list[pygame.event.Event]):
        pressed_keys = pygame.key.get_pressed()

        if pressed_keys[pygame.K_a]:
            # Handle A key press
            print("A pressed")
        if pressed_keys[pygame.K_d]:
            # Handle D key press
            print("D pressed")
        if pressed_keys[pygame.K_w]:
            # Handle W key press
            print("w pressed")
        if pressed_keys[pygame.K_s]:
            # Handle S key press
            print("S pressed")

    def draw(self, screen: pygame.Surface):
        # ...

    def create_spacecraft_surface():
        # ...

Šį metodą panaudokite asteroids.py faile. Paspaudus rodyklės klavišus, erdvėlaivio pozicija lange turėtų kisti.

Sukimasis

Kaip sukūrėte pozicijos kintamąjį erdvėlaivio klasėje, taip sukurkite ir self.rotation kintamąjį, šiame saugosime erdvėlaivio pasukimo kampą, kurį nurodysime laipsniais. Pasukimo kampą keiskite, jeigu spaudžiate AD. Naudokite (dokumentacija):

Jeigu viskas pavyko, paspaudus AD klavišus, erdvėlaivis turėtų suktis (toliau pavaizduotas erdvėlaivis išdidintas, jo paviršus nustatytas raudonas):

Erdvėlaivio judėjimas

Atkreipkite dėmesį į tai, kaip keičiasi paviršiaus dydis jį sukant.

Kuriama...
Sekanti pamoka

Sukurta
su meile

Kontaktai

2026 — Vilius P.