Sündmused¶
Mängu mängimiseks peab saama kasutaja (mängija) anda korraldusi mängule. Sellised korraldused (näiteks klaviatuuri või hiire klahvi vajutamised) on sündmused (events). Mängutsüklis me tegelikult juba kasutasime sündmuste kontrolli selleks, et akna sulgemisel lõpetataks mäng ära.
PyGame hoiab sündmusi järjekorras (queue). Sündmused lisatakse järjekorda nende tekkimise järjekorras. Sündmusi saab lugeda selles järjekorras, nagu need on järjekorda lisatud. Ehk järjekorrast võetakse järgmisena kõige varasemana salvestatud sündmus (FIFO - first in first out). Sündmuste järjekorral on limiit. Kui järjekord saab täis, siis uusi sündmusi sinna ei lisata (viga selle kohta ka ei anta). Seega mõistlik oleks, kui mängutsükli igal sammul loetakse järjekorrast sündmused. Vastasel juhul, kui sündmused kuhjuvad, võib operatsioonisüsteem pidada rakendust (mängu) hangunuks ning programmi töö lõpetatakse.
Selleks, et kõiki sündmusi lugeda, saab kasutada pygame.event.get()
funktsiooniga. See tagastab järjendi Event
tüüpi objektidest. get()
funktsioonile saab ette anda ka sündmuste tüübi, milliseid sündmusi soovime saada. get()
meetod eemaldab kõik tagastatud sündmused sündmuste järjekorrast. Soovitatav on igal tsükli sammul sündmusi lugeda. Kui mingil põhjusel otseselt sündmusi pole vaja kasutada, siis on soovitatav kasutada funktsiooni pygame.event.pump()
. See tagab kõikide sündmuste korrektse käsitlemise ning sündmuste järjekord tehakse tühjaks.
Igal sündmusel on tüüp. PyGame toetab väga palju erinevaid tüüpe. Aga vaatame siinkohal peamisi tüüpe, millega mängu juhtida. Need on klaviatuuri ja hiire sündmused.
Klaviatuuri sündmused¶
Klaviatuurilt saab lugeda klahvivajutusi, täpsemalt klahvi allavajutamise (KEYDOWN
) ja klahvi lahtilaskmise (KEYUP
) sündmusi. Seda tüüpi sündmustel on kaasas ka info klahvi enda kohta (atribuut key
).
Vaatame näidet, kus ring liigub ekraanil ringi vastavalt noolte vajutustele:
import pygame
pygame.init()
# create a window
screen = pygame.display.set_mode((800, 600))
running = True
player_x = 100
player_y = 100
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
# closing window finishes the game loop
running = False
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_UP:
player_y -= 10
if event.key == pygame.K_DOWN:
player_y += 10
if event.key == pygame.K_LEFT:
player_x -= 10
if event.key == pygame.K_RIGHT:
player_x += 10
# fill screen with white color
screen.fill((255, 255, 255))
# draw a blue circle at player's position (x, y) with radius 50
pygame.draw.circle(screen, (0, 0, 255), (player_x, player_y), 50)
# everything drawn will be visible
pygame.display.flip()
Loome alguses kaks muutujat, mis tähistavad mängija x ja y koordinaate (vastavalt player_x
ja player_y
muutujad). Anname neile alguses väärtused 100 ja 100. Ja nüüd vastavalt klahvivajutustele hakkame mängijat liigutama. Hetkel selles näites on mängija lihtsalt üks ring. Aga see võib vabalt olla mingi pilt või ristkülik vms.
Tsükkel, kus käiakse läbi kõik sündmused, omab eraldi kontrolli selle kohta, kas sündmuse tüüp on pygame.KEYDOWN
. Kui on, siis teame, et loetud sündmus event
on klahvivajutus. Edasi kontrollime, mis on selle sündmuse klahv (event.key
). Võrdluseks kasutame PyGame’is eeldefineeritud klahvide konstante, mille saame kätte pygame moodulist ning mille nimi algab K_
. Näiteks noolteklahvide konstandid on:
pygame.K_UP
- ülesnoolpygame.K_DOWN
- allanoolpygame.K_LEFT
- vasakule noolpygame.K_RIGHT
- paremale nool
Vastavalt klahvivajutusele muudame mängija koordinaati. Näiteks kui vajutatakse ülesklahvi, siis mängija y-koordinaat väheneb 10 võrra.
Harjutus
Siin näites saab ekraanilt välja liikuda. Võid proovida koodi muuta nii, et kui koordinaadi väärtus läheb liiga suureks või väikeseks, siis seda enam ei muudeta. Selliselt ei saa pall akna raamides välja liikuda.
Antud kood töötab ja saab mängijat liigutada. Aga kui paned tähele, siis klahvi all hoides midagi ei toimu. Asi on selles, et KEYDOWN
sündmus registreeritakse vaid sellel hetkel, kui klahv alla vajutatakse. Kui seda all hoitakse, siis ühtegi sündmust ei registreerida. Üks võimalus selle „lahendamiseks“ on see, et kui klahv vajutatakse alla, määratakse mängijale liikumiskiirus. Kui klahv lastakse lahti (KEYUP
), siis määratakse kiiruseks 0 (ehk mängija jääb seisma).
Lisame praegu kiiruse vaid x-suunalisele liikumisele:
import pygame
pygame.init()
# create a window
screen = pygame.display.set_mode((800, 600))
running = True
player_x = 100
player_y = 100
speed_x = 0
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
# closing window finishes the game loop
running = False
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_LEFT:
speed_x = -1
if event.key == pygame.K_RIGHT:
speed_x = 1
if event.type == pygame.KEYUP:
speed_x = 0
if speed_x != 0:
player_x += speed_x
# fill screen with white color
screen.fill((255, 255, 255))
# draw a blue circle at player's position (x, y) with radius 50
pygame.draw.circle(screen, (0, 0, 255), (player_x, player_y), 50)
# everything drawn will be visible
pygame.display.flip()
Lisasime ühe täiendava muutuja speed_x
, mis algselt on 0. Kui toimub klahvivajutus (KEYDOWN
), siis määratakse speed_x
väärtus. Kui klahv lastakse lahti (KEYUP
), siis määratakse speed_x
väärtus nulliks.
Mõtle
Miks pole KEYUP
tingimuse all vaja eraldi kontrollida, kas lahti lasti vasakule või paremale nool?
Harjutus
Proovi eelnevat koodi muuta nii, et ka y-suunaliselt toimub sarnane liikumine (kuni klahvi hoitakse all, siis mängija liigub; kui klahv lastakse lahti, siis mängija jääb seisma).
Klahvivajutusi on võimalik saada ka pygame.key.get_pressed()
funktsiooniga. See tagastab sõnastiku, kus võti on klahvi kood (pygame.K_
konstantide seast) ja väärtus on tõeväärtus, kas seda klahvi on vajutatud.
Seda kasutades saab ümber kirjutada mängija liikumise vastavalt klahvivajutusele järgnevalt:
keys = pygame.key.get_pressed()
if keys[pygame.K_UP]:
player_y -= 1
if keys[pygame.K_DOWN]:
player_y += 1
if keys[pygame.K_LEFT]:
player_x -= 1
if keys[pygame.K_RIGHT]:
player_x += 1
keys
muutujasse pannakse sõnastik, kus klahvidele on vastavuses kas True
või False
. Kui klahv on hetkel all, siis vastava võtme puhul väärtus on True
.
Siin näites on muudetud sammu pikkuseks 1 (algses näites oli see 10) piksel, kuna klahvivajutus kestab tavaliselt mõne hetke ning kogu selle aja vältel on sõnastikus vastava klahvi väärtus tõene ja seetõttu toimub ka liikumine.
Kuigi selline kirjapilt on üldiselt lühem ja võib-olla ka loetavam, siis peab arvestama, et sellise lahenduse puhul ei ole sündmuste järjekord teada. Kui näiteks mängija peab midagi kirjutama, siis vajutuste järjekord ei pruugi siit selge olla (vajutab väga väikese vahega kaks klahvi alla, siis selle tsükli sammu sees võivad mõlemad olla sõnastikus tõese väärtusega, aga kumb enne vajutati pole teada).
Harjutus
Proovi eelnevat koodi muuta nii, et kasutad taustapilti ning kujundi asemel omakorda pilti. See tähendab, et screen.fill()
asemel tuleks kasutada blit()
funktsiooni. Mõistlik oleks proovida asendada vaid see osa ekraanist, kus midagi on muutunud (kust tuleb „kustutada“ vana objekti pilt).
Hiire sündmused¶
Hiirelt saab sarnaselt klaviatuurile lugeda hiire klahvivajutusi (MOUSEBUTTONDOWN
ja MOUSEBUTTONUP
). Lisaks saab lugeda hiire liikumise sündmust MOUSEMOTION
. Klahvisündmuste objektis on olemas positsioon (pos
), kus hiir oli klahvivajutuse hetkel, ja klahv (button
), mida vajutati.
Vaatame näidet, kus ekraanil on pall (ring). Kui selle peal klikkida, liigub pall uuele positsioonile:
import pygame
import random
pygame.init()
# create a window
screen = pygame.display.set_mode((800, 600))
running = True
player_x = 100
player_y = 100
player_radius = 50
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
# closing window finishes the game loop
running = False
if event.type == pygame.MOUSEBUTTONDOWN and event.button == pygame.BUTTON_LEFT:
# let's use vector here
vector = pygame.Vector2(event.pos[0] - player_x, event.pos[1] - player_y)
if vector.length() <= player_radius:
player_x = random.randint(0 + player_radius, screen.get_width() - player_radius)
player_y = random.randint(0 + player_radius, screen.get_height() - player_radius)
# fill screen with white color
screen.fill((255, 255, 255))
# draw a blue circle at player's position (x, y) with radius 50
pygame.draw.circle(screen, (0, 0, 255), (player_x, player_y), player_radius)
# everything drawn will be visible
pygame.display.flip()
Lisaks mängija asukohale (player_x
ja player_y
) loome muutuja player_radius
, mis tähistab ringi raadiust. Kuna kasutame seda raadiust mitmes kohas, siis on mõistlik see muutujaks määrata.
Sündmuste tsüklis kontrollime, kas sündmuse event
tüüp on pygame.MOUSEBUTTONDOWN
ja vajutatud klahv on hiire vasak klahv (event.button == pygame.BUTTON_LEFT
). Nüüd on meil teada, et toimus hiire vasaku klahvi vajutus. Me peame kontrollima, kas klikk toimus ringi sees või mitte. Selleks arvutame hiire positsiooni kauguse ringi keskpunktist.
Pildil on kujutatud ring koos keskpunktiga (sinine täpp). Punane täpp on positsioon, kus toimus hiire klikk. Leiame x- ja y-teljel kahe punkti vahe (vastavalt dx
ja dy
). Nendest kahes väärtusest saame luua vektori (joonisel roheline joon v
). Kui selle vektori pikkus on väiksem või võrdne ringi raadiusega (joonisel R
), siis järelikult toimus klikk ringi sees. Kui kaugus on raadiusest suurem, siis toimus klikk ringist väljas.
dx
arvutamiseks leiame sündmuse (hiirekliki) positsiooni x-koordinaadi (event.pos[0]
) väärtuse ja ringi keskpunkti x-koordinaadi (player_x
) väärtuse vahe (event.pos[0] - player_x
). See vahe võib ka negatiivne olla. Aga vektori pikkuse jaoks ei ole vahet. Sarnaselt arvutama dy
vahe. Me võiks vektori asemel ka näiteks Pythagorase teoreemi kasutada, et v
pikkus arvutada (kuna dx
ja dy
vaheline nurk on täisnurk, v
on hüpotenuus), aga vektoriga on seda mugavam teha.
Harjutus
Proovi koodi muuta nii, et kui arvutatakse uus ringi asukoht, siis see ei tohiks sattuda sellisesse asukohta, et ta juba jääb hiire positsiooni alla.
Siin võib mõelda nii, et uute koordinaatide arvutmine võiks toimuda lõputus tsüklis. Seda tsüklit korratakse seni, kuni uued koordinaadid ei too ringi hiire alla. Selleks saab peale koordinaatide arvutamist leida nende kaugus hiire positsioonist (näiteks kasutades vektorit). Kui kaugus on piisav, siis lõpetatakse lõputu tsükkel ära. Muul juhul korratakse tegevust uue tsükli sammu sees.
Täpsemat lugemist sündmuste kohta: https://www.pygame.org/docs/ref/event.html