Wprowadzenie do biblioteki SFML – narzędzia do szybkiego, wygodnego i przyjemnego pisania gier

Jeżeli pisałeś wcześniej jakąkolwiek grę w języku C++ to zapewne spotkałeś się z takimi bibliotekami jak SDL czy Allegro. Pisząc w którejkolwiek z nich pewną niedogodnością jest to, iż są napisane w języku strukturalnym, przez co tworzenie kodu bywa czasem uciążliwe i męczące. Istnieje jednakże lepsze rozwiązanie – SFML. Jest to prosta i szybka biblioteka do obsługi multimediów zorientowana obiektowo.

Przedmowa

Artykuł został napisany dla Magazynu Programista z tego powodu, iż bardzo go polubiłem i bardzo go sobie cenię. Powstał na samym początku maja br. (tak, pół roku temu!). Włożyłem w go naprawdę sporo wysiłku, by był jak najlepszy (choć teraz wiem, jak można by napisać o wiele lepiej). Niestety, współpraca z redakcją pozostawiała wiele do życzenia – gwoli ścisłości tej współpracy prawie nie było. Kontakt z osobami wyznaczony stricte do współpracy ze mną zazwyczaj urywał się po kilku mailach. Postanowiłem więc opublikować ten artykuł, by moja ciężka praca nie poszła na marne i by inni mogli znaleźć coś wartościowego.

Wprowadzenie

SFML, czyli Simple and Fast Multimedia Library to open source’owa biblioteka do obsługi multimediów. Wykorzystuje ona m.in. takie biblioteki jak OpenGL, OpenAL czy libpng, dzięki czemu jest wieloplatformowa. Udostępnioną ją na licencji zlib/png, co pozwala na wykorzystanie jej w celach komercyjnych. Wielką zaletą SFML-a jest to, iż jest on zorientowany obiektowo, ma wygodną dokumentację i jest prosty w użyciu. Biblioteka wprawdzie jest stworzona z myślą o grach 2D, ale wspiera także shadery napisane w GLSL, dzięki czemu można uzyskać ciekawe efekty 3D. SFML jest tworzony w języku C++, jednakże z powodu swej popularności doczekał się także bindingów do m.in. C, C#, Pythona i Ruby’iego. Jest dostępny na takie platformy jak Windows, Linux oraz Mac OS X i – wg informacji na stronie domowej biblioteki – niedługo ma wspierać także iOS oraz Androida. Na ostatnią z wymienionych platform pojawiła się już nieoficjalna wersja.

Moduły

Cała biblioteka została podzielona na pięć modułów:

  • System – jest wymagany przez resztę modułów, zawiera podstawowe elementy jak np. wektory, wątki czy obsługę czasu
  • Window – jest odpowiedzialny za obsługę okien, klawiatury, myszy oraz kontrolerów gier, wymagany przez moduł Graphics
  • Graphics – moduł odpowiedzialny za renderowanie
  • Audio – służy do obsługi dźwięku i w przeciwieństwie do modułu Graphics obsługuje przestrzeń 3D (wyjaśnienia dalej w artykule)
  • Network – moduł sieciowy; wspiera takie protokoły jak TCP, UDP czy nawet HTTP oraz FTP

Bardzo ciekawą dogodnością jest możliwość integrowania SFML-a z takimi bibliotekami jak WinAPI, Qt, wxWidgets, X11, Cocoa czy nawet OpenGL. Odpowiednie przykłady są dostarczane wraz z biblioteką*, do których analizy zachęcam. Znajduje tam się również przykładowy klient FTP, odtwarzanie dźwięku, jego nagrywanie i przesyłanie przez sieć oraz przykładowa gra.
* przykłady dla Qt oraz wxWidgets są dostarczone wraz z wersją 1.6 (paczka do ściągnięcia oraz tutoriale na stronie) i nie mam pewności, czy po poprawie kodu, związanej z nieco zmienionym API, będą nadal działać z wersją 2.0 lub wyższą.

Instalacja biblioteki

Na początku maja br. będąca ponad rok w stadium RC wersja 2.0 SFML-a doczekała się oficjalnej premiery. Końcem lipca ukazał się SFML 2.1, w którym zmiany polegały głównie na naprawieniu istniejących bugów. Skompilowaną bibliotekę wraz z dokumentacją i przykładami można ściągnąć ze strony http://www.sfml-dev.org/download/sfml/2.1/.

Nazwy modułów, które należy skonsolidować z aplikacją są zgodne z następującym wzorem: sfml-<moduł>(-s)(-d), gdzie -s oznacza bibliotekę linkowaną statycznie, a -d wersję Debug. Przykładowo moduł dźwięku w wersji Release konsolidowany statycznie nazywa się sfml-audio-s. Ważne jest dodanie globalnej makrodefinicji SFML_STATIC w ustawieniach projektu podczas jego konfigurowania, jeśli biblioteka ma być skonsolidowana statycznie. Zwracam także uwagę na konieczność korzystania z bibliotek openal32.dll oraz libsndfile-1.dll dołączonych do SFML-a, bowiem użycie innej wersji ściągniętej z Internetu może skutkować złym działaniem modułu odpowiedzialnego za dźwięk. Nie są one skonsolidowane statycznie z bibliotekami SFML-a z powodu ich licencji. W przypadku wystąpienia problemów odsyłam do oficjalnych tutoriali: http://www.sfml-dev.org/tutorials/2.1/ . Zostało tam opisane konfigurowanie projekt dla kilku popularnych środowisk programistycznych.

Pierwsza aplikacja w SFML-u

Pozwolę sobie najpierw zamieścić kod, a następnie go omówić. Jak można zauważyć – zrozumienie poniższego kodu jest niemalże intuicyjne.

Rysunek 1. Efekt działania przykładowego kodu

Rysunek 1. Efekt działania przykładowego kodu

Konstruktor klasy sf::RenderWindow przyjmuje jako argumenty tryb graficzny oraz tytuł okna. Pozostałe parametry pozostawiamy domyślne. sf::VideoMode jest klasą zawierającą informacje o rozdzielczości okna oraz głębi koloru, która domyślnie wynosi 32 bity na piksel. Następne dwie linie kodu tworzą prymityw – koło o promieniu 100 pikseli (parametr konstruktora) i wypełnieniu kolorem zielonym. sf::Color jest prostą klasą reprezentującą kolor w formacie RGBA. Zawiera ona także kilka predefiniowanych podstawowych kolorów, w tym właśnie wykorzystany przez nas kolor zielony.

Dalej w kodzie znajduje się podstawowy element gry – pętla. Za pierwszym razem warunek będzie prawdziwy, bowiem skorzystaliśmy z wersji konstruktora, która od razu tworzy okno. Istnieje możliwość wykorzystania konstruktora domyślnego i późniejsze utworzenie okna metodą sf::RenderWindow::create() – lista argumentów ta sama co wcześniej użyta w konstruktorze. Pierwsza część pętli to obsługa komunikatów okna. Niektóre wartości pola type klasy sf::Event przedstawia tabelka 1.

Komunikat Znaczenie
sf::Event::Closed Wysłano komunikat zamknięcia okna (naciśnięto X na belce, alt+F4, itp.)
sf::Event::Resized Zmieniono rozmiar okna
sf::Event::KeyPressed Naciśnięto klawisz na klawiaturze
sf::Event::MouseButtonPressed Przyciśnięto przycisk myszy
sf::Event::MouseMove Poruszono myszą
sf::Event::JoystickButtonReleased Zwolnionio przycisk kontrolera gier

Tabelka 1. Niektóre wartości pola type klasy sf::Event

Wszystkie możliwe komunikaty są wymienione w dokumentacji biblioteki. Ze względów oczywistych nie zamieszczałem ich w powyższej tabelce. Każdy komunikat niesie ze sobą trochę informacji jak np. kod naciśniętego klawisza dla sf::Event::KeyPressed , co z resztą wykorzystuje powyższy przykład. Ostatnie trzy linie kodu pętli są odpowiedzialne za renderowanie. Metoda clear() wypełnia bufor określonym kolorem – domyślnym argumentem jest kolor czarny. Następna funkcja jak można się łatwo domyślić rysuje koło. Bardzo interesująca jest trzecia metoda display() . Tu uwidacznia się kolejna zaleta biblioteki – SFML wykorzystuje podwójne buforowanie i wywołanie owej metody powoduje skopiowanie bufora roboczego do bufora ekranu, czyli innymi słowy wyświetlenie obrazu. Dzięki temu nie musimy martwić się o miganie elementów na scenie. Warto w tym momencie jeszcze wspomnieć o pewnej możliwości biblioteki – otóż, gdy użytkownik zmieni rozmiar okna, my – jako programiści – nie musimy się tym przejmować. SFML sam przeskaluje obraz. Oczywiście – gdybyśmy nie chcieli umożliwiać zmiany rozmiarów okna wystarczy tylko zmienić jego styl:

Rysunek 2. Automatyczne skalowanie obrazu

Rysunek 2. Automatyczne skalowanie obrazu

Można także w obsłudze komunikatu sf::Event::Resized sprawdzać nowy rozmiar okna i gdy jest on nieodpowiedni zmienić go przy pomocy sf::RenderWindow::setSize (const Vector2u size). W przypadku, gdy korzystamy z automatycznego skalowania, należy pamiętać, iż piksel (10,10) już niekoniecznie będzie oznaczał współrzędnych (10,10). Do konwertowania pozycji można wykorzystać następujące metody klasy sf::RenderWindow:

Nie powinno się jednakże zapominać, iż pozycja piksela jest reprezentowana przez sf::Vector2i, a współrzędne obiektu poprzez sf::Vector2f. Nadmienię jeszcze w tym miejscu informację o ostatnim argumencie wykorzystywanym podczas tworzenia nowego okna, a konkretniej sf::ContextSettings. Owa struktura jest odpowiedzialna za parametry kontekstu OpenGLa, w tym m.in. poziomu antialiasingu.

Wektory w SFML-u

SFML do reprezentacji punktu oraz wektora w przestrzeni 2D oraz 3D wykorzystuje następujące szablony: sf::Vector2<T> oraz sf::Vector3<T> zawierające pola x, y oraz – dla wersji 3D – z, wszystkie typu T. Biblioteka maskuje je instrukcją typedef dodając odpowiedni przyrostek:

Należy zwrócić uwagę na brak sf::Vector3u.

Ograniczanie FPS

Aby gra nie zabierała sporej ilości czasu procesora powinno się ograniczyć FPS (klatki na sekundę). Można w tym celu wykorzystać pętlę stałokrokową korzystając z sf::Clock. W najprostszym ujęciu wygląda ona mniej więcej tak:

Wyróżnioną część kodu umieszczamy oczywiście w głównej pętli, najlepiej po obsłudze komunikatów, ale przed logiką gry. Istnieje jednakże jeszcze prostsze rozwiązanie. Mianowicie po utworzeniu okna możemy wywołać jego metodę setFramerateLimit(unsigned int) podając jako parametr maksymalny FPS jaki chcemy osiągnąć albo skorzystać z synchronizacji pionowej – metoda setVerticalSyncEnabled(bool).

Wczytywanie i wyświetlanie grafiki

W SFML-u można renderować wszystko to, co dziedziczy po sf::Drawable. Aby wyświetlić własną grafikę wykorzystamy w tym celu klasy sf::Texture oraz sf::Sprite. sf::Texture jest obiektem przechowującym grafikę, natomiast sf::Sprite jest potomkiem sf::Drawable zawierającym wskaźnik do sf::Texture. Teksturę można wczytać m.in. z pliku, z pamięci, a nawet poprzez własny strumień danych (w tym celu trzeba odziedziczyć i zaimplementować interfejs klasy sf::InputStream), dzięki czemu można wczytywać grafikę np. poprzez gniazda internetowe. Całkiem przyjemnym ułatwieniem jest możliwość zaktualizowania tekstury na podstawie zawartości konkretnego okna.

Na Listingu 3. widać jak łatwo można przygotować teksturę do wyświetlenia. Metoda wczytująca grafikę zwraca wartość true w przypadku powodzenia. Jeśli nie uda się wczytać grafiki, ponieważ przykładowo plik nie istnieje, powyższy kod zakończy działanie programu zwracając -1.

Przemieszczanie, obracanie i skalowanie grafiki

W SFML-u wszystko jest proste, także wymienione w śródtytule czynności. W tym celu klasa sf::Sprite dostarcza następujących metod:

Pierwsza część ww. metod ustawia określone parametry na te przesłane w argumentach. Pozostałe funkcje dokonują względnej zmiany, przykładowo move(5,5) nie ustawi pozycji (5,5), tylko przesunie grafikę o wektor (5,5). Warto zwrócić uwagę na jednostkę kąta, którą obsługuje SFML, bowiem wykorzystuje on stopnie w przeciwieństwie do funkcji ze standardowej biblioteki języka C++ posługujących się radianami. Godna uwagi jest także możliwość zmiany lokalnego początku układu współrzędnych (ang. origin) sprite’a, dzięki czemu możemy go skalować i obracać względem dowolnie wybranego punktu. W trakcie wywoływania powyższych funkcji transformujących modyfikuje on swoje atrybuty przechowywane w obiekcie typu sf::Transform. Zamiast wywoływać ww. metody można utworzyć instancję wspomnianej klasy i podać ją jako drugi parametr podczas renderowania, co zostało zaprezentowane na listingu 5. Istnieje również możliwość pobrania omawianych atrybutów korzystając z metod mających w nazwie słowo get zamiast set.

Warto przyjrzeć się drugiemu argumentowi metody draw(), a mianowicie klasie sf::RenderStates. W przykładzie przedstawionym na listingu 5. korzystamy z niejawnej konwersji poprzez konstruktor. Ta klasa umożliwia nam ustawienie trybu renderowania, tekstury, shadera oraz znanej już nam klasy sf::Transform. Posiada ona wiele konstruktorów z jednym argumentem, dzięki czemu możemy korzystać z niejawnej konwersji oraz jedną wersję zawierającą wszystkie wcześniej wymienione możliwości. Atrybuty omawianej klasy są publiczne, więc możemy je później dowolnie modyfikować. Wspomniany wcześniej tryb renderowania reprezentowany poprzez klasę sf::BlendMode określa jak ma być nakładana jedna tekstura na drugą, a dokładniej – jak obliczyć kolor piksela na podstawie bazowego bufora i tekstury.

Wyświetlanie tekstu

W celu wyświetlenia tekstu na ekranie trzeba najpierw wczytać czcionkę, a później wykorzystać ją do renderowania. Z wykorzystaniem SFML-a te czynności także nie przysporzą nam problemów. Czcionkę reprezentuje klasa sf::Font, natomiast sf::Text jest przedstawicielem tekstu.

Trzecim zagadkowym parametrem konstruktora sf::Text jest wielkość czcionki. Rozmiar renderowanego obszaru jak i jego położenie można odczytać za pomocą metod getGlobalBounds() oraz getLocalBounds(). Przykład ich użycia znajduje się na listingu 5. Istnieje także możliwość pobrania współrzędnych określonego znaku. Służy do tego celu funkcja findCharacterPos(std::size_t index) zwracająca obiekt typu sf::Vector2f. W przypadku gdybyśmy chcieli poznać wymiary konkretnego znaku trzeba już się trochę pomęczyć. Aby to osiągnąć należy skorzystać dodatkowo z klasy sf::Glyph. Glif jest to model konkretnego znaku w określonej czcionce. Przykładowy kod realizujący to, co chcemy osiągnąć znajduje się na listingu 7.

Rysunek 3. Rezultat działania kodu z listingów 6. oraz 7.

Rysunek 3. Rezultat działania kodu z listingów 6. oraz 7.

Renderowanie tekstur

Jedną z ważnych rzeczy jest renderowanie tekstur, np. gdy chcemy uzyskać ciekawy efekt na konkretnym obszarze wykorzystując shader albo wyrenderować bohatera gry składającego się z kilku grafik. Klasa, której poszukujemy nazywa się sf::RenderTexture i podobnie jak sf::RenderWindow dziedziczy po sf::RenderTarget, czyli innymi słowy posiada ten sam interfejs bazowy. Do utworzenia tekstury wykorzystuje się metodę create(), która jako parametry przyjmuje rozmiary nowej grafiki oraz opcjonalnie wartość logiczną decydującą o wykorzystaniu bufora głębokości (domyślnie fałsz). Do jej pobrania wykorzystuje się getTexture().

Wykorzystanie kamery

SFML udostępnia bardzo ciekawe narzędzie, które zostało nazwane widokiem. Nie jest to nic innego jak kamera. Jest ona pomocna, gdy np. chcemy podzielić ekran na kilka części dla trybu wielu graczy albo narysować minimapkę w rogu. Zasada użycia sf::View jest prosta: określamy obszar widoczny przez kamerę, ustawiamy ją dla okna lub renderowanej tekstury (sf::RenderTarget) i rysujemy. Tutaj należy zwrócić uwagę na dwie istotne rzeczy. Po pierwsze – ustawiony widok obowiązuje do jego kolejnej zmiany, a nie wywołania metody czyszczącej okno. Należy więc pamiętać o przywróceniu domyślnej kamery, jeśli mamy zamiar z niej korzystać. Druga sprawa – widok nie obowiązuje dla metody display(). Innymi słowy jeśli wyrenderujemy sobie prostokąt w standardowym widoku to zmiana kamery na inną przed wyświetleniem obrazu nic nie zmieni.

Na listingu 9. kod przywracający domyślny widok przed wywołaniem metody display() został celowo umieszczony w komentarzu. Kamera nie żadnego wpływu na tę metodę. display() w celu narysowania sceny posłuży się domyślnym widokiem, co zostało ukazane na rysunku 4.

Rysunek 4. Efekt działania kodu z listingu 9. wykorzystujący sf::View

Rysunek 4. Efekt działania kodu z listingu 9. wykorzystujący sf::View

Korzystanie z shaderów

W SFML-u w celu uzyskania ciekawych efektów można wykorzystać shadery. Są to proste programy wykonywane na procesorze graficznym, dzięki czemu są szybkie i efektowne. SFML wspiera dwa rodzaje shaderów: vertex shader oraz fragment shader. Pierwszy z nich jest odpowiedzialny za przetwarzanie pojedynczych wierzchołków i jest uruchamiany wcześniej, natomiast drugi przetwarza pojedyncze piksele, dzięki czemu można dowolnie modyfikować ich kolor. Dodam, iż fragment shader otrzymuje już zinterpolowane piksele. Interpolacja jest to proces wyliczania płynnego przejścia różnych wartości (np. koloru) w danym trójkącie na podstawie wartości określonych wcześniej w wierzchołkach. Można oczywiście przesłać teksturę do fragment shadera i na podstawie pozycji konkretnego piksela odpowiednio go oteksturować.
SFML korzysta z OpenGLa, więc shadery muszą być napisane w GLSL-u (ang. Graphics Library Shading Language). Jest to obszerny temat wykraczający poza ramy tego artykułu, więc przedstawię tylko przykład ich zastosowania. Wykorzystamy w tym celu klasę sf::Shader. Pierwszą sprawą, którą musimy się zająć, zanim zaczniemy używać shaderów jest sprawdzenie, czy są one wspierane w aktualnym systemie. W tym celu wystarczy wywołać jedną statyczną funkcję:

Kod źródłowy shadera, podobnie jak teksturę, można wczytać na trzy sposoby: z pliku, z pamięci oraz ze strumienia. Wykorzystać można tylko jeden typ (sf::Shader::Vertex / sf::Shader::Fragment) albo oba naraz:

Po wczytaniu shadera można go już używać, jednakże powinno się pamiętać o ustawieniu wartości zmiennych uniform. Służą one do przesyłania parametrów z CPU do GPU. W shaderze te zmienne są traktowane jako stałe globalne. Poniżej znajduje się przykład, który wykorzystuje efekt „wyszarzania” obrazu.

W tym miejscu warto wyjaśnić co robi pierwsza instrukcja listingu 11. Otóż ustawienie tekstury jako sf::Shader::CurrentTexture oznacza, iż konkretna zmienna uniform będzie zawierała grafikę aktualnie renderowanego obiektu. Typy, jakie można przesłać do shadera poprzez SFML-a przedstawia tabelka 2.

C++ / SFML GLSL
float float
2x float lub sf::Vector2f vec2
3x float lub sf::Vector3f vec3
4x float lub sf::Color vec4
sf::Transform mat4
sf::Texture sampler2D

Tabelka 2. Typy parametrów możliwe do ustawienia przy pomocy SFML-a
Na koniec jeszcze wspomnę o mojej modyfikacji SFML-a wprowadzającej obsługę geometry shadera. Ten typ shadera jest wywoływany między vertex shaderem a fragment shaderem. Przetwarza on, jak sama nazwa głosi, geometrię. Na wejściu otrzymuje punkty, linie tudzież trójkąty i na wyjście może podać dowolną ilość prymitywów konkretnego typu. Ponieważ ten typ shadera ma informacje o figurze, można go wykorzystać do np. liczenia dynamicznych cieni. Link do mojego pull requesta wprowadzającego ową funkcjonalność: https://github.com/LaurentGomila/SFML/pull/432.

Pozostałe możliwości renderowania grafiki

Poza wymienionymi wcześniej klasami do wyświetlania grafiki chciałbym wspomnieć o paru innych możliwościach. Najprostszym sposobem jest odziedziczenie interfejsu sf::Drawable, dzięki czemu będziemy mogli podać obiekt naszego typu do metody draw() okna tudzież tekstury. Niemałym ułatwieniem jest wykorzystanie klasy sf::Shape, która jest potomkiem sf::Drawable i po której dziedziczą prymitywy (w tym sf::Convex). Ostatnią już możliwością jest wykorzystanie dość niskopoziomowego mechanizmu, a konkretniej tablicy wierzchołków. W tym celu można posłużyć się interfejsem sf::VertexArray (potomek sf::Drawable) albo stworzenie zwykłej tablicy obiektów typu sf::Vertex i wykorzystania dedykowanej wersji metody draw(). Zainteresowanych odsyłam do oficjalnego tutoriala: http://www.sfml-dev.org/tutorials/2.1/graphics-vertex-array.php.

Bezpośredni dostęp do myszy, klawiatury oraz kontrolera gier

Jeżeli chcemy napisać prostą grę to nie może w niej zabraknąć sterowania bohaterem. Poszukiwana funkcjonalność znajduje się w module Window. Generalnie radzę zajrzeć do dokumentacji. Poniżej zaprezentowałem krótki opis i przykład zastosowania.

Klawiatura

Jedyna dostarczona funkcjonalność w tej części biblioteki to prosta w użyciu statyczna funkcja:

Mysz

W przypadku myszy istnieje analogiczna funkcja służąca do sprawdzenia, czy jest wciśnięty jeden z dostępnych przycisków. SFML obsługuje 3 podstawowe przyciski, tj. lewy, prawy i środkowy oraz 2 dodatkowe. Możemy także pobrać oraz ustawić pozycję kursora myszy względem całego ekranu bądź konkretnego okna. Obsługa rolki myszy jest możliwa tylko poprzez komunikaty okna.

Kontrolery gier

W SFML-u funkcje odpowiedzialne za obsługę kontrolerów gier zostały zebrane w klasę sf::Joystick. Zawiera ona dwa typy wyliczeniowe oraz statyczne metody. SFML obsługuje do 8 kontrolerów gier naraz, każdy posiadający maksymalnie 32 przyciski oraz 8 osi.

Odtwarzanie dźwięków

W celu odtwarzania muzyki i wszelakiego rodzaju dźwięków SFML dostarcza dwie klasy. Pierwszą z nich jest sf::Sound. Korzysta ona z wczytanego wcześniej dźwięku znajdującego się w pamięci. Z tegoż to powodu służy głównie niewielkim plikom dźwiękowym, jak np. wystrzał z broni palnej czy odgłos kroków. Kolejną klasą jest sf::Music, która strumieniuje muzykę m.in. z dysku. Jest ona przeznaczona wielkim plikom, które w pamięci mogłyby zajmować dziesiątki, jak nie setki megabajtów. Przykładowe wykorzystanie powyższych klas zostało pokazane na listingu 15. Warto zwrócić uwagę na metodę openFromFile() klasy sf::Music. Celowo zostało tu użyte słowo open zamiast load, bowiem ma to przypominać, iż muzyka nie jest wczytywana tylko strumieniowana – plik pozostaje otwarty.

Dźwięki w przestrzeni 3D

Przyzwyczailiśmy się już do tego, że podczas słuchania jakiegoś utworu zazwyczaj ze wszystkich głośników wydobywa się ten sam dźwięk. Czasami tylko zdarzają się wyjątki od tej sytuacji. Są jednakże gry, które realistycznie przedstawiają świat za pomocą dźwięków odgrywanych z odpowiednich głośników, dzięki czemu możemy poczuć się niczym bohater gry. SFML udostępnia taką możliwość i niezwykle ułatwia uzyskanie świetnego efektu. Jako programiści nie musimy się „bawić” w wyliczanie oczekiwanego dźwięku i zagranie go na odpowiednim głośniku. Wystarczy tylko ustawić punkt w przestrzeni 3D będący źródłem dźwięku i określić dodatkowe parametry. SFML sam wykrywa aktualny system dźwiękowy i wylicza odpowiednie kanały. Jest tylko jedno ograniczenie – ten efekt działa wyłącznie na dźwiękach posiadających jeden kanał.

Druga linia powyższego listingu określa, czy pozycja dźwięku jest relatywna względem listenera, który symbolizuje punkt, w którym znajduje się gracz. Ta opcja jest domyślnie ustawiona na false. Gdy ustawimy ją na true to niejako „przyczepiamy” źródło dźwięku do gracza na określonej pozycji względem niego. MinDistance z trzeciej linii określa odległość do której dźwięk jest słyszalny ze 100% głośnością. Im źródło znajduje się dalej poza minimalny dystans, tym ciszej go słychać. Stopień (prędkość) tego wyciszania określa czwarty parametr – Attenuation. Wzór na współczynnik głośności dla konkretnego dźwięku można obliczyć z następującego wzoru, gdzie Distance oznacza odległość od listenera:

W celu poruszania się po „świecie dźwięków” służy zbiór statycznych metod wspomnianej wcześniej klasy sf::Listener. Udostępniają one globalną zmianę głośności dźwięku, ustalenie nowej pozycji listenera oraz jego zwrotu.

Warto zauważyć, iż pozycja przesyłana do metody setDirection() jest relatywna względem listenera i nie oznacza tzw. Point of Interest, czyli punktu do którego byłyby zwrócone „uszy świata”.

Moduł sieciowy

SFML obsługuje cztery protokoły: TCP, UDP, HTTP oraz FTP. Z racji, iż ten artykuł nie jest dedykowany programowaniu sieciowemu zaprezentuję tu tylko proste użycie protokołu UDP. Cała komunikacja opiera się na tzw. gniazdach sieciowych (ang. sockets). Każde z nich ma przyporządkowany unikalny numer zwany portem. Jest on przedstawiany przy pomocy dwóch bajtów, więc łatwo jest określić zakres wartości – od 0 do 65535 włącznie. Zaleca się korzystanie z wyższych numerów portów, ponieważ te niższe (mniejsze niż 1000) są zarezerwowane dla popularnych protokołów. W SFML-u aby wysłać dane przy pomocy protokołu UDP nie potrzeba żadnych przygotowań. Jednakże aby móc odebrać jakiekolwiek dane trzeba wcześniej zbindować gniazdo, czyli powiedzieć systemowi operacyjnemu: „Jeżeli przyjdą jakieś dane na port X, to proszę mi je przekazać”. Sprawa jest dość oczywista – w końcu jeżeli tego nie zrobimy, to skąd system operacyjny ma wiedzieć, dokąd przekazać dane? Aplikacji sieciowych uruchomionych jednocześnie może być multum. Powracając do meritum czym prędzej utwórzmy gniazdo i je zbindujmy:

Wysyłanie i odbieranie danych

Protokół UDP jest bezpołączeniowy, co oznacza, iż dwa gniazda nie nawiązują ze sobą połączenia. Dzięki temu nie jesteśmy ograniczani do konkretnego odbiorcy, jednakże ma to także swoje wady – przy każdym wysyłaniu danych musimy określić dokąd mają one trafić. Podobnie jest przy odbiorze pakietów – otrzymujemy adres i port nadawcy. SFML umożliwia dwa sposoby wysyłania i odbierania danych. Pierwszy z nich to bufor danych binarnych. Jest on o tyle niewygodny, iż trzeba uważać na kolejność bajtów (Endians) i wielkość konkretnych typów wbudowanych na określonych platformach. Drugim sposobem, który rozwiązuje ten problem jest specjalna klasa sf::Packet, na której operuje się bardzo podobnie jak na std::cin/std::cout. Warto w tym momencie nadmienić, iż gniazda domyślnie pracują w trybie synchronicznym (blokującym) i metoda odbierająca dane będzie na nie czekać zatrzymując cały wątek aplikacji. Gniazda można przełączyć w tryb asynchroniczny (nieblokujący) wykorzystując metodę setBlocking(bool).

Podsumowanie

W tym artykule przedstawiłem sporą część biblioteki SFML. Pokazałem podstawowe i ciekawsze elementy – renderowanie grafiki i tekstu, posługiwanie się widokiem, obsługę shaderów, obsługę myszy, klawiatury oraz kontrolera gier, odtwarzanie dźwięku i komunikację sieciową. Jeżeli zaciekawiłeś się tą biblioteką to na początek polecam przeczytanie oficjalnych tutoriali. Omawiają one wiele interesujących aspektów, jak np. integrowanie OpenGLa, nagrywanie dźwięku, rozszerzanie pakietów sieciowych czy obsługę protokołów TCP, HTTP oraz FTP. Następnie warto przejrzeć dokumentację i przeanalizować dołączone do biblioteki przykłady. SFML dzięki swej popularności zgromadził społeczność chętną do pomocy w rozwoju biblioteki. Stworzyła ona m.in. takie rozszerzenia do SFML-a jak SFGUI do obsługi GUI czy Thora dodającego wiele ciekawych udogodnień jak np. system cząsteczek, animacje czy minutnik z callbackiem. Można znaleźć także kody integrujące SFML-a z Box2D, umożliwiające odtwarzanie plików wideo albo ułatwiających stworzenie dynamicznego oświetlenia.
Na cele tego artykułu napisałem prostą grę, aby pokazać jak wygląda kod aplikacji w SFML-u i jak bardzo łatwo jest coś takiego napisać samemu. Poniosła mnie wena i kod niemiłosiernie się rozrósł, więc zamieszczam tylko link: https://bitbucket.org/Mrowqa/cargame-simple-sfml-2.0-game. Prócz źródeł gry znajduje się tam także skompilowana wersja dla platformy Windows i Linux. Jak można zauważyć na rysunku 5. jest to prosta gra, w której sterując pojazdem zbiera się kanistry paliwa. W grze zostało oprogramowane m.in. nitro, system punktów, tryb pełnego ekranu, animacja napisów oraz obsługa klawiatury i kontrolerów gier. Zachęcam do analizy kodu źródłowego – początkującym zalecam rozpocząć od wersji pierwszej (katalog /src/v1/ w repozytorium), bowiem jest to pojedynczy, krótki plik (ok. 400 linii kodu).

PS gra wykorzystuje wprawdzie bibliotekę w wersji 2.0, jednakże głównie różnice między nowszą wersją 2.1 polegają na naprawie bugów.

Rysunek 5. Prosta gra napisana przy pomocy biblioteki SFML dla celów tego artykułu

Rysunek 5. Prosta gra napisana przy pomocy biblioteki SFML dla celów tego artykułu

W sieci

Jeżeli artykuł Ci się spodobał, śmiało daj „łapkę w górę” i podeślij linka znajomym, którzy mogą być nim zainteresowani, podziel się tym linkiem na swoim ulubionym forum albo daj znać światu o tym artykule w jakikolwiek inny sposób. Będę Ci za to bardzo wdzięczny. Pozdrawiam!

2 myśli nt. „Wprowadzenie do biblioteki SFML – narzędzia do szybkiego, wygodnego i przyjemnego pisania gier

Dodaj komentarz