Erdvėlaivio piešimas
Š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:
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:
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:
# ...
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)
# ...
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ą.
# ...
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:
# 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:
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ą:
# 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.
# ....
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:
# ....
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):
pygame.draw.lines():draw multiple contiguous straight line segments
lines(surface, color, closed, points) -> Rectlines(surface, color, closed, points, width=1) -> RectDraws a sequence of contiguous straight lines on the given surface. There are no endcaps or miter joints. For thick lines the ends are squared off. Drawing thick lines with sharp corners can have undesired looking results.
Parameters:
surface (Surface) -- surface to draw on color (Color or int or tuple(int, int, int, int)) -- color to draw with, the alpha value is optional if using a tuple (RGBA) closed (bool) -- if True an additional line segment is drawn between the first and last points in the points sequence points (tuple(coordinate) or list(coordinate)) -- a sequence of 2 or more (x, y) coordinates, where each coordinate in the sequence must be a tuple/list/pygame.math.Vector2a 2-Dimensional Vector of 2 ints/floats and adjacent coordinates will be connected by a line segment, e.g. for the points (x1, y1), (x2, y2), (x3, y3) a line segment will be drawn from (x1, y1) to (x2, y2) and from (x2, y2) to (x3, y3), additionally if the closed parameter is True another line segment will be drawn from (x3, y3) to (x1, y1) width (int) -- (optional) used for line thickness if width >= 1, used for line thickness (default is 1) if width < 1, nothing will be drawnReturns:
a rect bounding the changed pixels, if nothing is drawn the bounding rect's position will be the position of the first point in the points parameter (float values will be truncated) and its width and height will be 0- Pygame 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į:
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:
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:
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):
pygame.transform.rotate():rotate an image.
rotate(surface, angle) -> Surface
Unfiltered counterclockwise rotation. The angle argument represents degrees and can be any floating point value. Negative angle amounts will rotate clockwise.
Unless rotating by 90 degree increments, the image will be padded larger to hold the new size. If the image has pixel alphas, the padded area will be transparent. Otherwise pygame will pick a color that matches the Surface colorkey or the topleft pixel value.
- Pygame 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.