Obsah
1. Hrátky se systémem LÖVE II – jednoduchá hra
2. Zdrojový kód hry
3. Základní kostra hry
4. Načtení všech potřebných objektů a inicializace hry
5. Zpracování událostí
6. Režimy hry a způsob jejich přepínání
7. Výpočet trajektorie hráče
8. Pohyb protihráčů a detekce kolizí
9. Závěrečná animace a ukončení hry
1. Hrátky se systémem LÖVE II – jednoduchá hra
V předchozích dvou částech tohoto seriálu jsme se seznámili se základy systému LÖVE, především s aplikačním programovým rozhraním (API) jednotlivých knihoven i způsobem využití takzvaných callback funkcí, tj. funkcí, které tento systém automaticky zavolá ve chvíli, kdy nastane nějaká událost, například stisk klávesy, pohyb myši, „tik“ časovače či kdy dojde k žádosti operačního systému o překreslení okna či obrazovky. Také již víme, jakým způsobem je možné načítat některé typy objektů, například rastrové obrázky, zvuky, hudbu či fonty ze souborů umístěných buď v adresáři s rozbalenou hrou (jedná se o adresář obsahující mj. i soubory game.conf a main.lua) nebo přímo v archivu s hrou, tj. v souboru hra.love. Tento archiv vznikne komprimací všech souborů umístěných v jednom adresáři programem typu zip s následnou změnou koncovky archivu, tj. například příkazem zip –9 hra.love .. Dnes si ukážeme, jak lze s využitím systému LÖVE a taktéž některých vlastností programovacího jazyka Lua vytvořit jednoduchou 2D hru s objektem představujícím hráče a několika počítačem řízenými protihráči.
Obrázek 1: Úvodní obrazovka hry
2. Zdrojový kód hry
Zdrojový kód hry, který mj. můžete získat po rozbalení archivu s hrou, popř. ho lze studovat na níže uvedeném výpisu, má přesně 256 řádků (bez komentářů dokonce pouze 200 řádků) a byl vytvořen za cca hodinu, protože snadno použitelné API systému LÖVE i dynamický jazyk Lua skutečně dokážou poměrně výrazným způsobem ušetřit programátorův čas.
----------------------------------------------------------------------- -- Seriál "Programovací jazyk Lua" -- -- Jednoduchá hra vytvořená s využitím systému LÖVE napsaná na 256 řádcích ----------------------------------------------------------------------- -- časový interval mezi jednotlivými snímky sleepInMs = 50 -- velikost okna window = { width = 800, height = 600 } -- objekt představující hráče - raketu rocket = { x = window.width/2, y = window.height/2, v = 0, rotation = 0, deltaAlfa = 8, acceleration = 1.5, dampingFactor = 0.95 } -- objekt představující protivníky stars = { count = 10, maxVelocity = 50 } -- výbuch rakety = dvacet červených teček redBalls = { count = 20, velocity = 6, numberOfFrames = 70 } -- callback funkce zavolaná při inicializaci aplikace function load() -- načíst fonty font1 = love.graphics.newFont("DejaVuLGCSansMono.ttf", 20) font2 = love.graphics.newFont("DejaVuLGCSansMono.ttf", 20*6) -- nastavit grafický režim love.graphics.setMode(window.width, window.height, false, false, 0) -- načíst všechny obrázky background = love.graphics.newImage("tile-space.jpg") rocket.image = love.graphics.newImage("rocket.gif") stars.image = love.graphics.newImage("orgstar.gif") redBalls.image = love.graphics.newImage("redball.gif") -- načíst zvuky crashSound = love.audio.newSound("pluck.wav") crashSound2 = love.audio.newSound("beam.wav") -- inicializace všech protivníků for i = 1, stars.count do stars[i] = { x = window.width/2 + 295 * math.cos(i * 2 * math.pi / stars.count), y = window.height/2 + 295 * math.sin(i * 2 * math.pi / stars.count), damping = 5 + i } end -- začít přehrávat úvodní zvuk love.audio.play(love.audio.newSound("drama.wav")) end -- callback funkce volaná průběžně ve chvíli, kdy je zapotřebí překreslit okno draw = function() drawBeginning() end -- první režim hry - pouze se přehrává melodie a zobrazuje se hrací pole function drawBeginning() drawScene() -- čekání na konec přehrání úvodní melodie if not love.audio.isPlaying() then -- změna režimu hry - bude se volat jiná překreslovací funkce draw = function() drawGame() end end love.graphics.setColor(0, 255, 255) love.graphics.setFont(font2) love.graphics.draw("Prepare...", 50, window.height/2-50) -- čekání před vykreslením dalšího snímku love.timer.sleep(sleepInMs) end -- druhý režim hry - vlastní hra function drawGame() -- posun hráče moveRocket() -- posun protivníků moveStars() -- vykreslení celé scény drawScene() -- čekání před vykreslením dalšího snímku love.timer.sleep(sleepInMs) end -- vykreslení scény ve druhém režimu function drawScene() drawBackground() drawRocket() drawStars() drawInfo() end -- třetí režim hry - výbuch s nápisem "Game over" function drawExplosion() drawBackground() -- čekání na konec animace výbuchu, potom ukončení hry redBalls.numberOfFrames = redBalls.numberOfFrames - 1 if redBalls.numberOfFrames > 0 then -- posun úlomků rakety for i = 1, redBalls.count do redBalls[i].x=redBalls[i].x+redBalls[i].vx redBalls[i].y=redBalls[i].y+redBalls[i].vy love.graphics.draw(redBalls.image, redBalls[i].x, redBalls[i].y) end end -- výpis "Game over" love.graphics.setFont(font2) love.graphics.setColor(255, 0, 255) love.graphics.draw("Game over", 50, window.height/2) -- čekání před vykreslením dalšího snímku love.timer.sleep(sleepInMs) end -- výpis informací v dolní části okna function drawInfo() love.graphics.setFont(font1) love.graphics.setColor(255, 200, 255) love.graphics.draw("Esc: exit, left: turn left, right: turn fight, up: accelerate", 10, window.height-30) end -- vykreslení hracího pole - několik na sebe navazujících dlaždic function drawBackground() local y = 0; while y <= window.height do local x = 0 while x <= window.width do love.graphics.draw(background, x, y) x = x + background:getWidth() end y = y + background:getHeight() end end -- vykreslení rakety function drawRocket() local x = rocket.x local y = rocket.y love.graphics.draw(rocket.image, x, y, rocket.rotation) end -- vykreslení protivníků function drawStars() for i = 1, stars.count do love.graphics.draw(stars.image, stars[i].x, stars[i].y) end end -- posun rakety a její natočení na základě kláves -- [Left], [Right] a [Up] function moveRocket() if love.keyboard.isDown(love.key_left) then rocket.rotation = rocket.rotation - rocket.deltaAlfa end if love.keyboard.isDown(love.key_right) then rocket.rotation = rocket.rotation + rocket.deltaAlfa end if love.keyboard.isDown(love.key_up) then rocket.v = rocket.v + rocket.acceleration end -- posun rakety na základě její rychlosti a natočení rocket.x = rocket.x + rocket.v * math.sin(math.rad(rocket.rotation)) rocket.y = rocket.y - rocket.v * math.cos(math.rad(rocket.rotation)) rocket.v = rocket.v * rocket.dampingFactor -- v případě opuštění hracího pole se raketa ukáže na protější straně if rocket.x < 0 then rocket.x = window.width love.audio.play(crashSound) end if rocket.x > window.width then rocket.x = 0 love.audio.play(crashSound) end if rocket.y < 0 then rocket.y = window.height love.audio.play(crashSound) end if rocket.y > window.height then rocket.y = 0 love.audio.play(crashSound) end end -- posun všech protivníků směrem ke hráči function moveStars() for i = 1, stars.count do moveStar(stars[i]) end end -- posun jednoho protivníka směrem ke hráči function moveStar(star) local vx, vy vx = rocket.x - star.x vy = rocket.y - star.y -- zamezit příliš rychlému pohybu vx = math.max(math.min(vx, stars.maxVelocity), -stars.maxVelocity) vy = math.max(math.min(vy, stars.maxVelocity), -stars.maxVelocity) -- vlastní posun o vypočtený vektor star.x = star.x + vx / star.damping star.y = star.y + vy / star.damping -- zjistit, zda nastala kolize if isCollision(star) then processColission() return end end -- funkce vrátí true v případě, že dojde ke kolizi rakety s protivníkem function isCollision(star) return math.abs(star.x - rocket.x) < rocket.image:getWidth()/2 and math.abs(star.y - rocket.y) < rocket.image:getHeight()/2 end -- pokud nastane kolize, změní se mód hry - bude se volat rutina drawExplosion() function processColission() love.audio.play(crashSound2) -- změna překreslovací rutiny draw = function() drawExplosion() end -- inicializace objektů představujících rozbitou raketu for i = 1, redBalls.count do redBalls[i]= { x=rocket.x, y=rocket.y, vx=redBalls.velocity * math.cos(i * 2 * math.pi / redBalls.count), vy=redBalls.velocity * math.sin(i * 2 * math.pi / redBalls.count) } end end -- callback funkce volaná ve chvíli, kdy uživatel stlačí nějakou klávesu function keypressed(key) if key == love.key_escape then love.system.exit() end end -- finito
3. Základní kostra hry
Herní scénář je velmi jednoduchý; vymýšlel ho můj malý syn, který si prosadil, že tam prostě musí být raketa nebo aspoň létající balon :-) (slíbená „plošinovka“ tedy bude ukázána příště, musím ještě nalézt vhodné obrázky do hry šířené pod licencí kompatibilní s LGPL). Na herní ploše se pohybuje raketka natáčená klávesami [Šipka doleva] a [Šipka doprava], jejíž motory se spouští klávesou [Šipka nahoru]. Poslední aktivní klávesou je [Esc], kterou je možné hru v jakémkoli okamžiku ukončit. Úkolem raketky je uniknout protihráčům, kteří se vždy pohybují směrem k ní (ovšem každý protihráč s jinou rychlostí). Protihráčům lze uniknout jak rychlým letem, tak i pomocí malého triku – po dosažení jednoho z okrajů herního pole se raketa ukáže na protilehlém okraji, ovšem sami protihráči nedokážou přes okraj přeletět. Po střetu raketky s některým z protihráčů dojde k výbuchu a hra je ukončena – po stisku klávesy Esc se okno hry uzavře (samozřejmě by bylo možné přidat výsledkovou tabulkou s dosaženými časy, hru pro více hráčů atd., v tomto seriálu nám však jde hlavně o vysvětlení herních principů).
Obrázek 2: Screenshot zachycený v průběhu hry
Celá hra je, jak z hlediska hráčů, tak i programátorů, rozdělena do tří režimů:
- Nejprve se přehraje úvodní melodie se zobrazeným hracím polem a nápisem „Prepare“ (viz první screenshot). Po přehrání úvodní melodie se hra automaticky přepíná do druhého režimu.
- Vlastní hra – pohyb hráče i protivníků s detekcí kolizí. Průběh hry je zachycený na druhém screenshotu.
- Kolize a následná exploze rakety (hráče) s ukončením hry, viz třetí screenshot.
Obrázek 3: Exploze rakety a ukončení hry
4. Načtení všech potřebných objektů a inicializace hry
Před prvním zobrazením úvodní obrazovky je nejprve nutné načíst všechny potřebné objekty do operační paměti. Průběžné načítání objektů je sice také možné, ovšem může zapříčinit prodlevy ve hře, které se projevují trháním animace. V naší jednoduché hře se používá pouze osm objektů uložených v samostatných souborech, které jsou následně zabaleny do archivu s hrou. Jedná se následující soubory:
Poř. | Soubor | Význam |
---|---|---|
1 | drama.wav | Úvodní melodie trvající cca 7 sekund |
2 | pluck.wav | Zvuk přehraný při přeletu raketky přes okraj hrací plochy |
3 | beam.wav | Zvuk přehraný v průběhu detonace raketky |
4 | tile-space.jpg | Dlaždice použitá pro vytvoření pozadí hrací plochy |
5 | rocket.gif | Obrázek raketky |
6 | orgstar.gif | Obrázek představující protihráče |
7 | redball.gif | Obrázek použitý při detonaci raketky |
8 | DejaVuLGCSansMono.ttf | Font použitý ve hře |

Obrázek 4: Dlaždice použitá pro vytvoření pozadí hrací plochy (dlaždice je obrázek, jehož okraje na sebe plynule navazují, což mj. znamená, že může být velmi malý a přesto s ním lze kontinuálně pokrýt velkou plochu). O vykreslení pozadí složeného z dlaždic se stará funkce drawBackground().
O načtení všech potřebných objektů se stará callback funkce load(), která kromě jiného provádí i inicializaci protihráčů a nakonec spustí úvodní melodii (poslední řádek). Povšimněte si například řádku začínajícího rocket.image =, v němž se do „objektu“ (zde spíše jen do datové struktury typu záznam) nazvaného rocket přidává další atribut.
-- callback funkce zavolaná při inicializaci aplikace function load() -- načíst fonty font1 = love.graphics.newFont("DejaVuLGCSansMono.ttf", 20) font2 = love.graphics.newFont("DejaVuLGCSansMono.ttf", 20*6) -- nastavit grafický režim love.graphics.setMode(window.width, window.height, false, false, 0) -- načíst všechny obrázky background = love.graphics.newImage("tile-space.jpg") rocket.image = love.graphics.newImage("rocket.gif") stars.image = love.graphics.newImage("orgstar.gif") redBalls.image = love.graphics.newImage("redball.gif") -- načíst zvuky crashSound = love.audio.newSound("pluck.wav") crashSound2 = love.audio.newSound("beam.wav") -- inicializace všech protivníků for i = 1, stars.count do stars[i] = { x = window.width/2 + 295 * math.cos(i * 2 * math.pi / stars.count), y = window.height/2 + 295 * math.sin(i * 2 * math.pi / stars.count), damping = 5 + i } end -- začít přehrávat úvodní zvuk love.audio.play(love.audio.newSound("drama.wav")) end
5. Zpracování událostí
Programové ovládání celé hry je založeno na takzvaných callback funkcích, které jsou automaticky volány systémem LÖVE po příchodu nějaké události. V demonstrační hře se používá několik callback funkcí. Aby je systém LÖVE rozpoznal, musí mít tyto funkce jednoznačná (předem daná) jména i parametry, což je rozdíl oproti jiným systémům, kde lze callback funkce či metody pojmenovat libovolně – ovšem na druhou stranu je zapotřebí tyto funkce/metody registrovat; tuto činnost není nutné v systému LÖVE provádět:
Callback funkce | Význam |
---|---|
load() | callback funkce zavolaná při inicializaci aplikace |
draw() | callback funkce volaná průběžně ve chvíli, kdy je zapotřebí překreslit okno se hrou |
keypressed(key) | callback funkce volaná ve chvíli, kdy uživatel stlačí nějakou klávesu |

Obrázek 5: Obrázek představující raketku (hráče). Jedná se o rastrový obrázek uložený ve formátu GIF, jehož jedna barva je průhledná, takže se ve skutečnosti na pozadí hry nevykreslí obdélník, ale pouze ty pixely, které skutečně tvoří tělo rakety. Malý kvíz: uhodne někdo, z které (velmi staré) hry tento obrázek pochází?
Zdrojový kód callback funkce load() byl již uveden v předchozí kapitole, tělo funkce draw() je dynamicky měněno při přepínání jednotlivých režimů hry (viz další kapitoly) a callback funkce keypressed(key) je velmi jednoduchá, protože se v ní pouze testuje, zda je stlačena klávesa [Esc]. Vzhledem k tomu, že se tato callback funkce volá nezávisle na zrovna aktivním režimu hry, lze klávesou [Esc] hru ukončit takřka v kterémkoli okamžiku:
-- callback funkce volaná ve chvíli, kdy uživatel stlačí nějakou klávesu function keypressed(key) if key == love.key_escape then love.system.exit() end end
6. Režimy hry a způsob jejich přepínání
Jedním ze zajímavých konceptů, který je ve hře použit, je způsob přepínání jednotlivých režimů hry (úvodní obrazovka, vlastní hra, detonace na konci hry). Celé řízení hry je provedeno v callback funkci draw(), která je volána v relativně pravidelných intervalech, takže není nutné využívat časovač. Mohlo by se zdát, že – vzhledem k tomu, že se v každém režimu ve scéně zobrazují jiné objekty – je nutné ve funkci draw() použít nějakou formu rozhodovací struktury typu if-elseif-else popř. switch-case řízené na základě globální proměnné či proměnné uložení v uzávěru (closure). Ve skutečnosti to však není nutné, protože tělo callback funkce draw() lze dynamicky měnit za běhu aplikace. Podívejme se nyní, jakým způsobem je provedeno přepnutí mezi prvním a druhým režimem, tj. mezi úvodní obrazovkou a vlastní hrou. Již víme, že v callback funkci load() se na závěr načte a spustí úvodní melodie. V pravidelně volané callback funkci draw() se posléze provádí test, zda je melodie stále přehrávána a ve chvíli, kdy se dojde k jejímu konci, je tělo funkce draw() přepsáno, takže příští událost volaná před překreslením okna je zpracovávána již jiným programovým kódem (z funkce draw() je přímo volána funkce drawGame()):
-- callback funkce volaná průběžně ve chvíli, kdy je zapotřebí překreslit okno draw = function() drawBeginning() end -- první režim hry - pouze se přehrává melodie a zobrazuje se hrací pole function drawBeginning() drawScene() -- čekání na konec přehrání úvodní melodie if not love.audio.isPlaying() then -- změna režimu hry - bude se volat jiná překreslovací funkce draw = function() drawGame() end end love.graphics.setColor(0, 255, 255) love.graphics.setFont(font2) love.graphics.draw("Prepare...", 50, window.height/2-50) -- čekání před vykreslením dalšího snímku love.timer.sleep(sleepInMs) end

Obrázek 6: Obrázek představující protihráče. Opět se jedná o rastrový obrázek s průhledným pozadím.
7. Výpočet trajektorie hráče
Trajektorie rakety (hráče) je počítána na základě její aktuální rychlosti a stavu kláves [Šipka doprava], [Šipka doleva] a [Šipka nahoru]. Pomocí těchto kláves, jejichž stav se zjišťuje přímo v callback funkci draw(), lze raketou natáčet doprava a doleva (úhel natočení je uložen v atributu rocket.rotation) a zapnout její motory, kterými se zvyšuje její rychlost. Ta je pro jednoduchost reprezentována pouze skalární hodnotou rocket.v a nikoli vektorem, což mj. znamená, že se raketa vždy pohybuje ve směru jejího natočení, na rozdíl od her typu Asteroids!, kde se bere do úvahy i setrvačnost (úprava pro výpočet setrvačnosti je jednoduchá, ovšem ovládání hry by se ztížilo). Rychlost rakety lze zvýšit klávesou [Šipka nahoru], ovšem navíc je rychlost v každém snímku snižována o hodnotu rocket.dampingFactor. Pokud je tato konstanta nastavena na jedničku, bude se raketa pohybovat v prostředí, které neklade žádný odpor (viz již zmiňovaná hra Asteroids!), hodnota menší než jedna naopak vede k tomu, že je raketa neustále brzděna, což mj. omezuje (bez nutnosti použití explicitní podmínky v programu) i její maximální rychlost, která může dosáhnout hodnoty vypočtené podle vztahu:
vmax=(rocket.acceleration × rocket.dampingFactor) / (1 – rocket.dampingFactor)
Pro představu, jak celá trajektorie rakety může vypadat, je možné původní programový kód hry nahradit následujícím skriptem. V něm se na základě stlačených kláves (kurzorových šipek) mění orientace i poloha rakety, přičemž se do pomocného asociativního pole nazvaného rockets ukládají i všechny předchozí souřadnice rakety v ploše, které jsou následně vykresleny v callback funkci draw().
Obrázek 7: Trajektorie hráče vypočtená na základě postupného natáčení rakety a zapínání či vypínání jejích motorů. Čím pomaleji se raketa pohybuje, tím menší prostor zůstává mezi jednotlivými obrázky (sprity). Pomalejší pohyb taktéž umožňuje zatáčet v oblouku s menším poloměrem než při rychlosti větší.
----------------------------------------------------------------------- -- Seriál "Programovací jazyk Lua" -- -- Zobrazení trajektorie rakety ----------------------------------------------------------------------- -- časový interval mezi jednotlivými snímky sleepInMs = 50 -- velikost okna window = { width = 800, height = 600 } -- objekt představující hráče - raketu rocket = { x = window.width/2, y = window.height/2, v = 0, rotation = 0, deltaAlfa = 8, acceleration = 1.5, dampingFactor = 0.95 } -- pole pro úschovu pozice a natočení rakety v minulosti rockets = {} -- callback funkce zavolaná při inicializaci aplikace function load() -- načíst fonty font1 = love.graphics.newFont("DejaVuLGCSansMono.ttf", 20) -- nastavit grafický režim love.graphics.setMode(window.width, window.height, false, false, 0) -- načíst všechny obrázky background = love.graphics.newImage("tile-space.jpg") rocket.image = love.graphics.newImage("rocket.gif") end -- callback funkce volaná průběžně ve chvíli, kdy je zapotřebí překreslit okno draw = function() -- posun hráče moveRocket() -- vykreslení celé scény drawScene() -- čekání před vykreslením dalšího snímku love.timer.sleep(sleepInMs) end -- vykreslení scény ve druhém režimu function drawScene() drawBackground() drawRocket() drawInfo() end -- výpis informací v dolní části okna function drawInfo() love.graphics.setFont(font1) love.graphics.setColor(255, 200, 255) love.graphics.draw("" .. #rockets, 10, window.height-30) end -- vykreslení hracího pole - několik na sebe navazujících dlaždic function drawBackground() local y = 0; while y <= window.height do local x = 0 while x <= window.width do love.graphics.draw(background, x, y) x = x + background:getWidth() end y = y + background:getHeight() end end -- vykreslení rakety i celé její trajektorie function drawRocket() local x = rocket.x local y = rocket.y for i=1, #rockets do local x = rockets[i].x local y = rockets[i].y local rotation = rockets[i].rotation love.graphics.draw(rocket.image, x, y, rockets[i].rotation) end end -- posun rakety a její natočení na základě kláves -- [Left], [Right] a [Up] function moveRocket() if love.keyboard.isDown(love.key_left) then rocket.rotation = rocket.rotation - rocket.deltaAlfa end if love.keyboard.isDown(love.key_right) then rocket.rotation = rocket.rotation + rocket.deltaAlfa end if love.keyboard.isDown(love.key_up) then rocket.v = rocket.v + rocket.acceleration end -- posun rakety na základě její rychlosti a natočení rocket.x = rocket.x + rocket.v * math.sin(math.rad(rocket.rotation)) rocket.y = rocket.y - rocket.v * math.cos(math.rad(rocket.rotation)) rocket.v = rocket.v * rocket.dampingFactor -- v případě opuštění hracího pole se raketa ukáže na protější straně if rocket.x < 0 then rocket.x = window.width end if rocket.x > window.width then rocket.x = 0 end if rocket.y < 0 then rocket.y = window.height end if rocket.y > window.height then rocket.y = 0 end -- zapamatovat si pozici rakety i její natočení -- (užitečný idiom, jak se k asociativnímu poli chovat jako k seznamu) rockets[#rockets + 1] = { x = rocket.x, y = rocket.y, rotation = rocket.rotation } end -- callback funkce volaná ve chvíli, kdy uživatel stlačí nějakou klávesu function keypressed(key) if key == love.key_escape then love.system.exit() end end -- finito
8. Pohyb protihráčů a detekce kolizí
Výpočet pohybu protihráčů je do značné míry zjednodušen. Protihráči se vždy pohybují ve směru rakety, ovšem každý s jinou rychlostí. Maximální horizontální a vertikální rychlost je omezena konstantou stars.maxVelocity, což mj. znamená, že se protihráči nejrychleji pohybují po úhlopříčce – tuto znalost lze využít ke snazšímu úniku před nimi. Maximální rychlost rakety je však vyšší než rychlost protihráčů, pokud samozřejmě nedojde ke změně konstant stars.maxVelocity, rocket.dampingFactor či rocket.acceleration. V programovém kódu pro výpočet pohybu protihráčů se nachází i detekce kolize protihráče s raketou. Její výpočet je taktéž zjednodušen, protože se pouze testuje, zda se protihráč (přesněji jeho střed) nenachází v obdélníku o velikosti bitmapy rakety, tj. 44×34 pixelů. Jednu z možných úprav, které vedou ke zpřesnění tohoto výpočtu, si ukážeme v navazující části tohoto seriálu. Následuje výpis části kódu, který se stará o výpočet trajektorie protivníků i detekci kolize (povšimněte si, že pokud dojde ke kolizi, tak se hra ve funkci processColission() přepne do třetího režimu):
-- posun všech protivníků směrem ke hráči function moveStars() for i = 1, stars.count do moveStar(stars[i]) end end -- posun jednoho protivníka směrem ke hráči function moveStar(star) local vx, vy vx = rocket.x - star.x vy = rocket.y - star.y -- zamezit příliš rychlému pohybu vx = math.max(math.min(vx, stars.maxVelocity), -stars.maxVelocity) vy = math.max(math.min(vy, stars.maxVelocity), -stars.maxVelocity) -- vlastní posun o vypočtený vektor star.x = star.x + vx / star.damping star.y = star.y + vy / star.damping -- zjistit, zda nastala kolize if isCollision(star) then processColission() return end end -- funkce vrátí true v případě, že dojde ke kolizi rakety s protivníkem function isCollision(star) return math.abs(star.x - rocket.x) < rocket.image:getWidth()/2 and math.abs(star.y - rocket.y) < rocket.image:getHeight()/2 end -- pokud nastane kolize, změní se mód hry - bude se volat rutina drawExplosion() function processColission() love.audio.play(crashSound2) -- změna překreslovací rutiny draw = function() drawExplosion() end -- inicializace objektů představujících rozbitou raketu for i = 1, redBalls.count do redBalls[i]= { x=rocket.x, y=rocket.y, vx=redBalls.velocity * math.cos(i * 2 * math.pi / redBalls.count), vy=redBalls.velocity * math.sin(i * 2 * math.pi / redBalls.count) } end end
9. Závěrečná animace a ukončení hry
Poslední režim hry je představovaný závěrečnou animací detonace a slavným nápisem „Game over“. Prozatím jsme si nepopsali, jakým způsobem lze v systému LOVE využít částicové systémy, proto je detonace rakety velmi primitivní – z místa kolize rakety a protihráče se rozlétnou červené balonky (další z požadavků syna :-) a současně se přehraje zvuk výbuchu. Počet snímků, které závěrečná animace obsahuje, je omezen konstantou redBalls.numberOfFrames, přičemž po zobrazení těchto snímků hra pouze zobrazuje pozadí s nápisem „Game over“ a čeká na stisk klávesy [Esc].
-- třetí režim hry - výbuch s nápisem "Game over" function drawExplosion() drawBackground() -- čekání na konec animace výbuchu, potom ukončení hry redBalls.numberOfFrames = redBalls.numberOfFrames - 1 if redBalls.numberOfFrames > 0 then -- posun úlomků rakety for i = 1, redBalls.count do redBalls[i].x=redBalls[i].x+redBalls[i].vx redBalls[i].y=redBalls[i].y+redBalls[i].vy love.graphics.draw(redBalls.image, redBalls[i].x, redBalls[i].y) end end -- výpis "Game over" love.graphics.setFont(font2) love.graphics.setColor(255, 0, 255) love.graphics.draw("Game over", 50, window.height/2) -- čekání před vykreslením dalšího snímku love.timer.sleep(sleepInMs) end

Obrázek 8: Obrázek s průhledným pozadím, který je použitý pro napodobení detonace raketky.