Eye-hand coordination igre

Eye-hand coordination



1.  UVOD

Kroz kolegij Asistivna tehnologija proučavali smo razne vrste asistivnih tehnologija koje ljudima s invaliditetom omogućavaju olakšano izvođenje određenih funkcija. U ovome projektu cilj nam je bio osmisliti i realizirati jednostavnu video igru koja bi bila ujedno i zabavna i korisna. Izrađena igra omogućava korisniku da vježba motoričke sposobnosti na zabavan način.

U igru su implementirane značajke poput povećavanja težine s obzirom na iskazane performanse, zahtjev na koordinaciji oko-ruka, brzo razmišljanje, prepoznavanje uzoraka i slične korisne značajke koje pomažu u izgradnji motoričkih sposobnosti. Dodani su i prikazi bodovnih rezultata tako da se korisnici mogu međusobno usporediti te uz malo natjecateljskog duha ostvariti još bolji rezultat.

Igra je u potpunosti izrađena u programskom jeziku Python verzija 2.7.11., koristeći pakete pygame i py2exe tako da su zahtjevi na računalnu podršku veoma mali te je igru moguće pokretati na velikom broju računala. Izvršavanje igre je testirano na operacijskom sustavu Windows 7 64-bit verzija, ali kako je igra izrađena u programskom jeziku Python ne bi trebalo biti problema sa izvođenjem na drugim operacijskim sustavima poput Linux distribucija ili novijih verzija Windows sustava.

 

2. PROGRAMSKI KOD

Projekt se sastoji od deset python skripti i četiri slike. Skripte „Balloon.py“, „Kitten.py“, „Sword.py“, „Settings.py“, „Instructions.py“, „Scoreboard.py“, „Engine.py“ i „Button.py“ sadrže opise istoimenih klasa koje se pozivaju iz glavne skripte naziva „balloon_ninja“. Također, zbog razvoja igre na Windows operacijskom sustavu, napravljena je skripta „pygame2exe.py“ koje stvara izvršnu datoteku radi lakšeg i intuitivnijeg pokretanja igre. Skripte će biti detaljnije objašnjene u sljedećim potpoglavljima.

Valja posebno naglasiti da su svi objekti koji su vizualno prikazani ustvari sprite, pojam koji proizlazi iz računalne grafike, odnosno dvodimenzionalne slike ili animacije koje su integrirane u veću scenu.

 

2.1. Balloon.py

Datoteka sadrži opis klase osnovnog objekta, odnosno balona koji će letjeti ekranom.

Stvara klasu class Balloon(Sprite) i stoji se od četiri funkcije.

 

def __init__(self, screen, settings): 
     Sprite.__init__(self)
     self.screen = screen 
     self.image=pygame.image.load('./images/green_balloon_50px.png').convert_alpha()
     self.image_w, self.image_h = self.image.get_size()
     self.speed = uniform(0.75,1.25)*settings.balloon_speed
     
     self.x_position = randint(self.image_w/2, self.screen.get_width()-self.image_w/2)
     max_offset = min(settings.batch_size*self.image_h, self.screen.get_height())
     self.y_position = self.screen.get_height() + self.image_h/2 + randint(0, max_offset)
     self.update_rect()

Nakon poziva klase, prvo se inicijalizira balon na sceni sa dobivenim postavkama.

Učitava se slika balona koja definira izgled i dimenzije našeg objekta, što postižemo funkcijom self.image.get_size() koja će dohvatiti dimenzije slike. Brzina kretanja balona mora biti promjenjiva zbog dinamičnosti i izazovnosti igre te se računa pomoću slučajnog broja koristeći uniformnu razdiobu i brzinu koja se nalazi u postavkama. Također postavljeno je stvaranje balona na lokaciji na dnu ekrana koja varira po x osi kako se baloni ne bi stvarali na istome mjestu. Za y os potrebno je naglasiti da se koordinata y=0 nalazi na vrhu scene, te je potrebno gornjim izračunom smjestiti stvaranje balona ispod ruba scene kako bi se dobio dojam prirodnog (postepenog) stvaranja balona.

 

def update(self, time_passed):
     self.y_position -= self.speed * time_passed self.update_rect()

 

Funkcija update osvježava lokaciju balona ovisno o prolasku vremena.

 

def blitme(self):
       draw_pos = self.image.get_rect().move(self.x_position-self.image_w/2, self.y_position-self.image_h/2)
            self.screen.blit(self.image, draw_pos) 

def update_rect(self):
       self.rect = pygame.Rect(self.x_position-self.image_w/2, self.y_position-self.image_h/2,self.image_w, self.image_h)

 

Prethodne dvije funkcije služe za iscrtavanje balona i pravokutnika oko njih koji nam služe za detekciju kolizije sa drugim objektima.

 

2.2. Kitten.py

Kitten.py ustvari poziva klasu Balloon, odnosno stvara novu vrstu balona koji će nositi sliku mačke i služiti će nam kao svojevrsni neprijatelj ove igre.

 

class Kitten(Balloon):
def __init__(self, screen, settings):
     Balloon.__init__(self, screen, settings)
self.image=pygame.image.load('./images/kitten_50px.png').convert_alpha()

 

2.3. Sword.py

Klasa koja stvara objekt mača koji ćemo bušiti balone u igri. Sadržajno je veoma slična klasi balona. Razliku koju valja naglasiti se nalazi u inicijalizaciji samoga mača.

def __init__(self, screen, scoreboard_height): (...)
     self.x_position = self.screen.get_width()/2 self.y_position = self.image_h/2 + scoreboard_height

 

U njegovom pozicioniranju u sceni, bilo je potrebno uzeti u obzir da se nakon početne inicijalizacije scena promijenila, odnosno dodana je ploča s bodovima (eng. Scoreboard), te je potrebno uzeti njenu veličinu u obzir prilikom određivanja y koordinate mača. Time osiguravamo da se mač ne stvori preko ili iznad Scoreboard-a već točno ispod. Osigurali smo dinamičnu promjenu ukoliko se postavke promjene.

Funkcije za iscrtavanje i osvježavanje pozicije mača prateći dobivenu poziciju pokazivača su veoma slične onima u klasi balona.

 

2.4. Scoreboard.py

Klasa sadrži postavke teksta, boje, dimenzije i poziciju samog scoreboard-a i njegovih elemenata, što je vidljivo u sljedećem isječku koda.

 

self.sb_height, self.sb_width = settings.scoreboard_height, self.screen.get_width()
self.rect = pygame.Rect(0,0, self.sb_width, self.sb_height) self.bg_color=(100,100,100)
self.text_color = (225,225,225)
self.font = pygame.font.SysFont('Arial', 18)
self.x_popped_position, self.y_popped_position = 20.0, 10.0 self.x_ratio_position, self.y_ratio_position = 150, 10.0 self.x_points_position, self.y_points_position = 350, 10.0 self.x_score_position, self.y_score_position =self.screen.get_width() - 200, 10.0
f.balloons_popped = 0 self.balloons_missed = 0 self.score = 0 self.popped_ratio = 1.0 self.batches_finished = 0 self.kittens_spared = 0 self.kittens_killed = 0

 

Također sadrži inicijalizirane vrijednosti svih statistika koje igrica prati. Funkcija prep_scores() priprema ispise za svaki element. Primjer jednog elementa se može vidjeti u sljedećem isječku koda:

 

self.score_string = "Bodovi: " + format(self.score, ',d')
self.score_image = self.font.render(self.score_string, True, self.text_color)

 

Funkcija set_ratio_string navedena je u nastavku;

 

def set_ratio_string(self):
     if self.popped_ratio == 1.0: 
          self.popped_ratio_string = "Stopa pucanja: 100%"
     else:
          self.popped_ratio_string = "Stopa pucanja:"+"{0:.3}%".format(self.popped_ratio*100.0)
     if self.popped_ratio < 0.95: 
          self.ratio_text_color = (255, 51, 51)
     else:
          self.ratio_text_color = self.text_color

 

a ona se brine za ispis stope uspješnosti pucanja balona. Uviđamo kako boja teksta varira: u slučaju da je stopa pucanja 100%, boja se ne mijenja, a ukoliko stopa pada ispod 95%, nakon izračuna stope pucanja dobivamo promjenu boje kako bi se upozorilo igrača da se približava stopi od 90%, ispod koje igra završava.

Na kraju funkcija iscrtava prazan scoreboard te zatim iscrtava sve elemente statistike.

Riječ „blit” se odnosi na iscrtavanje objekta na ekran.

 

2.5. Button.py

Veoma slično poput „Scoreboard.py” i drugih klasa služi nam za stvaranje objekta, odnosno gumba (eng. button) sa određenim tekstom.

 

2.6. Instructions.py

Skripta u kojoj se nalaze uputstva, odnosno skup poruka koje će se igrača uputiti u cilj same igre i način igranja. Osim inicijaliziranih poruka definirane su pozicije za ispis spomenutih elemenata.

 

2.7. Settings.py

Skripta koja sadrži postavke igre; možemo je smatrati kao opcijom postavki koju možemo pronaći u svakoj igrici gdje možemo postaviti skoro sve vizualne i igraće parametre ove igrice.

 

def _init_(self):
     self.screen_width, self.screen_height = 800, 600 self.bg_color = 200, 200, 200 self.scoreboard_height = 50
     self.button_width, self.button_height = 250, 50 self.button_bg = (0,163,0) self.button_text_color = (235,235,235)
     self.button_font, self.button_font_size = ‘Arial’, 24
     self.game_active = False
     self.min_popped_ratio = 0.9 self.games_played = 0
     self.initialize_game_parameters()

 

U prikazanoj funkciji inicijaliziramo postavke ekrana poput širine, visine, pozadinske boje i visine scoreboard-a. Iste postavke postoje za gumbe (eng. buttons). Omogućeno je postavljanje minimalne stope pucanja balona, koja je postavljena na 90%.

 

def initialize_game_parameters(self): 
     self.balloon_speed = 0.1
     self.speed_increase_factor = 1.05 self.points_per_balloon = 10
     self.batch_size = 1 self.batches_needed = 3 self.kitten_ratio = 0.10 self.kitten_score_factor = 3

 

Isječak koda prikazuje inicijalizaciju postavki igre poput brzine balona, bodovanja balona, početni broj balona koji se pojavljuju, broj potrebnih puknutih balona kako bi se broj balona povećao te vjerojatnost pojave mačića.

 

2.8. Engine.py

„Engine.py” je skripta koja je zaslužna za inicijalizaciju (vidljivo u sljedećem isječku koda) i osvježavanje cijele igre i njenih objekata. Možemo ju nazvati pogonom ove igre budući da sadrži njenu logiku.

 

def __init__(self, screen, settings, scoreboard, balloons, kittens, sword):
     self.screen = screen
     self.settings = settings
     self.scoreboard = scoreboard
     self.balloons = balloons self.kittens = kittens
     self.sword = sword

 

„Engine.py” dohvaća sve druge klase te ih inicjalizira.

 

def release_batch(self):
     for  x in range(0, self.settings.batch_size):
          self.spawn_balloon()

 

Prikazana funkcija release_batch() se bavi stvaranjem balona.

 

def check_balloons(self, time_passed): 
     for balloon in self.balloons:
          balloon.update(time_passed)
          if balloon.rect.colliderect(self.sword.rect): 
               self.pop_balloon(balloon)
               continue

          if balloon.y_position < -balloon.image_h/2 + self.settings.scoreboard_height:
               self.miss_balloon(balloon) self.spawn_balloon()
               continue

          balloon.blitme()

          if self.scoreboard.balloons_popped > 0 or self.scoreboard.balloons_missed > 0:
               self.scoreboard.popped_ratio = float(self.scoreboard.balloons_popped)/(self.scoreboard.balloons _popped + self.scoreboard.balloons_missed)
          
          if self.scoreboard.popped_ratio<self.settings.min_popped_ratio: 
               self.settings.game_active = False self.settings.games_played += 1

 

 

Prikazana funkcija se bavi provjerom kolizije, odnosno da li se balon “susreo” sa mačem i puknuo, odnosno da li balon izašao iz scene bez da je puknuo, te poziva odgovarajuće funkcije koje osvježavaju statistiku igre. Također sastoji se i od logike izračuna stope pucanja balona ovisno o puknutim odnosno promašenim balonima. Ukoliko stopa puknutih balona padne ispod zadanog minimuma igrica se zaustavlja.

def check_kittens(self, time_passed): 
     for kitten in self.kittens:
          kitten.update(time_passed)
          if kitten.rect.colliderect(self.sword.rect): 
              self.kill_kitten(kitten)
              continue

          if kitten.y_position < -kitten.image_h/2 + self.settings.scoreboard_height:
              self.spare_kitten(kitten) 
              continue
     kitten.blitme()

Slična logika provjere pucanja balona se primjenjuje na provjeru stradanja mačića.

Slijedi niz funkcija koje služe za zapis i osvježavanje statistike u slučaju određenih događaja, kao na primjer: pucanje balona, stradavanje mačića, spašavanje mačića, itd. Tu se nalazi i funkcija za osvježavanje položaja mača, stvaranje novih balona i stvaranje mačića.

 

def check_events(self, game_over_button, play_button, mouse_x, mouse_y, settings):
     for  event in pygame.event.get():
          if event.type == pygame.QUIT:
               pygame.quit()
               sys.exit()
          if settings.game_active == False:
               if event.type == pygame.MOUSEBUTTONDOWN:
                    if play_button.rect.collidepoint(mouse_x, mouse_y): 
                         del self.balloons[:] 
                         self.scoreboard.initialize_stats() 
                         self.settings.initialize_game_parameters() 
                         self.settings.game_active = True
                    
                    if game_over_button.rect.collidepoint(mouse_x, mouse_y): 
                         pygame.quit()
                         sys.exit()

 

Ujedno i posljednja funkcija ove skripte, služi za provjeru stanja igre, odnosno da li je pritisnut gumb za izlaz, za početak nove igre ili da li je došlo do kraja igre.

 

2.9. balloon_ninja.py

Iz ove skripte možemo pokrenuti igru, ona spaja sve druge elemente igre u jednu funkcionalnu cjelinu. Za početak importa sve druge skripte kako bi mogla koristiti sve stvorene objekte i elemente. Stvaramo funkciju run_game() u kojoj će se odvijati naša glavna petlja. Zatim, kao što je vidljivo u sljedećem isječku koda, inicijaliziramo sve postavke koje su potrebne za pokretanje same igre.

 

def run_game():
     settings = Settings() pygame.init()
     screen = pygame.display.set_mode( (settings.screen_width, settings.screen_height), 0, 32)
     pygame.display.set_caption("Balloon ninja") clock = pygame.time.Clock()
     scoreboard = Scoreboard(screen, settings)
     play_button=Button(screen,settings.screen_width/2-settings.button_width/2,settings.screen_height/2-settings.button_height/2, settings, "Igraj")
     game_over_button = Button(screen, play_button.x_position, play_button.y_position-2*settings.button_height,settings,"Kraj igre")
     instructions = Instructions(screen, settings)

 

Zatim stvaramo, prije glavne petlje, liste za spremanje balona i mačića, stvaramo naš mač i engine sa pristupom postavkama igre.

 

balloons = [] kittens = []
sword = Sword(screen, settings.scoreboard_height)
engine = Engine(screen, settings, scoreboard, balloons, kittens, sword)

 

Glavna petlja:

 

while True:
     time_passed = clock.tick(50)
     mouse_x, mouse_y = pygame.mouse.get_pos()[0], pygame.mouse.get_pos()[1] 
     engine.check_events(game_over_button, play_button, mouse_x, mouse_y, settings)
     screen.fill(settings.bg_color)
     if settings.game_active: 
          engine.update_sword(mouse_x, mouse_y) 
          engine.check_balloons(time_passed) 
          engine.check_kittens(time_passed)

     if len(balloons) == 0:
          if scoreboard.balloons_popped > 0:
               settings.balloon_speed *= settings.speed_increase_factor 
               settings.kitten_ratio *= settings.speed_increase_factor
               settings.points_per_balloon = int(round(settings.points_per_balloon * settings.speed_increase_factor)) 
               scoreboard.batches_finished += 1

     if scoreboard.batches_finished % settings.batches_needed == 0 and scoreboard.batches_finished > 0:
          settings.batch_size += 1 engine.release_batch()
     else:
          play_button.blitme()

     if settings.games_played < 3: 
          instructions.blitme()
     if settings.games_played > 0: 
          game_over_button.blitme()
     
     scoreboard.blitme()
     pygame.display.flip()
     run_game()

Prvo pomičemo vrijeme u igri, čitamo poziciju pokazivača i provjeravamo događaje. Iscrtava se prazna scena prije iscrtavanja objekata na nju. Ako je igra aktivna, osvježavamo položaj mača te provjeravamo postoji li puknutih ili promašenih balona, ako je slučaj da su svi baloni nestali (pucanjem ili promašivanjem) stvaramo novi set balona. Ukoliko ovo nije prvi balon povećamo brzinu balona i bodove po balonu. Ako je igrač ispunio uvjet, odnosno dovoljno balona je nestalo, povećava se set balona koji se stvaraju.

U drugom slučaju da igra nije aktivna prikazuje se gumb za početak igre i uputstva za igru. Naime, uputstva za igru će se prestati pojavljivati nakon par igranja. Ukoliko je igra već odigrana onda će se prikazati gumb za izlaz iz igre i gumb za ponovno igranje. Naposljetku iscrtavamo scoreboard i ponovno iscrtavamo cijelu scenu naredbom pygame.display.flip().

Igra se pokreće pozivom funkcije run_game().

 

2.10. pygame2exe

Skripta, kao što je ranije spomenuto, služi za kreiranje dist direktorija koji će sadržavati izvršne datoteke, sve direktorije. Sve knjižnice će biti spojene u jednu izvršnu datoteku koju ćemo koristiti za pokretanje igrice.

 

3. KORISNIČKA UPUTSTVA

 

Za uspješno pokretanje igre potrebno je instalirati:

 

Instaliraju se prethodno nabrojanim redoslijedom. Prilikom instalacije programa Python u 3. koraku potrebno je označiti opciju „add Python to system PATH“, kako bi nam pokretanje bilo jednostavnije. Nakon instalacije paketa, vrijeme je za otvaranje arhivirane datoteke sa programskim kodom igre. Preporučuje se da se sadržaj balloon_ninja.rar datoteke otpakira na lokalni disk kako bi nam pokretanje bilo lakše te da se navedeni instalacijski postupak provodi na računu sa administratorskim ovlastima.

Pozivamo command prompt otvarajući Start meni i zatim u tražilicu upišemo „cmd“ te stisnemo enter ili pritiskom na pronađeni program pokrenemo command prompt. U prozor command prompt-a navigiramo do datoteke u kojoj nam se nalaze prije otpakirane datoteke.

Rad u command prompt-u:

  • pomoću komande „cd C:\balloon_ninja“ navigiramo do prije otpakirane datoteke
  • zatim, upišemo „python pygame2exe.py“
  • nakon uspješnog izvršenja potrebno je stisnuti bilo koju tipku kako bi program završio

 

Posljednji korak je kopiranje datoteke „images“ u datoteku „dist“ koje su se izradile u direktoriju u kojem je igra otpakirana. Igra se pokreće dvostrukim klikom na „balloon-_ninja.exe“ ikonu.

Nakon pokretanja igre dočekati će nas prozor sa instrukcijama za igranje i određenim pravilima igre. Igramo pomičući kursor (računalni miš) lijevo desno unutar prozora igre. Cilj igre je probušiti što više balona pritom izbjegavajući mačiće, ukoliko probušimo balon dobivamo bodove, a ukoliko dotaknemo mačiće gubimo bodove. Ukoliko stopa pucanja balona padne ispod 90% igra prestaje te je moguće ponovno igrati ili izaći iz igre. Prolaskom vremena težina igre se povećava, pojavljuje se sve veći broj balona i mačića koji se, kako vrijeme u igri prolazi, ubrzano kreću. Na slikama u nastavku teksta prikazan je izgled igre u raznim fazama igranja.

 

111.jpg

Slika 1. Početni prikaz igre

 

112.jpg

Slika 2. Zaslon prilikom igranja

 

113.jpg

Slika 3. Zaslon na kraju igre

 

4. ZAKLJUČAK

Prilikom izrade ovog projekta razmišljali smo o osobama sa invaliditetom te kako bi im mogli pomoći u rehabilitaciji kroz računalnu igru. Smisao ove igre nije da se vidi najbolji igrač već da se kroz zabavu razviju motoričke sposobnosti i poboljša koordinacija. Smatramo da je kroz slične interaktivne i zabavne načine razvijanja sposobnosti i učenja lakše doprijeti do korisnika te da će rezultati biti bolji ukoliko se nekakva terapija provodi na zabavan način.

Postoje velike mogućnosti za daljnji razvoj ovoga projekta, u vidu omogućavanja interakcije ne samo pokretom računalnog miša već korištenjem raznih naprednijih sučelja poput BCI (brain-computer interface), eye-gaze, Kinect ili pak korištenjem nekih od specijaliziranih tipkala.

 

LITERATURA

http://www.py2exe.org/  http://www.py2exe.org/index.cgi/WorkingWithVariousPackagesAndModules

http://www.pygame.org/docs/

http://www.pygame.org/docs/tut/tom/MakeGames.html

https://docs.python.org/2/install/index.html  https://docs.python.org/2/using/index.html  https://docs.python.org/2/tutorial/index.html

 

Igru možete preuzeti OVDJE.

 


Denis Ibrahimagić, Nikola Lacković, Ivan Matković, Edo Terzić