Dev.log(1) – silnik gry

Zazwyczaj używamy gotowych silników do tworzenia gier. Możemy wyróżnić ich od groma i wybrać taki, w którym będzie nam się najlepiej pracowało. Unity, Unreal Engine, CryEngine, Godot Engine to tylko cztery spośród ich niezliczonej ilości. Najważniejszą ich zaletą jest to, że nie trzeba wymyślać koła na nowo. Pamiętajmy jednak, że napisanie czegoś samemu, od początku do końca, daje więcej frajdy i to co bardzo istotne – rozwiązujemy problemy i programujemy!

W dzisiejszym wpisie nakreślę podstawy tworzenia prostego silnika do gry. Podzielimy projekt na mniejsze części, z których każda będzie odpowiedzialna za swoją funkcję.

Od czego zacząć?

Na początku zacznijmy od odpowiedniego ułożenia katalogów w projekcie. Wbrew pozorom jest to ważna czynność, która pozwala na uporządkowanie plików źródłowych w celu ich szybszego odnalezienia w przyszłości. Poniżej znajduje się ułożenie z podziałem projektu na sceny, logikę, obiekty, menadżerów, sieć i zasoby. Nie jest to bardzo profesjonalne, ale dopiero zaczynamy, z czasem dokonamy odpowiednich zmian. Nie musisz wzorować się na mnie, możesz podzielić swój projekt na części z którymi będzie Ci się lepiej pracowało.

Przejdźmy więc dalej. Na screenie powyżej widać parę plików (w katalogu Managers), które dodałem od ostatniego wpisu. Pierwsze dwa (WindowManager.hpp i .cpp) są odpowiedzialne za zarządzanie oknem aplikacji. Znajduje się tam kod tworzący okno, proste zarządzanie scenami, o których opowiem więcej w dalszej części wpisu, oraz główną pętlę renderowania obiektów. Kolejne dwa pliki to EventManager.hpp i .cpp, czyli klasy odpowiedzialne za przechwytywanie akcji związanych z okienkiem gry. Tymi akcjami są, dla przykładu, zamykanie okna, zmiana jego wymiarów czy wciśnięcie klawisza. Nie będziemy tutaj przechwytywać klawiszy odpowiedzialnych za rozgrywkę, ponieważ chcę żeby kod był maksymalnie czytelny. Obsłużymy tutaj klawisz ESC czy skróty klawiszowe, które nie wpływają na rozgrywkę, ale mogą modyfikować ustawienia związane z dźwiękiem czy wyświetlaniem obrazu.

Mamy również plik main.cpp, który w tym przypadku zostanie z nami do końca i raczej nic się w nim już nie zmieni. W nim uruchomimy aplikację poprzez wykonanie poniższego kodu.

#include "WindowManager.hpp"

int main(int, char const**)
{
    WindowManager windowManager;
    windowManager.displayLoop();
    
    return 0;
}

Sceny

W filmie sceny mają podobne znaczenie co w programowaniu. Spójrzmy na sceny z serialu Mr. Robot. Pierwsza z nich ukazuje Elliota (aktor Rami Malek) w środku miasta. Bohater znajduje się w pozycji stojącej, podnosi ręce w górę, w tle możemy zaobserwować budynki, billboard za jego plecami, widać, że bohater jest zadowolony, jest pod wrażeniem, że udało mu się czegoś dokonać (nie będzie spoilerów, spokojnie).

Mr. Robot (USA Network)

 

Drugi kadr pokazuje bohatera w pozycji siedzącej, zdecydowanie próbuje odciąć się od otaczającego go świata, jest głęboko zamyślony, obserwuje ludzi grających w koszykówkę. W tle widać trybuny i paru ludzi siedzących na nich.

Mr. Robot (USA Network)

Jak możesz zauważyć te dwie sceny przedstawiają dwa zupełnie inne stany tego co obserwuje widz. Zmiany dotyczą kadru, kolorystyki sceny czy sytuacji emocjonalnej i fizycznej, w której aktualnie znajduje się Elliot.

Dlaczego o tym piszę? Chcę przybliżyć ci znaczenie pojęcia scen. Programowanie wcale nie jest oderwane od rzeczywistości. Jak możemy zauważyć powyżej – sceny ukazują coś co jest ze sobą połączone (epizody serialu, aktor), ale dzieli je jednocześnie wiele aspektów jak stan emocjonalny czy czas i miejsce akcji. Tak samo jest w naszym silniku. Menu główne będzie różniło się od widoku rozgrywki czy od napisów końcowych.

Zadeklarujmy na początek kilka rodzajów widoków (scen) w pliku WindowManager.hpp (jest to plik nagłówkowy, w którym deklarujemy pola i metody klasy WindowManager).

enum GameState {
        SPLASH_SCREEN,
        MAIN_MENU,
        SETTINGS_MENU,
        CREDITS_MENU,
        LOADING_SCREEN,
        GAME_SCREEN,
        EXIT_STATE
    };

W powyższym fragmencie kodu możemy zauważyć takie sceny jak ekran ładowania aplikacji (SPLASH_SCREEN), widok ustawień (SETTINGS_MENU) czy widok gry (GAME_SCREEN).

Co faktycznie dają nam sceny?

Dzięki scenom, silnik, potrafi rozróżnić co powinien zrobić w danym momencie, ale w oparciu o to, jaka akcja została aktualnie wykonana. Daje nam to możliwość zaserwowania użytkownikowi odpowiednich treści. Logiczne jest to, że nikt podczas rozgrywki nie chciałby mieć na ekranie widocznego menu głównego z przyciskami „nowa gra” lub „wyjdź” kiedy celuje myszka w przeciwników.

Podsumowanie

Dziś napisaliśmy bardzo mało kodu, ale myślę, że więcej zaburzyło by zrozumienie jak to wszystko działa.

Projektowanie silnika gry nie jest łatwe, ale też nie będziemy robili w naszej grze zaawansowanej fizyki obiektów, bo nie o to chodzi w tym projekcie. Pragnę pokazać ci, jak podejść do tworzenia prostej gry, o czym musimy pamiętać, co musimy zrobić na samym początku by potem nie pogubić się we własnym kodzie i jakie mam (tak, ja, dlatego nie zawsze najlepsze) pomysły na rozwiązanie wielu problemów początkujących gamedevów.

W kolejnym wpisie zaprezentuję ci jak zbudować podstawową funkcję update, która będzie wywoływana z każdą iteracją pętli gry oraz wyświetlimy sobie jakiś przykładowy obiekt i nauczymy go chodzić.

Kod źródłowy aplikacji znajdziesz na moim GitHubie. Także przeanalizuj go dokładnie, bo wraz z każdym kolejnym wpisem zacznie robić się coraz ciężej.

Do następnego!

#shareShare on FacebookShare on Google+Tweet about this on TwitterShare on TumblrPin on PinterestShare on LinkedInShare on VKShare on RedditEmail this to someone