#Piszemy gre w SFML’u – Lekcja 3

W tej lekcji dowiesz się w jaki sposób można przystosować swoją grę do wczytywania różnych poziomów z pliku.

!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

W tym poradniku skorzystam ponownie z gotowych tekstur stworzonych przez riidom, a konkretnie z pliku prev_all.png (link), które umieściłem w folderze data/images/tiles.png.

tiles.png

Na początku zmieńmy rozmiar tych kafli w dowolnym programie graficznym na 120 x 80 pikseli dzięki czemu nie będziemy się bawili w dopasowywanie wielkości kafli w grze. W ten sposób otrzymamy poziom o wymiarach 32 x 18 kafli (1280/40, 720/40 gdzie 40 to wymiary kafla).

Skoro posiadamy już wstępnie materiały do naszej gry to czas stworzyć nową klasę: Level, która będzie się zajmowała wczytywaniem poziomów z pliku. Poziomy będą zwykłymi plikami tekstowymi, ale za to z własnym rozszerzeniem: „.level”.

Przeanalizujmy to co już mamy w samym pliku nagłówkowym. Tak jak widzimy możemy wczytać już poziom bezpośrednio z konstruktora i to tyle jeżeli chodzi o samo wczytywanie poziomu na tym etapie.

Dalej widzimy coś co nazwałem „podklasami”, które w rzeczywistości to nic innego jak enum, który informuje nas o rodzaju tekstury. To co już widzimy to wczytamy jedynie 2 tekstury (NONE to pole puste): kamień oraz ziemię.

Tile, czyli nasz kafel przechowuje informację o typie kafla, który ma zostać narysowany oraz to czy jest ścianą (czy można przez niego przejść).

Ostatnią sekcją są „zmienne”, które przechowują stały rozmiar mapy podany w kaflach oraz wymiary kafelków. Polecam zapisywać sobie takie rzeczy jako zmienne, ponieważ później gdy będziemy chcieli zmienić kafle i ich rozmiar to przyjdzie nam zmienić wymiary jedynie w paru miejscach, a nie całym kodzie.

Oczywiście jest jeszcze tablica, która reprezentuje nasz poziom w grze, jest to zwykła tablica o wymiarach poziomu.

Poziom zapisany w pliku będzie wyglądał tak jak poniżej, oto przykładowa mapa (data/levels/map.level):

Możesz powiedzieć, że to nic nie znacząca masa zer, jedynek i dwójek. Każda z tych cyfr oznacza pojedynczy kafel, a ich cyfra to reprezentacja  enum FieldType (pamiętasz, że enum jest numerowany od 0 i u nas NULL = 0, STONE  = 1, DIRT = 2). Jeżeli chcesz możesz dodać do pliku wymiary poziomu, jeżeli chcesz je zrobić zmienne.

Czas na wczytanie poziomu:

Proponuje samotnie przeanalizować w jaki sposób działa wczytywanie poziomu, mówiąc szczerze nie powinno tutaj nic sprawić problemu. Jedyne co może zastanowić to fakt, że na początku w tablicy mamy pozycję Y, a nie X. Jest to nie bez powodu, a to dlatego, że podczas wczytywania poziomu poruszamy się po pliku fragmentami idąc z lewej strony do prawej, a nie z góry do dołu.

Najwyższy czas wyświetlić nasz poziom w klasie Engine. Ale wcześniej czas przygotować sobie kilka nowych typów:

Tworzymy sobie obiekt klasy Level, będzie on nam przetrzymywał nasze dane poziomu, nowością mamy także tablicę przechowujące tekstury poszczególnych kafelków oraz tablica ze sprite’ami.

Zaszły także zmiany w konstruktorze:

W konstruktorze wczytujemy poziom, tekstury i ustawiamy kafelki na odpowiednich pozycjach

Ostatnią zmianą jest narysowanie poziomu, robimy to w metodzie runEngine().

Jak widzimy doszła podwójna pętla, która wyświetla nasze kafelki, warto zaznaczyć że kolejność w jakiej wyświetlamy mapę i kafelki jest ważna, jest to związane ściśle z kolejnością wykonywania instrukcji w c++, kompilator odbiera to w ten sposób: wyczyść pozostałe piksele z poprzedniej klatki, narysuj tło (poziom), później gracza (o tym była mowa w Kursie Podstawowym SFML)

W tej lekcji nauczyliśmy się wczytywać poziomy o stałej i niezmiennych wymiarach, jeżeli chcesz wiedzieć jak wczytać rozmiar poziomu z pliku i jak zrobić odpowiednio dużą tablicę do tego poziomu to zapraszam tutaj.

Jednak przy dużych poziomach trzeba odpowiednio zapanować nad ilością rysowanych kafelków (aby niepotrzebnie wyświetlać np 100 kafelków, które jest poza widoczną częścią gry) o tym przeczytasz w tym wpisie.

 

Kod źródłowy:

>>Pobierz<< | >>GitHub<<


  • Stanisław

    Cześć! Mam parę pytań co do tworzenia mapy 😉 Czy jeżeli chciałbym żeby poziom był dość długi to czy można skorzystać z Twojego sposobu? Czy gra nie będzie wtedy zacinała? A jeżeli chciałbym zrobić grafikę podobną do tej w Rayman Origin, lub Limbo to czy da się wyświetlić ją w kafelkach? Jeżeli miałbyś czas to proszę o odpowiedź 🙂

    • Szymon Siarkiewicz

      Można, tylko że najlepiej zrobić kilka modyfikacji, bo jak zauważyłeś gra będzie się przycinała przy odpowiednio dużym poziomie. Wtedy tablica sprite’ów, która reprezentuje poszczególne kafelki powinna być nieco większa od ilości kafli jakie się mieszczą na scenie, np na ekranie mieści się 32×32 kafli, ty masz poziom większy niż ekran tylko w szerokości więc robisz tablicę 36×32, aby zawczasu kafelki których nie widać na ekranie zostały wczytane (coś w rodzaju http://informatyka.wroc.pl/node/474?page=0,1).

      Co do drugiego pytania, to w zasadzie możesz, ale się bardzo namęczysz, bo takie obiekty będziesz musiał albo wyświetlić na mniejsze elementy (na kafelki http://i.imgur.com/on62P.png), albo napisać system, który będzie akceptował „większe kafelki” tzn obiekty zapisane jako 1 obrazek, ale o rozmiarze np 2 x 2 kafli.

      W razie gdybym coś napisał/wytłumaczył w sposób niezrozumiały pisz śmiało 😉

      • Stanisław

        Dzięki wielkie za odpowiedź! 😉 Spróbowałem zrobić kawałek takiego poziomu i działa wszystko (Przesunięcie kamery, wyświetlanie mapy) tylko że mam jeszcze jeden problem. Mianowicie, gdy rysuję dużo kafelków na mapie to gra zaczyna baardzo spowalniać, a kiedy minę ten moment i pójdę postacią dalej to postać już normalnie idzie. Zauważyłem że jest to spowodowane tą instrukcją:

        for(int i=0;i<level.height;i++)
        for(int j=0;j<level.width;j++)
        if(level.poziom[i][j].type != Level::NONE)
        window.draw(sprite[i][j]);

        Czyli rysuje się co każde wykonanie pętli pewne X kafli gdzie X jest dużą liczbą 😉 I tu pytanie, czy dało by się wyświetlić mapę przed pętlą? Lub czy da się to w jakiś inny sposób zoptymalizować?

        • Stanisław

          Ups, przepraszam, z jakimiś krzaczkami się wyświetlił komentarz powyżej :/

        • Szymon Siarkiewicz

          Jeżeli możesz wrzuć ten kod na pastebin.com i daj do niego link to będzie łatwiejszy do przeanalizowania 😉

          • Stanisław

            http://pastebin.com/1MLSjGh1 – to jest w sumie Twój kod do rysowania mapy na scenie 😉 z tym że rysuje się ona co każde wykonanie głównej pętli programu, wiem że trzeba clear’ować scenę inaczej będzie rozmazanie ruchu postaci, ale właśnie tak częste rysowanie dużej ilości kafli spowalnia grę ;/ I właśnie nie wiem jak to zoptymalizować, np. czy da się wczytać cały jeden poziom przed główną pętlą, czy coś w tym rodzaju. Bo jestem trochę w desperacji bo nawet Mario miało na scenie więcej kafli i nie ścinało … 😀

        • Szymon Siarkiewicz

          A wczytujesz cały poziom, czy tylko potrzebny fragment? Jeżeli tylko potrzebny fragment to zamień tablicę Sprite’ów na VertexArray, które teorytycznie powinno lepiej działać (Paragraf „Mapa kafelkowa”http://wp.me/p4FVK5-6M ).

          • Stanisław

            Zrobiłem teraz na Vertexach i dodałem timer, którego nie miałem. Teraz postać chodzi wszędzie z tą samą prędkością (do czego też dążyłem) ale niestety nawet jak wczytam mapę na jeden cały ekran(poza nim nie wczytuje nic) to gra przycina. wczytuje 576 kafli i kiedy postać chodzi po nich(ekran się nie porusza) to widać że przycina :/ Już nie mam pojęcia o co chodzi. Linkowanie mam statyczne i na Releasu nawet tnie …

          • Szymon Siarkiewicz

            Jeżeli ten timer sprawia także że rysowanie poziomu jest w jakichś odstępach czasu to gra ma prawo ciąć. Dzisiaj nieco później wrzucę przykładowy kod z taką liczbą kafli i zobaczymy co jest nie tak

          • Stanisław

            Ekran: 1280×760 , 18 kafli – wysokość, 32 – szerokość. Jeden kafelek 40×40. Co ciekawe pamięci zabiera program mi 10MB ale procesor jest wykorzystywany w 27% (intel i3 2×2.50GHz (logicznych 4) Karta graficzna Radeon HD 7600M, 6GB RAM) Hm, co do timera to raczej nie on sprawia te problemy, ponieważ gdy postać idzie w lewo a wraz z nią kamera to przycina tylko do czasu gdy scena jest zapełniona w ponad połowie kaflami, jeżeli jest np. 1/4 mapy w kaflach to już nie tnie 😉

          • Szymon Siarkiewicz

            najlepiej to podrzuć kod to tak będzie najłatwiej ewentualnie wejdź na IRC’a ja powinienem być gdzieś do 20 na nim (http://webchat.quakenet.org/?channels=programowanie&uio=d4)

          • Stanisław

            Ok, sprawdziłem tą wydajność tylko pod kątem mapy i poruszania postaci. Postać dalej tnie przy poruszaniu się na dużej mapie. Co ciekawe jeżeli dodałem 576 kafli ale były one małe np. 20×20 to nie cięło, a już 40×40 to przycina bardzo widocznie. Mapę wziąłem od Ciebie z VertexArray a ruch postaci to już tam dorobiłem. Tekstury na jakich sprawdzałem to tekstura mapy ta z tej 3 lekcji. Postać zaś to pierwsza klatka animacji z obrazka z lekcji 2.
            Tutaj jest ten kod:
            http://pastebin.com/bAeEReRG
            http://pastebin.com/QhWsEkcz
            http://pastebin.com/HAws2REm
            I dzięki jeszcze raz że się interesujesz moim problemem 😉

          • Szymon Siarkiewicz

            Z tego co ja widziałem u siebie to postać się poruszała cały czas z tą samą prędkością (tak samo płynnie), dla różnych wielkości kafli miałem różny framerate ale przekraczający 600fps (64×64 okolice 700, 40×40 800-900, 20×20 1000). Sprawdzałem dla wielkości 20×20 jak i 64×64. Niewiele mogę więcej pomóc nie mając tego problemu w tym samym kodzie.

          • Stanisław

            Tak jak podejrzewałem, mój problem dotyczy mojego visuala, którego mogłem źle skonfigurować z SFML’em. Dzięki wielkie że mi pomogłeś 🙂 Chociaż najprawdopodobniej nie dotyczy to sprzętu to czy mógłbyś mi powiedzieć czy Twój komputer jest dużo lepszy od mojego? Moje parametry – intel i3 2×2.50GHz, Karta graficzna Radeon HD 7600M, 6GB RAM. Dzięki jeszcze raz 😉 odetchnąłem z ulgą bo myślałem że coś z kodem cały czas robię źle 😀

          • Stanisław

            Sprawdziłem teraz na starym komputerze. Tam działa płynnie. Czyli problem rozwiązany, dotyczył jakichś zepsutych plików na moim laptopie 🙂 Dzięki za pomoc :))

          • Nie ma problemu 🙂

  • Corvus

    Cześć mam takie malutkie pytanie… Chciałbym się zapytać jak można to zrobić dla kilku map zamiast jednej… Możesz mi pomóc?

    • Musisz nieco dokładniej wyjaśnić o co ci chodzi pisząc „dla kilku map”, podany sposób jest uniwersalny o ile pliki map są zapisane wg 1 schematu. Jeżeli chcesz wczytać inny poziom niż ten podany tutaj zmieniasz argument w linii: level.loadFromFile(nazwa_poziomu)”, tylko że musisz pamiętać, że akurat w tym wypadku poziom musi się znajdować w data/levels/ bo tą ścieżkę domyślnie podajemy w implementacji wczytywania poziomu.

      • Corvus

        Wcześniej rysowałem w ten sposób ( zanim przeczytałem kurs ) :
        if( swiat.aktywnaMapa==0)
        {
        swiat.rysujMapeGlowna(oknoAplikacji);

        if (swiat.mapaGlowna[ postac.py ][ postac.px ] == 6 )
        {
        swiat.aktywnaMapa=1;
        }
        }
        else if (swiat.aktywnaMapa==1)
        {
        swiat.rysujlokacjaMiasto(oknoAplikacji);
        }

        Chodzi mi o zmiane mapy tak jak. np. w Margonem po najściu na kafelek oznaczający przejście zmienia się mapa… A ty to robisz w Konstruktorze więc zastanawiam się jak to przejrzyście zrobić…

  • menhils

    Level.cpp|35|error: no matching function for call to ‚std::basic_fstream::open(std::basic_string, const openmode&)’|
    Linia 35 : file.open(„data/levels/”+filename, std::ios::in);
    Coś ten kod nie chce ze mną współpracować

    • Jest to kwestia różnych standardów i tak np. u mnie visual konwertuje string na c-string, a u Pana argument powinien być w całości c string, czyli wystarczy napisać: (… filename.c_str() ). Pozdrawiam

      • Ktoś_kogo_znasz

        w któym miejscu to napisać?

  • „to” czyli konkretnie, który fragment?

    • Ktoś_kogo_znasz

      a konkretnie gdzie mam napisać (filename.c_str().
      Bo jak piszę file.open(„data/levels/”+filename, ios::in, filename.c_str()) to nie działa.
      Wersja: file.open(„data/levels/”+filename.c_str(), ios::in) też nie 😀

      • W takim razie zrób oddzielnego string’a: file.open(string(„data/levels/”+filename).c_str()) lub string str=”data/levels/”+filename;
        file.open(str.c_str())

        • Ktoś_kogo_znasz

          Żaden nie działa…

        • Ktoś_kogo_znasz

          Mógłbyś podawać kod w pasterbin? Bo tak troche nie czytelnie 😀

        • Ktoś_kogo_znasz

          Mam już, działa, dzięki za chęci 😀

          • Możesz napisać co zrobiłeś dla kolejnych osób które będą miały ten problem, ale mi się wydaje że w C::B właśnie chodziło o te c_str() 😛

  • Kacper

    Wszystko pięknie, wszystko ładnie, ale jak zrobić tak, że mam 3 rodzaje kafelków, ale chcę żeby 1 nie był ścianą i można było przez niego normalnie przechodzić?

    • Nie za bardzo rozumiem probelmu, rozwiązanie jest dość trywialne. Wiadomo że nie podałem kodu rozwiązującego napisanie każdej gry (trzeb go dostosować pod swoją co trudne nie jest). w Twoim wypadku if(tmp == 1) …isWall = false, u mnie isWall oznacza, że dany kafelek generuje kolizję

  • Kamil

    Zawsze miałem problem z przechodzeniem na kolejne mapy… np. wychodzimy poza mapę (prawastrona) i wczytuje nam dalej ten sam plik, ale tylko to co nie udało się wczytać z pliku z prawej strony, to samo z górą, dołem i lewą stroną, a jeśli dalej nic nie ma, to po prostu nie można wyjść. Napisałbyś jak takie coś osiągnąć? Byłbym wdzięczny

  • Paweł

    Mógłbyś spojrzeć na mój kod, proszę? Wydaje mi się, że pisałem wszystko krok po kroku analogicznie jak Ty w tym kursie, ale jakoś mi nie wyświetla się mapa. https://www.dropbox.com/s/7r2m0ubeft57vs5/Mechanized%20Techno%20Explorer.zip?dl=0

    • Wybacz, ale mam wystarczająco sporo do roboty, a analiza całego kodu (gdzie nazwy zmiennych niekoniecznie dobrze pod kątem przeznaczenia) by pochłonęło mi z wieczór lub dwa, bo tego kodu wręcz nie znam (nie wiem gdzie mogłeś coś pozmieniać). Polecam sprawdzić jak wyświetla ci się poziom w konsoli, tzn niech po wywołaniu wczytajZpliku zostanie przez cout(for()for()cout<<level.level[i][j].type) wyświetlona warstwa "logiczna" poziomu (czyli 0, 1 z których się składa poziom) wtedy będziesz miał zawężenie kodu do sprawdzenia, jeżeli nie zostanie wyświetlony (lub źle) poziom to będzie oznaczać że problem najprawdopodobniej tkwi we wczytywaniu poziomu (w klasie level), jeżeli wyświetli się prawidłowo w konsoli to masz problem z rysowaniem (klasa Engine)

    • Wybacz, ale mam wystarczająco sporo do roboty, a analiza całego kodu (gdzie nazwy zmiennych niekoniecznie dobrze pod kątem przeznaczenia) by pochłonęło mi z wieczór lub dwa, bo tego kodu wręcz nie znam (nie wiem gdzie mogłeś coś pozmieniać). Polecam sprawdzić jak wyświetla ci się poziom w konsoli, tzn niech po wywołaniu wczytajZpliku zostanie przez cout(for()for()cout<<level.level[i][j].type) wyświetlona warstwa "logiczna" poziomu (czyli 0, 1 z których się składa poziom) wtedy będziesz miał zawężenie kodu do sprawdzenia, jeżeli nie zostanie wyświetlony (lub źle) poziom to będzie oznaczać że problem najprawdopodobniej tkwi we wczytywaniu poziomu (w klasie level), jeżeli wyświetli się prawidłowo w konsoli to masz problem z rysowaniem (klasa Engine)

  • Terminatorek

    Dzięki za ten kurs, bardzo mi pomógł 😀 ale mam pytanie 🙂 Zrobiłem sobie mapę z różnymi rodzajami kafli m.in. drzwi otwarte, drzwi zamknięte. Chciałem zrobić tak, że jak kliknę na kafelek z zamkniętymi drzwiami, to zamieniają się one na otwarte drzwi. Próbowałem na różne sposoby, najróżniejsze, ale nie udało mi się 😛 jakaś porada?

    • To: http://pastebin.com/E4dLqFRB chyba powinno nieco rozjaśnić problem i ułatwić rozwiązanie problemu 🙂

      • Terminatorek

        tak trochę ogarniam ale nie do końca. Byłbym wdzięczny za przykład jakiegoś kogu z wyjaśnieniem bo nie chcę zrobić czegos czego i tak nie zrozumiem. Z góry dzięki 😀

        • Moim zdaniem ten kod jest podany wręcz wprost jak zrobić, wystarczy jedynie go dopasować do kodu z własnej gry. Napisz czego dokładnie nie rozumiesz, ewentualnie zapraszam jutro na IRC o 20:00

          • terminatorek

            Nie znałem tej strony IRC wcześniej. Chociażby co to FT, nie ma go wcześniej opisanego.
            I co to p.second?

          • terminatorek

            Dobra, mam 14 lat dopiero, doszedłem do wniosku że musze jeszcze raz porządnie obiektówkę przerobić 😀 pozdrawiam 😀

          • wybacz, nie zedytwałem tego tam, zamiast tych p.second możesz przyjąć że tam jest posY i i p.first posX (to są elementy std::pair), a co do FT to FieldType, czyli rodzaj kafli, ale do tego można było dojść po analizie kodu SQUARE (nie bez powodu podałem link).