SDL: Hry nejen pro Linux (15)

31. 5. 2005
Doba čtení: 7 minut

Sdílet

V dnešním dílu o knihovně SDL dokončíme popis událostního systému. Budeme se mimo jiné věnovat změnám velikosti okna, jeho aktivacím a deaktivacím, posílání uživatelských zpráv a dalším věcem, které ještě zbývá probrat.

Ukončení aplikace

Požadavek na ukončení posílá operační systém/správce oken, když z nějakého důvodu požaduje po aplikaci, aby se ukončila. Ve většině případů se jedná o pokus uživatele zavřít okno programu.

Dalším důvodem pro vložení SDL_QUIT do fronty může být příchozí signál od operačního systému. SDL_Init() vždy instaluje handlery pro SIGINT (přerušení klávesnicí) a SIGTERM (požadavek na ukončení od operačního systému). Pokud programátor handlery pro tyto signály nevytvoří, jsou použity defaultní, které generují událost SDL_QUIT. U ní nelze žádným způsobem zjistit, z jakého důvodu byla poslána, ale nastavením handleru může být standardní chování přepsáno.

SDL_QUIT je nejjednodušší ze všech událostí. V SDL_Event sice existuje objekt struktury SDL_QuitEvent (event.quit), ten však neobsahuje jinou informaci, než je typ události.

typedef struct
{
    Uint8 type
} SDL_QuitEvent; 

Nejvhodnější reakcí na tuto zprávu je buď rovnou ukončit aplikaci, nebo, ve výjimečných případech, začít s ukončovacím dialogem, jako je zobrazení hlavního menu hry a podobně.

Kdykoli v programu se lze pomocí SDL_QuitRequested() zeptat, jestli náhodou nebyl vznesen požadavek na ukončení, v takovém případě vrátí toto makro nenulovou hodnotu.

Změna velikosti okna

Změnu velikosti okna oznamuje SDL posláním zprávy SDL_VIDEORESIZE, která v event.resize, objektu struktury SDL_ResizeEvent, poskytuje novou šířku a výšku okna.

typedef struct
{
    Uint8 type;
    int w, h;
} SDL_ResizeEvent; 

V reakci na událost by měla být zavolána funkce SDL_SetVideoMode() (viz 4. díl), která aktualizuje velikost klientské oblasti okna, do níž program kreslí.

Pozn.: V MS Windows s OpenGL způsobuje SDL_SetVideoMode() jisté problémy, viz 8. díl věnovaný OpenGL a SDL.

Pokud chceme z nějakého důvodu zakázat uživateli, aby mohl změnit velikost okna, není nic snazšího než NEpředat funkci SDL_SetVideoMode() parametr SDL_RESIZABLE.

Požadavek na překreslení

Událostí SDL_VIDEOEXPOSE oznamuje SDL programu, že je z nějakého důvodu nutné překreslit obsah okna. Tento stav může nastat, když je okno modifikováno vně aplikace, obvykle správcem oken. Objekt struktury SDL_ExposeEvent, který lze najít v event.expose, neobsahuje kromě typu žádné parametry.

typedef struct
{
    Uint8 type
} SDL_ExposeEvent; 

(De)aktivace okna

Přijde-li aplikaci zpráva SDL_ACTIVEEVENT, znamená to, že uživatel okno buď aktivoval, nebo deaktivoval (např. minimalizace). Specifické informace se pak hledají v event.active.

typedef struct
{
    Uint8 type;
    Uint8 gain;
    Uint8 state;
} SDL_ActiveEvent; 

Proměnná gain má v případě deaktivace nulovou hodnotu, jednička naopak označuje aktivaci. State může být nastaveno celkem na tři různé konstanty: SDL_APPMOUSEFOCUS, SDL_APPINPUTFOCUS a SDL_APPACTIVE.

První z nich vyjadřuje, že okno ztratilo/získalo fokus myši, což de facto znamená, že myš opustila oblast okna nebo do ní vstoupila. U druhé je předmětem zájmu klávesnice. Tento parametr obyčejně vyjadřuje, že se jiná aplikace stala aktivní. Konečně poslední možnost oznamuje minimalizaci, respektive obnovení minimalizované­ho okna.

Podobným stylem, jako se přistupovalo ke klávesnici nebo myši, se lze dotazovat i na stav okna. Funkce SDL_GetAppState() vrací kombinaci tří výše zmíněných symbolických konstant.

Uint8 SDL_GetAppState(void); 

Deaktivační událost se hodí například v případě, kdy kvůli animacím periodicky překreslujeme scénu. Je zbytečné, aby se tato činnost prováděla i tehdy, je-li okno minimalizované, protože uživatel nemá šanci cokoli zahlédnout.

Zachytí-li aplikace v příkladu níže minimalizaci okna (událost SDL_ACTIVEEVENT, state obsahuje SDL_APPACTIVE a gain je nulový), je zavolána funkce SDL_WaitEvent(), která uspí program. Parametr NULL v tomto případě říká, že nechceme, aby byla událost, jež probudí aplikaci, odstraněna z fronty.

// Zpracování událostí
case SDL_ACTIVEEVENT:
    if(event.active.state & SDL_APPACTIVE)
    {
        if(event.active.gain == 0)
        {
            SDL_WaitEvent(NULL);
        }
    }
    break; 

Tento kód však nemusí fungovat vždy! Je to způsobeno tím, že ve frontě může být za právě zpracovávanou událostí ještě nějaká další, která způsobí okamžité ukončení SDL_WaitEvent() a opětovné spuštění programu.

Řešení může spočívat v přesunutí SDL_WaitEvent() mimo událostní smyčku do podmínky třeba if(wait), kde wait je bool proměnná nastavená na true na stejném místě, na kterém se v tuto chvíli nachází SDL_WaitEvent(). Funkce se tedy spustí až tehdy, máme-li jistotu, že je fronta prázdná.

Mimochodem, další důležitou podmínkou pro to, aby se dala aplikace uspat, je vypnutí všech systémových časovačů – generují zprávy, které by opět vedly k předčasnému probuzení.

Uživatelské události

Pamatujete, jak jsme v 11. dílu popisovali funkci SDL_PushEvent()? Řekli jsme si, že uvnitř aplikace se neposílají standardní, ale většinou tzv. uživatelské události. Při jejich používání musí program zajistit nejen jejich zpracování, ale také posílání.

typedef struct
{
    Uint8 type;
    int code;
    void *data1;
    void *data2;
} SDL_UserEvent; 

Parametr type může nabývat hodnot z rozsahu SDL_USEREVENT až SDL_NUMEVENTS-1. Vzhledem k tomu, že má SDL_USEREVENT hodnotu 24 a celkový počet je 32, není počet událostí nijak závratný. Řešením může být druhý parametr, jenž může být použit, stejně jako vše u uživatelských událostí, naprosto libovolným způsobem – čili i na rozlišení „typu“ události. Tímto malým podvodem se jejich počet právě rozrostl, v případě 32 bitového procesoru, z původních osmi na několik desítek miliard (přesně 8 * 232).

Díky tomu, že jsou další dva parametry datového typu ukazatel na void, mohou se spolu s událostmi posílat naprosto libovolná data (resp. ukazatele na ně), jejich velikost navíc není omezena.

V následujícím příkladu pošleme funkci zpracovávající události testovací zprávu. Pro jednoduchost bude bez parametrů.

// Nejlépe do vyhrazeného hlavičkového souboru
#define USR_EVT_MOJE_UDALOST 0

// Kdekoli v kódu
SDL_Event event;

event.type = SDL_USEREVENT;
event.user.code = USR_EVT_MOJE_UDALOST;
event.user.data1 = NULL;// Bez parametrů
event.user.data2 = NULL;
SDL_PushEvent(&event); 

Pozn.: Doporučuji vytvořit si nějaký speciální hlavičkový soubor, ve kterém se budou uchovávat jména/kódy všech uživatelských událostí hezky přehledně na jednom místě. U větších projektů, zvlášť když se na vývoji podílí více lidí, se pak nedostanete do situace, kdy si začnete říkat ‚Ne, událost číslo 15 už bude určitě zabraná, ale 735689 by ještě mohla být volná ;-)‘.

Uživatelská událost se dá ošetřit úplně stejně jako kterákoli jiná, podrobnosti se tentokrát nacházejí v podobjektu event.user.

// Zpracování událostí
case SDL_USEREVENT:
    switch(event.user.code)
    {
    case USR_EVT_MOJE_UDALOST:
        // K parametrům by se přistupovalo takto:
        // param1 = (pretypovani*)event.user.data1;
        // param2 = (pretypovani*)event.user.data2;
        NecoUdelej();
        break;

    default:
        break;
    }
    break; 

Systémově závislé události

Poslední typ událostí, které ještě zbývá probrat, jsou události závislé na systému, v němž aplikace běží. Program samozřejmě nikdy nedostává zprávy, které se běžně používají v SDL, ale jen jejich ekvivalenty poskytované daným systémem.

Například pošle-li MS Windows zprávu WM_KEY_DOWN, SDL ji přeloží na SDL_KEYDOWN a umístí do fronty zpráv. U jiného systému se událost stisku klávesy může jmenovat úplně jinak, ale díky SDL program pracuje vždy jen s obecnou zprávou SDL_KEYDOWN.

Pro události, které nemají svůj ekvivalent, poskytuje SDL speciální událost SDL_SYSWMEVENT, která je v sobě všechny zahrnuje. Podrobnosti se hledají v event.syswm. Vkládání těchto událostí do fronty je standardně vypnuto, zapnutí je umožněno klasicky pomocí funkce SDL_EventState().

typedef struct
{
    Uint8 type;
    SDL_SysWMmsg *msg;
} SDL_SysWMEvent; 

Jediným parametrem události je ukazatel na objekt struktury SDL_SysWMmsg. Ta je pomocí komplikovaných příkazů preprocesoru deklarována v hlavičkovém souboru SDL_syswm.h vždy pro daný systém, na němž se program právě kompiluje. Například pro zprávy MS Windows vypadá deklarace struktury následovně (Win32 programátorům jistě povědomá).

struct SDL_SysWMmsg
{
    SDL_version version;
    HWND hwnd;
    UINT msg;
    WPARAM wParam;
    LPARAM lParam;
}; 

Systémovým událostem další prostor věnován nebude. Tyto techniky jsou především naprosto nepřenositelné, u kódu napsaného pro MS Windows nelze předpokládat, natož ani doufat, že půjde pod Linuxem, a to samé samozřejmě platí i opačným směrem.

SDL běží na desítkách systémů, zkusíte-li implementovat danou funkčnost pro každý zvlášť, většinou skončíte se dvěma nebo maximálně se třemi nejpoužívanějšími, které jsou pro vás dostupné, a ostatní jednoduše podporovány nebudou.

Navíc, pokud se pustíte do využívání událostí závislých na systému, pravděpodobně máte dostatek znalostí, že to zvládnete i bez pomoci tohoto seriálu…

Ukázkové programy

Menu

Ukázkový program demonstruje vytvoření jednoduchého menu, které je zapouzdřené do speciální třídy (resp. do dvou). Rodičovské QMenu se stará o operace, jako je vkládání položek, pohyb v menu, posílání událostí apod., a potomek QSDLMenu o vykreslování. Nechcete-li použít pro vykreslování SDL/SDL_ttf, ale například OpenGL, není nic snazšího než naprogramovat dalšího potomka, základní funkčnost zůstává zachována v QMenu.

Pozn.: To písmenko Q na začátku označuje třídy z jedné mé knihovničky (zatím soukromá, neustále spousta změn). Tímto programem jsem de facto zaplácl dvě mouchy jednou ranou – ukázkový program k článku a menu do semestrální práce z C++ ;-)

Co se týče funkcí programu, tak jednotlivé položky menu umožňují výběr obrázku na pozadí, skrytí menu a ukončení aplikace. Mezi položkami se dá pohybovat šipkami a výběr zprostředkovává klávesa enter. Po jejím stisknutí generuje třída uživatelskou událost, jejíž zpracování je už na aplikaci.

ict ve školství 24

Zdrojový kód se zvýrazněním syntaxe: aplikace, qmenu.h, qmenu.cpp, qsdlmenu.h, qsdlmenu.cpp

SDL 15: menu

Download

Pokračování

Příště se budeme zabývat timery, FPS regulací rychlosti a v podstatě vším s tematikou času.

Autor článku

Backend programátor ve společnosti Avast, kde vyvíjí a spravuje BigData systém pro příjem a analýzu událostí odesílaných z klientských aplikací založený na Apache Kafka a Apache Hadoop.