Allegro Vivace

Copyright © 1997-1999 George Foot

Preklad (Translation) © 2002 Anino Belan

Táto dokumentácia sa môže šíriť bezo zmeny v ľubovoľnej podobe bez obmedzenia. V prípade šírenia zmenených kópií je nutné uviesť, že došlo ku zmene.

Toto je siedma verzia, posledná zmena v origináli bola urobená 26 Augusta 1999


[ Ďalší: | Hore:Začiatok ]

Právne ustanovenia

Šírenie

Tento dokument môžete bezo zmien voľne šíriť v akejkoľvek forme. Nechcem za to nič, ale e-mail poteší.

Môžete tiež šíriť zmenené kópie v akejkoľvek forme, ak sa postaráte o to, aby bolo zrejmé, že tieto kópie sú zmenené.

Vyhlásenie

Neprijímam žiadnu zodpovednosť za akúkoľvek škodu, ktorú použitie alebo zneužitie tohto tutoriálu môže komukoľvek spôsobiť. Nie je zaručené, že príklady, ktoré sú súčasťou tohto balíka budú fungovať ani nie je zaručené, že sú bezpečné. Vo všeobecnosti by ste v žiadnom prípade nemali kompilovať programy, ktorým nerozumiete.


[ Ďalší: | Predošlý:Právne ustanovenia | Hore:Začiatok ]

1. Úvod


[ Ďalší: | Hore:Úvod ]

1.1 O tutoriáli

Tento tutoriál je dostupný v niekoľkých verziach - textový súbor, Info formát (ktorý funguje aj s RHIDE) a v zdrojovom tvare ako Texinfo. Zdrojová distribúcia obsahuje nástroje na vytvorenie iných formátov včítane DVI a Postscript-u, ktoré môžu byť vytlačené. Podpora pre HTML sa plánuje. (pozn. prekl. V slovenčine existuje len HTML verzia, ak z toho spravite aj nejaké iné, nech sa páči.)

K tutoriálu patrí aj súbor ukážkových programov, väčšinou napísaných Grzegorzom Adamom Hanciewiczom. Demonštrujú funkcie a techniky o ktorých sa hovorí v texte tutoriálu a sú hodnotným zdrojom informácií, ak sa vám nedarí niečo pochopiť.

Všetky časti Allegro Vivace - tutoriál v hlavných formátoch, zdrojová distribúcia tutoriálu a ukážkové programy - môžu byť stiahnuté jednotlivo alebo po skupinách z webovej stránky:

http://www.canvaslink.com/gfoot/vivace/

Ak ma chcete kontaktovať, moja e-mailová adresa je:

gfoot@users.sourceforge.net

Komentáre, návrhy a sťažnosti sú, ako vždy, veľmi cenené.


[ Ďalší: | Predošlý:O tutoriáli | Hore:Úvod ]

1.2 Zámer

Zámerom tohto tutoriálu je viesť nováčikov k programovaniu hier a Allegru s pomocou procesu vytvárania jednoduchej hry.


[ Ďalší: | Predošlý:Zámer | Hore:Úvod ]

1.3 Cieľová skupina

Ako bolo povedané vyššie, tento tutoriál je určený pre nováčikov v programovaní hier a Allegre. Nie je určený pre úplných začiatočníkov v jazyku C. Tým myslím, že sa predpokladá istá znalosť a skúsenosti s používaním jazyka C a očakávam, že už máte funkčný kompilátor, ktorý podporuje Allegro.


[ Ďalší: | Predošlý:Cieľová skupina | Hore:Úvod ]

1.4 Požiadavky

Pod funkčným kompilátorom myslím taký, ktorý vie kompilovať programy v jazyku C a urobiť z nich programy spustiteľné na vašej platforme. Tiež samozrejme musí byť schopný pracovať s knižnicou Allegro, ale tomu sa budeme venovať nižšie.

Ak váš kompilátor nefunguje správne, potrebujete problém odstrániť a toto nie je miesto, kde by som túto problematiku mal rozoberať do detailov. Ak používate djgpp, prečítajte si readme.1st a djgpp FAQ; ak to nepomôže, konajte podľa inštrukcií, ktoré sú vo FAQ ohľadom poslielania otázok na mailing list a usenetovú skupinu venovanú djgpp. Ak používate verziu djgpp v2.00, vrelo odporúčam upgrade.

Tento tutoriál bol poslednýkrát zmenený, keď bola na svete verzia Allegro 4.0, ale nepokrýva ešte úplne všetky nové vlastnosti. Napriek tomu je Allegro 4.0 hlavnou verziou, pre ktorú je tento tutoriál písaný a čoskoro ho budem aktualizovať.

Väčšina vecí bude fungovať aj pre staršie verzie, ale niektoré nemusia -- taký je život. Urobím, čo budem môcť, aby som opísal zmeny potrebné pre Allegro 3.x. Aby mohli byť skompilované verzie Allegra staršie než 3.0, potrebujú C++. Nemusíte v C++ programovať, ale váš počítač ho musí podporovať. Ak nepodporuje, buď nainštalujte najnovšiu verziu Allegra (odporúča sa, sú samozrejme veľmi stabilné) alebo nainštalovať podporu C++ (pre djgpp pozrite readme.1st) Ak nenainštalujete novú verziu allegra, budete musieť zmeniť časti kódu ukážok z tohto tutoriálu.

Dúfame, že novšie verzie zmienených balíkov sú spätne kompatibilné so staršími; ak nie, je opäť na vás, aké zmeny urobíte.

Budete potrebovať program GNU make, napriek faktu, že je v súbore readme.1st z djgpp označený ako nepovinný. Je to náramne užitočný balík a aj Allegro aj príklady sprevádzajúce tento tutoriál sú navrhnuté tak, aby sa dali (bezbolestne) skompilovať s jeho pomocou. Najnovšia verzia pre djgpp (počas písania) je mak3761b.zip, ktorá patrí k verzii djgpp 2.01 a nefunguje správne s verziou 2.00.

Väčšina ľudí pri programovaní používa jeden z troch spôsobov: niekto používa IDE ako napríklad RHIDE, niekto má radšej Emacs a iní používajú jednoduché textové editory a utilitu Make. Tento tutoriál sa nesnaží vnútiť vám niektorý z týchto systémov; programujte v tom prostredí, ktoré máte najradšej. Príklady môžu byť skompilované utilitou Make, ale jednoducho pre ne môžete vytvoriť RHIDE projekty.


[ Predošlý:Požiadavky | Hore:Úvod ]

1.5 Predtým, než začneme ...

Tento tutoriál je určený ľuďom s rôznymi programátorskými schopnosťami, od tých, ktorí sa ešte len učia až po tých, ktorí už majú množstvo skúseností. Ak máte pocit, že niektorá časť zachádza priveľmi do podrobností, tak to všetko nečítajte -- táto časť je najskôr určená ľuďom s nižším štandardom, než máte vy a stačí, keď ju preletíte, aby ste sa zoznámili s príkazmi, ktoré sú v nej uvedené.

Nadpisy niektorých sekcií som uzavrel medzi [ a ]. Znamená to, že kapitoly obsahujú témy pre pokročilých, komplikované, ktoré je namáhavé programovať alebo sú len okrajové. Ak nechcete, nečítajte to.

Veľkým problémom pri písaní hier je inšpirácia -- ak nemáte v hlave nič, čo by ste chceli napísať, tak to ani nenapíšete. V prvom momente vás pravdepodobne buď nenapadne nič, alebo dostanete spústu nápadov, ktoré ale budú vyzerať príliš zložito na to, aby sa dali naprogramovať.

Ak vás nenapadne nič, relaxujte a čítajte tento tutoriál. Použite ukážkové príklady ako základ, pridajte k nim nové veci a prispôsobte si ich podľa svojej potreby. Počas čítania získate lepší prehľad o tom, čo sa dá urobiť a čo nie. Mrknite sa aj na komerčné hry -- skúste, či viete na prvý pohľad povedať, ako robia to, čo robia a potom to skúste vylepšiť. Nehovorte si, že je to niečo, čo nikdy nezvládnete -- ako budete získavať skúsenosti, budete prichádzať na rôzne spôsoby, ako veci spraviť. Pamätajte si, že ak to autori nejako napísali, musí sa to dať urobiť.

Ak sú vaše nápady príliš zložité, to čo bolo napísané vyššie platí tiež -- čím viac hier napíšete, tým lepší v tom budete a tým viac hier budete schopní napísať. V takmer každom prípade je pre začiatočníkov najlepšie, keď začnú s jednoduchými vecami a urobia rýchle fungujúcu hru a potom na tom základe stavajú. Táto technika má svoje nevýhody, ale nateraz bude celkom dobrá.

A nakoniec -- hry musia prinášať zábavu; je to ich cieľom. Nemôžete napísať peknú hru, ak sa netešíte z toho, že ju píšete. Ak sa vám bude tento tutoriál zdať nudný, tak som ho napísal zle a treba ho zmeniť, dajte mi, prosím, vedieť. Tento tutoriál je pre vás a vám podobných; užite si ho!


[ Ďalší: | Predošlý:Úvod | Hore:Začiatok ]

2. Ako získať, nainštalovať a používať Allegro


[ Ďalší: | Hore:Ako získať, nainštalovať a používať Allegro ]

2.1 Čo je to Allegro?

Allegro je knižnica určená na programovanie hier, ktorej hlavným autorom a koordinátorom je Shawn Hargreaves. Je to veľmi dobrá knižnica; je rýchla, má množstvo vlastností a od verzie 4 predstavuje skvelú multiplatformnú vrstvu, ktorá vám umožňuje písať kód, ktorý bude fungovať aj na systémoch, ktoré ste v živote nevideli. Ak chcete podrobnejšie informácie, pozrite si domovskú stránku projektu:

http://www.talula.demon.co.uk/allegro/


[ Ďalší: | Predošlý:Čo je to Allegro? | Hore:Ako získať, nainštalovať a používať Allegro ]

2.2 Kde získať Allegro

Allegro je dostupné v prvom rade na linkách uvedených na jeho domovskej stránke (pozri Čo je to Allegro?) a tiež ako súčasť distribúcie djgpp na ftp.simtel.net. Verzie, ktoré sú pribalené k djgpp sú zvyčajne posledné stabilné verzie. Ak chcete najnovšiu vývojovú verziu, musíte ísť na stránku allegra.

Odporúčam vám stiahnuť si vývojovú verziu, pretože tento tutoriál bol písaný s prihliadnutím na ňu. Je celkom stabilná. Počas písania tohto tutoriálu bola vonku už niekoľko týždňov verzia 3.9.25 a musím povedať, že som s ňou nemal žiadne väčšie problémy. (pozn.prekl. Už pomerne dlho sú na svete aj stabilné verzie radu 4. Ja osobne som mal lepšie skúsenosti so stabilnými, než s vývojovými verziami.)


[ Ďalší: | Predošlý:Kde získať Allegro | Hore:Ako získať, nainštalovať a používať Allegro ]

2.3 Ako nainštalovať Allegro

Tieto inštrukcie majú slúžiť len ako stručný návod - postup sa líši od platformy k platforme a je vysvetlený oveľa podrobnejšie v zodpovedajúcich readme súboroch, ktoré sú vo verzii, ktorú ste si stiahli.

Vytvorte pre Allegro adresár kdekoľvek chcete a rozbaľte do neho Allegro tak, aby ste zachovali jeho adresárovú štruktúru (parameter -d pre pkunzip pod DOSom; Unixové rozpakovávače to spravia automaticky a žiadne prepínače nie sú nutné). Uistite sa, že bol vytvorený napríklad adresár s názvom docs.

Keď to bude hotové, choďte do adresára kde Allegro máte. Ak používate vývojovú verziu, treba spustiť fix, ktorý distribúciu upraví pre vašu platformu. Pre DOS a djgpp napíšte:

fixdjgpp

Pre Unixové systémy napíšte:

chmod 700 fixunix.sh
./fixunix.sh

Potom bude treba skonfigurovať kompiláciu. Pozrite si readme súbor pre vašu verziu a platformu, aby ste sa dozvedeli detaily.

Aby ste skompilovali knižnicu a nástroje, zadajte 'make':

make

To by potom malo bežať samo a skončiť krátkou správou, ktorá vám povie, že to funguje. Ak sa tak nestane, prečítajte si dokumentáciu k Allegru ak ste tak ešte nespravili (v adresári v ktorom máte Allegro súbory allegro.txt, faq.txt a čiastočne aj readme.txt).

Nakoniec potrebujete nainštalovať knižnicu a hlavičkové súbory, aby ich vaše programy mohli jednoducho používať:

make install

Pod Unixom budete pravdepodobne potrebovať práva roota, aby ste to mohli urobiť.

Ak ste niekde zamrzli, skúste sa popýtať na Allegrovskej konferencii -- allegro@canvaslink.com. Ak potrebujete informáciu o tom, ako sa do konferencie prihlásiť, pozrite si časť Contact Info na konci Allegrovského súboru readme.txt. Väčšina ľudí píše odpovede iba do konferencie, takže ak nie ste prihlásení, v maili to uveďte a poproste ľudí, aby poslali Cc: aj vám.


[ Ďalší: | Predošlý:Ako nainštalovať Allegro | Hore:Ako získať, nainštalovať a používať Allegro ]

2.4 Testovanie inštalácie a kompilácia ukážkových programov

Inštalácia je v podstate samotestujúca, pretože sa pri nej kompilujú všetky Allegrovské vzorové príklady a nástroje. Napriek tomu som pre úplnosť zaradil krátky program na kontrolu toho, či nie sú problémy s nájdením hlavičkového súboru a knižnice. Ak chcete test spustiť, vojdite do adresára examples a napíšte:

make test

Ak tam nie sú žiadne chyby, skvelé - môžete skompilovať ostatné vzorové programy z Vivace znovuspustením make bez argumentu test.

Ak ale kompilátor nemôže nájsť allegro.h, liballeg.a alebo -lalleg, inštalácia neprebehla správne a vy budete musieť znovu podrobnejšie prečítať dokumentáciu.


[ Predošlý:Testovanie inštalácie | Hore:Ako získať, nainštalovať a používať Allegro ]

2.5 Použitie Allegra

Táto časť stručne opakuje veci, ktoré sú v dokumentácii Allegra

Za prvé, každý zdrojový kód, ktorý používa funkcie alebo procedúry z Allegra, musí obsahovať #include <allegro.h>. Dajte to až za všetky štandardné hlavičkové súbory -- konkrétne musí ísť až za #include <stdio.h> -- aby sa predišlo kolíziam medzi hlavičkovými súbormi.

Za druhé treba zavolať allegro_init() krátko po štarte vášho programu. Inicializuje to niektoré dôležité všeobecné veci, ktoré sú pre knižnicu podstatné. Tiež budete potrebovať inicializovať jednotlivé subsystémy, ktoré budete používať, ale k tomu sa vrátime neskôr. Odporýčam, aby bola allegro_init() prvá vec, ktorú spustíte vo vašej funkcii main.

Za tretie, musíte [V Allegro 4 a vývojových verziach] napísať END_OF_MAIN() za uzatváraciu zátvorku vašej funkcie main. Ak to nespravíte, váš program sa nebude správne linkovať s knižnicou Allegro.

A nakoniec, pri linkovaní musíte prilinkovať knižnicu Allegro. Spraví sa to tak, že na koniec vášho linkovacieho príkazu pridáte -lalleg. (pozn. prekl. V Linuxe treba pridať aj -lalleg_unsharable) Linkovací príkaz je ten, ktorý vám vyrobí spustiteľný súbor; môže to byť jediný príkaz, ktorý používate. Ale počas tohto tutoriálu vás budem povzbudzovať, aby ste vaše projekty rozdelili na viacero súborov.

Ako príklad tohto všetkého si pozrite súbor test.c v podadresári examples/test/ex_1 hlavného adresára tutoriálu.

V každom prípade dosť už o tom -- poďme sa mrknúť na zaujímavejšie veci.


[ Ďalší: | Predošlý:Ako získať, nainštalovať a používať Allegro | Hore:Úvod ]

3. Základná štruktúra hry


[ Ďalší: | Hore:Základná štruktúra hry ]

3.1 Čo všetko musí hra robiť?

Väčšina hier je postavená na rovnakom základnom princípe. Sú samozrejme výnimky a ja nie som profesionály programátor hier, ale podľa mojich skúseností nasledovný systém funguje veľmi dobre.

  1. Inicializácia

    Na začiatku potrebujeme dostať hru do nejakého známeho stavu a zakaždým sa to deje tým istým spôsobom. Napríklad môžete chcieť, aby hráč začínal vždy v strede obrazovky, vytvoriť bludisko alebo vygenerovať náhodnú mapu. Všetko toto sa udeje tu.

  2. Hlavný cyklus hry

    Hra potrebuje zabezpečiť, aby sa veci diali znova a znova kým hráč neumrie, nevyhrá, nevzdá sa alebo niečo také. To sa nám deje v hlavnom cykle hry. Ten má tri hlavné zložky:

    1. Vstup -- získanie vstupu od hráča
    2. Spracovanie -- zmena stavu vecí, odozva na vstup
    3. Výstup -- odoslanie informácií späť hráčovi, zvyčajne vykreslením na obrazovku, ale niekedy aj inými spôsobmi, napríklad prehratím zvukov
  3. Záver hry

    Keď hra skončí, môžme potrebovať urobiť ešte nejaké ďalšie veci ako napríklad povedať hráčovi, prečo hra skončila, zmeniť tabuľku najlepších výsledkov alebo niečo na ten spôsob.

V skutočnosti je zvyčajne vyššie uvedená postupnosť súčasťou väčšieho cyklu, takže hra sa môže hrať znova a znova bez toho, aby ste sa medzitým vždy museli vracať do OS a pred vonkajším cyklom je tiež nejaká inicializácia a po ňom nejaký upratovací kód, napr. nahrávanie tabuľky najlepších výsledkov z disku na začiatku a jej znovuuloženie na konci. Pre začiatok ale budeme robiť len jeden beh hry za spustenie.


[ Ďalší: | Predošlý:Čo všetko musí hra robiť? | Hore:Základná štruktúra hry ]

3.2 Návrh štruktúry

Takto vyzerá vyššie opísaná štruktúra v jazyku C:

#include <allegro.h>
#include "game.h"

int end_game;                   /* príznak, ktorý nastavíme, ak chceme skončiť */

int main (void)
{
    allegro_init();             /* inicializuje knižnicu Allegro */
    init();                     /* inicializuje hru */

    end_game = 0;               /* príznak, že končíme */
    do {                        /* cyklus */
        input();                /* vezmi vstup */
        process();              /* spracuj ho */
        output();               /* vyrob výstup */
    } while (end_game == 0);    /* kým je príznak v poriadku */

    shutdown();                 /* zhoď veci, ktoré potrebuješ */
    allegro_exit();             /* pre istotu */
    return 0;                   /* povedz operačnému systému, že je všetko O.K. */
}
END_OF_MAIN()

V uvedenom príklade je game.h hlavičkový súbor, v ktorom sú deklarácie funkcií init, input, process, output a shutdown, ktoré musia byť definované niekde inde v projekte.


[ Predošlý:Návrh štruktúry | Hore:Základná štruktúra hry ]

3.3 Projekt rozdelený do súborov

Už som spomínal, že vás budem povzbudzovať k tomu, aby ste svoj zdrojový kód rozdelili na viacero súborov. Mnohým ľuďom sa to zo začiatku zdá ťažké a nevedia pochopiť, na čo je to dobré. V tejto sekcii tutoriálu mám v úmysle vyjasniť to a ukázať, ako má podľa mňa vyzerať dobrý sytém organizácie projektu.

Objasnenie

Takže najprv, prečo je projekt rozdelený do viacerých súborov dobrá vec? Veď to vyzerá, že len pribúdajú problémy -- vyžaduje to vytvárať hlavičkové súbory, robiť externé deklarácie a máte pocit, že musíte prehľadať viacero súborov, kým nájdete funkciu, ktorú hľadáte.

V skutočnosti má ale tento prístup mnohé pozitíva. Keď vo svojom kóde zmeníte jeden riadok, gcc, aby vytvorilo nový spustiteľný súbor, prekompiluje všetko. Ale ak je váš projekt rozdelený do viacerých súborov a vy zmeníte jeden z nich, gcc už má k dispozícii objektové súbory, ktoré vygenerovalo z vašich zdrojových súborov, keď ste ich kompilovali posledne a teraz potrebuje prekompilovať iba súbor, ktorý ste zmenili. Pri väčších projektoch to môže znamenať rozdiel v dĺžke (5 minút alebo viac, v závislosti na rýchlosti počítača) rekompilácie a dvadsaťsekundovou úpravou.

Pri troche organizáce môže rozdelenie projektu do súborov značne uľahčiť hľadanie kusu kôdu, ktorý práve zháňate. Je to jednoduché -- rozdeľte kód medzi súbory podľa toho, čo robí. Ak potom hľadáte funkciu, ktorá kreslí hráča, viete, že bude v zdrojovom súbore s grafikou.

Okrem toho, ak je váš program dostatočne modulárny a zdieľánie medzi súbormi je obmedzené na minimum, ľahšie sa hľádajú chyby. Ak majú k danej premennej prístup iba niektoré programy, vadná hodnota musí byť spôsobená chybou v jednom z týchto súborov.

Navrhovaný systém

To, čo bude nasledovať je len môj skromný názor; rôzni ľudia si môžu chcieť zariadiť veci rôzne. Ale keďže som zistil, že nasledujúce pravidlá sú užitočné, odporúčam vám dodržiavať ich.

Dúfam, že ste pochopili výhody rozdelenia vášho projektu, ale ak nechcete, rozhodne ho nechajte v jednom súbore. Príklady, ktoré budem používať neskôr, budú štandardne mnohosúborové, založené na odporúčaniach podaných vyššie a na skôr diskutovanej štruktúre hry (pozri Návrh štruktúry), takže budete mať veľá možností oceniť výhody tohto prístupu.

Ak ste z toho stále zmätení, pozrite sa, prosím, na článok, ktorý som napísal pre elektronický magazín C-Scene:

http://www.cscene.org/

Článok je v časti 2 a volá sa Multi-file projects and the GNU Make utility. Opakuje informácie spomenuté tu, lenže podrobnejšie a vysvetľuje začiatočníkom v tejto technike niektoré bežné problémy.


[ Ďalší: | Predošlý:Základná štruktúra hry | Hore:Začiatok ]

4. Úvod do grafiky

Poznámka

Táto časť predpokladá, že ste oboznámení s pojmami programovania v grafike ako napríklad súradnicový systém, rozlíšenie atď. Ak nie ste, aj tak si ju skúste prečítať; ak nebudete rozumieť, mali by ste si pozrieť nejakú knižku o počítačovej grafike.


[ Ďalší: | Hore:Úvod do grafiky ]

4.1 Nastavenie grafického režimu

Aby ste dostali počítač do grafického režimu, použite túto funkciu:

int set_gfx_mode (int card, int w, int h, int v_w, int v_h);

Miesto card dajte jednu z GFX_* konštánt, bežne GFX_AUTODETECT. w a h je minimálna šírka a výška obrazovkového priestoru, ktorú požadujete; v závislosti na režime, ktorý zvolíte, môžete dostať kúsok väčšiu plochu. Ako v_w a v_h dajte zatiaľ 0.


[ Ďalší: | Predošlý:Nastavenie grafického režimu | Hore:Úvod do grafiky ]

4.2 Kreslenie objektov


[ Ďalší: | Hore:Kreslenie objektov ]

4.2.1 Štruktúra BITMAP

Grafické funkcie Allegra nie sú obmedzené na zápis na obrazovku; zapisujú do bitmáp. Bitmapa môže byť obrazovka, môže to byť blok pamäte alebo to môže byť podbitmapa (časť inej bitmapy). Keď voláte grafické funkcie Allegra, dáte im ako parameter smerník na štruktúru BITMAP, ktorý obsahuje informácie o bitmape, do ktorej chcete zapisovať.

Najlepšie bude, keď si zvyknete na váš smerník pozerať jednoducho ako na odkaz na bitmapu -- je to niečo ako identifikačne číslo. Allegro na začiatku určí jeho hodnotu a vy ho potom uvádzate grafickým procedúram, aby ste im dali vedieť, kde majú kresliť.

Jediné zaujímavé časti štruktúry BITMAP aspoň pokiaľ sme sa zaujímali sú polia w a h fields; je v nich uložená šírka a výška bitmapy v pixeloch.

Allegro definuje premennú screen ako smerník na štruktúru BITMAP reprezentujúcu obrazovku, takže napríklad:

clear_to_color (screen, 5);

vyplní obrazovku farbou 5 (často fialová).


[ Ďalší: | Predošlý:Štruktúra BITMAP | Hore:Kreslenie objektov ]

4.2.2 Kreslenie pixelov

Na nakreslenie pixelu použite funkciu Allegra:

void putpixel (BITMAP *bmp, int x, int y, int color);

Toto nastaví pixel bitmapy bmp, ktorý sa nachádza na súradniciach x,y na farbu color. Jednoduché.

Ako príklad použitia set_gfx_mode, clear_to_color a putpixel si pozrite ukážkový program examples/chap_04/ex_2_2.


[ Ďalší: | Predošlý:Kreslenie pixelov | Hore:Kreslenie objektov ]

4.2.3 Nejaké ďalšie primitívy

Funkcie ako putpixel sa volajú grafické primitívy, pretože sú to základné grafické výstupné funkcie.

Niektoré ďalšie jednoduché grafické funkcie sú:

void vline(BITMAP *bmp, int x, int y1, int y2, int color);
void hline(BITMAP *bmp, int x1, int y, int x2, int color);
void line(BITMAP *bmp, int x1, int y1, int x2, int y2, int color);
void triangle(BITMAP *bmp, int x1, y1, x2, y2, x3, y3, int color);
void rect(BITMAP *bmp, int x1, int y1, int x2, int y2, int color);
void rectfill(BITMAP *bmp, int x1, int y1, int x2, int y2, int color);
void circle(BITMAP *bmp, int x, int y, int radius, int color);
void circlefill(BITMAP *bmp, int x, int y, int radius, int color);

Skúste pozmeniť ukážkový program z predošlej sekcie (examples/chap_04/ex_2_2) tak, aby ste niektoré z nich použili. Informácie o nich nájdete v súbore allegro.txt v podadresári docs vášho adresára obsahujúceho Allegro.

Ak by ste s tým mali problémy, pozrite sa na príklad examples/chap_04/ex_2_3.


[ Predošlý:Nejaké ďalšie primitívy | Hore:Kreslenie objektov ]

4.2.4 Písanie textu

Na písanie textu na obrazovku použite nasledovné funkcie:

void textout(BITMAP *bmp, FONT *f, unsigned char *s, int x, y, int c);
void textout_center(BITMAP *bmp, FONT *f, unsigned char *s, int x, y, int c);
void textprintf(BITMAP *bmp, const FONT *f, int x, y, int c, const char *fmt, ...);

bmp je samozreme bitmapa, na ktorú chcete písať.

Štruktúra FONT je definovaná v allegro.h a používa sa na odkazovanie na rôzne fonty načítané v pamäti. Väčšinou ju budete používať rovnako, ako používate štruktúru BITMAP -- poväčšine len budete smerníky na ne dávať rôznym funkciam ako parametre a nebudete potrebovať vedieť nič o tom, čo sa vo vnútri štruktúry nachádza.

Môžete si vytvoriť vlastné fonty, použiť fonty z distribúcie GRX (iná grafická knižnica), ktoré pôvodne pochádzajú z XFree86 (okienkoidný systém pre Unix), alebo skonvertovať fonty z Windowsovského TTF formátu. V každom z týchto prípadov musíte vedieť používať allegrovský Grabber a datasúbory. Ak chcete viac informácií môžete si pozrieť súbor grabber.txt, ale o práci s datasúbormi budeme hovoriť v tomto tutoriáli až oveľa neskôr. Zatiaľ môžete do fukcií písať font; je to FONT * deklarovaný v allegro.h a odkazuje na 8x8 BIOS font.

s je reťazec, ktorý chcete vypísať, x a y sú súradnice a c je farba. Ak c je -1 a font je proporcionálny, Allegro použije informáciu o farbe obsiahnutú vo fonte, takto sa dajú použiť viacfarebné fonty.

Funkcia textout_center je takmer rovnaká; jediný rozdiel je, že text bude vycentrovaný na súradnicu x.

Funkcia textprintf je vhodne obalený textout, ktorý prijíma formátovací reťazec tým istým spôsobom ako printf. textout je rýchlejší, ale textprintf univerzálnejší.

Farba pozadia textového výstupu sa dá nastaviť funkciou

void text_mode(int mode);

kde mode je nová farba pozadia. Ak je mode záporný, pozadie sa nekreslí a bude viditeľný podklad, ktorý tam bol predtým.

Pohrajte sa s tým sami, alebo sa pozrite na examples/chap_04/ex_2_4, kde nájdete ukážku toho, ako sa to používa.


[ Ďalší: | Predošlý:Kreslenie objektov | Hore:Úvod do grafiky ]

4.3 Práca s paletou

Poznámka

V prvom rade treba podotknúť, že palety majú význam iba v 8-bitových (256 farieb) režimoch. Môžete nastaviť režimy aj s vyššími farebnými hĺbkami; ak to urobíte, obsah tejto časti pre vás nebude mať význam. Ak chcete informáciu o vyšších farebných hĺbkach, pozrite si dokument Pot of Gold od Shawna Hargreavesa na ktorý je linka z allegrovských webových stránok odkazujúcich na dokumentáciu.


[ Ďalší: | Hore:Práca s paletou ]

4.3.1 Čo je to paleta

Keď chcete kresliť na obrazovku v 8-bitovom režime, musíte určiť číslo farby od 0 do 255. Zvyčajne prvých šestnásť farieb býva: čierna, modrá, zelená, tyrkysová, červená, fialová, hnedá, svetlošedá, tmavošedá, ..., svetlofialová, žltá, biela (ale nemusí to tak byť vždy).

Zvyšných 240 farieb nemusí myť vždy rovnakých. Pred tým, než ich použijete, môžete počítaču povedať, aká farba sa má objaviť na obrazovke, keď nastavíte pixelom určité číslo farby. Najbezpečnejšie je nastaviť všetky farby, ktoré budete používať, včetne prvých šestnástich; tak si budete istí, že skutočne budú vyzerať tak, ako chcete, aby vyzerali.

Čísla farieb, ktoré budete uvádzať grafickým funkciam budem nazývať logické farby a farby k ním priradené, ktoré sa objavia na obrazovke fyzické farby. A teda potrebujeme funkcie, ktoré priradia logickým farbám fyzické.

Logické farby sú vyjadrené číslami od 0 do 255. Každá možná fyzická farba je vyjadrená tromi číslami, každé z nich z intervalu od 0 do 63. Tieto čísla reprezentujú veľkosť červenej, zelenej a modrej zložky vo farbe. 63 je samozrejme maximálna veľkosť. Na reprezentáciu fyzických farieb sa používa štruktúra RGB definovaná v allegro.h:

typedef struct RGB
{
    unsigned char r, g, b;
} RGB;

Netreba hovoriť, že r, g and b sú intenzity červenej, zelenej a modrej vo farbe, ako je to popísané vyššie.


[ Ďalší: | Predošlý:Čo je to paleta | Hore:Práca s paletou ]

4.3.2 Zmena fyzického významu jednej logickej farby

Ak chcete zmeniť fyzický význam logickej farby, použite funkciu

void set_color (int index, RGB *p);

index je číslo logickej farby a p je smerník na RGB záznam, ako je popísané vyššie.

Ukážkový program examples/chap_04/ex_3_2 ukazuje použitie tejto funkcie - ako stláčate klávesy, farba 0 (farba pozadia) nadobúda rôzne hodnoty. Ak chcete skončiť, stlačte <ESC>.

Pripomeňme, že je tu jedna drobná komplikácia -- ak sa pokúšate meniť farby rýchle za sebou, mali by ste medzi zmenami volať funkciu Allegra vsync (ktorá tiež pridá isté zdržanie). Hlavný dôvod je ten, že niektoré grafické karty nemajú radi, ak im brnkáte po palete, keď sa snažia kresliť po obrazovke, niekedy potom zobrazia nesprávne farby a vyzerá to ako zasnežené. vsync počká, kým monitor dokreslí obrazovku, čo problém odstráni. O vsync si neskôr ešte niečo povieme.


[ Ďalší: | Predošlý:Zmena položky palety | Hore:Práca s paletou ]

4.3.3 Zmena celej palety

Ak chceme zmeniť celú paletu naraz, mohli by sme opakovane volať set_color, ale bolo by to pomalé a nešikovné. Miesto toho allegro.h definuje typ PALETTE ako pole 256 RGB záznamov, jeden pre každú logickú farbu. V tomto poli môžete vytvoriť celú vlastnú paletu a potom ju nastaviť jedným volaním funkcie

void set_palette (PALETTE p);

Príklad examples/chap_04/ex_3_3 demonštruje túto funkciu tak, že nakreslí dúhu farieb a potom prepne paletu na čiernobielu.


[ Predošlý:Zmena celej palety | Hore:Práca s paletou ]

4.3.4 Stmievanie a rozsvecovanie

V málofarebných režimoch (napr. 256 farieb) je to jednoduché: ak chcete spraviť stmievanie, spravte paletu tmavšiu a tmavšiu až bude všetko čierne, pri rozsvecovaní to urobte naopak.

Allegro má niekoľko funkcií, ktoré to robia; najzákladnejšie sú:

void fade_in (PALETTE p, int speed);
void fade_out (int speed);

Poznamenajme, že fade_in chce, aby ste jej povedali, do akej palety treba rozsvietiť; fade_out to nepotrebuje, pretože predpokladá, že sa bude stmievať z aktuálnej palety.

speed je rýchlosť stmievania v intervale od 1 do 64, kde 64 znamená okamžitú zmenu.

examples/chap_04/ex_3_4 ukazuje použitie týchto funkcií.


[ Predošlý:Práca s paletou | Hore:Úvod do grafiky ]

4.4 Jednoduchá animácia


[ Ďalší: | Hore:Jednoduchá animácia ]

4.4.1 Čo je to animácia?

Animácia je, keď spôsobíte, že to vyzerá, že sa veci hýbu. Môže to znamenať, že vám po obrazovke pobehuje pixel, ale zvyčajne sa tým myslí, že dookola meníte výzor niečoho tak, že vyzerá, že sa to hýbe.

Teraz sa pozrieme iba na to, ako pohybovať po obrazovke s jednoduchými vecami; druhá interpretácia bude rozobratá neskôr, keď sa pozrieme na sprity.


[ Ďalší: | Predošlý:Čo je to animácia | Hore:Jednoduchá animácia ]

4.4.2 Ako urobiť, aby veci vyzerali, že sa hýbu

Najjednoduchší spôsob, ako vytvoriť zdanie, že sa niečo hýbe, je zmazať to na tom mieste, kde to je a znovu to vykresliť na obrazovku niekde inde. Je otázkou pre filozofov, či to je skutočne pohyb; ale keďže to aj tak neexistuje, nie je veľmi dôvod trápiť sa tým. A tak budeme hovoriť, že sa to pohlo.

Na to, aby vyplnený kruh prešiel cez obrazovku, môžete urobiť niečo takéto:

int x;
install_timer();                           /* potrebuje to `rest' */
circlefill (screen, 0, 100, 5, 15);        /* nakreslí prvýkrát */
for (x = 1; x < 320; x++) {               /* cyklus cez obrazovku */
    rest (10);                             /* spomaľ */
    circlefill (screen, x - 1, 100, 5, 0); /* vymaž tam, kde to bolo */
    circlefill (screen, x, 100, 5, 15);    /* nakresli na novom mieste */
}

Vyskúžajte, či to funguje. Teraz to skúste s väčším polomerom (povedzme 50) a pozrite sa, čo sa stane.


[ Predošlý:Ako urobiť, aby veci vyzerali, že sa hýbu | Hore:Jednoduchá animácia ]

4.4.3 Zníženie blikania

No áno - začalo to trochu blikať. Ak nezačalo, skúste to s väčším kruhom. Ak stále nezačalo, hmm, začne ak použijete režim obrazovky s vyšším rozlíšením (vyskúšajte si to). Ak to ani tak nezačalo blikať, potom gratulujem, máte superpočítač.

My, obyčajní smrteľníci, ale s tým blikaním niečo budeme musieť urobiť. Je viacero možných prístupov; niektoré sú komplikovanejšie než iné, niektoré efektívnejšie a niektoré sú jednoducho dobré, pretože nežerú toľko procesorového času (a teda hry môžu bežať s vyššou obnovovacou frekvenciou).


[ Ďalší: | Hore:Zníženie blikania ]

4.4.3.1 Synchronizácia na zatemnenie

Toto je najdôležitejšia vec, ktorú treba urobiť. Zatemnenie je časový úsek, počas ktorého sa váš monitor pripravuje na vykreslenie nového rámca. Počas tohto času sa nič na obrazovke nedeje, takže ak stihneme vymazať objekt a znovu ho nakresliť, kým monitor začne s novým rámom, je po probléme.

Funkcia vsync čaká, kým sa nezačne ďalšie zatemnenie. Keď to nastane, treba prekresliť obrazovku tak rýchlo, ako je to možné. Takže nahraďte rest(10) funkciou vsync() a skúste to znovu. Oveľa lepšie.

Pripomeňme, že zatemnenie je svojim spôsobom niečo ako časovač. Objavuje sa v pravidelných intervaloch (pre daný grafický režim; medzi jednotlivými režimami sa to líši). Z toho vyplýva, že (okrem toho, že ho aj tak používame na synchronizáciu) to môže byť užitočný spôsob, ako regulovať rýchlosť hry. V kapitole 9 sa ale dajú nájsť aj lepšie spôsoby, ako sa to dá urobiť - budeme tam hovoriť o systéme časovačov, ktorý Allegro obsahuje.


[ Ďalší: | Predošlý:Synchronizácia na zatemnenie | Hore:Zníženie blikania ]

4.4.3.2 Maximalizácia času vykreslenia

Ak toho máme na kreslenie príliš veľa, tak to všetko nezvládneme počas jedného zatemnenia. Potrebujeme teda blikanie znížiť nejakým iným spôsobom. Predstavte si situáciu, že máme množstvo kruhov, a predtým, než ich prekreslíme, ich všetky vymažeme. Budú vymazané pomerne dlhý čas; je preto veľká ôravdepodobnosť, že sa monitor bude snažiť zobraziť časť obrazovky vtedy, keď ešte nebudú vykreslené.

Tento problém môžeme vyriešiť tak, že ich budeme prekreslovať jeden po druhom. Môže to pomôcť znížiť blikanie, ale môže to viesť k iným problémom. To, čo je podstatné ale je, že je rozumné snažiť sa o to, aby boli veci vykreslené čo najdlhšie.

Ak napríklad zmažete všetky vaše objekty a až potom zisťujete, kde budú vykreslené, nie sú na obrazovke po celý čas výpočtu. Ak spravíte výpočty pred tým, než ich zmažete, budú na obrazovke dlhšie a čas, keď sú preč, sa skráti.


[ Ďalší: | Predošlý:Maximalizácia času vykreslenia | Hore:Zníženie blikania ]

4.4.3.3 Optimalizácia poradia vykreslenia

Ako sme poznamenali vyššie, niekedy nestihneme celé vykreslenie počas jedného zatemnenia. Čo sa stane potom? Monitor začne prekreslovať obrazovku z hora dole. Takže ak máme nakreslenú iba časť, je lepšie, ak je horná časť obrazovky v poriadku, keď ju monitor začne prekreslovať.

V podstate ide o to, aby práve nebolo vymazané niečo, čo sa monitor pokúša zobraziť. Žiaľ, ak nemáme k dispozícii časovač s veľmi vysokým rozlíšením, nevieme povedať, ktorá časť obrazovhy sa práve prekresľuje. A ak taký časovač máme, má to zas iné tienisté stránky.

Rozumné môžu byť rôzne poradia vykresľovania. Môžete vykresľovať veci od hora nadol. Toto okrem iného urýchli prekresľovanie rámcov v režimoch s vyšším rozlíšením/farebnou hĺbkou zredukovaním prepínaní rozsahu videa. (??? This also improves the frame redraw speed in higher resolution/colour depth modes by reducing video bank switches.) Tiež môžete zvážiť, aké zložité sú jednotlivé objekty a začať prekresľovanie od jednoduchších.

Iná technika je vykreslovanie zdola nahor. Môže to znieť zvláštne, ale ak to tak spravíte, zatemnenie stihnete len raz a to na kratučký moment. Ak budete používať tento systém, dôjde asi k nejakému blikaniu, ale pravdepodobne to nebude až také tragické.

Môžete si to predstaviť ako rýchlu jazdu autom na diaľnici, počas ktorej sa pozeráte bočným oknom. (Uistite sa, že šoféruje niekto iný.) Snažíte sa mať dobrý výhľad na živý plot. Autá, ktoré idú tým istým smerom ako vy, majú nízku relatívnu rýchlosť a teda stoja v ceste vášmu výhľadu na živý plot dlhšiu dobu. Zato autá ktoré idú v protismere blokujú váš výhľad len veľmi krátko.


[ Ďalší: | Predošlý:Optimalizácia poradia vykreslenia | Hore:Zníženie blikania ]

4.4.3.4 Dvojitý buffering

Toto je veľmi populárny a jednoduchý systém, ktorý funguje dobre v rýchlych grafických režimoch (teda tých s nízkym rozlíšením). Novšie grafické karty (AGP a v menšej miere PCI) ho môžu použiť aj pri vyšších rozlíšeniach.

Technika dvojitého bufferingu znamená, že celý grafický výstup sa zapisuje do dočasnej bitmapy, kým nie je prekreslený celý rámec a výsledný obraz sa potom skopíruje do reálnej videopamäte ako jeden veľký kus.

Toto môže byť efektívne z viacerých dôvodov. Po prvé, znamená to, že z obrazovky nemusíme nič mazať -- iba nahradíme jeden obraz (na ktorom je všetko vykreslené) iným (na ktorom je tiež všetko vykreslené).

Po druhé, videopamäť je o dosť pomalšia. Ak mienime počas vykresľovania rámca častokrát prepisovať jednotlivé oblasti, bude rýchlejšie, ak to všetko zapíšeme do (rýchlej) systémovej pamäte a potom to skopírujeme do (pomalej) videopamäte, než keby sme zapisovali priamo na obrazovku. Navyše čítanie z videopamäte je dokonca ešte pomalšie; a teda ak budeme chcieť čítať z obrázka, je oveľa lepšie čítať z kópie v systémovej pamäti. Je to niečo podobné ako vyrovnávacia pamäť disku, kde sa časti disku uchovávajú v systémovej pamäti aby sa vyhlo priamemu prístupu na disk.

Tak teda namiesto kreslenia na obrazovku, kreslíme do inej bitmapy v pamäti. Potom, keď sme hotoví, zavoláme vsync() a potom celý zásobník (buffer) skopírujeme na obrazovku. V režime 320x200x256 sa to s rezervou vojde do prekreslovacieho intervalu, takže k žiadnemu blikaniu nedôjde. Pri vyšších rozlíšeniach alebo farebných hĺbkach je treba samozrejme skopírovať viac obrazových dát a teda to trvá dlhšie. Dôsledky môžu byť rôzne, počnúc rozdelením (kde je na hornej časti obrazovky vykreslený jeden rámec a na dolnej druhý) a končiac znížením frekvencie vykresľovania (pretože vykresľovanie trvá pridlho, takže zmeškáme ďalšiu prekresľovaciu periódu, takže vsync bude čakať až na tú, ktorá nasleduje po nej).

Pozrite si ukážkový prgram examples/chap_04/ex_4. Nechceme zatiaľ rozoberať príliš podrobne pamäťové bitmapy a funkciu blit; vrátime sa k tomu podrobnejšie v kapitole 7.


[ Ďalší: | Predošlý:Dvojitý buffering | Hore:Zníženie blikania ]

4.4.3.5 Špinavé obdĺžniky

Toto je varianta dvojitého bufferingu, ktorá nevyžaduje taký rýchly grafický režim, ale je ťažšia na programovanie. Teoreticky to vyzerá tak, že spravíte presne to isté, ako pri dvojitom bufferingu, teda všetko vykreslíte do pamäťovej bitmapy, ale potom na obrazovku skopírujete iba tie oblasti, ktoré sa zmenili.

To ale znamená, že treba sledovať, ktoré oblasti sa zmenili; Najjednoduchší spôsob je označiť obdĺžniky ako špinavé keď do nich píšete. Potom môžete ľahko tieto obdĺžniky skopírovať na obrazovku.

Tento prístup je lepší než dvojitý buffering, ak sa toho veľa nezmenilo. Ak bolo zmien veľa, môže sa ukázať ako horší, čiastočne preto, lebo jednorázové prekreslenie veľkej plochy je efektívnejšie, než mnoho prekreslení menších oblastí a čiastočne preto, že sa jednotlivé špinavé obdĺžniky môžu prekrývať; pokiaľ sa nepostaráte o to, aby sa tak nestalo, budú sa niektoré časti obrazu prekresľovať dvakrát. Avšak ak situácie v ktorých bude jednoduchšie použiť dvojitý buffering viete rozpoznať, môžete jednoducho skopírovať celý obraz naraz, ako pri dvojitom bufferingu. Tieto dve techniky majú mnoho spoločné.

Špinavé obdĺžniky sú náročnejšie na realizáciu a ak spravíte malú chybičku, môžete sa dočkať podivných výsledkov. V kapitole 7 sa vrátime aj k tejto téme.


[ Predošlý:Špinavé obdĺžniky | Hore:Zníženie blikania ]

4.4.3.6 Striedavé vykreslovanie riadkov

Toto je ďalšia varianta dvojitého bufferingu. Hlavná myšlienka je kopírovať na obrazovku iba každý druhý riadok. Môžete kopírovať iba párne riadky a nepárne nechať čierne. Dostanete tak trochu tmavší obraz s efektívne zníženým zvislým rozlíšením. Tiež môžete skopírovať párne riadky v jednom rámci a nepárne v ďalšom. Takto nestratíte jas obrazu, ale niektoré riadky budú "staršie", než iné, čo spôsobí malé rozmazanie obrazu. Tieto techniky sa uplatňujú najlepšie vo veciach ako video.


[ Ďalší: | Predošlý:Úvod do grafiky | Hore:Začiatok ]

5.Ako zariadiť, aby sa niekoľko vecí dialo súčasne


[ Ďalší: | Hore: ]

5.1 Pohyb viacerých kruhov

Znie to celkom jednoducho a aj môže byť, ale znova ukážeme viacero spôsobov, ako sa to dá urobiť. Našim cieľom bude, aby sa to hodilo do štruktúry hry, ktorá bola prezentovaná skôr.

Na to, aby sme mali pohromade všetky údaje, ktoré popisujú každý kruh -- jeho umiestnenie, farbu, polomer, rýchlosť, ktorou sa pohybuje v každom smere, budeme používať štruktúru. Tiež bude výhodné zapamätať si, kde bol kruh nakreslený, aby sme si zjednodušili mazanie. Takže začnime definíciou štruktúry:

struct circle_t {
    int x,y,r;         /* x, y, polomer */
    int col;           /* farba */
    int xs,ys;         /* x rýchlosť, y rýchlosť */
    int dx,dy;         /* nakreslene x, nakreslene y */
};

Ďalej, dobrý spôsob, ako písať modulárny kód ako tento je najprv popremýšľať trochu o rozhraní; rozhodnite sa, čo má vrstva, ktorá bude modul používať, vidieť. Potom môžete napísať funkcie, zatiaľ stačí len zhruba určiť, čo budú robiť -- alebo môžete napísať programy, ktoré ich volajú, dokonca bez toho, že by ste urobili volané funkcie. Výhoda tohto prístupu je, že ak máte viac než jedného človeka, ktorý na hre pracuje, jeden z vás môže písať obslužné programy objektu a druhý program, ktorý ich volá.

Ďalšou výhodou, ktorú uvidíme neskôr je, že do tohoto typu systému je jednoduché pridať nové objekty -- všetky objekty budú používať tie isté funkcie rozhrania. A tak človek, ktorý píše program, ktorý volá všetky obslužné programy objektu môže začať s použitím fiktívnych objektov, ktoré toho veľa nerobia, aby skontroloval, či obsluha vlasne funguje; reálne objekty môžu byť pridané neskôr.

Poďme rozhodnúť, aké funkcie by každý objekt mohol mať. V ideálnom prípade chceme mať pre kruh k dispozícii nasledovné funkcie: funkciu, ktorá ho nakreslí, funkciu, ktorá ho zmaže a funkciu, ktorá upraví vnútorné premenné. Tieto funkcie budú často volané z vnútorného cyklu hry. Okrem toho potrebujeme funkciu, ktorá ho vytvorí a inicializuje jeho vnútorné premenné, spolu s funkciou, ktorá ho zlikviduje. Táto funkcia v tomto konkrétnom prípade nebude robiť veľa, ale mohla by, ak by napríklad bolo nutné uvoľniť dynamicky alokovanú pamäť potrebnú pre kruh.

Prototypy našich funkcií môžu vyzerať nasledovne:

void circle_draw (struct circle_t *circle, BITMAP *bmp);
void circle_erase (struct circle_t *circle, BITMAP *bmp);
void circle_update (struct circle_t *circle);
struct circle_t *circle_init (int new_x, int new_y, int new_radius, int new_col, int new_xs, int new_ys);
void circle_destroy (struct circle_t *circle);

Všimnite si, že som mená funkcií zvolil tak, aby začínali na circle_. Je užitočné urobiť to, pretože tieto funkcie budú globálne a nechceme, aby dochádzalo ku kolíziam s funkciami, ktoré obsluhujú iné typy objektov. Začiatok názvu nás tiež upozorní, že patria do modulu "kruh". Keby sme si tým neboli istí, mohli by sme sa samozrejme pozrieť do hlavičkového súboru, aby sme zistili, do ktorého modulu vlastne patria, ale je rýchlejšie, keď to viete zistiť iba pohľadom na meno funkcie.

Budeme mať pole smerníkov na štruktúry circle_t a naša hlavná funkcia init ich všetky inicializuje s pomocou volania circle_init s rôznymi parametrami.

Keď už sú inicializované, tak ich ešte predtým, než odídeme z funkcie init vykreslíme.

Funkcia input zatiaľ nebude robiť nič okrem toho, že nastaví príznak "koniec hry" (premenná end_game), ak bolo stlačené <ESC>. Použitie klávesnice bude vysvetlené v ďalšej časti.

Vo funkcii process vypočítame novú polohu všetkých kruhov. Použijeme na to for cyklus, ktorý prejde pole kruhov a zavolá circle_update pre každý z nich.

Vo funkcii output budeme potrebovať podobný cyklus, ktorý pre každý kruh zavolá circle_draw. Vo väčšine prípadov (záleží od toho, ktorú animačnú techniku použijeme) budeme najprv musieť zavolať funkciu circle_erase.

Na záver, keď skončíme, je slušné uvoľniť z pamäte kruhy vytvorené s pomocou circle_init funkciou circle_destroy.

Teraz by som vám navrhol, aby ste sa pokúsili naprogramovať to sami pre jeden kruh a potom program rozšíriť tak, aby sa ich hýbalo viacero. Ukážkové programy examples/chap_05/1_a a examples/chap_05/1_b demonštrujú, ako sa to robí. Ak by ste mali problémy s vytvorením programu, ktorý hýbe len jedným kruhom, navrhujem vám pozrieť si prvý príklad a pokúsiť sa ho upraviť tak, aby hýbal s viacerými kruhmi.

Všimnite si, že v prípade, že hýbete viacerými kruhmi, ak ich mažete a vykreslujete jeden po druhom, mazanie ďalších môže vygumovať kúsok z už vykreslených. Rieši sa to tak, že vymažete všetky kruhy naraz a potom ich všetky nakreslíte. Toto ale môže zväčšiť blikanie obrazovky (pamätáte sa na maximalizáciu času vykreslenia?), ak nepoužívate dvojitý buffering.


[ Ďalší: | Predošlý:Ďalšie kruhy | Hore:Ako zariadiť, aby sa niekoľko vecí dialo súčasne ]

5.2 Ako pridať pohyb štvorcov

Vyššie opísaná technika je vhodná, ak máme mnoho podobných objektov. Ale v reálnej hre je zvyčajne mnoho rôznych typov objektov. Tak sa teda pozrime na nejaké spôsoby, ako narábať napríklad so štvorcami súčasne s kruhmi.

Jedno riešenie je pridať do štruktúry informácie, ktoré jej umožnia popísať, či sa jedná o kruh alebo o štvorec tak, že pridáme premennú, ktorá to rozlíši. Potom môžeme modifikovať funkcie tak, aby rozlišovali oba prípady a kreslili ich róznym spôsobom. Niektoré funkcie dokonca nebude treba meniť.

Iné riešenie je vytvoriť novú štruktúru pre štvorce a nový súbor funkcií, ktoré s nimi budú narábať.

Obe tieto techniky majú svoje výhody. Prvá je vhodná vtedy, keď sú dva typy objektov veľmi podobné; v takýchto prípadoch nebude nutné mnoho funkcií meniť. Všetky objekty môžu byť uložené v jednom poli a bude stačiť jeden cyklus na to, aby boli vykreslené, aby sa pohli atď. Navyše podobnosť môže byť využitá tak, že môžeme vytvoriť spoločné procedúry pre veci ako zisťovanie kolízie.

Druhá technika je vhodná, ak sú objekty úplne rôzne. V takom prípade je nešikovné robiť štruktúru, ktorá bude udržiavať dáta pre všetky z niekoľkých objektov a funkcie nebudú mať príliš veľa kódu spoločného. Pri použití tejto techniky potrebujete zvláštne pole pre každý typ objektu a ak chcete vykresľovať/mazať/meniť objekty, musíte prezerať polia jedno po druhom.

V praxi sada viacerých rôznych typov objektov môže byť rozdelená do tried typov objektov. Kritériom napríklad môže byť správanie, výzor alebo to, ktorému hráčovi objekt patrí. S každou triedou sa potom môže zaobchádzať prvým zo zmienených systémov a bude mať spoločnú štruktúru, ktorá bude schopná popísať každý objekt v tejto triede. Rozdielne triedy sú potom spravované ako rôzne objekty druhým systémom.

Ak ste tomu porozumeli, pokúste sa vyššie uvedený program na pohyb viacerých kruhov upraviť tak, aby sa tam hýbali aj štvorce s použitím každej z uvedených dvoch metód. V prípade, že zatuhnete, sú tu ukážky, na ktoré sa môžete pozrieť: examples/chap_05/ex_2_a a examples/chap_05/ex_2_b. Ak sa vám nepodarí urobiť to vlastným spôsobom, pozrite si ich a pokúste sa do každej pridať ďalší typ objektov.

Ako cvičenie založené na zmiešanej technike sa pokúste urobiť program, kde sa budú hýbať štvorce, obdĺžniky a... hmmm... trojuholníky (ktoré budú prechádzať hranicu!), pričom štvorce a obdĺžniky sa budú hýbať tak, ako predtým, ale trojuholníky sa nebudú odrážať; spravte to tak, že sa vrátia na plochu z opačnej strany, než je tá, kadiaľ odišli. Príklad tohoto programu je examples/chap_05/ex_2_c; môžete ho spustiť, aby ste videli, čo to robí a potom sa pokúste napodobniť jeho správanie vašim vlastným programom.


[ Ďalší: | Predošlý:Aj štvorce | Hore:Ako zariadiť, aby sa niekoľko vecí dialo súčasne ]

5.3 Uchovávanie viacerých vecí

Predpokladajme, že chceme počas behu programu meniť počet útvarov. Vo všetkých predošlých príkladoch bol počet útvarov jednotlivých tvarov dopredu daný. Môžeme počet útvarov znížiť tak, že skrátime cyklus, ale takto vždy odstránime posledný útvar v zozname a okrem toho nemáme možnosť útvary pridať (pokiaľ predtým nejaké nezničíme). Skrátka to, čo hra môže robiť, je limitované v čase kompilácie veľkosťou polí.

Sú dve techniky, ktoré by som tu chcel predstaviť: dynamická alokácia a zreťazené zoznamy. Zreťazené zoznamy závisia od dynamickej alokácie, takže túto sekciu budete pravdepodobne čítať ako prvú.


[ Ďalší: | Hore:Uchovávanie viacerých vecí ]

5.3.1 Dynamická alokácia

V tejto sekcii uvidíme, čo to dynamická alokácia je a pozrieme sa, ako ju môžeme použiť na rozšírenie systému lineárnych polí, ktorý sme používali doteraz.

Dynamická alokácia nám dovoľuje vyžiadať si pamäť počas behu programu (teda keď už program beží, ako protiklad k času kompilácie). To napríklad znamená, že sa na začiatku behu programu môžeme spýtať užívateľa, koľko kusov z každého útvaru budeme animovať a potom si vytvoríme polia také dlhé, ako potrebujeme. A ešte lepšie je, že môžeme meniť veľkosť polí počas behu hry, takže ak by sme mali mať málo miesta, tak ich len trochu zväčšíme.

Na to, aby sme to urobili, použijeme funkciu malloc. Táto štandardná funkcia jazyka C dostane parameter typu size_t (v podstate číslo) a alokuje daný počet bytov pamäte. Návratová hodnota je void *, ktorá ukazuje na blok alokovanej pamäte. Takže chceme vytvoriť pole kruhov dĺžky num_circles. Každý kruh bude reprezentovaný struct circle_t. Môžeme použiť kľúčové slovo sizeof, s pomocou ktorého zistíme, koľko bytov potrebujeme pre každú štruktúru a alokovať pamäť takto:

struct circle_t *circles;
...
circles = (struct circle_t *) malloc (num_circles * sizeof (struct circle_t));

Pretypovanie na struct circle_t * v jazyku C nie je nutné; kompilátor C++ si ale bude sťážovať, ak ho nepoužijete. Jeho použitie je väčšinou vecou vkusu.

Ak alokácia zlyhá (napr. pre nedostatok pamäte), malloc vráti NULL. Za určitých okolností to môžete chcieť zistiť a informovať užívateľa. Je dobrý zvyk robiť to, ale ľudia sa o to často nestarajú, pretože nemusí existovať zmysluplný spôsob, ako z toho von. Ak používate djgpp s nejakým robustným DPMI klientom (napr. CWSDPMI), tento ukončí váš program, kedykoľvek sa pokúsite použiť smerník NULL. Niektorí iní DPMI klienti nechajú program bežať, čo pred vami problém skryje; najlepšie je tomu vyhnúť sa počas vývoja, alebo aspoň občas použiť CWSDPMI (teda z čistého DOSu, nie z Windows), aby ste skontrolovali, či veci vyzerajú byť v poriadku.

Po alokácii sa môžeme na kruhy odvolávať spôsobom circles[0], circles[1], atď., ako keby sme mali skutočné pole. To, čo máme, nie je celkom presne pole v jazyku C; je to len smerník na blok pamäte. Predsa sa to ale v mnohých ohľadoch správa ako pole.

Na záver, ak sme s používaním pamäte skončili, mali by sme ju uvoľniť, aby ju malloc mohol ak to bude potrebné znovu použiť:

free (circles);

Teraz sa pokúste zmeniť váš program z časti 5.1 (iba kruhy, štvorce a trojuholníky netreba) tak, aby si užívateľ mohol na začiatku programu zvoliť počet kruhov. Ak zatuhnete, pozrite sa na examples/chap_05/ex_3_1.

Zmeniť príklad examples/chap_05/ex_2_a tým istým spôsobom bude celkom jednoduché. Zmena examples/chap_05/ex_2_b bude vyžadovať viac mallocov.

A čo takto dovoliť hre zväčšovať pole počas behu? To pri skutočných poliach nie je možné, ale ako sme spomenuli predtým, naše "pole" nie je skutočné pole. Bolo alokované dynamicky, takže môžeme použiť funkciu realloc(), aby sme ho realokovali s inou veľkosťou. Údaje, ktoré v ňom boli predtým, tam budú stále aj potom, keď alokáciu rozšírime. Ak ho skracujeme, údaje, ktoré sú aj v novom bloku budú samozrejme zachované, ale dáta, ktoré sú na konci odrezané sa stratia. Dokonca ak blok pamäte znovu zväčšíte, nedá sa spoliehať na to, že dáta znovu získate.

Problém so znovualokovaním takýchto blokov je, že ak je oblasť pamäti, ktorá sa nachádza za našim blokom už obsadená, realloc potrebuje skopírovať celý blok niekde inde. Toto nie je zvlášť pomalé (operácie kopírovania pamäte sú skutočne celkom rýchle) ale nie je to vec, ktorú by ste chceli, aby sa vám diala príliš často.

Väčšina implementácií malloc nechávajú nejaké voľné miesto na konci každého bloku, ale nemôžeme sa na to príliš spoliehať. Je lepšie znížiť počet realokácií nejakým iným spôsobom. Najjednoduchší spôsob, ako to spraviť je vždy si zapýtať veľa miesta navyše --- potom nemusíme volať realloc veľmi často.

Takže potrebujeme sledovať niekoľko vecí:

int num_objs;      /* koľko je ich aktívnych */
int alloced_objs;  /* koľko je ich alokovaných */
int block_size;    /* koľko ich budeme na jeden krát alokovať */

Keď potom chceme pridať nový objekt, najprv skontrolujeme, či máme dosť miesta (je alloced_objs > num_objs?). Ak áno, zväčšíme iba num_objs a použijeme ten priestor. Inak musíme zväčšiť alloced_objs o block_size a znovualokovať blok napríklad takto:

objects = (struct obj_t *) realloc (objects, alloced_objs * sizeof (stuct obj_t));

Všimnite si, že syntax realloc je podobná ako malloc až na to, že musíte zadať starú hodnotu smerníka. Po zavolaní realloc už nemôžete používať jeho starú hodnotu -- funkcia realloc blok presunula, stará hodnota je teraz neplatná. Ak ste nastavili iný smerník na hodnotu objects a realloc posunula blok niekde inde, druhý smerník bude ukazovať na nesprávny blok.

Po znovualokovaní novej veľkosti bloku môžeme samozrejme zväčšiť num_objs a použiť novoalokované objekty.


[ Predošlý:Dynamická alokácia | Hore:Uchovávanie viacerých vecí ]

5.3.2 Zreťazené zoznamy

Zreťazené zoznamy sú extrémne užitočnou dátovou štruktúrou. Nanešťastie sú z istých dôvodov pre mnohých ľudí ťažko pochopiteľné. Ak si však o nich vytvoríte správnu predstavu, sú skutočne veľmi jednoduché a keď ich niekoľkokrát použijete, získate intuitívny vhľad do toho, ako ich používať.

V tejo časti sa pozrieme na tri typy zreťazených zoznamov a vyskúšame použiť ich v krúžkovo/štvorčekovo/trojuholníkových programoch vytvorených predtým.


[ Ďalší: | Hore:Zreťazené zoznamy ]

5.3.2.1 Jednoducho zreťazené zoznamy

V prvom rade potrebujete byť dôverne oboznámení so smerníkmi. Smrníky sú jednoducho objekty, ktoré môžu ukazovať na iné objekty. Snažte sa nemyslieť na ne presne ako na adresy v pamäti; je to bežná analógia, ale nemusí byť nutne správna a ak o nich budete uvažovať takto, smerníková aritmetika sa bude zdať mätúcou.

Takže smerníky môžu ukazovať na iné dátové objekty. Väčšinou sú smerníky navrhnuté tak, aby ukazovali na istý konkrétny typ dát. Konkrétne môžu ukazovať na užívateľom definované štruktúry:

struct little_struct {
    int number;
} *ptr;

Smerník ptr môže ukazovať na ľubovoľný objekt typu struct little_struct. Ak chceme pristupovať k poľu number objektu, na ktorý ptr ukazuje, môžeme písať (*ptr).number alebo použiť skrátený zápis ptr->number, ktorý znamená to isté. Všimnite si, že definícia uvedená vyššie nevytvára hneď objekt, na ktorý by ptr ukazoval; vytvorí len objekt ptr, ktorý má možnosť ukazovať na objekty typu struct little_struct ale na začiatku neukazuje nikam (smozrejme niekam ukazuje, ale kam presne záleží na iných okolnostiach; a teda sa nemôžete spoliehať, že je to platný smerník). Teraz sa pozrite na toto:

struct linked_list_node {
    int number;
    struct linked_list_node *next;
} *ptr;

Tu štruktúra linked_list_node neobsahuje len celé číslo number, ale aj smerník next, ktorý môže ukazovať na objekt typu struct linked_list_node. Takže ak vytvoríme objekt tohto typu a nasmerujeme na neho ptr, smerník ptr->next môže ukazovať na iný objekt tohto typu. A ptr->next->next môže ukazovať na ďalší... ako vidíte, môžeme mať celý zoznam takýchto štruktúr, pričom alokujeme každú zvlášť (radšej ako v jednom veľkom bloku) a môžeme k nemu pristupovať s použitím jediného smerníka na začiatok zoznamu. Aby sme označili koniec zoznamu, môžeme nastaviť smerník next posledného uzla na NULL.

ptr --> +------------+    +------------+    +---------------+
        | number : 7 |    | number : 3 |    | number : 285  |
        | next   :  ----> | next   :  ----> | next   : NULL |
        +------------+    +------------+    +---------------+

Na diagrame vyššie vidíte ptr, ktorý ukazuje na prvú zo zoznamu troch položiek. Prvá položka s hodnotou number 7 má svoj smerník next nastavený na druhú s hodnotou number 3. Smerník next tejto položky ukazuje na tretiu položku, ktorá má hodnotu number 285, a smerník next tretej položky je NULL, pretože je to posledný prvok v zozname. Celý zoznam obsahuje čísla 7, 3 a 285 v uvedenom poradí.

Pravdepodobne sa čudujete, prečo sme sa trápili s takýmto systémom. Ak ho porovnáme s lineárnym dynamickým blokovým zásobníkom zmieneným vyššie, má niekoľko výhod a niekoľko nevýhod.

V prvom rade narábať s vecami v zreťazenom zozname je veľmi jednoduché. Nové položky môžu byť pridané kdekoľvek do zoznamu, staré položky vymazané, existujúce pložky môžu byť preusporiadané alebo presúvane zo zoznamu do zoznamu a zoznamy sa môžu deliť alebo spájať s minimálnou námahou. Stačí zmeniť hodnoty niektorých smerníkov next. Každá z týchto operácií by bola nepríjemná a zdĺhavá, keby mala byť vykonaná v lineárnom bloku -- museli by sme presúvať veľké množstvo dát.

Predstavte si napríklad vymazanie druhej dátovej položky v dlhom sekvenčnom zozname -- museli by sme posunúť celý zvyšok zoznamu o jedno miesto. V zreťazených zoznamoch stačí len nastaviť smerník next prvej položky na to, na čo ukazuje smerník next druhej položky (teda buď na tretiu položku, alebo na NULL, ak žiadna tretia položka neexistuje) a potom môžeme druhú položku funkciou free uvoľniť.

Podobne je jednoduché pridať novú položku. Predpokladajme, že máme smerník na položku v zozname pred miestom, kde chceme našu novú položku pridať. Stačí nastaviť smerník next našej položky na rovnakú hodnotu ako má smerník next položky zo zoznamu a potom nastaviť smerník next položky zo zoznamu na našu novú položku.

Nevýhodami zreťazených zoznamov je využitie pamäte a neefektívnosť vyhľadávania položiek podľa čísla. Použitá pamäť je väčšia kvôli všetkým smerníkom v zozname a pretože je väčšinou alokované veľké množstvo malých kúskov (konkrétne v čase písania tohto textu bola minimálna veľkosť bloku alokovaného funkciou malloc v kompilátore djgpp 4096 bajtov). Vyhľadávanie podľa čísla je neefektívne, pretože musíme prejsť celý zoznam od začiatku, aby sme danú položku našli, zatiaľ čo v sekvenčnom zozname môžeme skoči priamo na ňu jednoduchým pripočítaním.

Ako vždy, ktorý spôsob použijete, záleží na tom, na čo ho chcete použiť.

Často kód zjednoduší, ak prvá položka v zreťazenom zozname je atrapa (niekedy sa nazýva aj hlavička zoznamu), ktorej dáta nemajú žiadny význam. Robí sa to preto, aby manipulácia s prázdnymi zoznamami bola jednoduchšia -- stanú sa zoznamami s jedinou položkou, s atrapou.

Teraz by som vám navrhol, aby ste sa pozreli do examples/chap_05/ex_3_2a na nejaké príklady vkladania, mazania a vyhľadávania položiek v zreťazených zoznamoch.

Myslím, že teraz pravdepodobne rozumiete, prečo zreťazené zoznamy môžu byť veľmi užitočné -- môžeme vytvoriť toľko útvarov koľko chceme, kedy chceme a pridávať ich do zoznamu veľmi efektívnym spôsobom. V skutočnosti sa nestaráme o to, koľko ich je v zozname; ak sa potrebujeme z nejakých dôvodov odkazovať na istú konkrétnu položku, použijeme miesto jeho poradového čísla smerník.


[ Ďalší: | Predošlý:Jednoducho zreťazené zoznamy | Hore:Zreťazené zoznamy ]

5.3.2.2 Dvojmo zreťazené zoznamy

Ak predpokladáme, že ste pochopili predošlú lekciu, toto bude zvládnuteľné jednoducho. V jednoducho zreťazených zoznamoch vyššie má každá položka jeden smerník na ďalšiu položku v zozname. V dvojmo zreťazených zoznamoch má každá položka dva smerníky -- jeden na ďalšiu položku v zozname a jeden na predošlú položku v zozname.

struct dllnode {
    struct dllnode *next;
    struct dllnode *prev;
    int number;
} *ptr;
ptr --> +--------+     +--------+     +--------+
        |  next -----> |  next -----> |  next ---> NULL
 NULL <--- prev  | <----- prev  | <----- prev  |
        | number |     | number |     | number |
        +--------+     +--------+     +--------+

Teraz stačí mať smerník na ľubovoľný prvok zoznamu a je možné pristupovať ku každému prvku. Stále je často užitočné mať ako prvý prvok v zozname (ten, ktorý má prev == NULL) atrapu, ktorej dáta sa nepoužívajú. Dôvody sú rovnaké ako predtým. Môže slúžiť ako rukoväť k zoznamu -- niečo, čoho sa dá zachytiť, čo bude stále súčasťou zoznamu. Pamätajte, že ostatné prvky sa budú priebežne pridávať a mazať; hlavička je jediný prvok o ktorom bude zaručené, že je tam celý čas.

Jednou z výhod dvojmo zreťazeného zoznamu je to, že môžete zmazať prvok zoznamu bez toho, že by ste potrebovali vedieť, v ktorom zozname sa vlastne nachádza. Dalšou výhodou je, že ak nejaký prvok zoznamu potrebuje prejsť celý zoznam, môže to urobiť bez toho, že by poznal smerník na hlavičku zoznamu. Okrem toho ak máte daný ľubovoľný prvok zoznamu, môžete pridať prvok na obe jeho strany bez toho, aby ste museli celý zoznam prejsť. Dvojité zoznamy sa vo všeobecnosti jednoduchšie (a efektívnejšie) spravujú, ale samozrejme za cenu trocha vyšších režijných nákladov.

examples/chap_05/ex_3_2b je príklad dvojmo zreťazeného zoznamu a funkcií, ktoré s ním narábajú.


[ Ďalší: | Preošlý:Dvojmo zreťazené zoznamy | Hore:Zreťazené zoznamy ]

5.3.2.3 Kruhovo zreťazené zoznamy

Znovu, ak ste porozumeli zreťazeným zoznamom až potiaľto, toto nebude príliš náročné. V našom jednoducho zreťazenom zozname sme mali smerník next posledného prvku nastavený na NULL, aby sme tak označili koniec zoznamu. V kruhovo zreťazenom zozname smerník next posledného prvku ukazuje späť na začiatok zoznamu -- teda to nie je v skutočnosti posledný prvok. Predstavte si to ako kruh prvkov, kde každý ukazuje na nasledujúci.

Ak je kruhový zoznam dvojmo zreťazený, smerník prev prvého prvku ukazuje na posledný prvok.

Prázdne kruhové zoznamy sú tiež problematické; použitie atrapového prvku je užitočné aj tu, ale musíte pamätať na to, že ho treba preskočiť, keď budete prechádzať okolo kruhu. Môžete to spraviť aj tak, že ak je zoznam úplne prázdny, váš smerník na neho bude NULL (ako to môžete spraviť aj s ostatnými zreťazenými zoznamami), ale stále budete musieť ošetrovať nejaké špeciálne prípady.

Kruhové zoznamy sú užitočné na iné veci, než lineárne zreťazené zoznamy; ak programujete hru, vo všeobecnosti chcete počas herného cyklu spravit niečo s každým prvkom zoznamu a na tieto účely sa často viac hodia lineárne zoznamy. Kruhové zoznamy som tu spomenul pre úplnosť a pretože sa od lineárnych príliš nelíšia. Ako príklad si pozrite examples/chap_05/ex_3_2c.


[ Predošlý:Kruhovo zreťazené zoznamy | Hore:Zreťazené zoznamy ]

5.3.2.4 Použitie zreťazených zoznamov

Pokúste sa prerobiť váš program na pohyb útvarov tak, že útvary uložíte do zreťazeného zoznamu a animujete ich odtiaľ. Tiež môžete každých 100 herných cyklov pridať nový útvar, alebo niektorý zmazať. Pozrite si examples/chap_05/ex_3_2d ako ukážku toho všetkého.

Táto metóda, keď všetky objekty hry vložíte do zreťazeného zoznamu, je veľmi užitočná. Môžete tam tiež vložiť hráčovu raketu/postavu/potvoru; ak hráčova postava vykonáva podobné akcie ako nepriatelia, môže to fungovať celkom dobre.


[ Predošlý:Uchovávanie viacerých vecí | Hore:Ako zariadiť, aby sa niekoľko vecí dialo súčasne ]

5.4 Objektovo orientované programovanie

Táto časť zatiaľ nebola napísaná, sorry.


[ Ďalší: | Predošlý:Ako zariadiť, aby sa niekoľko vecí dialo súčasne | Hore:Začiatok ]

6. Uživateľský vstup

Vstupné zariadenie je čokoľvek, čo použijete, aby ste dali počítaču informácie. U štandardných PC je to najmä klávesnica, aj keď väčšina má myš a mnohé joystick. Podľa uvedenej definície disketová jednotka je tiež istým druhom vstupného zariadenia rovnako ako mikrofóny, skenery a modemy, ale to väčšinou nie sú tie najlepšie veci na ovládanie hry.

Pozrieme sa na každé z hlavných troch zariadení podrobnejšie, potom premyslíme abstrakciu nášho vstupného systému. Nakoniec sa pozrieme na rôzne spôsoby použitia vstupnej informácie.


[ Ďalší: | Hore:Uživateľský vstup ]

6.1 Vstup z klávesnice


[ Ďalší: | Hore:Vstup z klávesnice ]

6.1.1 Prečo sú štandardné klávesnicové funkcie nepoužiteľné

Štandardné rozhranie PC klávesnice je takmer, aj keď nie celkom, úplne nepoužiteľné ako zariadenie na ovládanie hry. Je na to niekoľko dôvodov; v prvom rade kedykoľvek stlačíte klávesu alebo len na ňu klepnete, hra určitý čas (zvyčajne štvrtinu sekundy) bude vidieť len jednu stlačenú klávesu. Potom, ak držíte klávesu stlačenú, naplníte zásobník mnohými stlačeniami, čo znamená, že hra ich stále bude čítať aj potom, čo klávesu pustíte. Navyše ak stlačíte jednu klávesu a klepnete na inú, bude to vyzerať, ako by ste prvú pustili.

Toto všetko dohromady robí štandardné PC klávesnicové funkcie nepoužiteľné pre riadenie akčných hier. Našťastie Allegro poskytuje sadu oveľa lepších funkcií.


[ Ďalší: | Predošlý:Prečo sú štandardné klávesnicové funkcie nepoužiteľné | Hore:Vstup z klávesnic ]

6.1.2 Klávesnicové funkcie allegra

Vo vašich allegrovských programoch treba počas inicializácie zavolať install_keyboard(). Potom môžete používať klávesnicové funkcie Allegra.

Predovšetkým je tam pole charov nazvané key ktoré obsahuje jeden prvok pre každú klávesu na klávesnici. Je indexované podľa scankódov (nie ASCII kódov). Jednotlivé scankódy môžete získať s použitím KEY_* konštánt definovaných v allegro.h. Napríklad, ak chcete zistiť, či je práve stlačený medzerník, zistite, či je key[KEY_SPACE] nenulové. Pravdepodobne si pamätáte, že ste vstupnú funkciu už videli, keď bolo potrebné skontrolovať, či bola alebo nebola stlačená klávesa <ESC>.

Potom, čo inštalujete klávesnicový ovládač Allegra, funkcie BIOSu prestanú fungovať. To sa týka getch z conio, getchar, gets, scanf z stdio a všetko ďalšie, čo používa stdin. Allegro poskytuje náhradu za getch nazvanú readkey, ktorá čaká, kým bude stlačená klávesa a vráti celé číslo zložené zo scankódu klávesy a ASCII hodnoty. Aby ste ich rozdelili, musíte urobiť niečo takéto:

int value, scancode, ascii;
value = readkey();
scancode = value >> 8;
ascii = value & 0xff;

Podobne keypressed je náhrada coniovskej funkcie kbhit, ktorá vracia nenulovú hodnotu, ak je vo vstupnom zásobníku stlačená klávesa.

Ďalšie informácie nájdete v dokumentácii k Allegru.


[ Ďalší: | Predošlý:Klávesnicové funkcie allegra | Hore:Vstup z klávesnice ]

6.1.3 Ako niečím pohnúť s pomocou klávesnice

Je niekoľko spôsobov, ktorými môžu objekty v hre reagovať na vstup. Nateraz budeme objektami hýbať konštantnou rýchlosťou v smere, ktorý užívateľ určí. Iné modely spomenieme neskôr.

Takže čo chceme je niečo takéto:

if (key[KEY_LEFT]) x--;  /* ak užívateľ stlačí ľavú šipku, ideme vľavo */
if (key[KEY_RIGHT]) x++; /* to isté vpravo */
if (key[KEY_UP]) y--;    /* to isté hore; pozn. aby sa išlo hore, y sa zmenšuje */
if (key[KEY_DOWN]) y++;  /* to isté dole (y sa zväčšuje) */

kde x a y sú súradnice objektu.

Pokúste sa pozmeniť (jednoduchý) kolieskový program, aby užívateľ mohol kolieskom hýbať. Pokúste sa urobiť program tak, aby vyhovoval navrhnutej štruktúre. Ukážkový program examples/chap_06/ex_1_3 demonštruje, ako sa to má spraviť.


[ Predošlý:Ako niečím pohnúť s pomocou klávesnice | Hore:Vstup z klávesnice ]

6.1.4 Ako nechať užívateľa vybrať si klávesy

S použitím explicitných KEY_* konštánt vo vašej hre je niekoľko problémov. Vo všeobecnosti je najlepšie použiť pole key vtedy, keď sa nestaráte, čo klávesa normálne značí. Ak od niekoho chcete, aby zadal <Q> potom na testovanie toho, či to spravil nemôžete použiť pole key, pretože mapovanie konštánt KEY_* na fyzickú klávesnicu je nezávislé od softwarového mapovania klávesnice. KEY_Q odkazuje na klávesu vedľa <TAB>, ale inojazyčné a alternatívne mapovania klávesnice tam nemusia mať <Q>. To znamená, že môžete KEY_* konštanty použiť na výber pohodlnej rozlohy ovládacích kláves, ale nemôžete ich použiť, ak je symbol na klávese dóležitý (napr. Q ako Quit)

Keďže klávesnica na PC nebola v skutočnosti navrhnutá ako zariadenie na ovládanie hier, nemôže sledovať každú klávesu nezávisle. Dokonca sa k tomu ani nepribližuje. Existujú isté kombinácie kláves, ktorých stlačenie spôsobí, že iné klávesy už nebudú snímané -- dokonca aj keď máte dobré klávesnicové funkcie ako v Allegre. Je to hardwarové obmedzenie. Príklad examples/chap_06/ex_1_4 je jednoduchý program, ktorý opakovane vypisuje zoznam všetkých kláves, ktoré Allegro registruje ako stlačené; spustite ho a vyskúšajte stláčať klávesy, kým nezistíte, že ďalšie stlačenie nie je detekované. Na mojej klávesnici spôsobí súčasné stlačenie kláves <Z>, <X> a <C> to, že ak sa s nimi stlačí <F>, zostane nepovšimnuté.

Čo je ešte väčšia smola, nie všetky klávesnice majú takéto prekrytia na tých istých miestach. Ako potom chcete vybrať klávesy, ktorými budete ovládať hru, ak nie je isté, že budú fungovať na všetkých systémoch?

Odpoveďou je samozrejme nechať na užívateľoch, nech si definujú svoje vlastné klávesy. Ak dôjde ku klávesovému prekrytiu, môžu si zvoliť iné klávesy.

Ak to budete chcieť spraviť, bude treba konštanty KEY_* vo vašich vstupných procedúrach nahradiť premennými, ktoré inicializujete na nejaké vstupné hodnoty a potom užívateľom dovoliť meniť ich ak to budú chcieť. Prirodzene nemôžete očakávať, že užívatelia budú vedieť, čo sú to scankódy; mali by ste napísať procedúru, ktorá to pre nich zistí.

Použitie poľa key je celkom jednoduché; jednoducho necháte bežať cyklus od 1 do KEY_MAX, najvyššieho scankódu a zistíte si, ktorá klávesa je stlačená. Vložíte ju do vašej premennej klavesa_vlavo, počkáte, kým užívateľ pôjde ďalej, spýtate sa na klávesu "vpravo" atď. Môžete predpokladať, že prvá stlačená klávesa, ktorú nájdete, je jediná; ktorý praštený idiot by stláčal dve klávesy naraz, keď je požiadaný o to, aby stlačil jednu? :)

Ukážkový program, ktorý užívateľovi dovolí predefinovať ovládacie klávesy uvidíme neskôr.


[ Ďalší: | Predošlý:Vstup z klávesnice | Hore:Uživateľský vstup ]

6.2 Vstup z joysticku


[ Ďalší: | Hore:Vstup z joysticku ]

6.2.1 Digitálne snímanie

Digitálne snímanie joysticku je jednoduché. Uistite sa, či je joystick centrovaný (t.j. povedzte im, aby dali joystick do stredu a počkajte, kým vám dajú vedieť, že to spravili) a potom zavolajte initialise_joystick(). Teraz kedykoľvek chcete snímať stav joysticku, zavolajte poll_joystick(). Táto funkcia zistí stav joysticku a popri iných veciach obnoví stav globálnych premenných joy_left, joy_right, joy_up a joy_down, z ktorých každá ukazuje, či je alebo nie je joystick tlačený v danom smere.

Príklad vstupného kódu uvedený v klávesnicovej sekcii vyššie bude teraz vyzerať:

poll_joystick();
if (joy_left) x--;  /* if the joystick is pushed to the left, go left */
if (joy_right) x++; /* same for right */
if (joy_up) y--;    /* same for up; note that y _decreases_ to go up */
if (joy_down) y++;  /* and for down y _increases_ */

Predsa len sa uistite, že joystick je centrovaný, keď ho inicializujete. Funkcia initialise_joystick vráti nulu iba vtedy, keď je joystick pripojený, takže to môžete skontrolovať, potom požiadať užívateľa, aby joystick vycentroval a nakoniec znovu zavolať funkciu initialise_joystick, aby sa správne nastavila stredová pozícia.

Pokúste sa zmeniť predošlý príklad tak, aby čítal vstup z joysticku miesto z klávesnice. Ak zatuhnete, pozrite sa na ukážkový program examples/chap_06/ex_2_1.


[ Ďalší: | Predošlý:Digitálne snímanie | Hore:Vstup z joysticku ]

6.2.2 Tlačítka na streľbu

Je jednoduché čítať ich; priemerný joystick ich má dve a ich stav je uložený v premenných joy_b1 a joy_b2 ako TRUE (nenulová hodnota), kéď sú tlačidlá stlačené. Ako pri všetkých premenných typu joy_*, ich hodnota sa obnovuje iba vtedy, keď zavoláte funkciu poll_joystick. Ak ju nikdy nezavoláte, ich hodnota bude stále rovnaká; nemusíte ju ale volať príliš často. Raz za herný cyklus úple stačí, ak náhodou nekontrolujete nasledujúci prípad...

Podľa všetkého väčšina joystickov má palebné tlačidlá s odskokom, takže by ste mali vedieť, že stlačenie palebného tlačidla vyvolá niekoľko zmien z "vypnyté" na "zapnuté" a späť; mne to nikdy nespôsobilo žiadne problémy, ale možno sa môj joystick skrátka o to nestará. Vo väčšine hier, ktorých sa to týka, je problém v nastavení maximálnej palebnej rýchlosti (t.j. užívateľ nemôže streliť druhýkrát, kým povedzme neprejde 20 herných cyklov.) Jediná vec, v ktorej odskakovanie spôsobí skutočne veľký chaos sú dvojkiky na tlačítku joysticku; jedno stlačenie môže vyzerať ako dve (prípadne viac) stlačení. Poznamenajme, že to je problém iba vtedy, ak čas medzi odozvami z joysticku je veľmi krátky.

Pokúste sa zmeniť príklad z digitálneho snímania tak, aby ktuh smenil farbu, keď stlačíte palebné tlačidlo. Pokúste sa to urobiť tak, aby pri každom stlačení zmenil farbu len raz (tak, že budete zakaždým čakať až dovtedy, kým sa joystick neuvoľní -- nezabudnite načítať stav joysticku v každom cykle, inak v prenemmých ostanú staré hodnoty). Ak sa farby nemenia správnym spôsobom, tlačidlo môže mať odskakovanie, pokúste sa to zrušiť. Teraz (ťažšie) urobte, aby sa mohol kruh pohybovať, aj keď je ltačidlo stlačené.

Príklady na všetko uvedené vyššie sú v examples/chap_06/ex_2_2; pozrite sa špeciálne na techniku použitú v poslednom prípade.


[ Predošlý:Tlačítka na streľbu | Hore:Vstup z joysticku ]

6.2.3 Analógové snímanie a kalibrácia

[Poznámka: táto informácia je zastaralá. Stále to bude fungovať ale pre Allegro 4 to chce zmenu.]

Predpokladá sa, že PC joystick je analógové zariadenie; inými slovami, môžete zistiť nielen ktorým smerom páka ukazuje, ale aj ako ďaleko tým smerom je naklonená. Ale ako zdôrazňuje Shawn v allegro.txt:

...presné pravidlá sa môžu líšiť v závislosti na type joysticku, rýchlosti vášho počítača, teplote v miestnosti a fáze mesiaca. Ak chcete získať zmysluplný vstup, musíte joystick pred použitím kalibrovať, čo je trest za programátorove hriechy.

Kalibrácia nie je v skutočnosti taká ťažká, len trocha irituje užívateľov. Aj keď väčšina PC hráčov je na klaibráciu joysticku zvyknutá.

V kalibrácii ide o to, že joystickovské procedúry potrebujú vedieť, aký rozsah hodnôt v smere X aj Y joystick vracia. Allegro to robí s použitím troch funkcií: initialise_joystick, ako sme povedali vyššie, zaznačí hodnoty v strede; calibrate_joystick_tl zaznačí hodnoty, keď je páka čo najďalej vľavo hore a calibrate_joystick_br robí to isté, keď je páka vpravo dole.

Znova, obyčajne budete volať initialise_joystick raz na začiatku, aby ste zistili, či je alebo nie je joystick pripojený. Ak je a vy ho chcete neskôr kalibrovať, postupujte takto:

  1. Požiadajte užívateľa, nech vycentruje joystick a počkajte, kým vám oznámi, že to urobil (napríklad stlačením klávesy)
  2. Zavolajte funkciu initialise_joystick, aby si poznačila stredovú pozíciu.
  3. Požiadajte užívateľa, aby dal joystick do ľavého horného rohu a oznámil vám to
  4. Zavolajte funkciu calibrate_joystick_tl
  5. Požiadajte užívateľa, aby dal joystick vpravo dole a oznámil to
  6. Zavolajte funkciu calibrate_joystick_br

Teraz má Allegro všetko, čo potrebuje vedieť, aby vám bolo schopné dať analógovú pozíciu. Z estetických dôvovdov je vhodné teraz joystick vrátiť do stredovej pozície; inak môžete spustiť niečo dôležité s joystickom, ktorý je stále nastavený vpravo dole. Buď užívateľa požiadajte aby joystick vycentroval a potvrdil to ešte raz, alebo sledujte stav joysticku, kým to neurobí (pozrite nižšie).

Ako ukážku takéhoto kódu si pozrite examples/chap_06/ex_2_3a.

Keď máme joystick zkalibrovaný, môžeme konečne jeho pozíciu čítať detailnejšie. Premenné joy_x a joy_y vrátia jeho X a Y súradnice od -128 do 128, kde (-128,-128) je ľavý horný roh, (0,0) je stred a (128,128) je pravý dolný roh.

Teraz zmeňte program na pohyb kruhom tak, aby sa kruh hýbal tak rýchlo, ako ďaleko potiahnete páku. examples/chap_06/ex_2_3b ukazuje ako na to v prípade, že to nezvládnete. Všimnite si #define neďaleko začiatku; ovláda maximálnu rýchlosť kruhu, ktorá zodpovedá úplnému nakloneniu páky. Použitie #define alebo premennej typu constant namiesto čísla zjednoduší zmenu rýchlosti v budúcnosti.


[ Ďalší: | Predošlý:Vstup z joysticku | Hore:Uživateľský vstup ]

6.3 Vstup z myši

Na myš môžete pozerať jedným z dvoch spôsobov -- buď je to "ukáž a klikni" zariadenie, alebo čosi ako analógový joystick, ktorý poskytuje priamu kontrolu nad postavou.


[ Ďalší: | Hore:Vstup z myši ]

6.3.1 Ukáž a klikni

Prístup "ukáž a klikni" znamená, že na obrazovke je nejaký druh ukazovátka a s pomocou myši sa ním pohybuje. Keď sa stlačí tlačítko, spravíte niečo, čo záleží od toho, kam ukazovátko ukazovalo. Príkladom takého niečoho sú WIMP GUIčka (ako Windows), Dune 1 a 2, Warcraft 1 a 2, spústa rozhraní (rôzne menu) atď. Je to veľmi populárna technika.


[ Ďalší: | Hore:Ukáž a klikni ]

6.3.1.1 Inicializácia myši

Na inicializáciu myši zavolajte install_mouse(). Ako sa píše v allegro.txt, funkcia vráti -1, ak inicializácia zlyhá alebo vráti počet tlačidiel na myši, ak uspeje.


[ Ďalší: | Predošlý:Inicializácia myši | Hore:Ukáž a klikni ]

6.3.1.2 Čítanie myši

Teraz môžete získať X-ovú a Y-ovú súradnicu ukazovateľa myši v premenných mouse_x a mouse_y (kde sú uložené súradnice na obrazovke) a stav tlačidiel myši uložený v premennej mouse_b, ktorá je bitovým poľom s týmto významom:

Bity:  2 1 0
       . . X = príznak ľavého tlačidla
       . X . = príznak pravého tlačidla
       X . . = príznak stredného tlačidla

Ostatné bity sú v súčasnosti nepoužité. Príznakové bity troch tlačidiel sú nastavené, ak je zodpovedajúce tlačidlo stlačené, nulové, ak stlačené nie je. Ak myš nemá stredné tlačidlo, patričný príznak je stále nulový.

Takže na zistenie stavu jednotlivých tlačidiel použijete na premennú mouse_b binárne AND (&) s číslami 1, 2 alebo 4 nasledujúcim spôsobom:

if (mouse_b & 1) printf ("Ľavé ");
if (mouse_b & 2) printf ("Pravé ");
if (mouse_b & 4) printf ("Stredné ");
printf ("\n");

Poznamenajme, že hodnota premennej mouse_b sa môže meniť (a aj sa mení) "vám za chrbtom"; nasledujúci spôsob testovania môže vrátiť pochybné výsledky:

if (mouse_b) {
    printf ("Stlačené tlačidlá: ");
    if (mouse_b & 1) printf ("Ľavé ");
    if (mouse_b & 2) printf ("Pravé ");
    if (mouse_b & 4) printf ("Stredné ");
    printf ("\n");
}

Predstavte si, čo sa stane, ak je na začiatku stlačené jedno tlačidlo, takže prvá podmienka if sa vyhodnotí, ale potom sa tlačidlo uvoľní; reťazec "Stlačené tlačidlá: " sa vypíše, ale pretože už žiadne tlačidlo stlačené nie je, žiadna z možností "Ľavé ", "Pravé ", "Stredné " sa nevypíše.

Ak by podobná nekalosť mala robiť problémy, môžete si vytvoriť kópiu premennej mouse_b a pracovať potom s ňou; mimochodom je to niečo podobné, ako čítanie z joysticku.

my_mouse_b = mouse_b;
if (my_mouse_b) {
    printf ("Stlačené tlačidlá: ");
    if (my_mouse_b & 1) printf ("Ľavé ");
    /* atď */
}


[ Ďalší: | Predošlý:Čítanie myši | Hore:Ukáž a klikni ]

6.3.1.3 Zobrazenie ukazovateľa myši

Ak chcete zobraziť ukazovateľ na obrazovke, jednoducho zavolajte funkciu show_mouse ktorej poviete, na ktorú bitmapu ho chcete ktesliť, napríklad:

show_mouse (screen);

Kedykoľvek sa myš pohne, Allegro obnoví ukazovateľ myši (ledaže by ste mu povedali, aby to nerobilo -- pozrite si dokumentáciu k Allegru). Preto predtým, než do bitmapy čokoľvek zapíšete, treba myš skryť, inak sa na monitore budú diať zlé veci. Spraví sa to tak, že zavoláte show_mouse(NULL).

Poznamenajme, že funkcie, ktoré priamo vykresľujú ukazovateľ myši (podobne ako niektoré iné časti Allegra) vyžadujú, aby boli nainštalované procedúry časovača -- je to čiastočne vecou toho, ako veci fungujú na pozadí. Kvôli ďalším informáciam si pozrite časovače a čiastočne aj Inicializácia systému časovačov.


[ Predošlý:Zobrazenie ukazovateľa myši | Hore:Ukáž a klikni ]

6.3.1.4 Ovládanie ukazovateľa myši

Ak potrebujete upraviť oblasť myši, môžete zavolať:

set_mouse_range (min_x, min_y, max_x, max_y);

kde min_x a min_y je minimálna hodnota X a Y a max_x a max_y sú maximálne hodnoty.

Ak z nejakého dôvodu chcete posunúť ukazovateľ myši na určité miesto na obrazovke, zavolajte funkciu position_mouse, ktorej odovzdáte ako parametre novú X-ovú a Y-ovú súradnicu.

Ukážkový program examples/chap_06/ex_3_1 demonštruje prístup "ukáž a klikni".


[ Predošlý:Ukáž a klikni | Hore:Vstup z myši ]

6.3.2 Priama kontrola myšou

Táto technika znamená, že zistíme, ako ďaleko sa v každom hernom cykle v jednotlivých smeroch pohla myš a potom s údajmi zaobchádzame ako s analógovými dátami z joysticku. Príklady takýchto hier sú:

Nemyslím si, že je zákonitosť v tom, že v tomto zozname sú iba hry v prvej osobe (teda hry, kde je zobrazená situácia z pohľadu hráča). Nehovorím, že je táto metóda nepoužiteľná v iných hrách; myslím si, že je bežnejšie, že sa v tomto type hier daný prístup využíva.

Keď používate tento systém, myš inicializujete rovnako ako v systéme "ukáž a klikni" (pozrite Inicializácia myši).

Pri použití tohto spôsobu ovládania žiadate ovládač myši o obnovu hodnoty mickey. Mickey je veľmi jemná miera pohybu myši a je určovaná relatívne k momentu, kedy ste ju poslednýkrát zisťovali. Allegro má na to funkciu get_mouse_mickeys:

void get_mouse_mickeys (int *mickeyx, int *mickeyy);

Dajte funkcii dva smerníky na premenné typu int a ona naplní premenné, na ktoré ukazujú hodnotami, o ktoré sa zmenila X-ová a Y-ová súradnica myši odvtedy čo sme naposledy volali túto funkciu. Napríklad, ak dx a dy sú premenné typu int, tak volanie funkcie

get_mouse_mickeys (&dx, &dy);

vloží mickey rozdiely do dx a dy. Poznamenajme, že jednotky nie sú rovnaké, ako jednotky v mouse_x and mouse_y -- mickey má oveľa jemnejšie jednotky.

Ukážka k tejto časti, examples/chap_06/ex_3_2, používa túto techniku na pohyb nášho skvelého kruhu, pričom pohyb myši spôsobuje zrýchľovanie. Ako je [ed: bude] zmienené neskôr, toto je všeobecný model aj pre realistickejšie hry -- pohyb myši je zviazaný so silou, ktorá pôsobí na objekt. Pozrite sa na ukážku, aspoň aby ste vedeli, ako to funguje.


[ Ďalší: | Predošlý:Vstup z myši | Hore:Uživateľský vstup ]

6.4 Vstup všeobecne

Keď máme k dispozícii uvedené typy vstupov, môžeme písať programy, v ktorých môžeme ovládať veci ktorýmkoľvek z nich. Ako ale napísať hru, ktorá dovoľuje užívateľovi použiť zariadenie, ktoré si vyberie?

Môžeme napísať verzie reagujúceho kódu, ktoré budú interpretovať každý typ informácie a potom použijeme ten, ktorý korešponduje so zariadením, ktoré vybral užívateľ, ale bolo by lepšie minimalizovať počet styčných bodov medzi vstupným kódom a reagujúcim kódom. Príklad examples/chap_06/ex_4 ukazuje spôsob, ako sa to dá spraviť; odporúčam vám pozerať sa do neho, kým budete čítať túto časť. Pozrite sa hlavne na input.c a input.h.

Potrebujeme všeobecný formát, ktorý budeme posielať reagujúcemu kódu. Potrebujeme informáciu o tom, ako ďaleko je vstupné zariadenie v každom smere, a aký je stav palebných tlačidiel. Na uschovanie týchto informácií som vytvoril nasedovnú štruktúru:

struct input_t {
    int dx, dy;
    int fire1, fire2;
};

Nech sú dx a dy čísla z rozsahu od -128 do 128 (ako pri joystickových procedúrach), ktoré ukazujú, ktorým smerom zariadenie ukazuje a ako ďaleko. fire1 a fire2 budú nenulové, ak je stlačené zodpovedajúce palebné tlačidlo. Ich počet sme obmedzili na dve, pretože väčšina joystickov má iba dve palebné tlačidlá; ak píšete kód, ktorý má fungovať pod viacerými systémami, často je rozumné obmedziť sa na to, čo je spločné.

Vstupná procedúra bude musieť získať informáciu zakaždým, keď ju zavolajú. Pre vstup z joysticku je to jednoduché; stačí skopírovať patričné hodnoty, keďže rozsah je rovnaký. Ak je joystick digitálny, budeme ale musieť skontrolovať joy_left, joy_right, atď. pričom použijeme rovnaký systém, aký opíšeme o chvíľu pre klávesnicu.

Pre klávesnicu musíme nejakým spôsobom určiť, kde vlastne ukazuje. Ak máme určené klávesy left_key a right_key, môžeme určiť vychýlenie vpravo (rightness) nasledovne:

rightness = (key[left_key] ? -1 : 0) + (key[right_key] ? 1 : 0);

Pozrite sa, čo to spraví v jednotlivých situáciach: Ak žiadna z kláves nie je stlačená, hodnota oboch zátvoriek je nulová a výsledok je 0. Ak sú stlačené obe, prvá zátvorka vráti -1 a druhá 1; takže výsledok je znovu 0. Ak je stlačená iba ľavá klávesa, prvá zátvorka bude -1 a druhá 0, akže dokopy -1 a ak je stlačená iba pravá klávesa, výsledok bude 0+1, čo je 1. Teraz to môžeme prispôsobiť našej štruktúre napríklad takto:

dx = (key[left_key] ? -128 : 0) + (key[right_key] ? 128 : 0);
dy = (key[up_key]   ? -128 : 0) + (key[down_key]  ? 128 : 0);
fire1 = key[fire1_key];
fire2 = key[fire2_key];

Každý vstup z klávesnice tu má rovnaký účinok ako úplné vychýlenie joysticku.

Čo sa týka myši, použijeme priame ovládanie; ak by sme chceli použiť rozhranie "ukáž a klikni", boli by sme príliš závislí na užívateľovi, ktorý myš používa. Tento systém môžete simulovať s pomocou vstupných procedúr na ľubovoľnom vstupnom zariadení, ale na tomto mieste to nechceme rozoberať.

Aby sme mohli zobraziť premenné dx a dy z myšieho kódu do rozsahu [-128,128] ktorý tu používame, potrebujeme ich vydeliť max_mousespeed a vynásobiť 128, pričom max_mousespeed je premenná ovládajúca citlivosť myši. Spravíme to ale v opačnom poradí, lebo potom to môže zostať ako výpočet v celočíselnej aritmetike. Pretypovaniu medzi celočíselnými a desatinnými typmi by sme sa mali vyhnúť všade, kde je to možné -- je pomalé.

Celý kód obsahujúci to, o čom sme sa až doteraz bavili je vo funkcii input_getinput.

Predtým, než to začneme používať, je ale potrebné vstupnému modulu povedať, aký druh vstupu očakávame. To môžete spraviť zavolaním funkcie input_create_*, ktorá môže mať nejaké parametre (v závislosti na tom, čo * v skutočnosti je). Táto funkcia musí byť urobená tak, aby vytvorila vstupnú štruktúru a vrátila smerník na ňu. Vstupná funkcia joysticku žiadnu informáciu naviac nepotrebuje, ale vstupná funkcia myši potrebuje vedieť, akú hodnotu použiť pre max_mousespeed, a vstupná funkcia klávesnice potrebuje vedieť, ktoré klávesy použiť. Mohli by sme použiť jedinú funkciu s premenlivým počtom parametrov, ale to je pre potreby tohto tutoriálu trocha komplikované.

Takže definujme funkcie"

input_t *input_create_joystick ();
input_t *input_create_mouse (int max_mousespeed);
input_t *input_create_keybd (int left_key, int right_key,
                             int up_key, int down_key,
                             int fire1_key, int fire2_key);

Tiež budeme potrebovať funkcie pre inicializáciu vstupného modulu, funkciu, ktorá ho zruší a funkciu, ktorá zruší už zaregistrované vstupné zariadenie (napr. ak zmeníte klávesy); ich deklarácia môže vyzerať takto:

void input_init ();
void input_shutdown ();
void input_destroy (input_t *what);

Takže ak to zhrnieme, programy na začiatku zavolajú input_init aby nastavili modul. Potom zavolajú niektoré z input_create_* funkcií, aby zaregistrovali vstupné zariadenia hráčov; input_create_keybd sa môže volať viac než raz s rôznymi klávesami, aby mohol hrať na klávesnici viac než jeden hráč. Funkcie input_create_* vrátia smerníky na štruktúry input_t, ktorých členské premenné budú obnovené pri každom zavolaní input_getinput. Zaregistrované zariadenia sa môžu odregistrovať zavolaním funkcie input_destroy, ktorá ako parameter dostane smerník na struct input_t; táto funkcia súčasne dealokuje štruktúru, na ktorú smerník ukazuje. Funkcia input_shutdown odregistruje všetky zariadenia a uvoľní všetku pamäť, ktorá je ešte alokovaná.

Štruktúra input_t obsahuje polia dx, dy, fire1 a fire2. dx and dy nadobúdajú hodnoty od -128 do 128, ktoré ukazujú množstvo pohybu v jednotlivých smeroch a fire1 a fire2 sú nenulové, ak je stlačené zodpovedajúce palebné tlačidlo.

Ešte raz, pozrite si program examples/chap_06/ex_4, aby ste videli, ako tento systém používať.


[ Predošlý:Vstup všeobecne | Hore:Uživateľský vstup ]

6.5 Spôsoby interpretácie vstupných dát

Táto časť zatiaľ nie je napísaná. Keď bude, bude obsahovať informácie o fyzikálnych modeloch (nie, nie je to taká nuda, ako to znie!) a ako môžete vstupné dáta použiť.


[ Ďalší: | Predošlý:Uživateľský vstup | Hore:Začiatok ]

7. Niečo viac z 2D grafiky

Táto časť zatiaľ nie je napísaná. Keď bude, bude hovoriť o:


[ Ďalší: | Predošlý:Niečo viac z 2D grafiky | Hore:Začiatok ]

8. Zvuk


[ Next: | Up:Zvuk ]

8.1 Konfigurácia zvuku


[ Next: | Up:Konfigurácia zvuku ]

8.1.1 Inicializácia ovládačov zvuku

Aby ste nainštalovali ovládače zvuku, treba zavolať funkciu:

install_sound (digi_driver, midi_driver, NULL);

kde na miesto parametra digi_driver vložíte konštantu DIGI_* a na miesto parametra midi_driver konštantu MIDI_*. Tretí parameter je zastaralý, dajte tam skrátka NULL.

Ak je funkcia úspešná, vráti nulu. Nenulová návratová hodnota pravdepodobne znamená, že ovládač, ktorý požadujete, nie je prístupný. Ak pre niektorý ovládač používate *_AUTODETECT, môže to znamenať, že konfiguračný súbor špecifikuje, ktorý ovládač presne treba použiť a tento ovládač je nepoužiteľný.

Myslím si, že sa málokedy vyskytne rozumný dôvod nepoužiť DIGI_AUTODETECT a MIDI_AUTODETECT. Moje dôvody sú také, že ak určíte zariadenie explicitne, hra nebude fungovať, ak toto zariadenie nie je dostupné a nebude schopná použiť lepšie zariadenie, ak toto dostupné bude. Dokonca ak máte v počítači nejaký hardwarový konflikt, ktorý spôsobuje, že táto voľba normálne zlyháva, nie je to dôvod na to, aby ste zrušili funkčnosť vašej hry na počítačoch iných ľudí -- môžete si vytvoriť konfiguračný súbor (pozrite nižšie). Konfiguračné súbory majú vždy prednosť pred autodetekciou, takže konflikt zmizne -- môžete sa spoľahnúť na to, že rovnako to bude fungovať aj u koncových užívateľov; ak sa vyskytnú problémy, môžu ich odstrániť. Ak parametre zadáte napevno, ľudia nebudú môcť nastavenia zmeniť, kým si to neprekompilujú -- čo je zlá novinka!

Takže ak nemáte veľmi dobrý dôvod (žiaden ma nenapadá), používajte konštanty *_AUTODETECT. Dokonca ukážkový program play.exe v adresári tests Allegra je pre mňa hraničným prípadom -- používa pevne určenú tabuľku čísel a mien ovládačov. To je dosť zlé; výhoda tohto prístupu (aspoň myslím) spočíva v tom, že sa ovládače otestujú rýchlo. Nie ktovieaká výhoda...

Treba poznamenať ešte jednu vec -- MIDI prehrávač Allegra poučíva pre svoju činnosť procedúry časovača, takže predtým, ako budete prehrávať MIDI súbory ho musíte inštalovať. Viac podrobností nájdete v kapitole Inicializácia systému časovača.


[ Predošlý:Inicializácia ovládačov zvuku | Hore:Konfigurácia zvuku ]

8.1.2 Konfiguračný súbor pre zvuk

Allegro v konfiguračných súboroch podporuje široký okruh oblastí; konfigurácia zvuku je len časťou štandardných dát a vy môžete pridávať pre vnútornú potrebu vašej hry dáta podľa vlastného výberu. Čo sa všetkých detailov použitia konfiguračných súborov týka, pozrite si dokumentáciu k Allegru (ak používate info, napíšte v príkazovom riadku info allegro config). Tu bude zmienka iba o tom, čo sa týka ovládačov zvuku.

Ak nepoviete inak, program skontroluje vo svojom domovskom adresári súbory allegro.cfg a sound.cfg. Ak chcete použiť iné meno súboru, dajte ho ako parameter funkcii set_config_file.

Informácia o konfigurácii zvuku je v časti [sound] súboru a obsahuje okrem iného informácie o tom, ktoré ovládače s akými nastaveniami sa majú použiť, ak zažiadate o autodetekciu. Allegro potom autodetekciu spracuje tak, že ak je ovládač explicitne určený v konfiguračnom súbore, tak sa ten ovládač použije. Ak žiaden ovládač neurčíte, Allegro sa vám začne hrabať v mašine, aby zistilo, čo tam vlastne je; toto môže mať vedľajšie efekty a navyše z technických dôvodov niektoré veci (ako napr. externé MIDI zariadenie) nikdy nebudú zistené

Najbezpečnejšia a najjednoduchšia cesta, ako vytvoriť konfiguračné súbory je program setup. Je navrhnutý tak, aby sa dal pribaliť k vašim hrám a aby si s jeho pomocou mohol užívateľ vybrať (alebo si nechať zistiť) a vyskúšať nastavenia. Setup potom uloží nastavenia do konfiguračného súboru, ktorý môže váš program čítať.

Teda najjednoduchší spôsob ako to distribuovať, je pribaliť súbor setup.exe do toho istého adresára, ako spustiteľný súbor vášho programu, takže výstup z programu setup bude presne tam, kde ho váš program bude očakávať. Je veľa vecí, ktoré s pomocou programu setup môžete nastaviť (alebo nechať tak). Úplnú informáciu nájdete v súbore setup.txt (je v tom istom adresári ako zvyšok programu setup).


[ Ďalší: | Predošlý:Konfigurácia zvuku | Hore:Zvuk ]

8.2 Digitálny zvuk


[ Ďalší: | Hore:Digitálny zvuk ]

8.2.1 Načítanie zvukových súborov

Allegro vie čítať digitálny zvuk z WAV a VOC súborov. Oba typy súborov musia byť mono. WAV súbory môžu byť 8 alebo 16 bitové; VOC súbory musia byť 8 bitové.

Načítanie súboru nemôže byť jednoduchšie -- meno WAV alebo VOC súboru (musí mať správnu koncovku) dáte ako parameter funkcii load_sample a ona vráti SAMPLE * ktorý ukazuje na zvuk alebo NULL, ak sa zvuk nepodarilo nahrať (teda nebola rozlíšená koncovka, nebol nájdený súbor alebo formát súboru je nesprávny alebo nepodporovaný).

Môžete tiež rovnakým spôsobom priamo zavolať load_voc alebo load_wav. Výhodou tohto prístupu môže byť, že sa spracuje aj súbor, ktorý nemá koncovku .WAV alebo .VOC.


[ Next: | Previous:Načítanie zvukových súborov | Up:Digitálny zvuk ]

8.2.2 Prehrávanie zvukov

Allegrovský prehrávč zvukov môže počas prehrávania narábať so zvukmi rôznymi spôsobmi. Parametre, ktoré na základnej úrovni môžete nastaviť sú hlasitosť, balanc a frekvencia. Zvukovému prehrávaču tiež môžete povedať, či chcete alebo nechcete vždy spustiť zvuk od začiatku, keď sa dohrá do konca.

Keď chcete prehrať zvuk, treba zavolať funkciu play_sample. Má nasledujúce parametre:

Poznamenajme, že zmena frekvencie môže viesť k nesprávnemu prehraniu -- Allegro je knižnica na programovanie hier, nie súbor rafinovaných programov na úpravu zvukov. Tiež prehrávanie zvukov na nižšej hlasitosti, ale s ovládačom hlasitosti na vašej stereosúprave vytočeným nadoraz doprava bude viesť k skresleniu, nehovoriac o šume ;). Snažte sa, pokiaľ je to možné, využívať celý rozsah hodnôt a používať zvuky, ktoré tiež vnútorne používajú celý prípustný rozsah.

Typické volanie funkcie play_sample potom bude vyzerať takto:

play_sample (gun_sound, 255, 128, 1000, 0);

čo prehrá gun_sound (ktorý ste predtým načítali) na maximálnej hlasitosti, zo stredu, s jeho pôvodnou frekvenciou a bez cyklenia.

play_sample (some_sound, 192, 96, 1200, 1);

bude hrať some_sound na 3/4 hlasitosti, posunutý jemne vľavo, trochu vyššie ako obyčajne a vždy, keď príde na koniec, začne sa hrať od začiatku.

Na zastavenie prehrávania zvuku slúži funkcia stop_sample:

stop_sample (some_sound);

To je zvlášť užitočné pri zacyklených zvukoch.


[ Ďalší: | Predošlý:Prehrávanie zvukov | Hore:Digitálny zvuk ]

8.2.3 Úprava parametrov prehrávaného zvuku

Tieto parametre môžete upraviť aj počas prehrávania súboru. Používa sa na to funkcia adjust_sample. Má rovnaké parametre ako play_sample a v zozname prehrávaných zvukov hľadá ten, ktorý ste zadali. Takže ak už spomínaný zvuk some_sound stále hrá, môžete zrušiť cyklenie takto:

adjust_sample (some_sound, 192, 96, 1200, 0);

Samozrejme môžeme zmeniť aj iné parametre. Ak chcete upravovať zvuky, kým sú prehrávané, dbajte na to, aby súčasne nebolo prehrávaných viacero kópií daného zvuku -- adjust_sample zmení iba prvú kópiu, ktorú nájde a ostatné sa nezmenia.

Rafinovanejší spôsob, ako prehrávať a ovládať zvuky, ktorý nemá spomínané problémy nájdete v kapitole Hlasové funkcie.


[ Ďalší: | Predošlý:Úprava parametrov zvuku | Hore:Digitálny zvuk ]

8.2.4 Uvoľnenie zvukov z pamäti

Keď už zvuk nepotrebujete, môžete ho odstrániť z pamäti funkciou destroy_sample nasledujúcim spôsobom:

destroy_sample (some_sound);

Ak zvuk ešte stále hrá, bude automaticky zastavený.


[ Predošlý:Uvoľnenie zvukov z pamäti | Hore:Digitálny zvuk ]

8.2.5 Hlasové funkcie

Táto časť ešte nebola napísaná. Bude obsahovať informácie o tom, ako používať hlasové funkcie priamo, aby ste získali nad zvukmi väčšiu kontrolu.

Je tu ale ukážkový program: examples/chap_08/ex_2_5


[ Predošlý:Digitálny zvuk | Hore:Zvuk ]

8.3 MIDI súbory


[ Ďalší: | Hore:MIDI súbory ]

8.3.1 Načítanie MIDI súborov

V súčasnosti podporuje Allegro iba MIDI súbory, z ktorých môže nahrať buď typ 0 alebo 1 (teda väčšinu).

Na načítanie MIDI súboru do pamäti zavolajte funkciu load_midi, ktorá ako parameter dostane meno súboru a vráti MIDI * alebo NULL, ak sa načítanie nepodarí.


[ Ďalší: | Predošlý:Načítanie MIDI súborov | Hore:MIDI súbory ]

8.3.2 Prehrávanie MIDI súborov

Ak chcete začať s prehrávaním MIDI objektu, jednoducho zavolajte play_midi:

play_midi (title_theme, 0);

Druhý parameter je príznak cyklenia; 0 znamená necykliť, čokoľvek iné spôsobí, že keď sa súbor dohrá, spustí sa od začiatku. Ak chcete, aby sa váš súbor opakoval od iného miesta (napríklad ak má úvod, ktorý chcete pri opakovaní preskočiť), môžete zavolať:

play_looped_midi (background_music, loop_start, loop_end);

Keď je dosiahnutá pozícia loop_end, prehrávač skočí späť na loop_start. Ak je loop_end -1, návratový bod bude až na konci skladby. Jednotky pre loop_start a loop_end sú takty, tak ako sú odmeriavané premennou midi_pos zmienenou nižšie.

Inak môžete hudbu zastaviť volaním stop_midi:

stop_midi();

Keďže Allegro môže naraz prehrávať iba jednu hudbu, táto funkcia nepotrebuje parametre. Začiatok prehrávania hudby zatiaľ, čo hrá nejaká iná, spôsobí zastavenie prehrávania predošlej. Volanie funkcie stop_midi má rovnaký efekt ako volanie play_midi s prvým parametrom NULL.


[ Next: | Previous:Prehrávanie MIDI súborov | Up:MIDI súbory ]

8.3.3 Ovládanie MIDI súborov

Počas prehrávania MIDI súboru Allegro zväčšuje raz za takt globálnu premennú midi_pos. Ak sa žiaden MIDI súbor neprehráva (alebo necyklický súbor už skončil), je nastavená na -1.

Poznamenajme, že zväčšenie nemusí nutne nastať v každom takte -- ak sa v nejakom takte nič nehrá, premenná sa zmení až pri hraní najbližšej noty. Ak chcete s premennou synchronizovať udalosti, uistite sa, či je v každom takte nejaká nota; môžete napríklad vložiť notu s nulovou hlasitosťou, ktorá bude slúžiť len na spustenie udalosti.

Prehrávanie môžete zastaviť a znovu spustiť s pomocou funkcii midi_pause a midi_resume. Môžete skočiť na určitú pozíciu v súbore ak cieľovú midi_pos hodnotu odovzdáte ako parameter funkcii midi_seek. Pamätajte na to, že vyhľadávanie dozadu spôsobí previnutie súboru na začiatok a potom hľadanie dopredu; teda nebude dobrý nápad robiť za sebou niekoľko malých vyhľadávaní dozadu.

Nakoniec, ak sa súbor prehráva cyklicky, môžete za jazdy zmeniť miesta cyklenia. Dve premenné, ktoré ich určujú, sú midi_loop_start a midi_loop_end a sú merané v midi_pos jednotkách. Hodnota -1 pre midi_loop_start alebo midi_loop_end znamená začiatok resp. koniec súboru.

Spomenul som už predtým, že časť MIDI súboru môže tvoriť predohru ktorú v cykle nechcete hrať zakaždým a funkcia play_looped_midi tento problém rieši; nastavenie bodov cyklenia vám dovoľuje spraviť podobnú vec, ak MIDI súbor obsahuje záver, ktorý nechcete hrať počas opakovania -- teda chcete raz zahrať úvod, potom nekoľkokrát cykliť hlavnú časť a nakoniec zahrať záver.

Aby ste to dosiahli, začnete prehrávať hudbu s použitím funkcie play_looped_midi kde ako parameter začiatku cyklu uvediete začiatok hlavnej časti a ako koniec cyklu koniec hlavnej časti. Potom necháte hudbu hrať; zakaždým, keď hlavná časť skončí, spustí sa znovu od začiatku. Neskôr môžete parameter konca cyklu nastaviť na koniec skladby.

Sú tam ale niektoré zádrhele:

Pravdepodobnosť, že niektorá z uvedených vecí bude robiť vážne problémy je pomerne malá, ale je lepšie to zabezpečiť, než ľutovať. (Týka sa to najmä prvej.)


[ Predošlý:Ovládanie MIDI súborov | Hore:MIDI súbory ]

8.3.4 Uvoľnenie MIDI súborov z pamäti

Na uvoľnenie MIDI súboru z pamäti použite funkciu destroy_midi:

destroy_midi (background_music);

Ak je súbor práve prehrávaný, hudba sa najprv zastaví.


[ Ďalší: | Predošlý:Zvuk | Hore:Začiatok ]

9. Časovače


[ Next: | Up:Časovače ]

9.1 Použitie časovačov

V tomto zozname nájdete rôzne použitie časovačov. Samozrejme nie je úplný -- jeho cieľom je poskytnúť vám predstavu, na čo sa väčšinou časovače používajú. Niektoré, prípadne všetky z nich sa neskôr objavia v príkladoch.


[ Ďalší: | Predošlý:Použitie časovačov | Hore:Časovače ]

9.2 Nastavenie obslužných funkcií časovača


[ Ďalší: | Hore:Nastavenie obslužných funkcií časovača ]

9.2.1 Inicializácia systému časovačov

Pred tým, než môžete použiť niektorú z funkcií časovača, musíte inicializovať systém časovačov Allegra. Ako už bolo povedané, potrebujú to aj mnohé iné časti Allegra (ako napríklad kód na vykreslovanie ukazovateľa myši alebo prehrávač MIDI súborov), takže ho už pravdepodobne budete mať nainštalovaný.

Na inicializáciu systému časovačov zavolajte:

install_timer();

Po tomto volaní vám prestane fungovať funkcia delay z knižnice libc; môžete použiť funkciu rest z Allegra, ktorá robí takmer to isté.


[ Ďalší: | Predošlý:Inicializácia systému časovačov | Hore:Nastavenie obslužných funkcií časovača ]

9.2.2 Zamykanie a volatilita

Allegrovské funkcie časovača sú ovládané cez prerušenia. Prerušenia sú vyvolávané istými udalosťami a prerušia vykonávanie vášho programu. V tomto momente procesor začne vykonávať kód ISR (interrupt service routine - funkcia obsluhy prerušenia). Allegro má rôzne mechanizmy, s pomocou ktorých sa dozvie o tom, že sa určité prerušenie vyskytlo -- v tomto prípade prerušenie od časovača.

Allegrovský ovládač prerušenia volá rôzne funkcie, ktoré určíte, nazývané obslužné funkcie (anglicky callbacks), v intervaloch, ktoré tiež môžete určiť. Dôležité je, že tieto funkcie sa volajú vo vnútri prerušenia.

Z rôznych technických dôvodov je veľmi nešťastný nápad pokúšať sa počas prerušenia presúvať veci na disk a z disku. To znamená, že sa musíte uistiť, že vaše obslužné funkcie nesmú byť počas behu programu dočasne odložené z pamäte na disk -- ak by boli, museli by sa počas prerušenia načítavať späť do pamäte. Tiež sa musíte uistiť, že všetky dáta, ktoré menia -- globálne premenné atď. -- sa nikdy neocitnú v swapovacom priestore na disku. Toto urobíte zamknutím pamäte, kde sú uložené. Djgpp má funkcie, ktoré to vedia urobiť a Allegro ich má pekne zabalené vo forme nasledovných makier:

END_OF_FUNCTION (function_name);
LOCK_FUNCTION (function_name);
LOCK_VARIABLE (variable_name);

LOCK_VARIABLE zamyká statickú alebo globálnu premennú a LOCK_FUNCTION zamyká funkciu. LOCK_VARIABLE funguje aj sama o sebe, ale LOCK_FUNCTION potrebuje, aby ste koniec zamykanej funkcie označili s pomocou END_OF_FUNCTION. Ak END_OF_FUNCTION uviesť zabudnete, dostanete spústu nebezpečne vyzerajúcich (ale celkom slušne vysvetľujúcich) varovaní kompilátora a chýb linkera.

Ďalšia vec, s ktorou sa stretneme, keď je reč o prerušeniach, je volatilita (premenlivosť). Každý slušný kompilátor sa snaží optimalizovať kód, ktorý generuje. Obyčajne pri tom predpokladá, že premenná, do ktorej nezapisujete, sa nemení. To je väčšinou pravda, ale ak niektorá funkcia volaná cez prerušenie túto premennú zmení, o tejto zmene nemusí kompilátor nič vedieť. Aby ste tento problém vyriešili, musíte všetky premenné, ktorých sa to týka deklarovať ako volatile. volatile je kľúčové slovo jazyka C, ktoré povie kompilátoru, aby nerobil žiadne predpoklady ohľadom obsahu premennej -- a navyše nebude dočasne uchovávať obsah premennej v registri.

Samozrejme nedeklarujte všetky vaše premenné ako volatile. Keby ste to spravili, značne by ste tým obmedzili schopnosť kompilátora optimalizovať váš kód a výsledný program by pravdepodobne bežal pomalšie. Ako volatile označte len tie premenné, do ktorých zapisujete alebo z ktorých čítate v súvislosti s prerušením (teda vo vnútri obslužnej funkcie časovača alebo iného prerušenia).


[ Ďalší: | Predošlý:Zamykanie a volatilita | Hore:Nastavenie obslužných funkcií časovača ]

9.2.3 Inštalovanie obslužných funkcií

Keď ste nainštalovali systém časovačov, zamkli obslužné funkcie a všetky dáta s ktorými narábajú a označili všetky takéto dáta ako volatile, môžete konečne povedať Allegru aby obslužné funkcie začalo volať.

install_int (callback, msecs);

Nahraďte callback menom funkcie, ktorá vracia void a nemá parametre (v jazyku C) alebo neurčený počet parametrov (v jazyku C++). msecs je interval, v akom sa bude funkcia volať.

Tu je príklad funkcie pre C:

volatile int counter = 0;      /* Budeme to v obslužnej funkcii meniť,
                                  takže to musí byť volatile. */
void my_callback_func()
{
    counter++;                 /* Pekné a jednoduché */
}
END_OF_FUNCTION (my_callback_func);   /* Všimnite si syntax */

Poznamenajme, že obslužná funkcia musí byť veľmi jednoduchá. Nesmie trvať príliš dlho, nesmie volať žiadne knižničné funkcie jazyka C, nesmie si dovoliť žiadny prepych ako napr. volanie funkcií DOSu. Ak volá procedúry Allegra, musia spĺňať rovnaké pravidlá.

Ak použivame C++, musíme zmeniť definíciu funkcie na:

void my_callback_func(...)

inak dostaneme chybu.

Ďalej je tu kód, ktorý použijeme na zamknutie vecí zmienených vyššie:

LOCK_FUNCTION (my_callback_func);    /* Zamkneme funkciu */
LOCK_VARIABLE (counter);             /* Mení sa v obslužnej funkcii,
                                        takže ju zamkneme tiež. */

Nakoniec nasledujúci riadok povie Allegru, aby našu funkciu volalo desaťkrát za sekundu (raz za sto milisekúnd):

install_int(my_callback_func, 100);

Všimnite si, že aj keď my_callback_func je funkcia, za jej menom nepíšeme zátvorky (( a )) -- keby sme to spravili, funkcia by sa zavolala a jej (neexistujúca) návratová hodnota by sa zadala install_int. My ale potrebujeme zadať miesto, kde v pamäti začína kód funkcie, takže zátvorky nepíšeme.

Mimochodom, ak chcete zmeniť interval volania funkcie, môžete zavolať install_int neskôr znova.

Ak chcete mať lepšiu kontrolu nad obslužnými funkciami, môžete použiť funkciu install_int_ex. Parameter tejto funkcie sa zadáva v tikoch hardwarových hodín, ale môžete použiť makrá, ktoré na tento formát vedia skonvertovať bežnejšie jednotky -- sekundy alebo milisekundy na jedno volanie, alebo počet volaní za sekundu alebo minútu. Čo sa týka detailov k tejto funkcii a ďalších informácií o tom, čo obslužné funkcie môžu alebo nemôžu urobiť, pozrite si Allegrovkú dokumentáciu.


[ Predošlý:Inštalovanie obslužných funkcií | Hore:Nastavenie obslužných funkcií časovača ]

9.2.4 Odinštalovanie obslužných funkcií

Odinštalovanie obslužnej funkcie je jednoduché -- stačí zavolať funkciu:

remove_int(my_callback_proc);

Teoreticky by ste teraz mohli odomknúť pamäť, ktorú funkcia zaberá, ale prakticky to nehrá žiadnu rolu.


[ Ďalší: | Predošlý:Nastavenie obslužných funkcií časovača | Hore:Časovače ]

9.3 Obmedzenia časovačov

Tu je zoznam niektorých vecí, ktoré musíte a ktoré nesmiete, keď píšete a používate obslužné funkcie časovačov (a vo všeobecnosti aj iných prerušení).

Musíte:

Nesmiete:


[ Predošlý:Obmedzenia časovačov | Hore:Časovače ]

9.4 Príklady časovačov


[ Ďalší: | Hore:Príklady časovačov ]

9.4.1 Časomiera hry

Tento príklad je celkom jednoduchý. Nastavíme globálnu premennú tak, aby obsahovala uplynulý počet sekúnd. Naša obslužná funkcia bude toto číslo zväčšovať. Zamkneme počítadlo aj obslužnú funkciu. Potom, keď chceme spustiť hodiny, nechháme obslužnú funkciu volať raz za sekundu. Potom môžeme z (volatile!) premennej zistiť, koľko času uplunulo.

Keď čas, ktorý stopujeme uplynie, môžeme si počítadlo skopírovať, alebo len odinštalovať obslužnú funkciu. Ak ju odinštalujeme, počítadlo sa už meniť nebude.

Ako variáciu môžeme naprogramovať časový limit. V tomto prípade nastavíme počítadlo na počet sekúnd, ktoré sú k dispozícií (napríklad na uhratie levelu). Obslužná funkcia bude túto hodnotu zmenšovať. V hlavnom cykle hry budeme kontrolovať, či je v počítadle kladná hodnota. Ak nie je, hráčovi vypršal čas. V tomto momente môžete obslužnú funkciu odinštalovať, aby ste zastavili ďalšie zmenšovanie počítadla.

Pozrite si examples/chap_09/ex_4_1.


[ Ďalší: | Predošlý:Časomiera hry | Hore:Príklady časovačov ]

9.4.2 Zisťovanie obnovovacej frekvencie hry

Toto je tiež celkom jednoduché

Najprv si urobíme dve volatile globálne premenné -- jednu nazvanú last_fps a druhú frame_counter. Potom spravíme obslužnú funkciu časovača, ktorá jednoducho skopíruje frame_counter do last_fps a potom nastavíte frame_counter na nulu. Zamkneme obe premenné a obslužnú funkciu a potom obslužnú funkciu necháme vyvolať raz za sekundu.

Teraz môžeme premennú last_fps zobraziť na monitore alebo zapísať do logu alebo čokoľvek. Na chvíľu to bude nula. Všetko, čo musíme urobiť, aby sme získali počet vykreslených rámcov za sekundu je, aby vykresľovacia funkcia po každom vykreslení rámca premennú frame_counter zväčšila o 1.

Funguje to preto, lebo obslužná funkcia sa volá každú sekundu a kopíruje hodnotu do last_fps, pričom vynuluje frame_counter. Potom vykreslíme nejaké rámce a kým to robíme, zväčšuje sa premenná frame_counter. Sekundu po predošlom volaní sa obslužná funkcia zavolá znovu a skopíruje počet vykreslených rámcov z frame_counter do last_fps.

Pozrite si examples/chap_09/ex_4_2.


[ Predošlý:Zisťovanie obnovovacej frekvencie hry | Hore:Príklady časovačov ]

9.4.3 Ovládanie rýchlosti hry

Táto téma je mojou nomináciou na "často pýtanú otázku" roka v mailingliste Allegra. Je veľmi dôležitá. Hry, ktoré nebežia rovnako rýchlo na rôznych počítačoch alebo za rôznych podmienok ma štvú. Nemyslím tým, že obnovovacia frekvencia musí byť na všetkých počítačoch rovnaká -- to samozrejme nie je možné. Hovorím o rýchlosti hry -- napríklad o rýchlosti pohybu postáv.

Aby ste to dosiahli, mohli by ste v ideálnom prípade chcieť hýbať postavami v pravidelných intervaloch. Žeby vo vnútri obslužnej funkcie časovača? V žiadnom prípade. Je to príliš komplikované; pamätajte, že obslužné funkcie musia byť jednoduché. Mimochodom, predstavte si, koľko problémov by narobilo snažiť sa zamknúť všetko, čoho sa takáto obslužná funkcia dotkne!

Najlepšia vec, ktorú môžeme reálne urobiť je časovač zväčšujúci premennú, ktorá obsahuje počet herných cyklov, ktoré by mali doteraz uplynúť. Potom môžeme upraviť herný cyklus tak, aby túto premennú porovnával s počtom herných cyklov, ktoré skutočne uplynuli. Ak sa omeškávame, musíme zavolať funkciu obsluhujúcu logiku hry ešte raz (prípadne niekoľkokrát), kým to nezrovnáme. Inak herným cyklom momentálne prejsť netreba.

Ak sme patričný počet cyklov prešli, môžeme si dať pohov a robiť iné veci, ako zobraziť jeden rámec grafiky. Zobrazovanie grafiky je často pomalá vec, čiastočne preto, lebo vykreslenie nejaký čas trvá a čiastočne preto, lebo sa s použitím vsync čaká na VBI. Pokiaľ kreslíme grafiku a čakáme na VBI, naša obslužná funkcia časovača je stále v pravidelných intervaloch volaná. Samozrejme s tým momentálne nerobíme nič; ono to počká, kým s grafikou skončíme. Ale keď sme s grafikou hotoví, vieme presne, koľko herných cyklov musíme prebehnúť, aby sme sa opäť dostali tam, kde chceme byť.

Takže ak to zhrnieme -- treba si spraviť dve počítadlá. Jedno sa bude zväčšovať v pravidelných intervaloch (cieľový interval cyklu hry) s pomocou obslužnej funkcie časovača a druhé bude zväčšovať raz za aktuálny herný cyklus funkcia, ktorá ho vykonáva. Na začiatku tieto počítadlá nastavíme na nulu. Pri každom prechode cez hlavný herný cyklus najprv nakreslíme jeden rámec grafiky. To vo väčšine hier zaberie relatívne dlhý čas. Potom zistíme, či je cieľový počet herných cyklov väčší, než aktuálny (to pravdepodobne bude) a robíme herné cykly, kým to nedoženieme. Potom začneme od začiatku.

Pozrite si examples/chap_09/ex_4_3.


[ Predošlý:Časovače | Hore:Začiatok ]

10. Datasúbory

Súbor grabber.txt v podadresári tools Allegra obsahuje dobrý popis takmer všetkých aspektov datasúborov. Ak je niektorý tu zmienený bod nejasný, ak chcete viac informácií alebo ak tu nie je niečo popísané do podrobností, je to miesto, kde by ste mali hľadať ďalšie informácie.


[ Ďalší: | Hore:Datasúbory ]

10.1 Koncept

Doteraz ste mohli vidieť, že hra vyžaduje mnoho rôznych súborov; je tam spustiteľný súbor, ktorý spúšťajú používatelia, potom grafika buď v niekoľkých veľkých súboroch z ktorých každý jej obsahuje spústu, alebo každý obrázok vo svojom vlastnom súbore (ja radšej používam prvý prístup). Tiež máme zvukové efekty (jeden súbor pre každý) a hudbu (jeden súbor pre každú časť).

To sú len štandardné súbory, ktoré potrebujú takmer všetky hry. Okrem toho môže každá hra obsahovať súbory s popisom levelov, umelej inteligencie protivníkov a iné potrebné dáta. Dohromady to môže byť značný počet súborov.

Vďaka spôsobu clusteringu pod DOSom musí byť každý súbor uchovávaný v blokoch určitej dĺžky. Na partíciach menších, než 1Gb majú tieto clustre (čítať "klastre") veľkosť 16K. Väčšina súborov nezaplní úplne posledný cluster a zostávajúci priestor je premrhaný -- v priemernom prípade je strata 8K na súbor, ale v praxi to pravdepodobne býva viac. Takto premrhaný priestor môže narastať, zvlášť ak používate veľa súborov, z ktorých každý je oveľa menší než veľkosť jedného clustra. Ak máte Windows 95, skúste napísať vo vašom adresári djgpp\zoneinfo príkaz dir /s/v a porovnajte "bytes" a "bytes allocated". Keď to spravíte, možno sa rozhodnete adresár vymazať, alebo aspoň zozipovať -- pravdepodobne ho nebudete potrebovať.

Množstvo súborov s koncovkami .PCX, .WAV alebo .MID tiež ľudí zvádza k tomu, aby si ich "požičali" pre svoje vlastné účely, alebo ich modifikovali a tak zmenili hru. Ak im to nechcete dovoliť, je rozumné nenechať tam tie súbory ležať len tak.

Odhliadnuc od predošlých dvoch dôvodov, je celkom chaos, ak máte všetky súbory uložené jednotlivo a vaša hra ich má na začiatku všetky nahrať. Datasúbory poskytujú spôsob, ako zabaliť všetky alebo niektoré vaše súbory do jedného veľkého datasúboru, ktorý môže byť načítaný naraz, prípadne môže byť skomprimovaný a dáta v ňom zašifrované.


[ Ďalší: | Predošlý:Koncept | Hore:Datasúbory ]

10.2 Vytvorenie datasúboru

Datasúbory sa vytvárajú zo súborov na disku. Allegro na narábanie s nimi poskytuje dva hlavné prostriedky: Grabber a nástroj DAT. Oba sú dobre zdokumentované v súbore grabber.txt, v adresári tools Allegra, ale spravím tu aspoň stručný popis.


[ Ďalší: | Hore:Vytvorenie datasúboru ]

10.2.1 Grabber

(nebolo zatiaľ napísané)


[ Predošlý:Grabber | Hore:Vytvorenie datasúboru ]

10.2.2 Nástroj DAT

(nebolo zatiaľ napísané)


[ Predošlý:Vytvorenie datasúboru | Hore:Datasúbory ]

10.3 Použitie datasúboru vo vašom programe

Je viacero spôsobov, ako môžete prečítať dáta z datasúboru. V prvom rade môžete prečítať celý datasúbor, všetko naraz. Za druhé môžete prečítať jednu zložku datasúbosu zvlášť. A nakoniec môžete otvoriť zložku datasúboru, ako keby to bol normálny súbor.


[ Ďalší: | Hore:Použitie datasúboru ]

10.3.1 Načítanie celého datasúboru

Toto je zvyčajný spôsob, akým sa veci robia. Pri tomto prístupe stačí zavolať jednu funkciu a Allegro načíta všetky objekty, ktoré ste uložili v datasúbore. Funkcia, ktorú treba zavolať je load_datafile:

DATAFILE *load_datafile(char *filename);

Dáte jej meno vášho datasúboru a vráti smerník na pole štruktúr DATAFILE, pre každý objekt je tam jedna. Takže ak data je DATAFILE * a vy napíšete:

data = load_datafile("datafile.dat");

potom data[0] bude prvý objekt datasúboru, data[1] druhý, atď.

Najdôležitejšia položka v štruktúre DATAFILE je položka dat. Je to smerník, ktorý ukazuje na aktuálne dáta objektu. Ak je objekt bitmapa, potom toto pole ukazuje na štruktúru BITMAP -- takže môžete písať:

blit(data[0].dat, screen, ...);

Ak je objekt zvuk, tak je to štruktúra SAMPLE:

play_sample(data[1].dat, 255, 128, 1000, 0);

Rovnako to funguje pre MIDI súbory, palety, animácie a niektoré ďalšie veci. V grabberi alebo nástroji DAT môžete vidieť, akého typu dát sú jednotlivé objekty, je to uvedené v zozname vľavo od mena objektu (napíšte dat -l <filename>, alebo sa pozrite na ľavú stranu obrazovky grabbera).

Tiež sa dá povedať, aký typ dát sa v objekte nachádza podľa položky type. Táto je nastavená na konštantu ako DAT_BITMAP, DAT_SAMPLE or DAT_MIDI. Ak dáta nie sú v zvláštnom formáte, je to DAT_DATA -- znamená to, že dat iba ukazuje na blok dát. Veľkosť tohto bloku môžete zistiť z položky size štrultúry DATAFILE:

load_map_data(data[2].dat, data[2].size);

odovzdá smerník na binárny blok a jeho veľkosť funkcii load_map_data (ktorú si sami napíšete).

Ďalej, ako zistiť, aké číslo objektu v datasúbore korešponduje s jednotlivými objektami, ktoré ste vložili do datasúboru? Sú tri spôsoby, ako to zistiť a ako vždy, je na vás, ktorý z nich zvolíte. Podľa okolností môžete zmixovať všetky tri dohromady.

Prvý spôsob je použiť hlavičkový súbor, ktorý vám môže vytvoriť grabber alebo nástroj DAT. Tento súbor bude obsahovať jedno makro pre každý súbor, ktoré #definuje meno objektu ako jeho index v datasúbore. Takže miesto použitia toho, čo som písal vyššie, môžete povedať:

blit(data[MY_BITMAP].dat, screen, ...);
play_sample(data[MY_SAMPLE].dat, 255, 128, 1000, 0);
load_map_data(data[MY_MAP].dat, data[MY_MAP].size);

Ak pri tomto prístupe pridáte do datasúboru nové objekty a uložíte ich pred staré, nemusíte v celom programe meniť všetky indexy.

Text každého #define je presne to, čo zadáte grabberu ako meno objektu. Ak na vytváranie objektu použijete nástroj DAT, jeho meno bude pravdepodobne pôvodné meno súboru napísané veľkými písmenami s tým, že znak . bude nahradený znakom _. Táto náhrada sa robí preto, aby výsledné meno objektu mohlo byť použiteľné v #define v Cčku -- bodky povolené nie sú.

V tejto súvislosti majte na pamäti, že keď vyberiete svojim objektom mená, tak budú použité vo vašom programe ako makrá v #define -- nevyberajte teda mená, ktoré môžu byť použité na ňiečo iné (napr.main, BITMAP, atď. ) V grabberi alebo nástroji dat môžete použiť nastavenie Prefix, čo spôsobí, že sa pred každé meno v hlavičkovom súbore niečo vloží. Ak ho napr. nastavíte na DATAFILE, dostanete:

blit(data[DATAFILE_BITMAP].dat, screen, ...);

ak bolo meno objektu BITMAP.

Ďalší spôsob, ako sa dá zistiť, kde sa objekt v datasúbore nachádza je založený na fakte, že sú uložené v abecednom poradí. Podľa autora je to pravda teraz a vždy aj bude. Neodporúčam používať túto vlastnosť pričasto, ale môže byť užitočná, ak napríklad máte viacero bitmáp (napríklad rámce jedného spritu), ktoré sú nazvané ENEMY_000, ENEMY_001, ENEMY_002, atď. Aby ste sa na ne odvolali, môžete samozrejme používať mená z #define, ale je výhodnejšie môcť vybrať niektorú z týchto premenných s pomocou čísla uloženého v premennej.

Kvôli spôsobu, akým preprocesing funguje (pre = pred, t.j. pred kompiláciou) nie je možné zmeniť počas behu programu ENEMY_000 na ENEMY_xxx. Ale keďže sú objekty uložené v abecednom poradí, viete, že ENEMY_000 bude uložená v súbore ako prvá a ENEMY_001 bude tesne za ňou -- teda n-tá bitmapa s nepriateľom bude objekt číslo ENEMY_000 + n (ak ako každý normálny programátor začínate číslovať od nily, teda ENEMY_000 je nultý objekt.) Potom nasledujúca vec bude fungovať:

void draw_nth_enemy (int n, BITMAP *where, int x, int y)
{
    draw_sprite(where, data[ENEMY_000 + n], x, y);
}

Tretí spôsob nájdenia objektu v datasúbore znamená prehľádávanie datasúboru, kým nenájdete objekt so správnym menom. Mená objektov sú uložené ako properties (vlastnosti) objektov. Všetky detaily nájdete v súbore grabber.txt. Stručne ale, ak chcete získať meno objektu, napíšte:

name = get_datafile_property(&dat[obj_number],
                              DAT_ID ('N','A','M','E'));

Všimnite si toho & -- odovzdávate smerník na prvok poľa. Ak je reťazec, ktorý sa vráti, prázdny (teda name[0] == '\0', nie name == NULL), potom objekt nemá vlastnosť NAME. To pravdepodobne znamená, že ste z datasúboru vysekali vlastnosti; v tom prípade musíte na vyhľadanie objektu použiť niektorú z predošlých dvoch metód.

Je spústa ďalších vecí, o ktorých by bolo dobré sa tu zmieniť. V prvom rade, ak používate C++, potom sa kompilátoru nebude páčiť, že dávate pole dat (smerník na void) ako parameter funkciam, ktoré očakávajú iný typ smerníka, takže ho musíte explicitne pretypovať na to, čo funkcia očakáva:

play_midi( (MIDI *) data[MUSIC].dat, 0);

Po druhé (a so vzťahom k predošlému) je často výhodné zriadiť si aliasy k objektom v datasúbore tak, že vytvoríte globálne premenné alebo polia, niečo ako:

BITMAP *enemy_bitmap;
MIDI *background_music;
SAMPLE *crash_sound;

potom ich nastavíte, aby ukazovali na určité miesta v datasúbore:

data = load_datafile("DATAFILE.DAT");
if (!data) barf();  /* loading failed */

enemy_bitmap = (BITMAP *) data[ENEMY_BITMAP];
background_music = (MIDI *) data[MUSIC];
crash_sound = (SAMPLE *) data[CRASH_SOUND];

a konečne ich použijete v hre:

draw_sprite(screen, enemy_bitmap, x, y);
play_midi(background_music, 1);
play_sample(crash_sound, 255, 128, 1000, 0);

Výhody tohto prístupu sú, že sa zbavíte obtiažneho pretypovávania potrebného v C++, výsledný kód bude krajší a trochu rýchlejší a tiež to zjednoduší prechod z kódu založeného na datasúboroch na kód založený na jednotlivých súboroch alebo naopak.

Po tretie, pole datasúboru je zakončené objektom typu DAT_END. Môžete a nemusíte to pokladať za veľmi užitočné; je to temer zbytočná informácia, čosi ako že argv[argc] je NULL. Ak chcete celý datasúbor z nejakého dôvodu prehľádať (napríklad, aby ste našli objekt s určitou vlastnosťou), môžete tento fakt použiť, aby ste vedeli, kde máte prestať.

Nakoniec, keď prácu s datasúborom skončíte, môžete ho odstrániť z pamäte použitím funkcie unload_datafile:

unload_datafile(data);

Je zbytočné hovoriť, že keď to spravíte, nemôžete viac pole data používať a ak máte nejaké aliasy pre objekty v poli (ako vyššie), nemôžete používať ani tie.

Pozrite si príklad: examples/chap_10/ex_3_1


[ Ďalší: | Predošlý:Načítanie celého datasúboru | Hore:Použitie datasúboru ]

10.3.2 Jednotlivé načítanie zložky datasúboru

Je možné z datasúboru načítať aj jednotlivé objekty, za predpokladu, že v datasúbore sú informácie o menách (teda, že ste v grabberi nevysekali všetky vlastnosti, alebo ste nepoužili -s2 v nástroji dat). Funkcia, ktorá to spraví, je load_datafile_object a používa sa spôsobom:

data_object = load_datafile_object ("datafile.dat", "object_name");

data_object bude DATAFILE *, rovnako ako keby ste načítali celý datasúbor. Na prístup k objektu len dereferencujete smerník:

music_object = load_datafile_object("datafile.dat", "MUSIC");
play_midi(music_object->dat);      /* alebo music_object[0].dat */

Keďže je načítaný len jeden objekt, funkcia vráti smerník na jedinú štruktúru DATAFILE, nie na celé pole. Takže nemôžete použiť index objektu z hlavičkového súboru a za vráteným objektom nenasleduje žiaden DAT_END objekt.

Na odstránenie objektu načítaného týmto spôsobom z pamäti použijete funkciu unload_datafile_object:

unload_datafile_object(data_object);

Pozrite príklad: examples/chap_10/ex_3_2


[ Predošlý:Jednotlivé načítanie zložky datasúboru | Hore:Použitie datasúboru ]

10.3.3 Čítanie zložky datasúboru ako normálneho súboru

Príjemná vlastnosť Allegrovských funkcií na prácu s pakovanými súbormi je schopnosť čítať objekt dátového súboru, ako by to bol súbor na disku. Stačí na to použiť kódovaný formát mena súboru meno_datasúboru#meno_objektu,napríklad:

fp = pack_fopen("datafile.dat#MUSIC", F_READ);

Ak potom fp nie je NULL, môžete z neho čítať ako z normálneho pakovaného súboru. Nemôžete do neho zapisovať. Keď ich chcete zatvoriť, použijete pack_fclose ako zvyčajne.

Pozrite si príklad: examples/chap_10/ex_3_3


[ Hore:Začiatok ]

Obsah