Arvutiteaduse instituut
  1. Kursused
  2. 2025/26 sügis
  3. Programmeerimine (LTAT.03.001)
EN
Logi sisse

Programmeerimine 2025/26 sügis

  • Üldinfo
  • 1. Muutuja ja avaldis
  • 2. Tingimuslause
  • 3. Funktsioon
  • 4. Korduslause
  • 5. Sõned. Lihtsam failitöötlus
  • 6. Kontrolltöö 1
  • 7. Järjend
  • 8. Järjend 2
  • 9. Kahekordne tsükkel. Failitöötlus
  • 10. Andmestruktuurid
  • 11. Andmestruktuurid 2
  • 12. Kontrolltöö 2
  • 13. Objektorienteeritud programmeerimine
  • 14. Objektorienteeritud programmeerimine 2
  • 15. Rekursioon
  • 16. Kordamine. Projektide esitlused
  • Viiteid
  • Silmaringimaterjalid

Arhiveeritud esialgsed silmaringimaterjalid

  1. Standardteek ja moodulid
  2. Rakendusliidesed
  3. Regulaaravaldised
  4. Andmebaasid
  5. Veebirakenduste loomine
  6. Objektorienteeritud programmeerimine
  7. Graafiliste mängude loomine
  8. Keerulisemad Pythoni võimalused
  9. Võistlusprogrammeerimine
  10. Veebisisu parsimine
  11. Pilditöötlus
  12. Pythoni siseehitus
  13. Helitöötlus
  14. Kodeerimine ja krüpteerimine
  15. NumPy
  • Materjalid

7. Graafiliste mängude loomine

Mängude loomine on väga suur valdkond, kus programmeerijad saavad oma võimeid kasutada. Mänge on muidugi võimalik ka Pythoniga luua. Selle jaoks on loodud erinevaid teeke: Pygame, pyglet, Cocos2d, Panda3D ja palju muid. Siin peatükis vaatame, kuidas luua üks lihtne mäng Pygame'iga.

Pygame on populaarne Pythoni mängude loomise teek, mis lubab lihtsasti tekitada graafikat ja heli ning saada kasutajalt sisendit mitmel erineval moel. See on hea viis, kuidas alustada oma mänguarendaja karjääri. See pole ka ainult alustamiseks: päris palju mänge on Pygame'iga tehtud.

Ettevalmistus

Enne jätkamist tuleb paigaldada moodul pygame. Seda saab teha käsuga pip install pygame. Katsetamiseks kirjuta Pythonis import pygame. Kui erindit ei visata, on moodul paigaldatud. Kui ei ole kindel, kuidas mooduleid installida, siis on õpikus moodulite paigaldamise juhised.

Et peatükist aru saada, peab olema läbitud õpiku esimene osa. Soovitatav on läbida peatükk "Objektorienteeritud programmeerimine" läbimine, aga see ei ole vajalik.

Mängu akna kuvamine

Kui moodul pygame importida, siis saab Pygame'i käivitada käsuga pygame.init(). Enne seda peab veel määrama akna suuruse käsuga pygame.display.set_mode(), mille parameetriks on ennik ekraani laiusega ja kõrgusega pikslites. Pygame'i akent saab sulgeda käsuga pygame.quit(). Teeme akna laiusega 640 pikslit ja kõrgusega 480 pikslit.

import pygame

pygame.display.set_mode((640, 480))
pygame.init()

pygame.quit()

Kui see programm käivitada, siis ilmub korraks must aken suurusega 640 korda 480 pikslit ja läheb kinni.

Pygame'i loodud aken töötab eraldiseisvalt Pythoni programmile. See tähendab, et pärast pygame.init() käsku kuvatakse eraldi aken, kuhu saab hakata joonistama ning programmi kood jookseb edasi. Aken läheb kinni alles siis, kui programm jõuab sulgemiskäsuni pygame.quit(). Kui enne sulgemiskäsku jookseks programm 3 sekundit, siis aken on püsti 3 sekundit.

Kui enne sulgemiskäsku jooksutada lõpmatu tsükkel, siis aken ei lähegi kinni ning ristist sulgemine ka ei aita. Programmi töö peab muudmoodi lõpetama. Saame selles lõputus while-tsüklis kontrollida, kas kasutaja on vajutanud sulgemisnupule ja sel juhul saame tsükli lõpetada.

import pygame

pygame.display.set_mode((640, 480))
pygame.init()

while True:
    sisend = pygame.event.poll()
    if sisend.type == pygame.QUIT:
        break

pygame.quit()

Kui see programm käivitada ja ristile vajutada, siis aken läheb kinni. Oleme kirjutanud väga algelise programmi, millega kasutaja saab suhelda! Vaatame hiljem, kuidas tsüklisse muud funktsionaalsust lisada ja teistmoodi kasutajalt sisendit saada. Kõigepealt proovime aknasse midagi joonistada.

Joonistamine

Siiamaani oleme loonud musta akna, mis on graafilisest mängust tsipa kaugel. Järgmisena proovime selle akna millegi värvilisega täita.

Värvid

Saame täita selle akna mingi värviga, mida saab esindada erinevatel viisidel, aga Pygame'is on tavaks kasutada RGB mudelit, mis koosneb punasest, rohelisest ja sinisest värvi kogusest, 0 kuni 255. Neid esindatakse kolmeliikmeliste ennikutena. Näiteks on must värv (0, 0, 0), valge värv (255, 255, 255), punane värv (255, 0, 0), akvamariinsinine (128, 255, 212) jne. Lihtne viis enda värvi valimiseks on guugeldada "colour picker" ja võtta valitud värvi RGB väärtused.

Täidame akna enda soovitud värviga. Selleks peame ekraanisuuruse muutmise funktsiooni tagastuse salvestama muutujasse ekraan. See on muutuja, kuhu hakkame edaspidi kõiki joonistusi lisama. Ekraani saab täita meetodiga fill(), mille parameetriks läheb värv. Tehtud muudatused tuleb salvestada käsuga pygame.display.flip().

import pygame

ekraan = pygame.display.set_mode((640, 480))
pygame.init()

ekraan.fill((128, 255, 212))
pygame.display.flip()

while True:
    sisend = pygame.event.poll()
    if sisend.type == pygame.QUIT:
        break

pygame.quit()

Programm käivitab nüüd akna, mis pole enam must, vaid akvamariinsinine. Edusammud!

Kujundid

Proovime lõpuks aknasse midagi lisada. Pygame võimaldab joonistada erinevaid kujundeid. Ristkülikut saab ekraanile joonistada funktsiooniga pygame.draw.rect(). See nõuab kolme parameetrit:

  1. pind, mille peale joonistada (meie puhul muutuja ekraan)
  2. värv, millega ristkülik täita (värve juba oskame)
  3. asukoha nelik: alguspunkti x-koordinaat, alguspunkti y-koordinaat, ristküliku laius, ristküliku kõrgus (kõik pikslites)

Koordinaatide lugemist alustatakse Pygame'is ülevalt vasakult ehk (0, 0) on üleval vasakul, (640, 0) on üleval paremal, (0, 480) on all vasakul ja (640, 480) on all paremal. Kõik vahepealsed on kuskil ekraani sees.

Joonistame roosa ristküliku, mis algab meie akna keskpunktist ehk (320, 240) ning on 200 piksli laiune ja 100 piksli kõrgune.

import pygame

ekraan = pygame.display.set_mode((640, 480))
pygame.init()

ekraan.fill((128, 255, 212))

pygame.draw.rect(ekraan, (255, 128, 171), (320, 240, 200, 100))
pygame.display.flip()

while True:
    sisend = pygame.event.poll()
    if sisend.type == pygame.QUIT:
        break

pygame.quit()

Pygame lubab ka teisi kujundeid joonistada. Loe dokumentatsioonist erinevaid võimalusi ja proovi ekraanile joonistada ring, kolmnurk ja joon.

Pildid

Sarnaselt kujunditele saab mängu lisada pilte. Kõigepealt peame leidma pildi, mida mängus kasutada. Neid saab muidugi ise luua, aga Internetis on suur hulk vabalt kasutatavaid materjale olemas: itch.io/game-assets, opengameart.org jpm. Kui neid kasutada mängudes, on vaja autorile viidata. Siin peatükis kasutame rotti, mis on võetud siit, ja juustu, mis on võetud siit.

Et joonistada pilt aknasse, tuleb see kõigepealt sisse laadida pildiobjektina funktsiooniga pygame.image.load(). Sulgudesse läheb failitee. Praegu paigutame pildifailid programmifailiga samasse kausta, aga suurema mängu puhul võiks need olla eraldi kaustas.

rott = pygame.image.load("rott.png")

Muutuja rott on nüüd pind (Surface), mida saab joonistada ekraanile. Pindade võimaluste kohta saab lugeda Pygame'i dokumentatsioonist.

Saame roti paigutada ekraanile meetodiga blit(). Sulgudesse läheb pildifail ning koordinaadipaariga asukoht. Paigutame loodud roti objekti ekraani koordinaatidele (100, 200).

ekraan.blit(rott, (100, 200))

Roti laiuse ja kõrguse eraldi saame kätte meetoditega get_width() ja get_height(). Proovi paigutada rott täpselt ekraani keskele, kasutades seda meetodit, et arvutada paigutuse koordinaadid. (320, 240) ei ole õige siin vastus.

>>> rott.get_width()
116
>>> rott.get_height()
72

Tekst

Mängudes on tavaliselt teksti, mis näitab vestluseid, õpetusi, skoori, menüüd jpm. Pygame võimaldab teksti kuvada samamoodi nagu piltegi.

Kõigepealt tuleb valida font, milles teksti kuvada ja teha sellest objekt funktsiooniga pygame.font.SysFont(). Esimeseks parameetriks läheb fondi nimi sõnena ja teiseks läheb fondi suurus. Kui fondi nimeks valida None, siis valib Pygame vaikimisi fondi. Funktsiooniga Font() saab ka fondi võtta failist. See on kasulik, kui fonti ei pruugi olla teiste arvutis.

font = pygame.font.SysFont("Comic Sans MS", 24)
font = pygame.font.SysFont(None, 32)
font = pygame.font.Font("Futura-Bold-Italic.ttf", 40)

Kasutades fonti saame meetodiga render() luua pinna objekti. Sulgudesse läheb tekst sõnena, tõeväärtus True ning teksti värv. Tõeväärtuse kohta võib lugeda dokumentatsioonist. Loodud objekt on põhimõtteliselt pilt ehk seda saab meetodiga blit() paigutada ekraanile. Loome teksti "Tere, maailm!" musta fondiga ning paigutame selle koordinaatidele (40, 40).

tekst = font.render("Tere, maailm!", True, (0, 0, 0))
ekraan.blit(tekst, (40, 40))

Kogu kood siiamaani:

import pygame

ekraan = pygame.display.set_mode((640, 480))
pygame.init()

ekraan.fill((128, 255, 212))

rott = pygame.image.load("rott.png")
ekraan.blit(rott, (100, 200))

font = pygame.font.SysFont(None, 40)
tekst = font.render("Tere, maailm!", True, (0, 0, 0))
ekraan.blit(tekst, (40, 40))

pygame.display.flip()

while True:
    sisend = pygame.event.poll()
    if sisend.type == pygame.QUIT:
        break

pygame.quit()

Pinna objekte saab moondada: pööramine, peegeldamine, suurendamine jne. Uuri dokumentatsiooni: pygame.transform.

Harjuta ekraanile joonistamist. Leia vastused küsimustele:

  • Kas ühte pinda on võimalik mitu korda ekraanile joonistada?
  • Kas pinda saab paigutada ekraanist väljapoole?
  • Mis juhtub, kui joonistada pilt enne ja täita ekraan mingi värviga hiljem?
  • Mis käskudega saab kätte peegelpildi ja pööramised?
  • Kuidas võiks toimuda animeerimine?

Animeerimine

Siiani oleme joonistanud ekraanile ühe kaadri. Et saada paigutatud objekte liikuma, ei saa me lihtsalt muuta nende asukohti: me peame iga kaadri algusest peale joonistama. See võib kõlada ebaintuitiivsena, aga Pygame töötabki just niimoodi.

Jätame alles roti ekraanile, aga liigutame tema joonistamise meie põhitsüklisse, mis programmi käimas hoiab.

import pygame

ekraan = pygame.display.set_mode((640, 480))
pygame.init()

rott = pygame.image.load("rott.png")

while True:
    sisend = pygame.event.poll()
    if sisend.type == pygame.QUIT:
        break
    
    ekraan.fill((128, 255, 212))
    ekraan.blit(rott, (100, 200))
    pygame.display.flip()

pygame.quit()

Kui programm käivitada, siis midagi väga muutunud ei ole. Rott on ikka paigal, ta lihtsalt joonistatakse igal tsükli käigul uuesti. See on kasulik, sest nüüd me saame näiteks muuta igal käigul roti asukohta.

Salvestame programmi alguses roti koordinaadid mingitesse muutujatesse, näiteks rott_x ja rott_y, algul olgu need 100 ja 200. Nüüd igal tsükli korral suurendame rott_x muutujat ühe võrra ja käivitame programmi uuesti.

rott_x = 100
rott_y = 200

...
    
    rott_x += 1

Kui see programm käivitada, siis rott tõepoolest liigub väga kiiresti paremale ja jääbki liikuma. Proovi täitmiskäsk ekraan.fill() tsüklist ära võtta ja ainult programmi algusesse panna. Mis juhtub?

Saime roti liikuma, aga mitte väga hästi. Probleem on praegu selles, et tsükli käik ei ole kuidagi ajaliselt piiratud: see kestab täpselt nii kaua, kui Pythonil läheb selle jooksutamiseks. See on esiteks väga lühike aeg: peaksime roti koordinaati liigutama palju vähem (nt 0.1 pikslit) iga kord. Teiseks: see võib erineda erinevates arvutites ja isegi samas arvutis erinevatel aegadel. Tahame, et mäng jookseks kõikides arvutites samamoodi igal hetkel.

Selle probleemi parandamiseks kasutame Pygame'i kella. Loome programmi alguses uue kella objekti:

kell = pygame.time.Clock()

Iga tsükli alguses peaks kell tiksuma ehk kutsume välja meetodi tick(). Sulgudesse läheb arv, mitu kaadrit peaks sekundis kuvama. Valime 60, sest enamik kuvaritest värskenduvad 60 korda sekundis. Selle meetodi käivitamisel ootab programm umbes 1/60 sekundit.

dt = kell.tick(60)

See meetod tagastab, mitu millisekundit on kulunud eelmisest kella tiksumisest. 60 kaadri sekundis puhul see on enamasti 16 või 17. Seda on väga kasulik teada, sest kui arvuti näiteks kiilub korraks kinni ja mõnda kaadrit joonistatakse natuke kauem, siis meile on see teada ja me saame seda oma mängus arvestada nii, et mängu tegevus ei kiiluks kinni, vaid liiguks vastavalt sellele, kui palju aega on kulunud. Kui kaader peaks kestma 16 millisekundit ja kestab hoopis 32, siis mängu tegevus peaks sellel kaadril liikuma 2 korda kiiremini. Selle abil tagame ka selle, et mäng jookseb kõikidel arvutitel sama kiiresti.

Seega peame igal liikumisel arvestama kulunud aega (muutujat dt). Roti liikumisel korrutame sellega läbi.

rott_x += 0.25 * dt

0.25 on siin roti kiirus, mis väljendab mitu pikslit me tahame, et rott liiguks millisekundis. 0.25 pikslit millisekundis on teisendatult 250 pikslit sekundis. Mitme sekundiga jõuab rott ekraanist välja, kui ekraani laius on 640 ja roti algne x-koordinaat on 100?

Kogu kood:

import pygame

ekraan = pygame.display.set_mode((640, 480))
pygame.init()

kell = pygame.time.Clock()

rott = pygame.image.load("rott.png")
rott_x = 100
rott_y = 200

while True:
    dt = kell.tick(60)
    
    sisend = pygame.event.poll()
    if sisend.type == pygame.QUIT:
        break
    
    ekraan.fill((128, 255, 212))
    ekraan.blit(rott, (rott_x, rott_y))
    pygame.display.flip()
    
    rott_x += 0.25 * dt
    
pygame.quit()

Proovime veel animatsiooni huvitavamaks teha. Kui rott jõuab teisele poole, siis paneme ta ümber pöörama. Selleks salvestame roti kiiruse eraldi muutujasse.

roti_kiirus = 0.25
...
...
...
    rott_x += roti_kiirus * dt

Kui roti parem ots jõuab paremalt poolt ekraanist välja, siis lihtsalt teeme tema kiiruse negatiivseks. Teeme sama, kui roti vasak ots jõuab vasakult poolt ekraanist välja.

if (rott_x + rott.get_width()) > 640 or rott_x < 0:
    roti_kiirus *= -1

Kui roti kiirus on negatiivne, siis tema x-koordinaat väheneb igas tsükli käigus ja ta liigub vasakule.

Pöörame ka roti ümber. Rohkem infot dokumentatsioonis.

    rott = pygame.transform.flip(rott, True, False)

Kogu kood:

import pygame

ekraan = pygame.display.set_mode((640, 480))
pygame.init()

kell = pygame.time.Clock()

rott = pygame.image.load("rott.png")
rott_x = 100
rott_y = 200

roti_kiirus = 0.25

while True:
    dt = kell.tick(60)
    
    sisend = pygame.event.poll()
    if sisend.type == pygame.QUIT:
        break
    
    if rott_x > (640 - rott.get_width()) or rott_x < 0:
        roti_kiirus *= -1
        rott = pygame.transform.flip(rott, True, False)
    
    ekraan.fill((128, 255, 212))
    ekraan.blit(rott, (rott_x, rott_y))
    pygame.display.flip()
    
    rott_x += roti_kiirus * dt
    
pygame.quit()

Rott nüüd põrkab lõputult akna parema ja vasaku ääre vahel. Proovi panna rott ringi liikuma, nagu vana DVD-mängija ekraanisäästja: kui jõuab ääreni, siis põrkab. Vihje: liikuval kahemõõtmelisel esemel saab olla 2 kiirust: horisontaalkiirus ja vertikaalkiirus.

Kasutajasisend

Panime roti liikuma, aga tavaliselt saab mänge mõjutada kasutaja. Paneme roti liikuma vastavalt klahvivajutustele!

Siiani oleme tegelikult mingit kasutajasisendit küsinud: kui ristile vajutada, siis mäng läheb kinni. Senist sisendipüüdmist peame natuke muutma. Kahe tsükli käigu vahel võib toimuda mitu sisendit, aga me oleme iga kord ainult ühe küsinud - peaksime kõike kontrollima. Kõik sisendid saab kätte funktsiooniga pygame.event.get(). Kuna need peab tsükliga läbi käima, siis break viiks meid ainult sellest tsüklist välja. Asendame selle sys.exit() väljakutsega, mis lõpetab programmi töö.

for sisend in pygame.event.get():
    if sisend.type == pygame.QUIT:
        sys.exit()

Samamoodi, nagu kontrollime ristile vajutamist, saame kontrollida ka klaviatuurivajutusi. Selle jaoks tuleb kontrollida, kas sisendi tüüp on pygame.KEYDOWN. Kui on, siis saame vaadata selle klahvi väärtust. Nooleklahvid on näiteks pygame.K_UP, pygame.K_DOWN, pygame.K_LEFT, pygame.K_RIGHT. Vastavalt nendele võiksime rotti liigutada. Erinevaid nuppe saab kätte dokumentatsioonist.

    for sisend in pygame.event.get():
        if sisend.type == pygame.QUIT:
            sys.exit()
        elif sisend.type == pygame.KEYDOWN:
            if sisend.key == pygame.K_UP:
                    rott_y -= roti_kiirus * dt
            elif sisend.key == pygame.K_DOWN:
                rott_y += roti_kiirus * dt
            elif sisend.key == pygame.K_LEFT:
                rott_x -= roti_kiirus * dt
            elif sisend.key == pygame.K_RIGHT:
                rott_x += roti_kiirus * dt

Kui nupule vajutada, siis rott tõepoolest liigub… natuke. Kui tahame, et nuppu all hoides rott jääkski liikuma, siis võiksime koordinaatide muutmise asemel hoopis roti kiiruseid muuta ning igal tsükli käigul rotti liigutada vastavalt kiirustele.

roti_kiirus = 0.25
roti_x_kiirus = 0
roti_y_kiirus = 0

...
            if sisend.key == pygame.K_UP:
                roti_x_kiirus = 0
                roti_y_kiirus = -roti_kiirus
            elif sisend.key == pygame.K_DOWN:
                roti_x_kiirus = 0
                roti_y_kiirus = roti_kiirus
            elif sisend.key == pygame.K_LEFT:
                roti_x_kiirus = -roti_kiirus
                roti_y_kiirus = 0
            elif sisend.key == pygame.K_RIGHT:
                roti_x_kiirus = roti_kiirus
                roti_y_kiirus = 0
...

    rott_x += roti_x_kiirus * dt
    rott_y += roti_y_kiirus * dt

Kui nüüd nupule vajutada, siis rott jääbki liikuma. See võib mõnes mängus olla soovitud, näiteks ussimängus, aga vaatame, kuidas nupust lahti laskmisel rott seisma jääks. Saame vaadata, et kui sisenditüüp on pygame.KEYUP ehk klahvist lastakse lahti, siis nullime kiirused vastavalt klahvile, mis lahti lasti. Kogu kood siiani:

    for sisend in pygame.event.get():
        if sisend.type == pygame.QUIT:
            sys.exit()
        elif sisend.type == pygame.KEYDOWN:
            if sisend.key == pygame.K_UP:
                roti_y_kiirus = -roti_kiirus
            elif sisend.key == pygame.K_DOWN:
                roti_y_kiirus = roti_kiirus
            elif sisend.key == pygame.K_LEFT:
                roti_x_kiirus = -roti_kiirus
            elif sisend.key == pygame.K_RIGHT:
                roti_x_kiirus = roti_kiirus
        elif sisend.type == pygame.KEYUP:
            if sisend.key == pygame.K_UP or sisend.key == pygame.K_DOWN:
                roti_y_kiirus = 0
            elif sisend.key == pygame.K_LEFT or sisend.key == pygame.K_RIGHT:
                roti_x_kiirus = 0

Selle programmiga on veel probleeme. Kui hoida all paremat noolt, seejärel hoida all vasakut noolt ja lasta parem nool lahti, siis rott jääb seisma, kuigi vasak nool on veel all. Saame igal hetkel kontrollida vajutatud klahve funktsiooniga pygame.key.get_pressed(). See on sõnastik, mille võtmeteks on klahvid ja väärtusteks on tõeväärtused, kas nuppu hoitakse all või mitte.

import pygame
import sys

ekraan = pygame.display.set_mode((640, 480))
pygame.init()

kell = pygame.time.Clock()

rott = pygame.image.load("rott.png")
rott_x = 100
rott_y = 200

roti_kiirus = 0.25
roti_x_kiirus = 0
roti_y_kiirus = 0

while True:
    dt = kell.tick(60)
    
    for sisend in pygame.event.get():
        if sisend.type == pygame.QUIT:
            sys.exit()

    vajutatud = pygame.key.get_pressed()
    if vajutatud[pygame.K_UP]:
        roti_y_kiirus = -roti_kiirus
    elif vajutatud[pygame.K_DOWN]:
        roti_y_kiirus = roti_kiirus
    else:
        roti_y_kiirus = 0
    
    if vajutatud[pygame.K_LEFT]:
        roti_x_kiirus = -roti_kiirus
    elif vajutatud[pygame.K_RIGHT]:
        roti_x_kiirus = roti_kiirus
    else:
        roti_x_kiirus = 0
            
    rott_x += roti_x_kiirus * dt
    rott_y += roti_y_kiirus * dt
    
    ekraan.fill((128, 255, 212))
    ekraan.blit(rott, (rott_x, rott_y))
    pygame.display.flip()

Mõningaid probleeme veel leidub. Kui hoida all nii üles- kui allanoole klahv, siis liigub rott alati ülespoole, sest seda kontrollitakse esimesena. Roti kiirus peaks igas suunas olema võrdne, aga diagonaalis liikudes on ta kiirem. Praeguseks on aga liikumine piisavalt hea, liigume edasi.

Juust

Mis mäng on mäng ilma eesmärgita? Lisame mängu juustu, mille rott peab kätte saama.

Laadi alla juust.png ülevalt ja tuleta meelde, kuidas laadida see mängu ning joonistada see. Juustul võiks olla ka koordinaadid muutujatena, sarnaselt rotile.

Kui proovida juustu peale liikuda, siis midagi ei juhtu. Paneme kokkupuutumisel juustu juhuslikku asukohta hüppama!

Kuidas me kontrollime kokkupuudet? Saaksime manuaalselt vaadata, kui roti koordinaadid on juustu koordinaatide lähedal, aga see on palju tööd ning Pygame lubab seda palju lihtsamalt teha.

Kui me paigutame pinna ekraanile meetodiga blit(), siis see tegelikult tagastab meile ühe ristküliku objekti (Rect). See on kasulik objekt, millelt saab kätte pildi koordinaate: keskpunkt, nurgad, servade keskpunktid, laius, kõrgus jne. Muuseas saab sellega kontrollida kahe ristküliku kokkupuutumist üksteisega, kasutades colliderect() meetodit. Liigutame kokkupuutumisel juustu uuele juhuslikule asukohale.

juust_rect = ekraan.blit(juust, (juust_x, juust_y))
rott_rect = ekraan.blit(rott, (rott_x, rott_y))
   
if rott_rect.colliderect(juust_rect):
    juust_x = random.randint(0, 640 - juust.get_width())
    juust_y = random.randint(0, 480 - juust.get_height())

Lõplik kood kommentaaridega:

import pygame
import sys
import random
# Loome akna ning käivitame Pygame'i
ekraan = pygame.display.set_mode((640, 480))
pygame.init()
# Alustame Pygame'i kella
kell = pygame.time.Clock()
# Laadime sisse roti pildi
rott = pygame.image.load("rott.png")
# Algväärtustame roti asukoha
rott_x = 100
rott_y = 200
# Kui kiiresti rott liigub klahvivajutustel
roti_kiirus = 0.25
# Kui kiiresti rott parajasti liigub
roti_x_kiirus = 0
roti_y_kiirus = 0
# Laadime sisse juustu pildi
juust = pygame.image.load("juust.png")
# Algväärtustame roti asukoha
juust_x = 400
juust_y = 200
# Põhitsükkel, mis hoiab akent lahti
while True:
    # Tiksume kella (ootame umbes 1/60 sekundit)
    # Salvestame kulunud aja eelmisest tiksumisest millisekundites
    dt = kell.tick(60)
    
    # Käime läbi kõik sisendid eelmisest tiksumisest saati
    for sisend in pygame.event.get():
        # Kui on soovitud aken sulgeda, sulgeme programmi
        if sisend.type == pygame.QUIT:
            sys.exit()
    # Võtame kõik parajasti allavajutatud klahvid
    vajutatud = pygame.key.get_pressed()
    # Kui üles- või allanool on all, määrame roti vertikaalkiiruse 
    if vajutatud[pygame.K_UP]:
        roti_y_kiirus = -roti_kiirus
    elif vajutatud[pygame.K_DOWN]:
        roti_y_kiirus = roti_kiirus
    else:
        # Vastasel juhul peatame roti vertikaalselt
        roti_y_kiirus = 0
  
    # Kui vasak- või paremnool on all, määrame roti horisontaalkiiruse
    if vajutatud[pygame.K_LEFT]:
        roti_x_kiirus = -roti_kiirus
    elif vajutatud[pygame.K_RIGHT]:
        roti_x_kiirus = roti_kiirus
    else:
        # Vastasel juhul peatame roti horisontaalselt
        roti_x_kiirus = 0
    # Liigutame roti asukohta vastavalt kiirustele
    # Korrutame liikumist kulunud ajaga, et rott liiguks sama
    # palju sõltumatult vahele jäänud kaadritest
    rott_x += roti_x_kiirus * dt
    rott_y += roti_y_kiirus * dt
    
    # Täidame akna akvamariinsinise värviga
    # Sellega kustutame ära kõik, mis siiamaani ekraanil on
    ekraan.fill((128, 255, 212))
    
    # Joonistame pildid nende koordinaatidele
    # Saame nende ristküliku objektid
    juust_rect = ekraan.blit(juust, (juust_x, juust_y))
    rott_rect = ekraan.blit(rott, (rott_x, rott_y))
    
    # Kui juustu ja hiire ristkülikud puutuvad kokku
    if rott_rect.colliderect(juust_rect):
        # Liigutame juustu juhuslikele koordinaatidele
        juust_x = random.randint(0, 640 - juust.get_width())
        juust_y = random.randint(0, 480 - juust.get_height())
    
    # Värskendame ekraani
    pygame.display.flip()

Kui Pythonis klasside kasutamine on selge, siis võiks igale objektile teha enda klassid, millel on isendiväljad suurustest ja kiirustest ning meetodid liigutamise ja muude tegevuste jaoks: https://stackoverflow.com/a/35642955/12123296 

Kokkuvõte

Vaatasime, kuidas Pygame töötab, joonistasime kujundeid, pilte ja teksti, panime pildi liikuma, liigutasime ise pilti klahvivajutustega, kontrollisime kahe eseme kokkupuudet ja saime lõpuks valmis midagi, mida saab enam-vähem mänguks nimetada. Graafiliste mängude loomise alustalad on nüüd all, aga väga palju jäi käsitlemata.

Et edasi õppida, pakub Pygame erinevaid õpetusi oma veebilehel. Tasub ka uurida Pygame'i projektide kogumikku. Sinna on postitatud palju erinevaid Pygame'iga tehtud mänge koos lähtekoodidega (Releases all), mida tasub uurida.

Ülikoolis saab arvutigraafikaga ja mängudega jätkata ainetes "Arvutigraafika" (MTAT.03.015) ning "Arvutimängude loomine ja disain" (MTAT.03.263).

Enesekontrolliküsimused

Ülesanded

1. Täienda peatüki jooksul loodud mängu skooriga, mis loeb juustu kättesaamise arvu. Kuva see skoor kuskil mänguaknas tekstina.

2. Kirjuta Pygame'iga animatsioon, kus mingi pilt või kujund liigub sinusoidi trajektoori või ringjoone järgi.

3. Kirjuta ise Pygame'iga üks mäng, millel on mingi eesmärk. Graafika joonistada ise või võtta Internetist. Viimase puhul tuleb lähtekoodis kommentaarina allikale viidata. Mõned ideed:

  • Labürindist pääsemise mäng. Labürindi võib näiteks võtta tekstifailist, sarnaselt pykkarile.
  • Lihtne Flappy Birdi kloon. Gravitatsiooni asemel võib vabalt ise kontrollida üles-alla liikumist.
  • Esemed lendavad mängija suunas, kes peab eest ära hüppama, et ellu jääda.
  • Arvutiteaduse instituut
  • Loodus- ja täppisteaduste valdkond
  • Tartu Ülikool
Tehniliste probleemide või küsimuste korral kirjuta:

Kursuse sisu ja korralduslike küsimustega pöörduge kursuse korraldajate poole.
Õppematerjalide varalised autoriõigused kuuluvad Tartu Ülikoolile. Õppematerjalide kasutamine on lubatud autoriõiguse seaduses ettenähtud teose vaba kasutamise eesmärkidel ja tingimustel. Õppematerjalide kasutamisel on kasutaja kohustatud viitama õppematerjalide autorile.
Õppematerjalide kasutamine muudel eesmärkidel on lubatud ainult Tartu Ülikooli eelneval kirjalikul nõusolekul.
Courses’i keskkonna kasutustingimused