Breakout
Breakout je arkádová hra vytvorená v sedemdesiatych rokoch spoločnosťou Atari. V nej hráč ovláda malú plošinu, ktorá odráža pohybujúcu sa loptičku. Cieľom hry je zničiť loptičkou všetky tehličky nachádzajúce sa v hornej časti obrazovky. Lopta sa nesmie dostať pod úroveň plošiny; v opačnom prípade stráca hráč život.
Neskoršia verzia hry s novými prvkami je známa pod názvom Arkanoid.
Tkinter
Tkinter je grafická knižnica pre tvorbu desktopových aplikácií. Je súčasťou štandardnej distribúcie jazyka Python. Ide o multiplatformovú knižnicu, ktorá beží na Linuxe, systéme Windows a Mac OS. Z technického hľadiska je Tkinter obálkou nad grafickou knižnicou Tk, ktorá bola vytvorená pre jazyk Tcl. Viac informácií o Tkinter si môžete naštudovať na oficiálnej stránke dokumentácie k Tk, tutoriály na ZetCode, alebo na tkinter.programujte.com.
Canvas
Komponenta Canvas
je grafickým plátnom, ktoré slúži v Tkinteri na prácu s grafikou. V prípade tejto komponenty pracujeme s grafikou na vyššej úrovni; t.j. nereagujeme na udalosti prekreslenia, ale tvoríme grafické objekty priamo volaním metód plátna. Pomocou komponenty Canvas
môžeme vytvárať jednoduché hry, kresliť grafy, alebo vytvárať vlastné komponenty.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 | #!/usr/bin/env python3 # -*- coding: utf-8 -*- """ This script creates a simple Breakout game clone. Author: Jan Bodnar Last modified: January 2016 Website: www.zetcode.com """ from tkinter import Tk, Canvas, BOTH, ALL from tkinter.ttk import Frame import colorsys BAR_WIDTH = 60 BAR_HEIGHT = 1 BAR_INIT_X = 170 BAR_Y = 250 BOTTOM_EDGE = 270 RIGHT_EDGE = 400 BALL_SIZE = 3 NEAR_BAR_Y = BAR_Y - BALL_SIZE - BAR_HEIGHT BALL_INIT_X = 200 BALL_INIT_Y = 150 INIT_DELAY = 800 DELAY = 30 class Example(Frame): def __init__( self , parent): Frame.__init__( self , parent) self .parent = parent self .initVariables() self .initBoard() self .after(INIT_DELAY, self .onTimer) def initVariables( self ): self .bricks = [] self .ballvx = self .ballvy = 3 self .ball_x = BALL_INIT_X self .ball_y = BALL_INIT_Y self .inGame = True self .bar_x = BAR_INIT_X self .bar_y = BAR_Y self .lives = 3 def initBoard( self ): self .parent.title( "Breakout" ) self .pack(fill = BOTH, expand = True ) self .parent.config(cursor = "none" ) self .canvas = Canvas( self , width = 400 , height = 300 , background = "#000000" ) self .canvas.bind( "<Motion>" , self .onMotion) self .bar = self .canvas.create_line( self .bar_x, self .bar_y, self .bar_x + BAR_WIDTH, self .bar_y, fill = "#ffffff" ) self .lives_item = self .canvas.create_text( 15 , 270 , text = self .lives, fill = "white" ) k = 0.0 for j in range ( 10 ): for i in range ( 10 ): c = colorsys.hsv_to_rgb(k, 0.5 , 0.4 ) d = hex ( int (c[ 0 ] * 256 )<< 16 | int (c[ 1 ] * 256 )<< 8 | int (c[ 2 ] * 256 )) d = "#" + d[ 2 : len (d)] k + = 0.01 brick = self .canvas.create_rectangle( 40 * i, (j * 10 ) + 20 , 40 + ( 40 * i), 30 + (j * 10 ), fill = d) self .bricks.append(brick) self .ball = self .canvas.create_oval( self .ball_x - BALL_SIZE, self .ball_y - BALL_SIZE, self .ball_x + BALL_SIZE, self .ball_y + BALL_SIZE, fill = "#cccccc" ) self .canvas.pack(fill = BOTH, expand = True ) def onMotion( self , e): if (e.x + BAR_WIDTH < = RIGHT_EDGE): self .bar_x = e.x def onTimer( self ): if self .inGame: self .doCycle() self .checkCollisions() self .after(DELAY, self .onTimer) else : self .gameOver() def doCycle( self ): self .ball_x + = self .ballvx self .ball_y + = self .ballvy self .canvas.coords( self .ball, self .ball_x - BALL_SIZE, self .ball_y - BALL_SIZE, self .ball_x + BALL_SIZE, self .ball_y + BALL_SIZE) self .canvas.coords( self .bar, self .bar_x, self .bar_y, self .bar_x + BAR_WIDTH, self .bar_y) if ( len ( self .bricks) = = 0 ): self .msg = "Game won" self .inGame = False def checkCollisions( self ): if ( self .ball_x > = RIGHT_EDGE or self .ball_x < = 0 ): self .ballvx * = - 1 if ( self .ball_y < = 0 ): self .ballvy * = - 1 for brick in self .bricks: hit = 0 co = self .canvas.coords(brick) if ( self .ball_x > co[ 0 ] and self .ball_x < co[ 2 ] and self .ball_y + self .ballvy > co[ 1 ] and self .ball_y + self .ballvy < co[ 3 ]): hit = 1 self .ballvy * = - 1 if ( self .ball_x + self .ballvx > co[ 0 ] and self .ball_x + self .ballvx < co[ 2 ] and self .ball_y > co[ 1 ] and self .ball_y < co[ 3 ]): hit = 1 self .ballvx * = - 1 if (hit = = 1 ): self .bricks.remove(brick) self .canvas.delete(brick) if (( self .ball_y = = NEAR_BAR_Y and self .ball_y < self .bar_y) and ( self .ball_x > self .bar_x and self .ball_x < self .bar_x + BAR_WIDTH)): self .ballvy * = - 1 if ( self .ball_y = = NEAR_BAR_Y and self .ball_x < self .bar_x + BAR_WIDTH / 2 and self .ball_x > self .bar_x and self .ballvx > 0 ): self .ballvx * = - 1 if ( self .ball_y = = NEAR_BAR_Y and self .ball_x > self .bar_x + BAR_WIDTH / 2 and self .ball_x < self .bar_x + BAR_WIDTH and self .ballvx < 0 ): self .ballvx * = - 1 if ( self .ball_y > BOTTOM_EDGE): self .lives - = 1 self .canvas.delete( self .lives_item) self .lives_item = self .canvas.create_text( 15 , 270 , text = self .lives, fill = "white" ) if self .lives = = 0 : self .inGame = False self .msg = "Game lost" else : self .ball_x = BALL_INIT_X self .ball_y = BALL_INIT_Y def gameOver( self ): self .canvas.delete( ALL ) self .canvas.create_text( self .winfo_width() / 2 , self .winfo_height() / 2 , text = self .msg, fill = "white" ) def main(): root = Tk() ex = Example(root) root.geometry( "+300+300" ) root.mainloop() if __name__ = = '__main__' : main() |
V tejto hre máme jeden objekt plošiny, jednu loptičku a sto tehličiek. Na vytvorenie herného cyklu sme využili časovač. Hráč ovláda plošinu pomocou myši. Ak dopadne loptička do bližšej polovice plošiny, loptička sa odrazí do protismeru; ináč sa odrazí v smere pohybu.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | BAR_WIDTH = 60 BAR_HEIGHT = 1 BAR_INIT_X = 170 BAR_Y = 250 BOTTOM_EDGE = 270 RIGHT_EDGE = 400 BALL_SIZE = 3 NEAR_BAR_Y = BAR_Y - BALL_SIZE - BAR_HEIGHT BALL_INIT_X = 200 BALL_INIT_Y = 150 INIT_DELAY = 800 DELAY = 30 |
Na začiatku si definujeme zopár konštánt. BAR_WIDTH
predstavuje dĺžku plošiny a BAR_HEIGHT
jej hrúbku. BAR_INIT_X
je počiatočná x-sová súradnica horného ľavého bodu plošiny a BAR_Y
je y-lonová súradnica. (Y-lonová súradnica sa počas hry nemení.) BOTTOM_EDGE
a RIGHT_EDGE
určujú hranice plochy hry. BALL_SIZE
je veľkosť loptičky. NEAR_BAR_Y
je miestom, pri ktorom dochádza ku kontaktu loptičky a plošiny. BALL_INIT_X
a BALL_INIT_Y
sú východzie súradnice loptičky. Nakoniec, INIT_DELAY
a DELAY
sú východzie a normálne zdržanie v časovači.
1 2 3 | self .initVariables() self .initBoard() self .after(INIT_DELAY, self .onTimer) |
V konštruktore inicializujeme herné premenné, hernú plochu a štartujeme časovač. Časovač sa spustí pomocou metódy after()
.
1 2 3 4 5 6 7 8 9 10 11 12 | def initVariables( self ): self .bricks = [] self .ballvx = self .ballvy = 3 self .ball_x = BALL_INIT_X self .ball_y = BALL_INIT_Y self .inGame = True self .bar_x = BAR_INIT_X self .bar_y = BAR_Y self .lives = 3 |
V metóde initVariables()
inicializujeme dôležité herné premenné. Zoznam bricks
obsahuje objekty tehličiek. Premenné ballvx
a ballvy
sú horizontálne a vertikálne rýchlosti loptičky. Premenné ball_y
a ball_x
sú súradnice bodu v hornom ľavom rohu loptičky. Premenná inGame
určuje, či je hra ukončená alebo či stále prebieha. Premenné bar_x
a bar_y
sú súradnicami horného, ľavého bodu plošiny. Nakoniec premenná lives
kontroluje počet životov hráča.
1 2 3 4 5 6 7 | def initBoard( self ): self .parent.title( "Arkanoid" ) self .pack(fill = BOTH, expand = True ) self .parent.config(cursor = "none" ) ... |
Metódou initBoard()
vytvárame hernú plochu. Pomocou metódy config()
vypneme kurzor myši; hra sa ovláda pomocou myši a jej kurzor by nám prekážal.
1 2 3 | self .canvas = Canvas( self , width = 400 , height = 300 , background = "#000000" ) self .canvas.bind( "<Motion>" , self .onMotion) |
Tu vytvárame komponentu Canvas
. Jej pozadie vyplníme čiernou farbou. Pomocou metódy bind()
napojíme udalosti vysielané myšou na nami vytvorenú metódu onMotion()
.
1 2 3 | self .bar = self .canvas.create_line( self .bar_x, self .bar_y, self .bar_x + BAR_WIDTH, self .bar_y, fill = "#ffffff" ) |
Naša plošina je jednoduchú biela čiara. Objekt čiary na plátne vytvoríme pomocou metódy create_line()
. Parametrami metódy sú súradnice bodov čiary; je možné špecifikovať viac ako dva body. Parameter fill
udáva farbu čiary.
1 2 | self .lives_item = self .canvas.create_text( 15 , 270 , text = self .lives, fill = "white" ) |
Textový objekt sa vytvorí v dolnom ľavom rohu okna. Tento objekt zobrazuje počet našich životov. Vytvoríme ho pomocou metódy create_text()
.
1 2 3 4 5 6 7 8 9 10 11 12 13 | k = 0.0 for j in range ( 10 ): for i in range ( 10 ): c = colorsys.hsv_to_rgb(k, 0.5 , 0.4 ) d = hex ( int (c[ 0 ] * 256 )<< 16 | int (c[ 1 ] * 256 )<< 8 | int (c[ 2 ] * 256 )) d = "#" + d[ 2 : len (d)] k + = 0.01 brick = self .canvas.create_rectangle( 40 * i, (j * 10 ) + 20 , 40 + ( 40 * i), 30 + (j * 10 ), fill = d) self .bricks.append(brick) |
V cykle for sa vytvorí sto objektov tehličiek v rôznych farbách. Tehličky sa uložia do premennej bricks
. Tehlička je obdĺžnikový objekt vytvorený pomocou metódy create_rectangle()
. Rôzne druhy farieb sú vytvorené modelom HSV (Hue, Saturation, Value).
1 2 3 | self .ball = self .canvas.create_oval( self .ball_x - BALL_SIZE, self .ball_y - BALL_SIZE, self .ball_x + BALL_SIZE, self .ball_y + BALL_SIZE, fill = "#cccccc" ) |
Loptička je jednoduchý kruh vytvorený pomocou metódy create_oval()
. Pre vytvorenie oválneho tvaru je treba poskytnúť obdĺžnik, ktorý daný ovál ohraničuje. Obdĺžnik je daný dvoma bodmi: horným ľavým a dolným pravým bodom.
1 2 3 4 | def onMotion( self , e): if (e.x + BAR_WIDTH < = RIGHT_EDGE): self .bar_x = e.x |
Metóda onMotion()
sa volá ako reakcia na udalosti generované myšou. Vo vnútri metódy nastavíme x-sovú súradnicu plošiny. Tá nesmie prekročiť hodnotu, kde by už plošina prešla cez pravý okraj hracej plochy. Čerstvú x-sovú súradnicu získame z atribútu x
udalostného objektu, ktorú nám Tkinter posiela ako parameter.
1 2 3 4 5 6 7 8 | def onTimer( self ): if self .inGame: self .doCycle() self .checkCollisions() self .after(DELAY, self .onTimer) else : self .gameOver() |
Každých DELAY
milisekúnd je volaná metóda onTimer()
. Ona kontroluje herný cyklus, testuje kolízie, alebo ukončuje hru.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | def doCycle( self ): self .ball_x + = self .ballvx self .ball_y + = self .ballvy self .canvas.coords( self .ball, self .ball_x - BALL_SIZE, self .ball_y - BALL_SIZE, self .ball_x + BALL_SIZE, self .ball_y + BALL_SIZE) self .canvas.coords( self .bar, self .bar_x, self .bar_y, self .bar_x + BAR_WIDTH, self .bar_y) if ( len ( self .bricks) = = 0 ): self .msg = "Game won" self .inGame = False |
Vo vnútri metódy doCycle()
aktualizujeme súradnice loptičky, premiestnime loptičku a plošinu pomocou metódy coords()
. Ak už nezostali žiadne tehličky, nastavíme premennú inGame
na False
.
1 2 3 4 5 6 7 8 | def checkCollisions( self ): if ( self .ball_x > = RIGHT_EDGE or self .ball_x < = 0 ): self .ballvx * = - 1 if ( self .ball_y < = 0 ): self .ballvy * = - 1 ... |
V metóde checkCollisions()
kontrolujeme či nedošlo ku kolíziám. Loptička zmení smer, ak narazí na horný, ľavý alebo pravý okraj hernej plochy.
1 2 3 4 5 | for brick in self .bricks: hit = 0 co = self .canvas.coords(brick) ... |
V tomto for cykle kontrolujeme kolízie medzi tehličkami a loptičkou. Metóda coords()
môže byť využitá na posun objektu plátna alebo na zistenie jeho súradníc. V tomto prípade zisťujeme súradnice objektu.
1 2 3 4 5 6 | if ( self .ball_x > co[ 0 ] and self .ball_x < co[ 2 ] and self .ball_y + self .ballvy > co[ 1 ] and self .ball_y + self .ballvy < co[ 3 ]): hit = 1 self .ballvy * = - 1 |
Ak loptička narazí na horný alebo dolný okraj tehličky, nastaví sa premenná hit
a zmení sa vertikálny smer loptičky.
1 2 3 4 5 6 7 | if ( self .ball_x + self .ballvx > co[ 0 ] and self .ball_x + self .ballvx < co[ 2 ] and self .ball_y > co[ 1 ] and self .ball_y < co[ 3 ]): hit = 1 self .ballvx * = - 1 |
Podobne ak loptička narazí na pravý alebo ľavý okraj tehličky, nastaví sa premenná hit
a zmení sa horizontálny smer loptičky.
1 2 3 4 | if (hit = = 1 ): self .bricks.remove(brick) self .canvas.delete(brick) |
Zasiahnutá tehlička je odstránená.
1 2 3 4 5 | if (( self .ball_y = = NEAR_BAR_Y and self .ball_y < self .bar_y) and ( self .ball_x > self .bar_x and self .ball_x < self .bar_x + BAR_WIDTH)): self .ballvy * = - 1 |
Ak loptička zasiahne plošinu, zmení sa jej vertikálny pohyb — odrazí sa od plošiny.
1 2 3 4 5 6 7 8 9 10 11 12 13 | if ( self .ball_y = = NEAR_BAR_Y and self .ball_x < self .bar_x + BAR_WIDTH / 2 and self .ball_x > self .bar_x and self .ballvx > 0 ): self .ballvx * = - 1 if ( self .ball_y = = NEAR_BAR_Y and self .ball_x > self .bar_x + BAR_WIDTH / 2 and self .ball_x < self .bar_x + BAR_WIDTH and self .ballvx < 0 ): self .ballvx * = - 1 |
Zmeny v horizontálnom pohybe loptičky závisia od toho, na ktorú polovicu plošiny padne loptička.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | if ( self .ball_y > BOTTOM_EDGE): self .lives - = 1 self .canvas.delete( self .lives_item) self .lives_item = self .canvas.create_text( 15 , 270 , text = self .lives, fill = "white" ) if self .lives = = 0 : self .inGame = False self .msg = "Game lost" else : self .ball_x = BALL_INIT_X self .ball_y = BALL_INIT_Y |
Ak loptička prejde cez spodok určený konštantou BOTTOM_EDGE
, strácame v hre život. Ak už nemáme viac herných životov, hra sa ukončí. Inak sa loptička znova zjaví vo svojej počiatočnej pozícii a hra pokračuje.
1 2 3 4 5 | def gameOver( self ): self .canvas.delete( ALL ) self .canvas.create_text( self .winfo_width() / 2 , self .winfo_height() / 2 , text = self .msg, fill = "white" ) |
V metóde gameOver()
vymažeme všetky objekty plátna a vytvoríme záverečný text; buď „Game over“ alebo „Game lost“. Text sa vykreslí v strede okna. Veľkosť komponenty zistíme metódami winfo_width()
a winfo_height()
.
1 2 3 4 5 6 | def main(): root = Tk() ex = Example(root) root.geometry( "+300+300" ) root.mainloop() |
V tomto kóde sa vytvára hlavné okno našej aplikácie a spúšťa sa slučka udalostí.
Zdroje
- Referenčná príručka k Tkinter
- Časť kódu pochádza z tohto postu
V tomto článku sme si ukázali, ako vytvoriť jednoduchú hru v Tkinteri. Na jej vytvorenie sme využili grafické plátno, ktoré nám poskytuje komponenta Canvas
. Odporúčame čitateľovi, aby si po prelúskaní kódu rozšíril hru o nové prvky alebo spravil jednoduché zmeny.