Az SDL multimédiás könyvtár
Czirkos Zoltán, Dobos-Kovács Mihály, Lant Gábor · 2021.08.24.
Grafikus programozás az SDL multimédiás könyvtárral.
Az SDL egy platformfüggetlen multimédiás függvénykönyvtár. A programozók számára egy egységes felületet biztosít a grafikus megjelenítéshez, hangok megszólaltatásához, billentyűk, egér és botkormányok kezeléséhez, miközben az egyes géptípusok, operációs rendszerek különbségeit elfedi. Így az SDL-lel megírt program működik különféle Windows verziókon, de Linuxokon, Mac OS X-en, és még néhány okostelefonon is.
Az alap SDL-ben nincsenek vonal, kör, és egyéb primitívek kirajzolásához függvények. Ahhoz további könyvtárakat kell telepíteni (pl. SDL_gfx). Ezen függvénykönyvtárak tudása viszont már elég nagy. Az SDL_ttf segítségével bármilyen betűtípust használva rajzolhatunk, az SDL_mixer több hang és zene megszólaltatását teszi lehetővé, az SDL_net pedig a hálózatprogramozás ügyes-bajos dolgait rejti egy platformfüggetlen réteg mögé. Az SDL_image nevű kiegészítő sokféle képformátumot (PNG, JPG) ismer; ilyen fájlokat lehet vele betölteni, és a programban kirajzolni.
Ez az írás tartalmaz néhány olyan információt (pl. a többmodulos programokkal kapcsolatban), amelyek csak egy későbbi előadás után lesznek teljesen érthetőek. De addig is használhatóak az instrukciókat pontosan követve. Az SDL telepítéséről egy külön írásban olvashattok.
Alább látható az első program. Ez kirajzol néhány kört a képernyőre, utána pedig addig vár, amíg a felhasználó be nem zárja az ablakot a nagy piros X-szel.
Az első lépés az SDL könyvtár inicializálása, ezt az SDL_Init()
nevű függvénnyel tehetjük meg. Az SDL alrendszerekből áll (grafika, hang, időzítés
stb.), legegyszerűbb azt mondani, hogy inicializáljuk mindegyiket
(SDL_INIT_EVERYTHING
).
Ezután létrehozunk egy 440×360 képpont méretű ablakot az
SDL_CreateWindow()
hívással. Ennek meg kell adni az ablak
pozícióját és méretét; az utolsó paraméterében pedig a tulajdonságait
(átméretezhető-e stb.), ez most egyszerűen 0
. A
visszatérési értéket eltároljuk az SDL_Window *window
változóba, így tudunk majd az ablakra hivatkozni – amúgy lehetne több
is.
A harmadik lépés egy megjelenítő (renderer) létrehozása az ablakhoz.
Ez végzi majd a rajzolást az ablakba. Azért kell ezt külön lépésként
kezelni, mert a megjelenítést végezheti a program is (szoftveres), vagy
a grafikus kártya adta lehetőségeket közvetlenül is kihasználhatnánk
(hardveres megjelenítés). Erről itt részletesen nem lesz szó, hanem
csak a 4. féléves Számítógépes grafika tárgyban. A Prog1 példaprogramjai
mindenhol szoftveres megjelenítést (SDL_RENDERER_SOFTWARE
)
használnak majd, mert azt könnyebb kezelni.
Ez a függvény is adhat NULL
pointert, ami azt jelenti,
hogy valami probléma történt. Ha nem, akkor viszont indulhat a
rajzolás!
#include <SDL.h>
#include <SDL2_gfxPrimitives.h>
#include <math.h>
#include <stdlib.h>
int main(int argc, char *argv[]) {
/* SDL inicializálása és ablak megnyitása */
if (SDL_Init(SDL_INIT_EVERYTHING) < 0) {
SDL_Log("Nem indithato az SDL: %s", SDL_GetError());
exit(1);
}
SDL_Window *window = SDL_CreateWindow("SDL peldaprogram", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 440, 360, 0);
if (window == NULL) {
SDL_Log("Nem hozhato letre az ablak: %s", SDL_GetError());
exit(1);
}
SDL_Renderer *renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_SOFTWARE);
if (renderer == NULL) {
SDL_Log("Nem hozhato letre a megjelenito: %s", SDL_GetError());
exit(1);
}
SDL_RenderClear(renderer);
/* rajzok */
int x, y, r;
r = 50;
/* karika */
x = 100;
y = 100;
circleRGBA(renderer, x, y, r, 255, 0, 0, 255);
circleRGBA(renderer, x + r, y, r, 0, 255, 0, 255);
circleRGBA(renderer, x + r * cos(3.1415 / 3), y - r * sin(3.1415 / 3), r, 0, 0, 255, 255);
/* antialias karika */
x = 280;
y = 100;
aacircleRGBA(renderer, x, y, r, 255, 0, 0, 255);
aacircleRGBA(renderer, x + r, y, r, 0, 255, 0, 255);
aacircleRGBA(renderer, x + r * cos(3.1415 / 3), y - r * sin(3.1415 / 3), r, 0, 0, 255, 255);
/* kitoltott kor */
x = 100;
y = 280;
filledCircleRGBA(renderer, x, y, r, 255, 0, 0, 255);
filledCircleRGBA(renderer, x + r, y, r, 0, 255, 0, 255);
filledCircleRGBA(renderer, x + r * cos(3.1415 / 3), y - r * sin(3.1415 / 3), r, 0, 0, 255, 255);
/* attetszo kor */
x = 280;
y = 280;
filledCircleRGBA(renderer, x, y, r, 255, 0, 0, 96);
filledCircleRGBA(renderer, x + r, y, r, 0, 255, 0, 96);
filledCircleRGBA(renderer, x + r * cos(3.1415 / 3), y - r * sin(3.1415 / 3), r, 0, 0, 255, 96);
/* szoveg */
stringRGBA(renderer, 110, 350, "Kilepeshez: piros x az ablakon", 255, 255, 255, 255);
/* az elvegzett rajzolasok a kepernyore */
SDL_RenderPresent(renderer);
/* varunk a kilepesre */
SDL_Event ev;
while (SDL_WaitEvent(&ev) && ev.type != SDL_QUIT) {
/* SDL_RenderPresent(renderer); - MacOS Mojave esetén */
}
/* ablak bezarasa */
SDL_Quit();
return 0;
}
A legalsó ciklusban található egy megjegyzés – az egy SDL hiba miatt van, ami macOS Mojave esetén jöhet elő. Lásd lentebb.
A program köröket rajzol, négyféleképpen. Az első három körnél egyszerűen kiszínezi azokat a képpontokat (pixel), amelyek a körívre esnek. A második háromnál ennél okosabb. Ahol a körív nem pont a képpontra esik, ott a szomszédos képpontok között színátmenetet képez. Ezt az eljárást úgy nevezik, hogy antialiasing. Így a rajz szebb, a körív nem annyira recegős.
A pozíció és a méret megadása után következik mindegyik függvénynél a szín megadása. Ezek három komponensből állnak: vörös, zöld és kék, mindegyik 0-tól 255-ig. A 255, 0, 0 jelenti a vöröset, 255, 255, 255 pedig a teljesen fehéret. A legutolsó paraméter az átlátszatlanságot adja meg, amely ugyancsak egy 0 és 255 közötti érték. 0 jelenti a teljesen átlátszót, 255 pedig a teljesen átlátszatlant. Ez látszik a jobb alsó sarokban, ahol a köröknél az érték 255 helyett csak 96. Így azok színei keverednek.
Miután elvégeztük az összes rajzolást, meg kell hívni az SDL_RenderPresent()
függvényt,
hogy az elkészült rajzot megjelenítsük. A rajzolások először csak a memóriában történtek, és igazából a hívás hatására
kerül ki minden az ablakba. Ez azért előnyös, mert így a felhasználó nem fogja látni, ahogy
egyesével jelennek meg az elemek, hanem csak a végeredményt – animációnál ez fontos lesz.
A további rajzolásokkal a meglévő képet módosítjuk; az eredmény pedig egy újabb
SDL_RenderPresent()
hatására jelenik meg.
Az SDL_gfx függvénykönyvtár néhány rajzeleme (grafikus primitíve):
pixelRGBA(kép, x, y, r, g, b, a)
– képpont rajzolása.lineRGBA(kép, x1, y1, x2, y2, r, g, b, a)
– szakasz.thickLineRGBA(kép, x1, y1, x2, y2, v, r, g, b, a)
– vastag szakasz.rectangleRGBA(kép, x1, y1, x2, y2, r, g, b, a)
– téglalap.boxRGBA(kép, x1, y1, x2, y2, r, g, b, a)
– kitöltött téglalap.circleRGBA(kép, x1, y1, R, r, g, b, a)
– kör.trigonRGBA(kép, x1, y1, x2, y2, x3, y3, r, g, b, a)
– háromszög.filledTrigonRGBA(kép, x1, y1, x2, y2, x3, y3, r, g, b, a)
– kitöltött háromszög.stringRGBA(kép, x, y, szöveg, r, g, b, a)
– szöveg.
A vonalas rajzokat (szakasz, kör, háromszög stb.) készítő függvényeknek mind van aa
-val
kezdődő párjuk is. Ezen felül minden függvénynek van egy nem RGBA
-ra,
hanem Color
-ra végződő nevű párja: az utóbbiak a négy, színt megadó paraméter
helyett csak egyetlen egyet várnak. Ez az egyetlen Uint32
típusú paraméter
0xRRGGBBAA
formában tartalmazza a színkomponenseket és az átlátszóság információt. Tehát
mind a vörös, zöld, kék komponensnek, mind az átlátszóságnak egyetlen egy bájt jut. Így egy szín
egyetlen egy változóban is eltárolható. A 32 biten megadott színkód bitműveletekkel állítható
elő (r<<24 | g<<16 | b<<8 | a
). Például az alábbi sorok teljesen
ekvivalensek, mindegyik félig átlátszó lila kört rajzol:
filledCircleRGBA(screen, 320, 240, 100, 255, 0, 255, 128);
filledCircleRGBA(screen, 320, 240, 100, 0xFF, 0, 0xFF, 0x80);
filledCircleColor(screen, 320, 240, 100, 0xFF00FF80);
Dokumentáció
Az egyszerű, konzolos programok lineárisan működnek: a printf()
-fel mondhatunk
valamit a felhasználónak, a scanf()
-fel pedig kérdezhetünk tőle valamit. Nem gond
az, hogy a scanf()
megakasztja a programot, mert amíg nincs meg a bemenő adat, addig
úgysem tudna továbbhaladni a program. Egy játéknál, meg általában a grafikus programoknál ez
nincs így. A programnak itt egyszerre több bemenete van: a billentyűzetre és az egérre is
reagálnia kell, arról nem is beszélve, hogy ha a felhasználó épp nem nyúl semelyikhez, akkor is
folytatódnia kell a képernyőn látható eseményeknek. Nem akadhat meg a játék attól, hogy éppen
nem nyomtuk meg egyik gombot sem!
Ezért találták ki az eseményvezérelt programozást. Az SDL a programhoz beérkező eseményeket összegyűjti (billentyűzet, egérmozdulatok, időzítések, ablak bezárása), és azokat keletkezésük sorrendjében adja nekünk. Ezt a programnak egy eseményhurokban (event loop) kell feldolgoznia, amely nagyon egyszerű:
SDL_Event event;
while (fut_a_program) {
SDL_WaitEvent(&event); /* várunk a következő eseményre */
switch (event.type) { /* esemény típusa szerinti esetszétválasztás */
... /* esemény feldolgozása */
}
}
Az SDL_WaitEvent()
függvény addig vár, amíg meg nem történik a következő
esemény; amint az bekövetkezik, akkor az adatait beteszi az event
nevű,
SDL_Event
típusú struktúrába (azért veszi át cím szerint, hogy ezt meg tudja
tenni). A várakozás után az eseményt feldolgozhatjuk, annak típusa szerint:
case SDL_QUIT:
kilépés, a felhasználó az ablak bezárása ×-re kattintott;break;
case SDL_MOUSEMOTION:
egérmozdulat;break;
case SDL_MOUSEBUTTONDOWN: case SDL_MOUSEBUTTONUP:
egérgomb kattintás és elengedés;break;
case SDL_KEYDOWN: case SDL_KEYUP:
billentyűzet események;break;
Az event
struktúra az esemény típusától függően további információkat tartalmaz.
Egérmozgás esetén az event.motion
struktúrát tölti ki az SDL_WaitEvent()
a koordinátákkal: event.motion.x
a vízszintes, event.motion.y
a függőleges koordináta. Kattintásnál az event.button
struktúra adattagjai vesznek fel értékeket: az event.button.button
adattag mutatja,
hogy melyik gombról van szó SDL_BUTTON_LEFT
, SDL_BUTTON_MIDDLE
,
SDL_BUTTON_RIGHT
.
Az alábbi C programban rajzolni lehet az egérrel. A működést a kód közepén lévő eseményhurok
irányítja. A bal gombbal lehet rajzolni, a jobb gombbal pedig törölni az ablak tartalmát.
Az eseményvezérlés kellemes vonása, hogy a program gyakorlatilag semennyire sem terheli le
a számítógépet. Amíg nincs esemény, addig ugyanúgy alszik, ahogyan azt egy scanf()
-re
várakozás esetén is teszi.
#include <SDL.h>
#include <SDL2_gfxPrimitives.h>
#include <math.h>
#include <stdbool.h>
#include <stdlib.h>
int main(int argc, char *argv[]) {
/* SDL inicializálása és ablak megnyitása */
if (SDL_Init(SDL_INIT_EVERYTHING) < 0) {
SDL_Log("Nem indithato az SDL: %s", SDL_GetError());
exit(1);
}
SDL_Window *window = SDL_CreateWindow("SDL peldaprogram", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 440, 360, 0);
if (window == NULL) {
SDL_Log("Nem hozhato letre az ablak: %s", SDL_GetError());
exit(1);
}
SDL_Renderer *renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_SOFTWARE);
if (renderer == NULL) {
SDL_Log("Nem hozhato letre a megjelenito: %s", SDL_GetError());
exit(1);
}
SDL_RenderClear(renderer);
/* az esemenyvezerelt hurok */
bool quit = false;
bool click = false;
int elozox = 0;
int elozoy = 0;
while (!quit) {
SDL_Event event;
SDL_WaitEvent(&event);
bool rajzoltam = false;
switch (event.type) {
/* eger kattintas */
case SDL_MOUSEBUTTONDOWN:
if (event.button.button == SDL_BUTTON_LEFT) {
click = true;
elozox = event.button.x;
elozoy = event.button.y;
}
else if (event.button.button == SDL_BUTTON_RIGHT) {
boxColor(renderer, 0, 0, 439, 359, 0x000000FF);
rajzoltam = true;
}
break;
/* egergomb elengedese */
case SDL_MOUSEBUTTONUP:
if (event.button.button == SDL_BUTTON_LEFT) {
click = false;
}
break;
/* eger mozdulat */
case SDL_MOUSEMOTION:
if (click) {
aalineColor(renderer, elozox, elozoy,
event.motion.x, event.motion.y, 0xFFFFFFFF);
rajzoltam = true;
}
/* a kovetkezo mozdulat esemenyhez */
elozox = event.motion.x;
elozoy = event.motion.y;
break;
/* ablak bezarasa */
case SDL_QUIT:
quit = true;
break;
}
if (rajzoltam)
SDL_RenderPresent(renderer);
}
SDL_Quit();
return 0;
}
Maga az eseményhurok ennél a progamnál tulajdonképpen egy állapotgép. Na nem azért, mert
switch
van benne (az csak az események típusának megállapításához kell), hanem
mert az egyes események jelentése eltérő attól függően, hogy mik történtek a múltban. Például az
egérmozdulatnál csak akkor rajzolunk, ha előzőleg egy kattintás eseményt már feldolgoztunk.
Minden mozdulatnál megjegyezzük a koordinátákat, hogy a legközelebbi ugyanilyen eseménynél
tudjuk, honnan hova kell húzni a vonalat.
SDL_WaitEvent vagy SDL_PollEvent?
Eseménykezelésre az SDL-ben két függvény is van, a WaitEvent
és a PollEvent
. A
kettő között a különbség az, hogy a WaitEvent
megvárja, amíg történik valami, és addig nem tér vissza, amíg nincs
feldolgozandó esemény. A PollEvent
azonnal visszatér mindig, akkor is, ha épp nincs teendő. A PollEvent
függvényt a legritkább esetben használjuk, ciklusban különösen nem, mert 100%-ra járatnánk a gépet. A legtöbbször a
WaitEvent
-re van szükség. Ha a várakozás idejét limitálni kell, akkor időzítőket érdemes használni – lásd a
következő részt.
Előbb arról volt szó, hogy a program futásának nem szabad megszakadnia amiatt, mert eseményre vár – és aztán jött egy program forráskódja, amely nem csinál semmit, azaz alszik az események között. Hogy fog akkor a játék tovább futni, amíg a felhasználó nem nyúl se a billentyűzethez, se az egérhez? Nagyon egyszerű: létre kell hozni egy időzítőt, amely adott időközönként generál egy eseményt. Ha létrejön az esemény, annak hatására fel fog ébredni az eseményhurok – de fel fog ébredni a billentyűzet vagy az egér hatására is.
Időzítőt létrehozni az SDL_AddTimer()
függvénnyel lehet. Ennek paraméterei a
következők: 1) mennyi idő múlva hívódjon meg (ezredmásodperc), 2) melyik függvény hívódjon meg,
3) egy tetszőleges mutató, amit paraméterként meg fog kapni a függvény. (Ha ez nem kell semmire,
akkor lehet NULL.) A függvény visszatérési értéke egy SDL_TimerID
típusú azonosító,
amivel hivatkozhatunk az időzítőre (pl. az SDL_RemoveTimer()
-nek paraméterként adva
letilthatjuk azt.) A hívás tehát így néz ki:
id = SDL_AddTimer(20, idozit, NULL);
A paraméterként adott függvény fejléce kötött, ilyen kell legyen:
Uint32 idozit(Uint32 ms, void *param);
Vagyis az SDL időzítője által meghívott függvény megkapja paraméterként azt, hogy milyen
időközökre lett beállítva, és a tetszőleges felhasználói paramétert. Visszatérési értéke pedig
egy egész szám, hogy legközelebb hány ezredmásodperc múlva hívódjon meg. Legegyszerűbb, ha egy
return ms;
sorral fejezzük be a függvényt, amiben általában amúgy sincs más, csak
egy felhasználói típusú esemény létrehozása, és beillesztése a várakozási sorba:
Uint32 idozit(Uint32 ms, void *param) {
SDL_Event ev;
ev.type = SDL_USEREVENT;
SDL_PushEvent(&ev);
return ms; /* ujabb varakozas */
}
Az eseménykezelő hurkot tehát ki kell egészíteni az SDL_USEREVENT típusú esemény(ünk) feldolgozásával. A labdát pattogtató program így néz ki:
#include <stdlib.h>
#include <stdbool.h>
#include <SDL.h>
#include <SDL2_gfxPrimitives.h>
/* ablak megnyitasa */
void sdl_init(int szeles, int magas, SDL_Window **pwindow, SDL_Renderer **prenderer) {
if (SDL_Init(SDL_INIT_EVERYTHING) < 0) {
SDL_Log("Nem indithato az SDL: %s", SDL_GetError());
exit(1);
}
SDL_Window *window = SDL_CreateWindow("SDL peldaprogram", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, szeles, magas, 0);
if (window == NULL) {
SDL_Log("Nem hozhato letre az ablak: %s", SDL_GetError());
exit(1);
}
SDL_Renderer *renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_SOFTWARE);
if (renderer == NULL) {
SDL_Log("Nem hozhato letre a megjelenito: %s", SDL_GetError());
exit(1);
}
SDL_RenderClear(renderer);
*pwindow = window;
*prenderer = renderer;
}
/* ez a fuggveny hivodik meg az idozito altal.
* betesz a feldolgozando esemenyek koze (push) egy felhasznaloi esemenyt */
Uint32 idozit(Uint32 ms, void *param) {
SDL_Event ev;
ev.type = SDL_USEREVENT;
SDL_PushEvent(&ev);
return ms; /* ujabb varakozas */
}
int main(int argc, char *argv[]) {
enum { ABLAK=360 };
enum { GOLYO_R=10 };
typedef struct Golyo {
int x, y;
int vx, vy;
} Golyo;
SDL_Window *window;
SDL_Renderer *renderer;
sdl_init(ABLAK, ABLAK, &window, &renderer);
/* idozito hozzaadasa: 20 ms; 1000 ms / 20 ms -> 50 fps */
SDL_TimerID id = SDL_AddTimer(20, idozit, NULL);
/* animaciohoz */
Golyo g;
g.x = ABLAK/2;
g.y = ABLAK/3;
g.vx = 3;
g.vy = 2;
/* szokasos esemenyhurok */
bool quit = false;
while (!quit) {
SDL_Event event;
SDL_WaitEvent(&event);
switch (event.type) {
/* felhasznaloi esemeny: ilyeneket general az idozito fuggveny */
case SDL_USEREVENT:
/* kitoroljuk az elozo poziciojabol (nagyjabol) */
filledCircleRGBA(renderer, g.x, g.y, GOLYO_R, 0x20, 0x20, 0x40, 0xFF);
/* kiszamitjuk az uj helyet */
g.x += g.vx;
g.y += g.vy;
/* visszapattanás */
if (g.x < GOLYO_R || g.x > ABLAK-GOLYO_R)
g.vx *= -1;
if (g.y < GOLYO_R || g.y > ABLAK-GOLYO_R)
g.vy *= -1;
/* ujra kirajzolas, es mehet a kepernyore */
filledCircleRGBA(renderer, g.x, g.y, GOLYO_R, 0x80, 0x80, 0xFF, 0xFF);
SDL_RenderPresent(renderer);
break;
case SDL_QUIT:
quit = true;
break;
}
}
/* idozito torlese */
SDL_RemoveTimer(id);
SDL_Quit();
return 0;
}
Itt nagyon fontos, hogy csak a kép teljes megrajzolása után hívjuk meg az
SDL_RenderPresent()
-et. Ha a törlés után is meghívnánk, akkor az animáció villódzna
(flicker), így viszont szép, folytonos a megjelenítés. Törölni viszont kell, hiszen mindig az
előzőleg megrajzolt képet módosítjuk.
Nem csak egy, hanem akár egyszerre több időzítőt is létrehozhatunk. Hogy ezeket meg lehessen
különböztetni, az általuk generált eseményeknek érdemes külön azonosítót adni. Az események
típusa, az event.type
adattag nem felsorolt típus, hanem egy egyszerű egész szám.
Az SDL dokumentációja pedig azt mondja, hogy az SDL_USEREVENT
konstanstól (ez egy
felsorolt típusú érték) fölfelé bármilyen saját eseményt definiálhatunk. Ezért ezeket
használhatjuk akár úgy is, hogy az egyik időzítőnk SDL_USEREVENT+1
, a másik
SDL_USEREVENT+2
stb. típusú eseményeket generál.
Ez nagyon egyszerű feladat: az SDL_image nevű függvénykönyvtárnak van egy
IMG_LoadTexture()
nevű függvénye. Ennek paramétere a megjelenítő mellett a betöltendő kép, ami
elég sokféle formátumú lehet (az SDL_image dokumentációja szerint BMP, GIF, JPEG, LBM, PCX, PNG,
PNM, TGA, TIFF, WEBP, XCF, XPM és XV). A függvény visszatérési értéke egy
SDL_Texture*
, vagyis egy mutató a betöltött képre. Ezzel tudunk később hivatkozni
rá, mert bent maradt a gép memóriájában. Ha már nincs rá szükség, fel kell azt
szabadítani, az SDL_DestroyTexture()
függvénnyel. Ha ezt nem tesszük meg, a
betöltött képek miatt a programunk egyre több memóriát foglal. Úgyhogy ez fontos!
A betöltött képpel sincsen nehéz dolgunk: az SDL_RenderCopy()
függvény tud képet megjeleníteni. Ennek a
paraméterei: megjelenítő, kép, forrás téglalap, cél téglalap. Vagyis nem csak a teljes képet tudja másolni, hanem annak csak egy
részletét is, a cél kép tetszőleges pozíciójára. A pozíciókat és a méreteket SDL_Rect
típusú struktúrákkal kell
megadni; ezekre a függvény pointereket vesz át:
SDL_Rect forrasterulet = { forras_x, forras_y, forras_szelesseg, forras_magassag };
SDL_Rect celterulet = { cel_x, cel_y, cel_szelesseg, cel_magassag };
SDL_BlitSurface(megjelenito, kep, &forrasterulet, &celterulet);
Ha a teljes forrás képet szeretnénk másolni, akkor a forrás téglalapra mutató pointer lehet
NULL
, de a cél méreteit ilyenkor is meg kell adni. Ha le szeretnénk kérdezni egy
betöltött kép méretét, azt a SDL_QueryTexture(source, NULL, NULL, &w, &h);
hívással tehetjük meg.
Az alábbi programban kihasználjuk azt, hogy a kép egy részét is lehet másolni. A program
tartalmaz egy felsorolt típust, amely a fenti képen látható figurák sorrendjében nevezi meg
azokat. A babu_rajzol()
függvényen belül kiszámítódnak a fenti képen belüli
koordináták (melyik figuráról van szó). Ez a kép
egyébként átlátszó képpontokat is tartalmaz, az SDL ezt is támogatja. A fájlt le kell tölteni,
és a futtatható (.exe) mellé tenni pieces.png
néven.
Néhány fontos dolog:
- A betöltött képet, amíg nem dobtuk el az
SDL_DestroyTexture()
függvénnyel, akárhányszor használhatjuk. - A képeket ide-oda adogathatjuk a programban: az
SDL_Texture*
típusú képhivatkozás akár függvény paramétere vagy visszatérési értéke is lehet. - Az
SDL_RenderCopyEx()
függvény forgatni is tud. - Az SDL_image teljes dokumentációja itt érhető el: SDL_image.
#include <SDL.h>
#include <SDL_image.h>
#include <SDL2_gfxPrimitives.h>
#include <stdlib.h>
/* mezon allo figura. ugyanolyan sorrendben vannak, mint a kepen,
* igy a kapott egesz szamok megegyeznek a png-beli indexekkel */
typedef enum Babu {
VKiraly, VVezer, VBastya, VFuto, VHuszar, VGyalog,
SKiraly, SVezer, SSastya, SFuto, SHuszar, SGyalog
} Babu;
/* a pieces.png fajlban levo figurak merete */
enum { MERET = 52 };
/* kirajzol egy babut; a forras a betoltott png, a cel nevu kepre rajzol.
* melyik babut, milyen koordinatakra: melyik, x, y. */
void babu_rajzol(SDL_Renderer *renderer, SDL_Texture *babuk, Babu melyik, int x, int y) {
/* a forras kepbol ezekrol a koordinatakrol, ilyen meretu reszletet masolunk. */
SDL_Rect src = { (melyik % 6) * 62 + 10, (melyik / 6) * 60 + 10, MERET, MERET };
/* a cel kepre, ezekre a koordinatakra masoljuk */
SDL_Rect dest = { x, y, MERET, MERET };
/* kepreszlet masolasa */
SDL_RenderCopy(renderer, babuk, &src, &dest);
}
/* ablak megnyitasa */
void sdl_init(int szeles, int magas, SDL_Window **pwindow, SDL_Renderer **prenderer) {
if (SDL_Init(SDL_INIT_EVERYTHING) < 0) {
SDL_Log("Nem indithato az SDL: %s", SDL_GetError());
exit(1);
}
SDL_Window *window = SDL_CreateWindow("SDL peldaprogram", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, szeles, magas, 0);
if (window == NULL) {
SDL_Log("Nem hozhato letre az ablak: %s", SDL_GetError());
exit(1);
}
SDL_Renderer *renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_SOFTWARE);
if (renderer == NULL) {
SDL_Log("Nem hozhato letre a megjelenito: %s", SDL_GetError());
exit(1);
}
SDL_RenderClear(renderer);
*pwindow = window;
*prenderer = renderer;
}
int main(int argc, char *argv[]) {
SDL_Window *window;
SDL_Renderer *renderer;
sdl_init(320, 200, &window, &renderer);
/* kep betoltese */
SDL_Texture *babuk = IMG_LoadTexture(renderer, "pieces.png");
if (babuk == NULL) {
SDL_Log("Nem nyithato meg a kepfajl: %s", IMG_GetError());
exit(1);
}
/* rajz keszitese */
boxRGBA(renderer, 0, 0, 319, 199, 0x90, 0xE0, 0x90, 0xFF);
babu_rajzol(renderer, babuk, VKiraly, 82, 74);
babu_rajzol(renderer, babuk, SGyalog, 82+MERET, 74);
babu_rajzol(renderer, babuk, VHuszar, 82+2*MERET, 74);
SDL_RenderPresent(renderer);
/* nincs mar ra szukseg: felszabaditjuk a memoriat */
SDL_DestroyTexture(babuk);
/* szokasos varakozas */
SDL_Event event;
while (SDL_WaitEvent(&event) && event.type != SDL_QUIT) {
}
SDL_Quit();
return 0;
}
Az SDL_gfx stringRGBA()
függvénye meg tud jeleníteni szövegeket (lásd az első
példaprogramot), de sajnos a használt betűk nagyon kicsik, és nem ismeri a magyar ékezetes
betűket sem (árvíztűrő tükörfúrógép). SDL_TTF függvénykönyvtár megoldja mindkét problémát. (A
dokumentációja itt érhető el.) Ez
tetszőleges True Type betűtípust be tud olvasni (Arial, Trebuchet stb.), és helyesen tudja
kezelni az ékezetes betűket is.
Bár az angol ábécé betűit kódoló ASCII szabvány gyakorlatilag mára egyeduralkodóvá vált a világon, az ékezetes betűket és egyéb karaktereket kódoló szabványokról ez sajnos nem mondható el. Több kódtáblát, azaz betű→szám táblázatot is használnak elterjedten a világon; az egységes Unicode kódolás még nem szorította ki a többit. (Ezekről bővebben a Rémtörténet a karakterkódolásokról írásban olvashatsz.) A többféle kódtábla miatt az SDL_TTF-ben minden szövegrajzoló függvénynek három változata van: 1) a Latin-1 kódolású szöveget, 2) a Unicode kódolású szöveget, és 3) az UTF-8 kódolású szöveget váró függvény.
A használat menete a következő. A használandó betűtípus fájlt először meg kell nyitni az
TTF_OpenFont()
függvényhívással. Ilyenkor meg kell adni a betűk méretét is. A
függvény visszatérési értéke egy TTF_Font
típusú mutató, amellyel hivatkozni lehet
a betűtípusra (ilyenből több is lehet), és amelyet a TTF_CloseFont()
függvénynek a
program végén oda kell adni, hogy felszabadítsa a memóriaterületet.
Minden alkalommal, amikor egy szöveget meg kell rajzolni, azt valamelyik
TTF_Render…()
függvénnyel kell tenni, függően a
rajzolás kívánt minőségétől és a karakterkódolástól. A függvények visszatérnek egy
SDL_Surface*
típusú mutatóval, mivel a rajzolások kimenete egy kép, amelyben meg van
rajzolva a felirat. Ez már átadható a megjelenítőnek, SDL_Texture
objektummá
alakítható a SDL_CreateTextureFromSurface()
függvénnyel. Ha
már nincs rá szükség, akkor pedig fel kell szabadítani a hozzá tartozó memóriaterületet az
SDL_FreeSurface()
hívással. (Természetesen ha sok, különféle felirat van, akkor
ehhez a műveletsorhoz érdemes saját függvényeket írni. A programozásban nem copy-pastelünk!)
A rajzolások módja a következő lehet, bár a kép mindent elmond:
TTF_Render..._Solid
: gyors, de a betűk széle recegős.TTF_Render..._Shaded
: nem recegős. A háttér egy megadott szín.TTF_Render..._Blended
: nem recegős. A háttér átlátszó.
Betűtípusokat a Windows C:\Windows\Fonts
mappájában, vagy a Linux
/usr/share/fonts/truetype
mappájában lehet találni. Meg a neten egy
csomó helyen, csak sajnos az ingyenes betűtípusokból hiányozni szokott a hosszú ő
és az ű
betű. A lenti program a Liberation Serif nevű betűtípust használja. A linkre
kattintva letölthető fájlt a Code::Blocks projekt mappájába kell tenni.
#include <SDL.h>
#include <SDL_ttf.h>
#include <SDL2_gfxPrimitives.h>
#include <stdlib.h>
void sdl_init(int szeles, int magas, SDL_Window **pwindow, SDL_Renderer **prenderer) {
if (SDL_Init(SDL_INIT_EVERYTHING) < 0) {
SDL_Log("Nem indithato az SDL: %s", SDL_GetError());
exit(1);
}
SDL_Window *window = SDL_CreateWindow("SDL peldaprogram", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, szeles, magas, 0);
if (window == NULL) {
SDL_Log("Nem hozhato letre az ablak: %s", SDL_GetError());
exit(1);
}
SDL_Renderer *renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_SOFTWARE);
if (renderer == NULL) {
SDL_Log("Nem hozhato letre a megjelenito: %s", SDL_GetError());
exit(1);
}
*pwindow = window;
*prenderer = renderer;
}
int main(int argc, char *argv[]) {
SDL_Window *window;
SDL_Renderer *renderer;
sdl_init(480, 200, &window, &renderer);
SDL_Color feher = {255, 255, 255}, piros = {255, 0, 0};
/* hatter */
for (int i = 0; i < 500; ++i)
filledCircleRGBA(renderer, rand() % 480, rand() % 200,
10 + rand() % 5, rand() % 256, rand() % 256, rand() % 256, 64);
/* betutipus betoltese, 32 pont magassaggal */
TTF_Init();
TTF_Font *font = TTF_OpenFont("LiberationSerif-Regular.ttf", 32);
if (!font) {
SDL_Log("Nem sikerult megnyitni a fontot! %s\n", TTF_GetError());
exit(1);
}
/* felirat megrajzolasa, kulonfele verziokban */
SDL_Surface *felirat;
SDL_Texture *felirat_t;
SDL_Rect hova = { 0, 0, 0, 0 };
/* ha sajat kodban hasznalod, csinalj belole fuggvenyt! */
felirat = TTF_RenderUTF8_Solid(font, "TTF_RenderUTF8_Solid()", feher);
felirat_t = SDL_CreateTextureFromSurface(renderer, felirat);
hova.x = (480 - felirat->w) / 2;
hova.y = 20;
hova.w = felirat->w;
hova.h = felirat->h;
SDL_RenderCopy(renderer, felirat_t, NULL, &hova);
SDL_FreeSurface(felirat);
SDL_DestroyTexture(felirat_t);
/* ha sajat kodban hasznalod, csinalj belole fuggvenyt! */
felirat = TTF_RenderUTF8_Shaded(font, "TTF_RenderUTF8_Shaded()", feher, piros);
felirat_t = SDL_CreateTextureFromSurface(renderer, felirat);
hova.x = (480 - felirat->w) / 2;
hova.y = 60;
hova.w = felirat->w;
hova.h = felirat->h;
SDL_RenderCopy(renderer, felirat_t, NULL, &hova);
SDL_FreeSurface(felirat);
SDL_DestroyTexture(felirat_t);
/* ha sajat kodban hasznalod, csinalj belole fuggvenyt! */
felirat = TTF_RenderUTF8_Blended(font, "TTF_RenderUTF8_Blended()", feher);
felirat_t = SDL_CreateTextureFromSurface(renderer, felirat);
hova.x = (480 - felirat->w) / 2;
hova.y = 100;
hova.w = felirat->w;
hova.h = felirat->h;
SDL_RenderCopy(renderer, felirat_t, NULL, &hova);
SDL_FreeSurface(felirat);
SDL_DestroyTexture(felirat_t);
/* tudja az ekezeteket */
/* ez az utf8 szoveg azert nez ki ilyen rosszul, mert szinte csak ekezetes betu van benne.
* amugy a code::blocks-ban utf8 forrasfajlt kell beallitani, es akkor irhato kozvetlenul
* ekezetes betu ide. */
felirat = TTF_RenderUTF8_Blended(font, "\xC3\xA1rv\xC3\xADzt\xC5\xB1r\xC5\x91 "
"t\xC3\xBCk\xC3\xB6rf\xC3\xBAr\xC3\xB3g\xC3\xA9p "
"\xE2\x98\xBA \xE2\x82\xAC", feher);
felirat_t = SDL_CreateTextureFromSurface(renderer, felirat);
hova.x = (480 - felirat->w) / 2;
hova.y = 140;
hova.w = felirat->w;
hova.h = felirat->h;
SDL_RenderCopy(renderer, felirat_t, NULL, &hova);
SDL_FreeSurface(felirat);
SDL_DestroyTexture(felirat_t);
SDL_RenderPresent(renderer);
/* nem kell tobbe */
TTF_CloseFont(font);
SDL_Event event;
while (SDL_WaitEvent(&event) && event.type != SDL_QUIT) {
}
SDL_Quit();
return 0;
}
A TTF_Init()
függvényhívást elég egyszer megtenni a program legelején. A
TTF_OpenFont()
által beolvasott betűtípus pedig akárhányszor használható – annyi
szöveget írhatunk ki vele, amennyit csak szeretnénk. Akár többféle betűtípus is lehet betöltve
egyszerre. Ha valamelyikre nincs már szükségünk, csak akkor kell felszabadítani a hozzá tartozó
memóriaterületet egy TTF_CloseFont()
hívással. Ha többször is szeretnénk használni
a betűtípust, nem szabad mindig törölni és újra betölteni azt, mivel nagyon időigényes az a
művelet! A betöltött betűtípusokat hivatkozó TTF_Font*
típusú mutatók a képekhez
hasonlóan függvényeknek is átadhatjuk.
A billentyűzet kezelése SDL-ben nem nagy ördöngősség: SDL_KEYDOWN
eseményt kapunk egy billentyű megnyomásánál, SDL_KEYUP
eseményt az
elengedésénél. Az esemény adatait tároló strktúrában az
alábbi adattagok érhetőek el:
event.key.keysym.sym
: a lenyomott billentyű azonosítója, ebből a táblázatból.event.key.keysym.mod
: módosító billentyűk (shift, ctrl stb.) ebből a táblázatból. Mivel egyszerre több módosító is le lehet nyomva, egy bitenkénti ÉS&
művelettel kell megvizsgálni azt, amelyik érdekes. A módosítók lenyomásakor külön esemény is érkezik.
Játékokban, ahol arra vagyunk kíváncsiak, hogy nyomva van-e tartva egy billentyű, nekünk kell
külön megjegyezni azt. Ez egyszerűen megoldható egy logikai típusú változóval, amelynek értékét
SDL_KEYDOWN
esemény esetén igazra, SDL_KEYUP
esemény esetén pedig
hamisra állítjuk. Az alábbi példaprogramban pontosan ez történik. A balra ←
és jobbra → nyilakat nyomva tartva lehet változtatni a háromszögek színét.
Az Esc gomb pedig az ablak bezárásához hasonlóan kilép a programból.
#include <SDL.h>
#include <SDL2_gfxPrimitives.h>
#include <stdbool.h>
#include <stdlib.h>
/* ablak megnyitasa */
void sdl_init(int szeles, int magas, SDL_Window **pwindow, SDL_Renderer **prenderer) {
if (SDL_Init(SDL_INIT_EVERYTHING) < 0) {
SDL_Log("Nem indithato az SDL: %s", SDL_GetError());
exit(1);
}
SDL_Window *window = SDL_CreateWindow("SDL peldaprogram", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, szeles, magas, 0);
if (window == NULL) {
SDL_Log("Nem hozhato letre az ablak: %s", SDL_GetError());
exit(1);
}
SDL_Renderer *renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_SOFTWARE);
if (renderer == NULL) {
SDL_Log("Nem hozhato letre a megjelenito: %s", SDL_GetError());
exit(1);
}
SDL_RenderClear(renderer);
*pwindow = window;
*prenderer = renderer;
}
int main(int argc, char *argv[]) {
SDL_Window *window;
SDL_Renderer *renderer;
sdl_init(350, 200, &window, &renderer);
bool quit = false;
bool left = false;
bool right = false;
bool rajz = true;
while (!quit) {
if (rajz) {
if (left)
filledTrigonRGBA(renderer, 50, 100, 150, 50, 150, 150, 0x00, 0xC0, 0x00, 0xFF);
else
filledTrigonRGBA(renderer, 50, 100, 150, 50, 150, 150, 0xFF, 0x00, 0x00, 0xFF);
if (right)
filledTrigonRGBA(renderer, 300, 100, 200, 50, 200, 150, 0x00, 0xC0, 0x00, 0xFF);
else
filledTrigonRGBA(renderer, 300, 100, 200, 50, 200, 150, 0xFF, 0x00, 0x00, 0xFF);
SDL_RenderPresent(renderer);
rajz = false;
}
SDL_Event event;
SDL_WaitEvent(&event);
switch (event.type) {
/* felhasznaloi esemeny: ilyeneket general az idozito fuggveny */
case SDL_KEYUP:
switch (event.key.keysym.sym) {
case SDLK_LEFT: left = false; rajz = true; break;
case SDLK_RIGHT: right = false; rajz = true; break;
case SDLK_ESCAPE: quit = true; break;
}
break;
case SDL_KEYDOWN:
switch (event.key.keysym.sym) {
case SDLK_LEFT: left = true; rajz = true; break;
case SDLK_RIGHT: right = true; rajz = true; break;
}
break;
case SDL_QUIT:
quit = true;
break;
}
}
SDL_Quit();
return 0;
}
A szövegbevitel kezelése nem olyan egyszerű feladat, mint amilyennek hangzik. A szöveg szerkesztését is kezelni kell (pl. Backspace gomb), de előfordulhat az is, hogy a szimpla billentyűzetnél bonyolultabb szövegbeviteli eszközt használ a felhasználó. Bár a magyar nyelvre ez nem jellemző, távolkeleti nyelveknél a billentyűzet használata mellett felugró ablakból kellhet kiválasztani az egyes karaktereket.
Az SDL szerencsére ezeket az eszközöket is kezeli, és a végeredményként előálló szövegdarabokat SDL_TEXTINPUT
típusú események paramétereiként megkapjuk. A törlést és a bevitelt nyugtázó Entert persze ettől függetlenül nekünk kell
kezelni.
Az alábbi program egy egyszerű szövegbeviteli mező megvalósítását mutatja. Ha valaki nem szeretne elmélyülni ebben, nem gond,
az input_text()
függvényt bátran fel lehet használni saját programokban vagy nagy házi feladatban. A paraméterei:
char *dest
: ide írja a beolvasott szöveget, UTF-8 karakterkódolással.size_t hossz
: maximum ennyi bájt hosszú szöveget olvas be.SDL_Rect teglalap
: a szövegbeviteli mező helyét és méretét megadó téglalap.SDL_Color hatter
: a mező színe.SDL_Color szoveg
: a szöveg színe.TTF_Font *font
: a betűtípus, amellyel rajzol.SDL_Renderer *renderer
: az ablak.
Visszatérési értéke azt mondja meg, sikeres volt-e a beolvasás. A példaprogram ezt a betűtípust használja: LiberationSerif-Regular.ttf (klikk a letöltéshez). A függvény csak szoftveres megjelenítés használata esetén működik helyesen (lásd fent).
#include <SDL.h>
#include <SDL_ttf.h>
#include <SDL2_gfxPrimitives.h>
#include <stdbool.h>
#include <string.h>
#include <stdlib.h>
void sdl_init(int szeles, int magas, const char* tipus, SDL_Window **pwindow, SDL_Renderer **prenderer, TTF_Font **pfont) {
if (SDL_Init(SDL_INIT_EVERYTHING) < 0) {
SDL_Log("Nem indithato az SDL: %s", SDL_GetError());
exit(1);
}
SDL_Window *window = SDL_CreateWindow("SDL peldaprogram", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, szeles, magas, 0);
if (window == NULL) {
SDL_Log("Nem hozhato letre az ablak: %s", SDL_GetError());
exit(1);
}
SDL_Renderer *renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_SOFTWARE);
if (renderer == NULL) {
SDL_Log("Nem hozhato letre a megjelenito: %s", SDL_GetError());
exit(1);
}
TTF_Init();
TTF_Font *font = TTF_OpenFont(tipus, 32);
if (font == NULL) {
SDL_Log("Nem sikerult megnyitni a fontot! %s\n", TTF_GetError());
exit(1);
}
*pwindow = window;
*prenderer = renderer;
*pfont = font;
}
void sdl_close(SDL_Window **pwindow, SDL_Renderer **prenderer, TTF_Font **pfont) {
SDL_DestroyRenderer(*prenderer);
*prenderer = NULL;
SDL_DestroyWindow(*pwindow);
*pwindow = NULL;
TTF_CloseFont(*pfont);
*pfont = NULL;
SDL_Quit();
}
/* Beolvas egy szoveget a billentyuzetrol.
* A rajzolashoz hasznalt font es a megjelenito az utolso parameterek.
* Az elso a tomb, ahova a beolvasott szoveg kerul.
* A masodik a maximális hossz, ami beolvasható.
* A visszateresi erteke logikai igaz, ha sikerult a beolvasas. */
bool input_text(char *dest, size_t hossz, SDL_Rect teglalap, SDL_Color hatter, SDL_Color szoveg, TTF_Font *font, SDL_Renderer *renderer) {
/* Ez tartalmazza az aktualis szerkesztest */
char composition[SDL_TEXTEDITINGEVENT_TEXT_SIZE];
composition[0] = '\0';
/* Ezt a kirajzolas kozben hasznaljuk */
char textandcomposition[hossz + SDL_TEXTEDITINGEVENT_TEXT_SIZE + 1];
/* Max hasznalhato szelesseg */
int maxw = teglalap.w - 2;
int maxh = teglalap.h - 2;
dest[0] = '\0';
bool enter = false;
bool kilep = false;
SDL_StartTextInput();
while (!kilep && !enter) {
/* doboz kirajzolasa */
boxRGBA(renderer, teglalap.x, teglalap.y, teglalap.x + teglalap.w - 1, teglalap.y + teglalap.h - 1, hatter.r, hatter.g, hatter.b, 255);
rectangleRGBA(renderer, teglalap.x, teglalap.y, teglalap.x + teglalap.w - 1, teglalap.y + teglalap.h - 1, szoveg.r, szoveg.g, szoveg.b, 255);
/* szoveg kirajzolasa */
int w;
strcpy(textandcomposition, dest);
strcat(textandcomposition, composition);
if (textandcomposition[0] != '\0') {
SDL_Surface *felirat = TTF_RenderUTF8_Blended(font, textandcomposition, szoveg);
SDL_Texture *felirat_t = SDL_CreateTextureFromSurface(renderer, felirat);
SDL_Rect cel = { teglalap.x, teglalap.y, felirat->w < maxw ? felirat->w : maxw, felirat->h < maxh ? felirat->h : maxh };
SDL_RenderCopy(renderer, felirat_t, NULL, &cel);
SDL_FreeSurface(felirat);
SDL_DestroyTexture(felirat_t);
w = cel.w;
} else {
w = 0;
}
/* kurzor kirajzolasa */
if (w < maxw) {
vlineRGBA(renderer, teglalap.x + w + 2, teglalap.y + 2, teglalap.y + teglalap.h - 3, szoveg.r, szoveg.g, szoveg.b, 192);
}
/* megjeleniti a képernyon az eddig rajzoltakat */
SDL_RenderPresent(renderer);
SDL_Event event;
SDL_WaitEvent(&event);
switch (event.type) {
/* Kulonleges karakter */
case SDL_KEYDOWN:
if (event.key.keysym.sym == SDLK_BACKSPACE) {
int textlen = strlen(dest);
do {
if (textlen == 0) {
break;
}
if ((dest[textlen-1] & 0x80) == 0x00) {
/* Egy bajt */
dest[textlen-1] = 0x00;
break;
}
if ((dest[textlen-1] & 0xC0) == 0x80) {
/* Bajt, egy tobb-bajtos szekvenciabol */
dest[textlen-1] = 0x00;
textlen--;
}
if ((dest[textlen-1] & 0xC0) == 0xC0) {
/* Egy tobb-bajtos szekvencia elso bajtja */
dest[textlen-1] = 0x00;
break;
}
} while(true);
}
if (event.key.keysym.sym == SDLK_RETURN) {
enter = true;
}
break;
/* A feldolgozott szoveg bemenete */
case SDL_TEXTINPUT:
if (strlen(dest) + strlen(event.text.text) < hossz) {
strcat(dest, event.text.text);
}
/* Az eddigi szerkesztes torolheto */
composition[0] = '\0';
break;
/* Szoveg szerkesztese */
case SDL_TEXTEDITING:
strcpy(composition, event.edit.text);
break;
case SDL_QUIT:
/* visszatesszuk a sorba ezt az eventet, mert
* sok mindent nem tudunk vele kezdeni */
SDL_PushEvent(&event);
kilep = true;
break;
}
}
/* igaz jelzi a helyes beolvasast; = ha enter miatt allt meg a ciklus */
SDL_StopTextInput();
return enter;
}
#define SZELES 480
#define MAGAS 240
#define FONT "LiberationSerif-Regular.ttf"
#define MAXHOSSZ 100
int main(int argc, char *argv[]) {
SDL_Window *window;
SDL_Renderer *renderer;
TTF_Font *font;
sdl_init(SZELES, MAGAS, FONT, &window, &renderer, &font);
SDL_Color feher = {255, 255, 255}, fekete = { 0, 0, 0 };
SDL_Rect hely;
char szoveg[100];
SDL_Event event;
SDL_Surface *screen = SDL_GetWindowSurface(window);
SDL_Surface *background = SDL_CreateRGBSurface(0, SZELES, MAGAS, 32, 0, 0, 0, 0);
/* hatter kirajzolasa, mentese */
for (int i = 0; i < 500; ++i)
filledCircleRGBA(renderer, rand() % SZELES, rand() % MAGAS,
20 + rand() % 10, rand() % 256, rand() % 256, rand() % 256, 64);
SDL_BlitSurface(screen, NULL, background, NULL);
SDL_RenderPresent(renderer);
/* szoveg bekerese */
SDL_Rect r = { 40, 80, 400, 40 };
input_text(szoveg, 100, r, fekete, feher, font, renderer);
/* szoveg kirajzolasa */
if (szoveg[0] != 0x0000) {
SDL_BlitSurface(background, NULL, screen, NULL);
SDL_Surface *felirat = TTF_RenderUTF8_Blended(font, szoveg, feher);
hely.x = (screen->w - felirat->w) / 2 + 2;
hely.y = (screen->h - felirat->h) / 2 + 2;
SDL_BlitSurface(felirat, NULL, screen, &hely);
SDL_FreeSurface(felirat);
SDL_RenderPresent(renderer);
while (SDL_WaitEvent(&event) && event.type != SDL_QUIT);
}
SDL_FreeSurface(background);
sdl_close(&window, &renderer, &font);
return 0;
}
Van pár apróság, amit tudni kell az SDL-es projektek fordításáról és futtatásáról.
Az egyik a többmodulos projektekkel kapcsolatos. A Code::Blocks beépített SDL projekt
varázslója egy olyan projektet hoz létre, amelyben a fő forrásfájl neve main.cpp
,
ami a kiterjesztése miatt alapértelmezetten nem C, hanem C++ fordítóval fordul. Ha újabb modult
adunk hozzá, annak pedig .c
a kiterjesztése, azt a C fordító fogja kapni. Ugyan
megoldható, hogy a kettővel együtt dolgozzunk, de ennek technikai részletei túlmutatnak a
Prog1 tárgyon. Használjuk inkább az SDL telepítős
írásból letölthető InfoC SDL projekt típust!
A másik az SDL-es programok futtatása. Az SDL könyvtár lefordított függvényei nem kerülnek be
a mi programunk futtatható, .exe
fájljába, hanem külön fájlokban vannak. A végleges
linkelést nem a fordító végzi, hanem az elindított program „húzza be” az indításakor a szükséges
programrészeket. Ezt dinamikus linkelésnek (dynamic linking) nevezzük. Ezek a függvénykönyvtárak
a *.dll
fájlokban (dynamic link library) vannak. Ha az SDL-es programot el
szeretnénk indítani a Code::Blockson kívülről, akkor vele egy mappába kell tenni az SDL
*.dll
fájljait is. Ezek a fent letölthető ZIP fájl bin
mappájában
vannak. A nagyobb programok telepítőinek (installer) egyébként éppen ez a feladata, hogy a
program futásához szükséges dinamikusan betöltött könyvtárakat is a megfelelő helyre másolják.
Fel szokott merülni az a kérdés is, hogyan kell olyan SDL-es programot csinálni, amelynek nem
nyílik külön konzol ablaka az indításkor. A Code::Blocks eltérő fordítási beállítások mellett tud
.exe
fájlt készíteni. Ha fent, a menüsor alatt a „Release” mód van kiválasztva a
„Debug” helyett, akkor olyan .exe
fájlt készít, amely nem nyit magának konzol
ablakot. Az elkészült .exe
a projekt mappájában, a \bin\Release
almappa alatt található.
Windowson alapvetően a grafikus programoknál nem szokás az, hogy írjanak a szabványos
kimenetükre. Ezért ott az SDL bezárja a szabványos kimenetet, és megnyit helyette egy
stdout.txt
nevű fájlt, amibe ezután a printf()
-elt szövegek kerülnek.
(Ugyanez történik a szabványos hibakimenettel is.) Ezért hiába printf()
-elünk az
SDL programokban, nem fog az a konzol ablakban megjelenni, még ha van is ilyen ablak. Ezt az SDL
FAQ-ban is megemlítik, itt. Ennek
elkerülésére azt javasolják, hogy újra kell nyitni a konzol ablakot. Ezt a main()
függvény elejére, de az SDL_Init()
hívás után az alábbi módon lehet megtenni:
#ifdef __WIN32__
freopen("CON", "w", stdout);
freopen("CON", "w", stderr);
#endif
Ezután már lehet printf()
-elni. A __WIN32__
makrót egyébként az SDL
definiálja. Mivel az csak a windowsos fordításoknál létezik, a fenti négy sort nyugodtan
betehetjük a programba (#ifdef
-fel együtt!), platformfüggetlen marad.
Az SDL igényli azt is, hogy a main()
függvénynek meglegyenek a paraméterei:
int argc
és char *argv[]
. Enélkül a program lefordítása nem fog mindenhol
sikerülni.
A parancssori argumentumokról és a többmodulos programokról egy külön előadáson beszélünk majd.
Ha valaki a macOS Mojave legújabb verzióját használja, akkor egy SDL bug miatt nem renderelődik automatikusan a képernyőre,
amit rajzolni szeretnénk. A probléma megoldódik, ha megmozdítjuk az SDL által készített ablakot ÉS az események kezelésébe
beleteszünk egy SDL_RenderPresent(renderer)
sort.
Az első SDL példaprogram esetében ez így nézne ki:
/* varunk a kilepesre */
SDL_Event ev;
while (SDL_WaitEvent(&ev) && ev.type != SDL_QUIT) {
SDL_RenderPresent(renderer);
}