#Piszemy gre w SFML’u – Lekcja 7

!UWAGA! Kurs leży w kategorii ‚Obsolete’ co oznacza, że może być nieaktualny, zawierać błędy i nie polecam z niego korzystać. [INFO]

Kod z poprzedniej lekcji

Dzisiaj zgodnie z zapowiedzią zajmiemy się dodaniem „specjalnych efektów” do menu, ale zanim do tego przejdziemy poprawny (tradycyjnie) kod z poprzedniej lekcji. Jeżeli testowałeś kod z poprzedniej lekcji to pewnie zauważyłeś, że po dodaniu bonusów po zakończeniu fali napisy nie aktualizują się. Bardzo łatwo można to naprawić:

Postanowiłem także poprawić irytujący mnie sposób w jaki strzela gracz, mianowicie nie podoba mi się to, że jeżeli się poruszamy w przód i nagle puścimy gaz i obrócimy się to wystrzelony w ten sposób pocisk wystrzeli w tym kierunku w którym ostatnio był pojazd podczas przyspieszania. Można to zmienić zmieniając kod w ten sposób:


Efekty specjalne

Przejdźmy do meritum naszej lekcji, czyli do efektów specjalnych. Czym one w ogóle są? Na pewno spotkałeś się z nimi podczas grania w gry, najprostsze z nich to przyciemnienie ekranu, ale pokażę wam także jak w łatwy sposób można zrobić ekran ładowania. Mógłbyś powiedzieć, że lepiej byłoby użyć shaderów w celu upiększenia naszej gry, jednak nimi zajmiemy się kiedy indziej.

Teraz będę pracował na projekcie „testowym”, aby w szybszy sposób wyświetlić wam działanie specjalnych efektów oraz dlatego, że w większości tych efektów nie użyjemy w naszym głównym projekcie, chcę jedynie pokazać ci przykłady jak w łatwy sposób można wiele osiągnąć. Do testów wystarczy nam dowolny obraz.

Zgodnie z przewidywaniami po prostu sobie go wyświetliliśmy. Możesz się zastanawiać co z nim jeszcze zrobimy?

  • dodamy efekt przyciemniający ekran,
  • analogicznie jak u góry tylko rozjaśniający,
  • połączenie dwóch powyżej,
  • bardziej ruchome menu,
  • przykłady innych „specjalnych efektów”

Przyciemnienie ekranu

Jeżeli pamiętasz ostatnią lekcję i sposób w jaki powodowaliśmy, że czas do końca bonusu znikał to być może domyślasz się w jaki sposób przyciemnimy ekran. Będziemy po prostu rysowali kwadrat o rozmiarach naszego okna i zmieniali jego wartość alpha. Tworzymy klasę SpecialEffects, która będzie tak wstępnie wyglądała:

Za chwilę utworzymy więcej metod, które sprawią, że będziemy mogli w łatwy sposób manipulować specjalnymi efektami.

W zasadzie możesz się zapytać dlaczego piszemy do tak prostych czynności jak zmiana kanału przezroczystości specjalną klasę. Tutaj częściowo masz rację, można takie coś napisać bardzo szybko w samej grze, ale ta klasa przyspieszy nam pisanie podobnych rzeczy w przyszłych projektach gier, nawet jeżeli nie będą w SFML’u to będziesz miał zrobiony w dużej części szkielet, który w łatwy sposób można będzie dostosować do innych bibliotek. Czas na utworzenie pierwszego efektu: przyciemniania ekranu:

Tworzymy zmienne int: mod,granica i ustawiamy je na 0. Tworzymy także bool in_progress i ustawiamy w konstruktorze na false. Dodajemy także metody (void): przyciemnij(), update():

Tutaj wyjaśnię ci do czego służą te zmienne. in_progress służy do sprawdzenia czy już jakiś efekt jest wykonywany, mod to modyfikator, który decyduje o ile ma się zmieniać kanał alfa (wybaczcie mi jeżeli piszę od czasu do czasu zamiast polskiej „alfy” = „alpha”, jest to takie przyzwyczajenie). Natomiast granica służy do ograniczenia zmniejszania/zwiększania kanału przezroczystości.

Metoda update służy do zaktualizowania zmian na naszym ekranie. Pewnie jesteś ciekawy jak to się prezentuje? Na chwilę obecną wrzucam jedynie kod:

Ważne jest aby rysowanie obiektu SpeciallEffect odbywało się po narysowaniu wszystkich innych obiektów, czyli na samym końcu, inaczej nasz prostokąt zostałby zasłonięty przez inne obiekty. Ważne jest także wywołanie metody update(), inaczej nic się nie stanie (ekran się nie przyciemni).


Rozjaśnianie ekranu

Ten element wykonuje się niemal w identyczny sposób jak przyciemnienie:

Rozjaśnianie przyporządkowujemy sobie pod klawisz [2], dodatkowo w update() zabezpieczymy się przed błędem który powoduje uruchomienie dwa razy tego samego efektu:

Filmik, który pokazuje działanie kodu w obecnym momencie, jednak muszę ci powiedzieć, że jest on lekko oszukany ponieważ fraps zablokował ilość klatek do 60, więc jeżeli uruchomisz kod na swoim komputerze wszystko rozjaśni się dużo szybciej:


Rozjaśnianie + przyciemnianie ekranu i vice versa

Teraz zajmiemy się czymś co można wykorzystać w splashscreenach, które po prostu wyświetlają się jedynie aby np. zaprezentować na jakim silniku jest gra, gdzie na początku widzimy ciemny ekran, następnie zostaje rozjaśniony ekran + wyświetlane logo firmy i znowu zostaje przyciemniony ekran aby go rozjaśnić pokazując menu lub kolejny splashscreen (myślę że wiecie o co mi chodzi).

Do tego napiszemy sobie kolejkę, wg której będą wykonywane zadania. Do tego wykorzystamy strukturę danych queue, jest to jedna z wielu przydatnych struktur STL, z tymi strukturami spotkałeś się już wcześniej w tym tutorialu, np z vectorem (nie mylić z Vector2f!), jeżeli nie spotkałeś się wcześniej z adapterem kolejki to polecam zajrzeć TUTAJ.

Do pliku nagłówkowego naszej klasy dodajemy #include <queue> oraz w dostępie publicznym enum TYPE {ROZJASNIJ,PRZYCIEMNIJ} oraz metodę void dodaj(TYPE type), a jako prywatną std::queue <TYPE> kolejka. Już tłumaczę co do czego służy, nasz enum określa jakiego typu zadanie ma być wykonane, kolejka to po prostu „zmienna” przechowująca elementy kolejki. Zadaniem dodaj(…) jest dodanie do kolejki kolejnych zadań/elementów:

Zmieniła nam się metoda update():

Teraz przed sprawdzeniem czy jest coś już w trakcie wykonywania (jakiś efekt) sprawdzamy czy nasza kolejka nie jest pusta oraz czy nie wykonuje się żadne zadanie (np. przyciemnianie ekranu), następnie są wywoływane odpowiednie metody i usuwamy to zadanie z kolejki. Użycie tej metody jest bardzo proste:

Po prostu dodajemy do kolejki odpowiednie polecenia i zostaną one wykonane. Warto zaznaczyć dlaczego użyłem tutaj innego systemu sprawdzenia wciśnięcia klawisza, a to dlatego że poprzednia instrukcja warunkowa dublowała dodawanie zadań do kolejki ponieważ dodawała je dla stanu klawisza wciśniętego (KeyPressed) oraz puszczonego (KeyReleased).

Teraz kolejka wykonuje się od razu więc chwilę po wykonaniu jednego zadania rozpoczyna się kolejne, ale spowolnieniem tego zajmiemy się później.


Usprawnienie menu

Teraz zajmiemy się elementami, które mogą sprawić, że menu wyda się bardziej atrakcyjne dla gracza. Generalnie może wiele elementów na sprawienie, że menu będzie ładniej wyglądało dla gracza. Jeden z takich elementów nasze menu już posiada i napisaliśmy je już w pierwszej lekcji (o ile mnie pamięć nie myli), było to podświetlanie napisów gdy najeżdża na nie myszka. Już dzięki temu prostemu zabiegowi gra wygląda profesjonalniej.

1.
W menu powinny być jakieś elementy, które się w jakiś sposób zmieniają, do podświetlania napisów zapraszam do lekcji 1.

2.
Innym elementem może być np odrobinę za duże tło, lub po prostu jakiś element, który się porusza w minimalny sposób gdy poruszamy myszką, coś w takim stylu:

Coś podobnego jak na filmiku powyżej właśnie napiszemy, taki efekt jak powyżej można osiągnąć za pomocą poruszania większego obrazka niż ekran oraz elementów pasujących do niego lecz nie umieszczonych bezpośrednio na nim, poruszając nim w odpowiedni sposób osiągniemy odpowiedni efekt.

Wykorzystamy nasz obrazek co wcześniej tylko o nieco większych rozmiarach niż 1280×720, czyli 1300×731. Następnie ustawmy Origin na jego środek (obraz.setOrigin(650,365.5)) oraz ustawmy go na środku (obraz.setOrigin(650,365.5)), te instrukcje wpisujemy zaraz po załadowaniu obrazka do programu. Teraz przydałoby się nam wiedzieć, w którą stronę gracz poruszył myszką.

If w pętli zdarzeń wygląda następująco:

Wyjaśnijmy go nieco, p to pozycja myszy względem okna, s to punkt środka tła. Na czym polega sprawdzanie w którą stronę poruszyła się mysz? Polega na sprawdzeniu jak pozycja myszy ma się do pozycji myszy podczas ostatniej aktualizacji, jeżeli x’owa punktu p jest większa to mysz musiała poruszyć się w prawo, kolejny warunek sprawdza czy można poruszyć obrazkiem, zapobiega on sytuacji gdy obrazek poruszy się za daleko, tak aby nie pokazał czarnego tła. W jaki sposób jest to liczone? s.x to pozycja środka obrazka, aby uzyskać prawą krawędź (w przypadku lewej odejmujemy) obrazka należy dodać połowę jego szerokości. Tylko że my mamy tło większe niż obrazek, a więc dodajemy wartość która nam wychodzi ze wzoru:

= szerokość_okna/2 – (szerokość_tła – szerokość_okna)/2

Następnie sprawdzamy czy ta wartość jest mniejsza od szerokości okna. Jeżeli zastanawiasz się skąd się wziął ten dziwny wzór to powstał on w trakcie eksperymentów jak napisać kod aby było dobrze 😉 Analogicznie postąpiłem z resztą warunków. Efekt wygląda następująco:

3.
Kolejnym i ostatnim ze sposobów jakie tu zaprezentuje jest wykorzystanie tego co już napisaliśmy w grze. W naszym wypadku można zmusić do pracy asteroidy, które będą latały po menu. Jest to bardzo fajny element do menu, czasami wykorzystywany przez twórców, jednak kod jak takie coś zrobić zaprezentuje na samym końcu tej lekcji gdzie wtłoczę do projektu parę zaprezentowanych tutaj sposób na urozmaicenie naszej gry.


Loadingscreen

Prawie zapomniałem o loading screenach, czy też ekranach ładowania jeżeli ktoś woli w języku polskim. Nie będziemy się tutaj nad nimi rozwodzić, przydają się w większych produkcjach gdzie jest więcej elementów do wczytania, u nas byśmy zobaczyli jedynie ich mignięcie, ale wspomnę jak je można zrobić. Największy problem pojawia się gdybyśmy chcieli wczytać duży/o plik/ów i jednocześnie wyświetlić w tle jakąś animację. Przy wczytywaniu dużych plików nasze okno by się zawiesiło gdybyśmy zrobili to w „tradycyjny” sposób, ponieważ korzystalibyśmy tylko z jednego wątku (threads) – głównego, który zająłby się tylko wczytywaniem plików, aby wyświetlić jakąś animację należy odpalić wątek „poboczny”. C++ wyposażone jest w wątki jednak SFML także je posiada. Aby dowiedzieć się co i jak zapraszam >>TUTAJ<<.


Implementacja niektórych elementów do naszej gry

My nie wykorzystamy żadnych elementów z klasy SpecialEffects, zrobimy 2 rzeczy: wykorzystamy asteroidy jako tło dla menu oraz poprawimy wygląd pocisków. Jeżeli chcecie abym dodał np. splashscreena to napiszcie o tym w komentarzu. Zaczniemy od dodania tła z asteroid.

W Game w menu() tworzymy obiekt Asteroids, następnie generujemy pasującą nam ilość asteroid oraz pamiętamy o użyciu z Asteroids metody update() oraz narysowaniu tych obiektów zaraz po wyczyszczeniu okna. Przy okazji poprawmy mały błąd w generowaniu Asteroid, który polegał na tym, że asteroidy generowały się zawsze w 1 punkcie, który losował się tylko raz. Można go naprawić w łatwy sposób, mianowicie nazwy argumentów w metodzie zmieniamy na _x i _y oraz tworzymy zmienne o nazwie x, y przed pętlą. Oto jak metoda wygląda po zmianach:

W Game, metoda update wygląda w następujący sposób po dodaniu 4 linijek (utworzenie obiektu Asteroids: Asteroids asteroids, wygenerowaniu 20 asteroid: asteroids.generate(20), zaktualizowaniu ich asteroids.update() oraz narysowaniu: window.draw(asteroids)).

Sprawienie aby pociski były bardziej widoczne polega na zamienieniu w klasie Bullet wykorzystywanego obiektu VertexArray na vector klasy CircleShape i podmianie metod odpowiednich dla vectora. Zmianie zostało poddane bardzo duża część metod, elementy które zmieniono są oznaczone //, poniżej wszystkie metody które zmieniłem.


Podsumowanie

Dzisiaj stworzyliśmy sporo rzeczy i małą część z nich wykorzystaliśmy w naszej grze, teraz powinieneś już wiedzieć w mniej więcej jaki sposób można urozmaicić grę. Jeżeli chcesz abym pokazał jak dodać jakiś efekt do naszej gry to pisz w komentarzu. Tak samo w przypadku gdy masz jakieś pytanie, znalazłeś błąd lub masz jakąś uwagę do mnie. Na koniec filmik z gry oraz oczywiście kod źródłowy zarówno do lekcji jak i „efektów specjalnych”.

Kod źródłowy
>>Pobierz<< | >>GitHub<<

Następna lekcja ->


  • Szymon

    Cześć. Mógłbyś napisać czemu ma służyć lub co to jest d.x ? (Usprawnienie menu to przesuwanie za kursorem ) 🙂

    • Hmm, w tej lekcji chyba pierwszy raz dałem strasznie nielogiczne nazwy zmiennych i z tego co widzę to Vector d ma służyć do analizowania w jaką stronę poruszył myszką użytkownik, tak aby aby wiedzieć w jaki sposób poruszyć obrazem.

      • Szymon

        Okej 🙂 A dałbyś jakąś wskazówkę lub kod aby to „d” działało ? Cały czas myślę i nic :/
        Super by było gdybyś znalazł czas 😛

  • Karol

    Nie wiem czy to jest dobre miejsce do tego typu pytania ale je zadam. A więc czytam pana poradnik dotyczący pisania gry i postanowiłem napisać jakiegoś własnego shootera w 2D. I chce zrobić zapis gry. Wiem że dane np. o ilości życia mam zapisywać do pliku binarnie ale co zrobić żeby po zapisaniu gry i jej wczytaniu program wrócił do tego samego miejsca?

    • Musisz te wszystkie dane zapisać do pliku (nie wiem po co binarnie, może być też tryb tekstowy), np jeżeli chcesz po wczytaniu gry mieć wrogów na tych samych pozycjach, ustawionych w ten sam sposób to musisz zapisać ich pozycje i rotacje do pliku. Aby być dokładniejszym nie musisz absolutnie wszystkich danych zapisywać, a tylko te na bazie których jesteś w stanie odtworzyć resztę, np jeżeli masz animacje bazującą na prędkości gracza, to nie ma sensu zapisywać do pliku klatki animacji a prędkość gracza etc.

  • Karol

    Czy żeby program napisany w SFML-u działał trzeba mieć na komputerze zainstalowany OpenGL ?

    • Nie, musisz skompilować grę w wersji „Release”, a następnie jeżeli linkowałeś SFML dynamicznie, to do folderu z plikiem .exe musisz dołączyć pliki .dll z folderu SFML-2.x/bin/ . Na pewno musisz wrzucić openal32.dll i najprawdopodobniej sfml-system-2.dll, sfml-graphics-2.dll, sfml-window-2.dll. Resztę dodajesz zależenie od tego z jakich modułów jeszcze korzystałeś w projekcie, jeżeli z audio to dodajesz sfml-audio-2.dll.

      Jeżeli całość kompilowałeś pod Visualem będziesz musiał dostarczyć odpowiednie dll od Microsoftu.

  • pw1602

    Nie działa obrazek, na którym testowane jest przyciemnianie itp.

    • Cóż, nic nie poradzę, był na zewnętrznej stronie, ale na szczęście do testów wystarczy jakikolwiek obraz.

      • pw1602

        Wiem, że ten obrazek nie był dość ważny, więc w zamian tutaj kolejny błąd:

        Przyciemnianie ekranu – za pierwszym kodem źródłowym:

        „(…)manipulować specjalny efektami.”