Hlavní navigace

Hrátky se systémem LÖVE

2. 6. 2009
Doba čtení: 20 minut

Sdílet

V dnešní části seriálu o programovacím jazyce Lua si na sedmi jednoduchých příkladech ukážeme, jakým způsobem je možné s využitím systému LÖVE vytvořit okno, vykreslit do tohoto okna geometrické obrazce, vytvářet animace a taktéž pracovat s fonty i rastrovými obrázky, které lze mj. použít jako sprity.

Obsah

1. Seznam demonstračních příkladů a způsob jejich spuštění
2. Zjištění všech dostupných celoobrazovkových režimů
3. Načtení externího fontu a vykreslení všech jeho znaků
4. Natočení textu při jeho vykreslování
5. Funkce určené pro změnu stylu vykreslování
6. Vykreslení jednoduchého obrazce pomocí funkcí z knihovny love.graphics
7. Nastavení míchání barev a použití průhlednosti při vykreslování
8. Základ animací
9. Načtení externího obrázku, vykreslení obrázku v animaci s jeho natočením

1. Seznam demonstračních příkladů a způsob jejich spuštění

V dnešním článku bude mj. uvedeno sedm demonstračních příkladů, na nichž si vysvětlíme některé základní funkce systému LÖVE. Nejprve si řekněme, jakým způsobem je možné tyto demonstrační příklady spustit (předpokladem samozřejmě je, že systém LÖVE je již nainstalovaný). Nejjednodušší metoda spočívá v tom, že se interpretr love či love.exe spustí s jedním parametrem, kterým je název (popřípadě doplněný i o cestu) k souboru s koncovkou .love. Tento soubor není ve skutečnosti nic jiného než archiv vytvořený programem typu zip, jenž obsahuje několik souborů: konfigurační soubor game.conf (který prozatím nevyužíváme, proto má jen velmi jednoduchou strukturu), vlastní skript představující aplikaci (hru) nazvaný main.lua a v případě potřeby i další potřebné soubory – většinou se v demonstračních příkladech jedná o font DejaVuLGCSansMo­no.ttf nebo o rastrový obrázek gnome-globe.png.

Výsledné balíky tedy obsahují všechna data potřebná pro jejich běh (pozor – musí se komprimovat přímo soubory se hrou, nikoli nadřazený adresář!). Druhou možností spouštění, kterou využijí především vývojáři, je vyvolání interpretru love či love.exe s jedním parametrem představujícím cestu k adresáři, který obsahuje všechny výše uvedené soubory (nesmí se uvést přímo cesta ke skriptu main.lua, ale k celému adresáři). Je tedy možné například soubor gfx_modes.love rozbalit programem unzip (či podobnou aplikací) do samostatného adresáře a cestu k tomuto adresáři předat interpretru. V následující tabulce jsou uloženy odkazy na všech sedm demonstračních příkladů popsaných v následujících kapitolách:

Název příkladu Balík s příkladem
Zjištění dostupných celoobrazovkových režimů gfx_modes.love
Načtení externího fontu fonts1.love
Natočení textu fonts2.love
Vykreslení jednoduchého obrazce drawing1.love
Nastavení míchání barev drawing2.love
Jednoduchá animace animation.love
Pohybující se rastrový obrázek images.love

2. Zjištění všech dostupných celoobrazovkových režimů

Již v předchozí části tohoto seriálu jsme si řekli, že systém LÖVE umožňuje běh hry či jiné multimediální aplikace jak v běžném okně (vytvářeném pomocí správce oken daného operačního systému), tak i v takzvaném celoobrazovkovém režimu – fullscreen mode. V celoobrazovkovém režimu má aplikace zajištěn exkluzivní přístup k části video paměti a při porušení této oblasti paměti (tj. například ve chvíli přepnutí do jiné aplikace) systém sám požádá o překreslení celé scény. Okno či celoobrazovkový režim je možné nastavit pomocí funkce love.graphics­.setMode(), ovšem před voláním této funkce je vhodné, zejména při snaze o nastavení celoobrazovkového režimu, zavoláním love.graphics­.getModes() nebo love.graphics­.checkMode() zjistit, zda je požadovaný grafický režim vůbec podporován (například na mnoha počítačích existují poměrně restriktivní omezení pro rozlišení celoobrazovkových režimů). V následující tabulce je vypsáno všech pět parametrů funkce love.graphics­.setMode(), kterou budeme používat ve všech dnešních demonstračních příkladech:

Parametry funkce love.graphics­.checkMode()

Název parametru Význam
width požadovaný počet pixelů na horizontální ose (buď se jedná o vnitřní šířku okna bez započítání jeho okrajů, nebo o horizontální rozlišení celoobrazovkového režimu)
height požadovaný počet pixelů na vertikální ose (buď se jedná o vnitřní výšku okna bez okrajů a horní lišty, nebo o vertikální rozlišení celoobrazovkového režimu)
fullscreen pravdivostní příznak určující, zda se má vykreslovat do okna či na celou obrazovku
vsync pravdivostní příznak určující, zda se má provádět synchronizace vykreslování s vertikálním návratem paprsku (vertikální zatmění); většinou je vhodné synchronizaci zapnout, aby se zabránilo nežádoucím „střihům“ ve scéně při přepínání předního a zadního bufferu
fsaa počet bufferů použitých při antialiasingu celé scény (antialiasing však lze zapnout lokálně, například při vykreslování geometrických tvarů, což bude patrné i ze screenshotů demonstračních příkladů), toto nastavení má význam především při práci s rastrovými obrázky

Funkce love.graphics­.getModes(), na rozdíl od výše popsané funkce love.graphics­.setMode(), nevyžaduje při svém volání předání žádných parametrů. Výstupní hodnotou této funkce je asociativní pole obsahující seznam všech podporovaných celoobrazovkových grafických režimů. Každý grafický režim je v tomto poli představován jednou položkou, která je taktéž (jako prakticky každý složitější datový typ jazyka Lua) asociativním polem s dvojicí klíčů width a height obsahujících horizontální a vertikální rozlišení každého celoobrazovkového grafického režimu. Dnešní první demonstrační příklad slouží ke zjištění všech těchto režimů a uložení informací o nich do globální proměnné modes. Následně je otevřeno okno, načteno standardní písmo (viz následující kapitoly) a do okna je celý seznam režimů vypsán. Vertikální velikost okna je přepočtena na základě celkového počtu podporovaných režimů. Pokud by bylo okno příliš vysoké, postačuje snížit hodnotu konstanty fontHeight, čímž dojde ke zmenšení písma použitého pro výpis informací. Zdrojový kód prvního demonstračního příkladu, tj. obsah souboru nazvaného main.lua, je následující:

-----------------------------------------------------------------------
-- Seriál "Programovací jazyk Lua"
--
-- Zjištění všech podporovaných celoobrazovkových grafických režimů
-----------------------------------------------------------------------

-- asociativní pole, do kterého se seznam podporovaných režimů uloží
modes = nil

fontHeight = 20

-- callback funkce zavolaná při inicializaci aplikace
function load()
    -- získat všechny podporované grafické režimy
    modes = love.graphics.getModes()
    -- načíst standardní font
    local font = love.graphics.newFont(love.default_font, fontHeight)
    -- vytvořit okno pro vykreslování
    love.graphics.setMode(450, 100 + #modes * fontHeight, false, false, 0)
    love.graphics.setFont(font)
end

-- callback funkce volaná průběžně ve chvíli, kdy je zapotřebí překreslit okno
function draw()
    love.graphics.setColor(255, 255, 255)
    love.graphics.draw("Gfx modes:", 10, 30)
    -- iterovat přes všechny režimy uložené v asociativním poli "modes"
    for i, mode in ipairs(modes) do
        -- výpočet intenzity jedné barvové složky pro změnu barvy textu
        local color = 255*(i-1)/#modes
        love.graphics.setColor(255-color, 255, color)
        -- formátování řetězce, který se vypíše do okna
        local desc = string.format("gfx.mode %d: %dx%d", i, mode.width, mode.height)
        love.graphics.draw(desc, 20, 50 + i * fontHeight)
    end
    love.graphics.setColor(255, 128, 128)
    -- přepočet vertikálního umístění řetězce v závislosti na počtu grafických režimů
    love.graphics.draw("Press escape to exit.", 30, 90 + #modes * fontHeight)
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 
lua13_01

Obrázek 1: Screenshot prvního demonstračního příkladu, který byl spuštěn na počítači s grafickou kartou Intel 82852 GM. Tato karta sice podporuje i vyšší rozlišení, než zde zobrazených 1024×768 pixelů, ovšem nabídka všech reálně nabízených režimů je omezena taktéž maximálním rozlišením displeje, které je v tomto případě rovno právě zmíněným 1024×768 pixelům (jedná se o starší notebook s displejem 4:3).

3. Načtení externího fontu a vykreslení všech jeho znaků

Ve druhém demonstračním příkladu si ukážeme, jakým způsobem je možné načíst libovolný font ve formátu TrueType či OpenType (třetím podporovaným formátem je rastrový obrázek s vykreslenými znaky, jehož použití si ukážeme příště). Soubor s fontem by měl být umístěný ve stejném adresáři, v jakém se nachází samotný skript s hrou (soubor main.lua), popř. ho lze umístit i do archivu s hrou. Font se při inicializaci hry načte pomocí funkce love.graphics­.newFont(). Tato funkce akceptuje buď jeden nebo dva parametry. Prvním parametrem je vždy název souboru s fontem, druhý (nepovinný) parametr udává velikost písma. Pokud není druhý parametr zadaný, doplní se automaticky velikost 12 bodů. Vzhledem k tomu, že ve formátech TrueTypeOpenType jsou hranice znaků popsány parametricky, je možné písmo takřka libovolně zvětšovat a zmenšovat (navíc se může při zmenšování uplatnit takzvaný hinting zlepšující kresbu malých znaků). Po načtení fontu je možné pomocí funkce love.graphics­.draw() provést výpis libovolného řetězce na obrazovku. Tato funkce je přetížená (viz následující kapitolu), přičemž v tomto demonstračním příkladu je použita její nejjednodušší podoba se třemi parametry: samotným řetězcem následovaným x-ovou a y-ovou souřadnicí začátku vykreslování. Následuje výpis zdrojového kódu druhého demonstračního příkladu:

-----------------------------------------------------------------------
-- Seriál "Programovací jazyk Lua"
--
-- Ukázka načtení fontu uloženého v externím souboru
-----------------------------------------------------------------------

-- callback funkce zavolaná při inicializaci aplikace
function load()
    --local font = love.graphics.newFont(love.default_font, 20)
    local font = love.graphics.newFont("DejaVuLGCSansMono.ttf", 20)
    love.graphics.setMode(450, 300, false, false, 0)
    love.graphics.setFont(font)
end

-- průměrná výška fontu
fontHeight = 30

-- callback funkce volaná průběžně ve chvíli, kdy je zapotřebí překreslit okno
function draw()
    love.graphics.setColor(200, 255, 200)
    -- vykreslit matici 32x8 znaků
    -- (prvních 32 znaků však většinou font neobsahuje, popřípadě obsahuje
    --  pouze samé prázdné obdélníky - podle typu fontu)
    for y = 0, 7 do
        local str = ""
        for x = 0, 31 do
            str = str .. string.char(x + y * 32)
        end
        -- vypsat celý řádek 32 znaků uložený v řetězci "str"
        love.graphics.draw(str, 20, fontHeight + y * fontHeight)
    end
    love.graphics.setColor(255, 200, 255)
    love.graphics.draw("Press escape to exit.", 30, 280)
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 
lua13_02

Obrázek 2: Screenshot druhého demonstračního příkladu – výpis prvních 256 znaků fontu nazvaného DejaVu LGC Sans Mono. Prvních 32 znaků je zcela prázdných a znaky na pozicích 128–160 obsahují pouze obdélník, tj. pro praktické využití jsou také neobsazené. Systém LÖVE nedokáže ve své současné verzi vypsat znaky s jiným kódem než 0–255, což způsobuje problémy při práci s lokalizovanými aplikacemi. Jedno z možných řešení tohoto problému si ukážeme příště.

4. Natočení textu při jeho vykreslování

V předchozí kapitole jsme si řekli, že funkce love.graphics­.draw() je přetížená, tj. akceptuje při svém volání různý počet i odlišné typy parametrů, které ovlivňují její chování. Pro vykreslování písma (řetězce znaků) je možné využít celkem čtyři podoby této funkce, které jsou vypsány v následující tabulce. Navíc jsou v tabulce pro úplnost vypsány i dvě podoby funkce love.graphics­.drawf() (draw formatted string), pomocí níž lze řetězec zalomit do odstavce se zadanou šířkou (v pixelech).

Podoby funkce love.graphics­.draw() při vykreslování písma

Volání funkce love.graphics­.draw() Význam
love.graphics­.draw(string, x, y) výpis řetězce na pozici [x,y]
love.graphics­.draw(string, x, y, angle) výpis řetězce na pozici [x,y] s natočením angle stupňů
love.graphics­.draw(string, x, y, angle, s) stejné, jako předchozí volání, ovšem text lze zvětšit či zmenšit podle hodnoty parametru s
love.graphics­.draw(string, x, y, angle, sx, sy) hodnoty parametrů sx a sy ovlivňují měřítko v horizontální a vertikální ose
love.graphics­.drawf(string, x, y, limit) výpis řetězce zalomeného do odstavce, jehož šířka je limit pixelů
love.graphics­.drawf(string, x, y, limit, align) výpis řetězce zalomeného do odstavce, jehož šířka je limit pixelů, řádky v odstavci jsou zarovnány podle parametru align: love.align_left, love.align_center či love.align_right

Ve třetím demonstračním příkladu je funkce love.graphics­.draw() volána se čtveřicí parametrů: vlastním řetězcem, který se má vykreslit, souřadnicemi počátku vykreslovaného řetězce v okně a konečně úhlem natočení řetězce. Po spuštění příkladu se vykreslí 36 řetězců, každý s odlišným natočením i různým počátečním bodem. Úhel natočení se postupně zvyšuje tak, aby po ukončení vykreslování všech řetězců byla dokončena celá smyčka o 360° a počátek vykreslování řetězce se posouvá z bodu [30, 20] směrem vpravo dolů. Zdrojový text třetího demonstračního příkladu má tvar:

-----------------------------------------------------------------------
-- Seriál "Programovací jazyk Lua"
--
-- Ukázka načtení fontu uloženého v externím souboru a vykreslení
-- textu spolu s jeho natočením a změnou barvy
-----------------------------------------------------------------------

-- callback funkce zavolaná při inicializaci aplikace
function load()
    local font = love.graphics.newFont("DejaVuLGCSansMono.ttf", 30)
    love.graphics.setMode(450, 450, false, false, 0)
    love.graphics.setFont(font)
end

-- callback funkce volaná průběžně ve chvíli, kdy je zapotřebí překreslit okno
function draw()
    -- vypisovaný řetězec
    str = "www.root.cz"
    for i=0, 255, 7 do
        -- nastavení barvy
        love.graphics.setColor(i, 255, 255-i)
        -- výpis textu s jeho natočením
        local uhel = i * 360 / 255
        love.graphics.draw(str, 30 + i, 1.5 * i + 20, uhel)
    end
    love.graphics.setColor(255, 200, 255)
    love.graphics.draw("Press escape to exit.", 30, 430)
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 

lua13_03

Obrázek 3: Screenshot třetího demonstračního příkladu – výpis řetězce s posunem počátku jeho vykreslení i úhlu natočení.

5. Funkce určené pro změnu stylu vykreslování

Knihovna love.graphics obsahuje několik funkcí určených pro vykreslování základních geometrických obrazců, především bodů (love.graphic­s.point), úseček (love.graphic­s.line) a kružnic (love.graphic­s.circle). Tyto obrazce jsou vykresleny nastavenou barvou, tloušťkou čáry, stylem čáry (plná, čárkovaná atd.) a se zapnutým či vypnutým antialiasingem. Styl čáry je ovlivněn šestnáctibitovou hodnotou představující bitový vzorek – bitová hodnota 1 znamená, že se ve vzorku nachází krátká čárka, nula naopak znamená mezeru. Použití si ukážeme příště v komplexnějším demonstračním příkladu, který bude všechny dostupné styly používat. Povolení antialiasingu funkcí love.graphics­.setLineStyle(lo­ve.line_smoot­h) poměrně výrazným způsobem zlepšuje kvalitu výsledného obrázku, především u šikmých linek, kde díky vlastnostem rastrového displeje dochází k tvorbě „schodů“ – jagged lines, ovšem toto vizuální vylepšení jde na úkor rychlosti vykreslování. U jednodušších her, ve kterých se používá omezený počet objektů (řádově stovky), si však s tímto typem antialiasingu soudobé grafické akcelerátory bez větších problémů poradí, protože vykreslování je prováděno (přes knihovnu OpenGL i ovladač grafické karty) samotným GPU umístěným na akcelerátoru. Seznam funkcí ovlivňujících způsob vykreslování je uveden v tabulce níže:

Název funkce Význam
love.graphics­.setColor() nastavení barvy vykreslovaných objektů
love.graphics­.setColorMode() určení, zda se má při vykreslování provádět modulace (násobení) barvy s pozadím
love.graphics­.setBlendMode() povolení či zákaz míchání barev s pozadím při vykreslování
love.graphics­.setLineWidth() šířka čáry při vykreslování
love.graphics­.setLineStyle() povolení či zákaz antialiasingu při vykreslování úseček
love.graphics­.setLineStipple() nastavení typu čáry podle zadaného bitového vzorku

6. Vykreslení jednoduchého obrazce pomocí funkcí z knihovny love.graphics

Ve čtvrtém demonstračním příkladu, kde je výše zmíněný antialiasing při vykreslování úseček povolen (jedná se ostatně o implicitní nastavení), je ukázán způsob vykreslení úseček funkcí love.graphics­.line() spolu se změnou jejich barvy pomocí funkce love.graphics­.setColor(), které lze předat buď trojici barvových složek červená, zelená, modrá barvového režimu RGB, popřípadě lze k této trojici přidat i míru průhlednosti (viz následující kapitolu). Hodnoty všech tří barvových složek i hodnota průhlednosti musí ležet v rozsahu 0 až 255, kde nula značí nulovou intenzitu složky a hodnota 255 naopak maximální intenzitu. Černá barva je tedy zadána trojicí (0, 0, 0), čistá červená barva trojicí (255, 0, 0), barva žlutá trojicí (255, 255, 0) a bílá pomocí (255, 255, 255). Při vývoji aplikace je možné potřebné barevné odstíny získat například z dialogu pro míchání barev, který se nachází mj. i v programu GIMP či PaintBrush. Pomocí úseček je vytvořen objekt sestavený ze čtyřiceti rovnostranných trojúhelníků s proměnnou barvou stran. Následuje výpis zdrojového kódu tohoto demonstračního příkladu:

-----------------------------------------------------------------------
-- Seriál "Programovací jazyk Lua"
--
-- Kreslení pomocí funkcí z knihovny love.graphics
-----------------------------------------------------------------------

-- rozměry okna
width = 450
height = 450

-- poloměr vykreslovaného obrazce
radius = 200

-- callback funkce zavolaná při inicializaci aplikace
function load()
    -- načtení standardního fontu a nastavení grafického režimu
    local font = love.graphics.newFont(love.default_font, 20)
    love.graphics.setMode(width, height, false, false, 0)
    love.graphics.setFont(font)
end

-- callback funkce volaná průběžně ve chvíli, kdy je zapotřebí překreslit okno
function draw()
    local max = 120
    -- vykreslení obrazce složeného ze sady rovnostranných trojúhelníků
    for i=1, max, 3 do
        -- natočení trojúhelníku
        local colorAngle = math.rad(i*360/max)
        -- výpočet a nastavení barvy
        love.graphics.setColor(128+127*math.cos(colorAngle), 255, 128+128*math.sin(colorAngle))
        for j=0, 2 do
            local angle1 = math.rad(i + j * 120)
            local angle2 = angle1 + math.rad(120)
            local x1 = width/2 + radius*math.cos(angle1)
            local y1 = width/2 + radius*math.sin(angle1)
            local x2 = width/2 + radius*math.cos(angle2)
            local y2 = width/2 + radius*math.sin(angle2)
            love.graphics.line(x1, y1, x2, y2)
        end
    end
    love.graphics.setColor(255, 200, 255, 255)
    love.graphics.draw("Press escape to exit.", 30, 443)
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 
lua13_04

Obrázek 4: Screenshot čtvrtého demonstračního příkladu – povšimněte si, že při vykreslování úseček byl použit antialiasing, takže jejich okraje vypadají poněkud rozmazaně a především se snížil (většinou) nežádoucí jagging šikmých čar.

7. Nastavení míchání barev a použití průhlednosti při vykreslování

V dnešním pátém demonstračním příkladu, jehož zdrojový kód je vypsán pod tímto odstavcem, je ukázáno použití funkce love.graphics­.setBlendMode() i význam čtvrtého (nepovinného) parametru funkce love.graphics­.setColor(), nazývaného alfa (alpha). Tento parametr určuje průhlednost nastavené barvy a tím i celkový vizuální vzhled objektu. Po spuštění demonstračního příkladu je možné pomocí klávesy B přepínat režim míchání barev mezi love.blending_nor­mal a love.blending_ad­ditive a klávesami až 9 se nastavuje aktuální hodnota alfa, jež musí ležet mezi nulou a 255. V režimu love.blending_nor­mal je nastavená barva pouze vynásobena hodnotou alfa (předaná hodnota je nejprve vydělena 255, aby se nacházela v rozsahu 0.0 až 1.0), ovšem pokud je nastavený režim love.blending_ad­ditive, je proveden součet barvy pozadí (tj. i dříve nakreslených objektů) s barvou vykreslovaného objektu, takže v místech, kde se objekty překrývají, dochází ke zvýšení intenzity barev:

-----------------------------------------------------------------------
-- Seriál "Programovací jazyk Lua"
--
-- Kreslení pomocí funkcí z knihovny love.graphics, nastavení režimu
-- směšování barev
-----------------------------------------------------------------------

-- rozměry okna
width = 450
height = 450

-- poloměr vykreslovaného obrazce
radius = 200

-- režim směšování barev
blending = love.blend_normal
-- konstanta pro směšování
blend_factor = 80

-- callback funkce zavolaná při inicializaci aplikace
function load()
    -- načtení standardního fontu a nastavení grafického režimu
    local font = love.graphics.newFont(love.default_font, 20)
    love.graphics.setMode(width, height, false, false, 0)
    love.graphics.setFont(font)
end

-- callback funkce volaná průběžně ve chvíli, kdy je zapotřebí překreslit okno
function draw()
    -- nastavení režimu směšování barev
    love.graphics.setBlendMode(blending)
    love.graphics.setColor(255, 255, 255, blend_factor)
    -- vykreslení obrazce
    for i=1, 90 do
        for j=0, 3 do
            local angle1 = math.rad(i + j * 90)
            local angle2 = angle1 + 90
            local x1 = width/2 + radius*math.cos(angle1*2)
            local y1 = width/2 + radius*math.sin(angle1*3)
            local x2 = width/2 + radius*math.cos(angle2*3)
            local y2 = width/2 + radius*math.sin(angle2*2)
            love.graphics.line(x1, y1, x2, y2)
        end
    end
    love.graphics.setColor(255, 200, 255, 255)
    love.graphics.draw("Press escape to exit.", 30, 443)
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
    if key == love.key_b then
        setBlending()
    end
    if key >= love.key_0 and key<= love.key_9 then
        setBlendFactor(key)
    end
end

-- povolení či zákaz směšování barev po stisku klávesy "b"
function setBlending()
    if blending == love.blend_normal then
        blending = love.blend_additive
    else
        blending = love.blend_normal
    end
end

-- nastavení hodnoty blend_factor po stisku kláves "0" až "9"
function setBlendFactor(key)
    local code = key - love.key_0
    blend_factor = code * 255 / 10
end

-- finito 
lua13_05

Obrázek 5: Screenshot pátého demonstračního příkladu.

8. Základ animací

V předposledním demonstračním příkladu je ukázáno, jakým způsobem je možné vytvořit jednoduchou animaci založenou na postupné změně parametrů použitých při vykreslování obrazce složeného z úseček. V příkladu je použita callback funkce update() volaná automaticky systémem LÖVE v relativně pravidelných intervalech po cca 100 milisekundách, tj. 10× za sekundu. V callback funkci se nejprve volá funkce love.timer.sle­ep(), která zajišťuje pasivní čekání s možností přepnutí řízení na jiný proces, což představuje rozdíl oproti aktivnímu čekání v programové smyčce, které zbytečně zatěžuje mikroprocesor a blokuje běh dalších aplikací v systému. Po ukončení funkce love.timer.sle­ep() se zvýší hodnota globální proměnné offset, která je následně využita v průběhu překreslení celé scény v callback funkci draw(). Pokud by bylo zapotřebí animaci časovat přesněji, lze pro tento účel využít hodnotu parametru dt předávaného funkci update(), popř. je také možné použít časovač dostupný přes funkce nabízené knihovnou love.timer.

lua13_06

Obrázek 6: Šestý demonstrační příklad – začátek animace.

Zdrojový kód šestého demonstračního příkladu má tvar:

-----------------------------------------------------------------------
-- Seriál "Programovací jazyk Lua"
--
-- Jednoduchá animace objektu složeného z úseček
-----------------------------------------------------------------------

-- rozměry okna
width = 450
height = 450

-- poloměr objektu
radius = 200

-- průběžně zvyšovaná hodnota
offset = 0

-- callback funkce zavolaná při inicializaci aplikace
function load()
    local font = love.graphics.newFont(love.default_font, 20)
    love.graphics.setMode(width, height, false, false, 0)
    love.graphics.setFont(font)
end

-- callback funkce volaná průběžně ve chvíli, kdy je zapotřebí překreslit okno
function draw()
    -- nastavení režimu kreslení (míchání stávajícího obrázku s novým objektem)
    love.graphics.setBlendMode(love.blend_additive)
    love.graphics.setColor(255, 255, 255, 80)
    -- vykreslení celého obrazce
    for i=1, 90 do
        for j=0, 3 do
            local angle1 = math.rad(i + j * 90)
            local angle2 = angle1 + math.rad(offset)
            local x1 = width/2 + radius*math.cos(angle1*2)
            local y1 = width/2 + radius*math.sin(angle1*3)
            local x2 = width/2 + radius*math.cos(angle2*3)
            local y2 = width/2 + radius*math.sin(angle2*2)
            love.graphics.line(x1, y1, x2, y2)
        end
    end
    love.graphics.setColor(255, 200, 255, 255)
    love.graphics.draw("Press escape to exit.", 30, 443)
end

-- callback funkce volaná po 10ms
function update(dt)
    love.timer.sleep(100)
    -- zvýší se hodnota globální proměnné "offset"
    offset = offset + 1
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 
lua13_07lua13_08

Obrázek 7 a 8: Další snímky z animace prováděné šestým demonstračním příkladem

9. Načtení externího obrázku, vykreslení obrázku v animaci s jeho natočením

I dnešní poslední demonstrační příklad bude po svém spuštění zobrazovat jednoduchou animaci. Animovaný objekt je představován rastrovým obrázkem uloženým ve formátu PNG, který je do aplikace načtený funkcí love.graphics­.newImage(). Po načtení je obrázek uložen do paměti grafického akcelerátoru ve formě textury, což mj. znamená, že s ním není možné provádět některé operace, například změnu barvy pixelu, čtení barev pixelů atd. Naopak je podporováno hardwarově urychlené vykreslení obrázků, které je interně provedeno namapováním obrázku (tj. rastrové textury) na obdélník, který je následně promítnut do vykreslované scény. S využitím systému LÖVE je vykreslení obrázku jednoduché, neboť se používá již výše zmíněná přetížená funkce love.graphics­.draw(). Této funkci lze předat objekt představující obrázek, souřadnice obrázku (buď jeho středu nebo bodu zadaného metodou setCenter(x,y)) a volitelně také úhel natočení obrázku a změnu jeho měřítka. V demonstračním příkladu je poloha obrázku (tj. vlastního objektu) měněna na základě výpočtu, který simuluje trajektorii míčku v gravitačním poli (odrazy míčku jsou beze ztráty pohybové energie). S každým odrazem se také mění směr otáčení obrázku. Povšimněte si, že funkce love.graphics­.newImage() ve skutečnosti vrací objekt s několika metodami, které lze volat, například image:getWidth().

UX DAy - tip 2

lua13_09

Obrázek 9: Tato bitmapa je použita jako „sprite“ v sedmém demonstračním příkladu.

-----------------------------------------------------------------------
-- Seriál "Programovací jazyk Lua"
--
-- Ukázka načtení rastrového obrázku uloženého v externím souboru,
-- natočení obrázku při jeho vykreslování
-----------------------------------------------------------------------

-- rozměry okna
width = 450
height = 450

-- parametry obrázku
image = nil
x = 100
y = 100

-- posun obrázku a míra gravitace
dx = 1
dy = 1
gravity = 0.01

-- natočení obrázku a změna natočení (doprava, doleva)
angle = 0
rotation = 1

-- callback funkce zavolaná při inicializaci aplikace
function load()
    -- načíst standardní font
    local font = love.graphics.newFont(love.default_font, 20)
    love.graphics.setFont(font)
    -- načíst externí obrázek
    image = love.graphics.newImage("gnome-globe.png")
    love.graphics.setMode(width, height, false, false, 0)
end

-- callback funkce volaná průběžně ve chvíli, kdy je zapotřebí překreslit okno
function draw()
    -- vykreslit natočený obrázek
    love.graphics.draw(image, x + image:getWidth()/2, y + image:getHeight()/2, angle)
    love.graphics.setColor(255, 200, 255, 255)
    love.graphics.draw("Press escape to exit.", 30, 443)
end

-- průběžně volaná callback funkce
function update(dt)
    -- posun obrázku
    x = x + dx
    y = y + dy
    -- gravitace = zrychlení ve směru osy y
    dy = dy + gravity
    -- změna rotace obrázku
    angle = angle + rotation
    -- otestovat nárazy do okrajů okna
    if x < 0 then
        x = 0
        dx = -dx
        rotation = 1
    end
    if x > width - image:getWidth() then
        x = width - image:getWidth()
        dx = -dx
        rotation = -1
    end
    -- "podlaha" a "strop"
    if y < 0 then
        y = 0
        dy = -dy
    end
    if y > height - image:getHeight() then
        y = height - image:getHeight()
        dy = -dy
    end
    love.timer.sleep(10)
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 
lua13_10

Obrázek 10: Jeden snímek animace prováděné sedmým demonstračním příkladem.

Byl pro vás článek přínosný?

Autor článku

Vystudoval VUT FIT a v současné době pracuje na projektech vytvářených v jazycích Python a Go.