Biblioteka SDL2 uchodzi za jedną z najlepszych do tworzenia prostych gier 2D w C++, jednocześnie jest nieskomplikowana i nie ma dużych zależności. Wydaje się że jest to idealny wybór dla początkującego gamemakera chcącego napisać lekką grę w C++. W tym kursie poprowadzę Cię od podstaw do własnej, prostej gry. To co, zaczynamy?
Instalacja biblioteki i plików nagłówkowych
To, w jaki sposób będziemy instalować SDL2 zależy od naszego środowiska i systemu operacyjnego:
Windows – z oficjalnej strony SDL2 ściągamy odpowiednie Runtime binaries czyli 32 lub 64 bitowe. Ściągamy również pliki nagłówkowe Development Libraries w wersji odpowiedniej dla naszego kompilatora. Nie wiesz które wybrać? Jeżeli piszesz w Visual Studio
to wybierz wersję VC
, w przeciwnym wypadku mingw
(chyba że używasz kompilatora od Visuala pod innym IDE, ale wtedy chyba wiesz co robisz 😉 )
Pobrane pliki wypakuj z archiwów do jednego folderu w wygodnej lokalizacji, na przykład C:/SDL2-2.0.0
Linux – skorzystaj z repozytorium (w zależności od dystrybucji – Centrum oprogramowania, Synaptic, albo i apt-get lub dowolne inne narzędzie do pobierania i instalacji pakietów) i zainstaluj pakiety libsdl2-2.0-0
oraz libsdl2-dev
W taki sam sposób będziesz instalować inne biblioteki wymagane w kolejnych częściach kursu, np.
libsdl2-image-2.0-0
wraz z libsdl2-image-dev
.
Konfiguracja IDE
- Tworzymy nowy projekt, może być pusty lub aplikacja konsolowa:
Oczywiście wybieramy C++, nazywamy projekt i wybieramy lokalizację dla plików. - Z menu wybieramy Project, a następnie Build options i przechodzimy na kartę Linker Settings. Naciskamy przycisk Add, wpisujemy nazwę biblioteki i zatwierdzamy – należy ten krok powtórzyć dla każdej biblioteki na liście. Zwróć uwagę żeby po lewej stronie był zaznaczony projekt, a nie któraś z opcji Debug czy Release!
- Przechodzimy na zakładkę Search directories > Compiler i podajemy tam ścieżkę do podkatalogu
include/SDL2
z pobranej paczki.
- Na pod-zakładce Linker wykonujemy to samo działanie, dodając folder
lib
z paczki.
Uwaga: podane ścieżki są przykładowe, u siebie możesz mieć bibliotekę SDL2 w dowolnym miejscu. Ważne jest to, żeby ścieżki dodane na powyższych zakładkach prowadziły do odpowiedniego miejsca.
- Tworzymy nowy projekt, może być pusty lub aplikacja konsolowa:
Oczywiście wybieramy C++, nazywamy projekt i ustawiamy lokalizację dla plików. - Z menu wybieramy Project, a następnie Build options i przechodzimy na kartę Linker Settings. Naciskamy przycisk Add, wpisujemy
SDL2
i zatwierdzamy. Zwróć uwagę żeby po lewej stronie był zaznaczony projekt, a nie któraś z opcji Debug czy Release!
- Utwórz nowy projekt aplikacji C++ nieużywający Qt:
- W widoku plików otwórz plik projektu
.pro
i na końcu dopiszLIBS += -lSDL2
:
Ładowanie SDL2
Skoro mamy skonfigurowane IDE wraz z bibliotekami to czas sprawdzić czy wszystko działa poprawnie. Pierwszy kod który powinien się skompilować i uruchomić:
1 2 3 4 5 6 7 8 9 10 11 12 |
#include <iostream> #include "SDL2/SDL.h" int main() { // Init SDL if (SDL_Init(SDL_INIT_EVERYTHING) != 0) { std::cout << "Blad SDL_Init(): " << SDL_GetError() << std::endl; return 0; } SDL_Quit(); } |
W systemie Windows należy do katalogu z plikiem binarnym („wyjście” kompilatora) wrzucić pliki DLL pobrane ze strony SDL2
Wierzę że powyższy kod uruchomił się Wam bez problemu. Co on właściwie robi? Całkiem dużo, bo inicjalizuje cały subsystem SDL2. Myślę że nie ma co się rozdrabniać i wybierać subsystemów, zatem używamy SDL_INIT_EVERYTHING
żeby mieć wszystko załadowane. W razie błędu funkcja zwróci wartość różną od zera – wtedy wystarczy wypisać błąd uzyskany z funkcji SDL_GetError()
na wybrane wyjście, u nas jest to konsola. W razie błędu nie chcemy również kontynuować wykonania, stąd return
. Na sam koniec kodu wywołujemy SDL_Quit()
w celu opuszczenia systemu SDL2.
Tworzenie okienka
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
// Create window SDL_Window *w = SDL_CreateWindow("Hello world!", 100, 100, 640, 480, SDL_WINDOW_SHOWN); // Check if window is created if (w == nullptr) { std::cout << "Blad SDL_CreateWindow(): " << SDL_GetError() << std::endl; return 0; } // Create renderer and check it SDL_Renderer *ren = SDL_CreateRenderer(w, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC); if (ren == nullptr) { std::cout << "Blad SDL_CreateRenderer(): " << SDL_GetError() << std::endl; return 0; } |
Powyższy kod utworzy okienko (linia 2), sprawdzi czy się utworzyło poprawnie (5-8), utworzy renderer (11) i również go sprawdzi (12-15). Nie należy zapomnieć o posprzątaniu obiektów na koniec (przed linijką z SDL_Quit()
):
1 2 |
SDL_DestroyRenderer(ren); SDL_DestroyWindow(w); |
Ale wytłumaczmy kod. W linii 2 tworzymy wskaźnik typu SDL_Window*
– obiekt tego typu zostanie zwrócony przez funkcję SDL_CreateWindow
. Jej argumenty to po kolei:
"Hello world!"
– tytuł okienka100, 100
– pozycja okienka (X,Y); możesz podać zamiast wartości liczbowych stałąSDL_WINDOWPOS_CENTERED
w celu wycentrowania okienka na środku ekranu640, 480
– wielkość okienka (szerokość, wysokość)SDL_WINDOW_SHOWN
– flaga; pełna lista tutaj, my na razie używamy tylko tej jednej
Jeżeli obiekt będzie pusty – nullptr
(C++11, włącz tą opcję w kompilatorze – przyda się też później) – to przerywamy program ponieważ bez okna nie chcemy nic robić. Na drugim listingu jest pokazana funkcja SDL_DestroyWindow
która powoduje… zamknięcie okienka 😉 i zwolnienie pamięci.
Oprócz samego okna potrzebujemy też renderera, czyli „urządzenia” które będzie odpowiadać za rysowanie. To właśnie tego obiektu oczekują wszystkie funkcje rysujące po ekranie. Tak samo jak poprzednio otrzymujemy obiekt SDL_Renderer*
, natomiast argumenty funkcji wyglądają tak:
w
– wskaźnik na obiekt okienkaSDL_Window
-1
– indeks sterownika, na dłuższy czas zostawiamy tą opcję- flagi:
SDL_RENDERER_ACCELERATED
– włącza akcelerację sprzętową dla renderera, bez tej flagi pracujemy na CPUSDL_RENDERER_PRESENTVSYNC
– wymuszamy vsync czyli synchronizację pionową, ilość odświeżeń okna na sekundę będzie zgodna z odświeżaniem monitora (zwykle 60Hz => 60fps)
Tak samo jak poprzednio, zwalniamy renderer za pomocą SDL_DestroyRenderer
.
Uwaga: przy zwalnianiu zasobów należy zachować odpowiednią kolejność, tj. odwrotną do tworzenia obiektów.
Ładowanie bitmapy
Na razie nie będziemy zajmować się ładowaniem innych formatów niż BMP. Kod ładujący jest podobny do poprzednich – naturalnie, ścieżka do pliku jest względna:
1 2 3 4 5 6 |
// Load bitmap SDL_Surface *bmp = SDL_LoadBMP("images/sense.bmp"); if (bmp == nullptr) { std::cout << "Blad SDL_LoadBMP():" << SDL_GetError() << std::endl; return 0; } |
Wybierzmy na razie bitmapę o takim samym rozmiarze co rozmiar okna, na przykład tą.
Oprócz samego załadowania do pamięci, potrzebujemy naszą bitmapę – w obiekcie typu SDL_Surface
– przekonwertować do tekstury, którą będziemy mogli wyświetlić na ekranie.
1 2 3 4 5 6 7 |
// Create texture from surface SDL_Texture *tex = SDL_CreateTextureFromSurface(ren, bmp); SDL_FreeSurface(bmp); if (tex == nullptr) { std::cout << "Blad SDL_CreateTextureFromSurface():" << SDL_GetError() << std::endl; return 0; } |
Zwróć uwagę że w trzeciej linijce (tego listingu) od razu zwalniamy pamięć po obiekcie SDL_Surface
tworzonym w poprzednim fragmencie kodu. Jest on nam już niepotrzebny ponieważ mamy teksturę SDL_Texture
po konwersji.
Nie zapomnij o usunięciu tekstury za pomocą SDL_DestroyTexture!
Wyświetlanie obrazka i opóźnienie zamknięcia okna
1 2 3 4 5 |
SDL_RenderClear(ren); SDL_RenderCopy(ren, tex, NULL, NULL); SDL_RenderPresent(ren); SDL_Delay(3000); |
Te cztery linijki powinny być po załadowaniu tekstury, a przed wyczyszczeniem pamięci. Tłumaczenie po kolei co robią:
SDL_RenderClear
czyści bufor renderera. Żeby zrozumieć jak to działa powinieneś wiedzieć o jednej ważnej rzeczy: karta graficzna ma dwa bufory, które są ciągle zamieniane. Program rysuje na nieaktywnym buforze, wyświetlany jest aktywny. Ekran (renderer – nieaktywny bufor) jest czyszczony przez tą funkcję na ustawiony kolor, domyślnie czarny (0,0,0).SDL_RenderCopy
kopiuje teksturę na renderer. Dwa parametryNULL
służą do określenia pozycji i rozmiaru tekstury, na razie tego nie używamy (mamy teksturę wielkości okna)SDL_RenderPresent
zamienia bufory, czyli wyświetla ten po którym rysowaliśmy.SDL_Delay
opóźnia działanie programu o 3 sekundy, inaczej okienko by się samo zamknęło od razu po wyświetleniu
Powyższe kroki mogą być dla Ciebie niejasne ale spokojnie, w kolejnych częściach kursu zrozumiesz działanie buforów i tych funkcji. Jeżeli poprawnie posklejałeś kod, powinieneś w tej chwili otrzymać program dający rezultat widoczny na zrzucie ekranu na samym początku wpisu.
Pełny kod źródłowy możesz pobrać z repozytorium tutaj.
- Program się nie kompiluje, „undefined function …”
Sprawdź ścieżki do plików nagłówkowych (include) w swoim IDE - Program się nie kompiluje, „undefined reference to …”
Sprawdź ścieżki do bibliotek w swoim IDE - Program się nie uruchamia, brakuje pliku DLL – SDL2.dll
Prawdopodobnie pominąłeś informację w tekście – pliki DLL pobrane z pierwszej paczki muszą być w katalogu ze skompilowanym programem - Program się nie uruchamia, brakuje pliku DLL – inny
Skopiuj plik z katalogu system32 – powinien tam być - Okienko od razu się zamyka bez komunikatu błędu
Brakuje poleceniaSDL_Delay
, dodaj je i ustaw odpowiedni czas w milisekundach - SDL nie znajduje obrazka, pomimo że mam go w poprawnej ścieżce
Sprawdź w IDE ścieżkę katalogu roboczego programu lub uruchamiaj z eksploratora plików zamiast z IDE - Nie mogę zamknąć okienka przed upływem czasu
To żaden błąd 😉 Nie ma pętli zdarzeń, SDL ignoruje przycisk zamknięcia okna
Na tą część kursu to tyle. Jak masz jakiekolwiek pytania czy problemy to śmiało pisz komentarz, pomogę jak mogę 🙂
Nullptr was not declared in this scope. Chyba problem z bibiloteka ??
nullptr to słowo kluczowe C++ od wprowadzone do stanfardu C++11. Ustaw w kompilatorze odpowiednią opcję aby móc skompilować ten program bez błędów