Przeglądaj kategorię

Poradniki

Kontrolowanie kamery przy użyciu View

Czym jest view?


View jak nazwa wskazuje jest podglądem poziomu, a konkretnie jakiegoś mniejszego obszaru. Stosuje się go w grach gdzie poziom gry jest większy niż ekran, dzięki czemu możemy decydować, który kawałek poziomu ma być wyświetlany.

W SFML istnieją 2 podstawowe założenia odnośnie view: możesz decydować jaki kawałek poziomu będzie wyświetlany, a także gdzie jest wyświetlany oraz w jaki sposób (czy jest powiększony itd).

Podsumowując view pozwoli ci powiększać, obracać i przewijać poziom. Jest on także przydatny przy robieniu split screenów oraz mini mapach.

 

Definiowanie podglądu view


Klasa służąca do podglądu w SFML została zhermetyzowana w sf::View. Możemy jeszcze w konstruktorze zdecydować co ma wyświetlać:

Obie definicje powyżej tworzą podgląd na obszar mapy o środku na pozycji (350,300) oraz wielkości 300×200.

Możesz także edytować je poza konstruktorem:

Teraz kiedy zdefiniowałeś już swój podgląd (view) to możesz go transformować w dowolny sposób.

 

Poruszanie podglądem (przewijanie/scrolling)

W przeciwieństwie to obiektów Drawable, których pozycja standardowo była umieszczona w lewym górnym rogu to w klasie View umieszczana jest ona zawsze w centrum obiektu. To jest także powodem dla którego ustawianie pozycji w tej klasie brzmi setCenter, a nie setPosition.

Obracanie view

Czyli przechylanie naszej kamery. Jako argument podajemy wartości w stopniach.

 

Powiększanie (zoomowanie/skalowanie)

Zoomowanie jest odpowiednikiem zmiany rozmiaru, a więc używamy tutaj setSize.

 

Definiowanie gdzie view ma być wyświetlane


Skoro już wiemy jak ustawić co view ma wyświetlać, czas zająć się tym jak ustawić gdzie ma być wyświetlane. Standardowo view zajmuje cały ekran, jeżeli podgląd jest tej samej wielkości co okno wszystko jest renderowane w stosunku 1:1. Jezeli jest innych wymiarów wszystkie obiekty są odpowiednio skalowane.

Standardowe zachowanie tej klasy jest odpowiednie dla większości przypadków jednak czasami chcemy aby było inne, np w przypadku gry multiplayer gdzie chcemy użyć dzielenia ekranu (split screen) poprzez użycie dwóch viewów, które będą zajmowały jedynie po połowie ekranu. Możesz także tego użyć to wyświetlania mapy, która będzie pomniejszoną wersją wyświetlanego obszaru. Do zrobienia czegoś takiego używamy viewPort.

Aby zdefiniować viewPort musisz użyć funkcji setViewPort.

Jak możesz zauważyć viewport nie jest definiowany w pikselach lecz za pomocą faktora size. Takie zastosowanie ma swoje powody, dzięki temu nie musisz pilnować czy nastąpił event resize, aby zaktualizować jego wielkość.

Użycie viewport dla split screena w większości gier wygląda podobnie.

lub dla minimapy:

 

Użycie View


Aby narysować coś za pomocą View, musisz wcześniej ustawić View w obiekcie, który może renderować inne obiekty (RenderWindow, RenderTexture)

Jeżeli nie chcesz używać już ustawionego przez siebie View, lecz chcesz skorzystać z normalnego poglądu okna możesz to zrobić w ten sposób:

Podczas uruchamiania setView jest tworzona kopia view, a nie wskaźnik do niego co oznacza, że po każdej zmianie na View (np skalowanie) musisz wywołać ponownie setView.

 

Zmiana zachowania view


Standardowo po zmianie rozmiaru okna wszystkie obiekty są obiekty są rysowane na tych samych pozycjach tylko odpowiednio przeskalowane do nowej wielkości okna.

Możliwe że będziesz chciał zmienić ten sposób zachowania i np. przy powiększeniu okna chciałbyś aby był widoczny większy obszar mapy. Wszystko co musisz w tym wypadku zrobić to zsynchronizować view do wielkości okna.

 

Konwersja współrzędnych


Podczas gdy używasz view piksele będą mogły nie pasować do pozycji swojej pozycji, ponieważ ich pozycja będzie zależna od przeskalowania. Np klikając na pozycję (10,40) będzie mogło się okazać, że kliknęliśmy rzekomo na pozycję (30, -40) w świecie gry. Aby to poprawić musisz skonwertować pozycję na ekranie do pozycji w świecie gry: mapPixelToCoords.

Standardowo mapPixelToCoords używa obecnie używanego view, jeżeli chcesz użyć view, które nie jest obecnie aktywne, możesz to zrobić poprzez podanie go jako argument tej funkcji.

Możesz także przeprowadzić konwersję w drugą stronę: mapCoordsToPixel.

Oryginalny artykuł

Użycie shader’ów

Wstęp


Shader to nic innego jak mały program, który uruchamiany jest bezpośrednio na kracie graficznej, które pozwolą nam programistom dodanie specjalnych efektów do naszej gry.

Shader’y są pisane w GLSL (OpenGL Shading Language), który jest bardzo podobny do C.

Istnieją dwa typy shader’ów: vertex shader, fragment (lub piksel) shader. Ten poradnik skupi się na wczytywaniu shader’ów nie pisaniu ich. Aby dowiedzieć się jak je pisać zapraszam do poradników GLSL dostępnych w internecie.

 

Wczytywanie


W SFML shader’y są reprezentowane przez sf::Shader. Mimo, że shader’y są coraz częściej spotykane to wciąż zdarzają się karty graficzne, które ich nie obsługują, dlatego powinieneś sprawdzić czy są obsługiwane:

Najczęściej używanym sposobem wczytywania shader’ów jest wczytywanie z pliku, do tego służy metoda loadFromFile.

Shader’y to po prostu pliki tekstowe z innym rozszerzeniem (tak jak w c++), a więc ich rozszerzenie nie ma znaczenia, wykorzystane tutaj „.frag” oraz „.vert” to tylko taka konwencja.

Shader’y mogą być także wczytywane bezpośrednio z klasy string, aby to zrobić musisz użyć loadFromMemory.

Tak jak reszta obiektów w SFML, można wczytać je także przy pomocy loadFromStream, o tym można przeczytać w TYM poradniku.

 

Używanie shader’wów


Aby użyć shader’a na jakimś obiekcie należy go dostarczyć bezpośrednio do draw.

 

Zmiennie w shaderach


Tak samo jak w innych programach komputerowych mogą mieć własne zmienne, deklarujemy je jako zmienne globalne z przedrostkiem uniform.

Zmienne uniform mogą być zmieniane (zmianie może ulec ich wartość) w programie c++, poprzez użycie setParameter z klasy Shader.

Przeładowania setParameter wspierają wszystkie typy dostarczone przez SFML:

  • float (typ GLSL: float)
  • 2 float‚y, sf::Vector2f (typ GLSL: vec2)
  • 3 float‚y, sf::Vector3f (typ GLSL: vec3)
  • 4 float‚y (typ GLSL: vec4)
  • sf::Color (typ GLSL: vec4)
  • sf::Transform (typ GLSL: mat4)
  • sf::Texture (typ GLSL: sampler2D)

Oryginalny artykuł (osoby które są zainteresowane shader’ami powinny przeczytać końcówkę dotyczącą minimal shader i openGL)

Pozycja, rotacja, skalowanie: przekształcanie obiektów

Przekształcenia


Wszystkie obiekty, które można przekształcać (transform) dziedziczą po klasie sf::Transformable, to ona pozwala im na te wszystkie czynności.

Ta klasa ma 4 właściwości: pozycję (position), rotację(rotation), skalę (scale) oraz origin. Ich użycie jest bardzo proste i intuicyjne.

Pozycja

Pozycja, to po prostu pozycja obiektu na scenie 2D, każdy kto miał do czynienia z układem współrzędnych wie o co chodzi :)

Standardowo obiekty porusza się względem punktu, który leży w lewym górnym rogu obiektu, można to zmienić, ale o tym później.

 

Rotacja

Jest to po prostu obracanie obiektów, jest ona definiowana w stopniach zgodnie z ruchem wskazówek zegara.

Zauważ że pobierana rotacja zawsze zawiera się w przedziale [0,360]. Tak jak w przypadku pozycji, tak samo rotacja jest dokonywana względem standardowo lewego górnego rogu.

 

Skala

Czyli faktor, który wskazuje zmianę rozmiaru obiektu. Jeżeli jest większy niż 1 to wiem że obiekt został powiększony, mniejszy od 1 pokazuje że obiekt został pomniejszony, a równy 1 mówi że obiekt jest nieskalowany.

 

Origin

Jest to punkt, wg którego wykonywane są operacje typu: przesuwanie, obracanie obiekt, itp. Standardowo jest on ustawiany na lewy górny róg obiektu. Jeżeli chcesz go zmienić to nie ma problemu, można w bardzo łatwy sposób go zmienić.

Zauważ, że po zmianie punktu origin, zmianie także ulega wizualna pozycja obiektu.

 

Przekształcanie (transformowanie) własnej klasy


sf::Transfromable możesz dziedziczyć, dzięki czemu możesz uzyskać jej właściwości w twojej klasie.

Aby użyć ostatecznie właściwości Transformable musisz użyć getTransform (zazwyczaj podczas wykonywania draw), poniżej nieco bardziej to wyjaśnię.

Jeżeli nie chcesz korzystać z całej funkcjonalności tej klasy, to możesz użyć jej po prostu jako członka swojej klasy, a funkcje możesz albo pozostawić w obecnej formie, albo możesz napisać własne przez zasłonięcie tych z tej klasy.

 

Rozszerzanie możliwości Transformable


Klasa Transformable jest łatwa w użyciu, ale jednocześnie bardzo ograniczona. Może się zdarzyć, że będziesz potrzebował więcej możliwości, typu łączenie kilku przekształceń w  jedno itp. Jeżeli tak jest w twoim przypadku, to jest nisko poziomowa klasa sf::Transform, która jest 3×3 matrix’em, dzięki czemu może być reprezentowana przez przekształcenia w świecie 2D.

Jest wiele sposobów na użycie sf::Transform:

  • przez użycie predefiniowanych funkcji dla najczęściej spotykanych transformacji (translacja, skala, rotacja)
  • przez połączenie dwóch transformacji
  • przez bezpośrednie określenie 9 elementów.

Kilka przykładów:

Możesz oczywiście wszystkie transformacje zawrzeć w jednej:

Aby wykorzystać te transformacje musisz:

Forma powyżej jest krótszą wersją:

 

Bounding boxes


Po wykonaniu  transformacji być może chciałbyś wykonać jakieś obliczenia na nich, np do sprawdzenia kolizji.

Obiekty w SFML mogą podać ci tzw bounding box, czyli najmniejszy prostokąt potrzebny do zamknięcia obiektu w prostpkącie.

Bounding box może być przydatny w sprawdzaniu kolizji i można go pobrać bardzo szybko.

Funkcja getGlobalBounds pobiera bounding box z uwzględnieniem transformacji (rotacja itp), getLocalBounds nie bierze tych zmian pod uwagę.

Jednak może się okazać, że ten rodzaj kolizji nie będzie dla ciebie odpowiednio dokładny, możliwe że przyda ci się to co przekazałem w moim kursie Piszemy grę w SFML w lekcji o kolizjach.

 

Hierarchia obiektów


Dzięki temu co napisaliśmy wcześniej możemy w łatwy sposób napisać klasę, która będzie decydowała o hierarchii obiektów, gdzie potomkowie danych obiektów (children) będą transformowane zgodnie z ich rodzicami (parents). Wszystko co musisz zrobić to przekazanie połączenia przekształcenia z rodzica na dzieci podczas ich rysowania.

Oryginalny artykuł

Tekst i czcionki

Wczytywanie czcionek


Zanim będziesz mógł wyświetlić tekst, będziesz musiał ustawić mu jakąś czcionkę. Czcionki są zawarte w klasie sf::Font (hermetyzowane) i zawierają 3 główne cechy: wczytywanie czcionek, pobieranie glifów (wizualna reprezentacja liter) oraz odczytywanie atrybutów czcionek.

Skupmy się na najbardziej popularnym wczytywaniem czcionek, czyli wczytywaniem z pliku:

Zauważ, że SFML nie wczytuje czcionek systemowych automatycznie, a więc coś takiego font.loadFromFile(„Courier New”) nie zadziała, ponieważ SFML potrzebuje nazwy pliku, nie nazwy czcionki oraz program nie znajdzie się w magiczny sposób do folderu z czcionkami w twoim systemie. Jeżeli chcesz wykorzystać jakąś to musisz ją dostarczyć razem z resztą plików potrzebnych do twojej gry.

Czasami SFML ma problemy z czcionkami z pozornie nie oczywistych powodów. Wtedy pierwszym z błędów, które sie pojawiają to komunikat w konsoli: „unable to open file”. Upewnij się  czy podałeś prawidłową ścieżkę do pliku (używaj ścieżek bezpośrednich) oraz z dobrym rozszerzeniem pliku. Jeżeli jesteś pewien że nazwa jest poprawna to zauważ że w przypadku gdy uruchamiasz grę z poziomu IDE to ścieżka bezpośrednia zaczyna się tam gdzie jest kod programu (zazwyczaj), a gdy uruchamiasz ją z pliku wykonywalnego (exe) to ścieżka bezpośrednia rozpoczyna się tam gdzie jest plik exe.

Czcionkę możesz wczytać także z pamięci (loadFromMemory) lub własnego strumienia danych (loadFromStream).

SFML wspiera najczęściej spotykane rozszerzenia czcionek, pełna lista jest dostępna w dokumentacji API.

 

Wyświetlanie tekstu


Do rysowania tekstu używamy klasy sf::Text i jest bardzo prosta w użyciu:

Tekst może także ulegać przekształceniom (transform) posiada pozycję, rotację i skalę. Funkcje są w większości takie same jak dla sf::Sprite i działają na tej samej zasadzie, wszystko jest wyjaśnione w poradniku o Przekształcaniu obiektów.

 

Rozwiązywanie problemów ze znakami nienależącymi do ASCII


Prawidłowe przechwytywanie znaków, które nie należą do ASCII może być dla nas (europejczyków) może być dosyć istotne, ale jest dosyć trudne. Wymaga to dobrej wiedzy kodowania podczas interpretacji danych znaków. Istnieje dosyć proste rozwiązanie: należy skorzystać z wide literal string (wybaczcie, ale nie wiem jak to ładnie przetłumaczyć). Wygląda to w następujący sposób:

Dodając to L przed tekstem dajemy kompilatorowi informację, że chcemy skorzystać z szerokich znaków. Generalnie to rozwiązanie nie pomaga jeżeli chodzi o niektóre znaki, ale w przypadku polskich czcionek daje sobie radę.

 

Tworzenie własnych klas do obsługi tekstu


 Jeżeli sf::Text ma twoim zdaniem zbyt dużo ograniczeń, albo po prostu jest dla ciebie zbyt słabo rozbudowany możesz stworzyć własną klasę obsługującą tekst. Wszystko czego potrzebujesz do stworzenia własnej klasy znajduje się w sf::Font.

Po pierwsze musisz pobrać teksturę, któa zwiera wszystkie pre-renderowane glify o określonym rozmiarze:

Zauważ, że glify są dodawane dopiero podczas gdy jest pobierana tekstura, jest to spowodowane dużą ilością znaków (więcej niż 100000) i dlatego nie są wczytywane podczas wczytywania czcionki, zamiast tego są wczytywane podczas uruchomienia getGlyph (zobacz poniżej).

Aby zrobić coś użytecznego z teksturą glifa musisz pobrać jej koordynaty, które są zawarte w:

character to kod UTF-32 glifa, którego chcesz pobrać. Musisz także podać wielkość znaków oraz to czy znaki mają być pogrubione czy normalne.

sf::Glyph posiada 3 członków:

  • textureRect zawiera współrzędne glifu,
  • bounds ograniczający prostokąt glifa, który pomaga w ustawieniu go w stosunku do linii bazowej tekstu,
  • advance to poziomy offset, który ma na celu pobranie następnego glifa w tekście.

Wreszcie możesz pobrać kilka właściwości twojego tekstu, takie jak wysokość linii czy kerning.

Oryginalny artykuł

Kształty w SFML

W SFML masz dostęp z góry do kilku typów prostych kształtów, a także nic nie stoi na przeszkodzie abyś mógł stworzyć kilka własnych. Tutaj dowiesz się jak obsługiwać potrzebne do tego klasy.

Wspólne właściwości kształtów


Przekształcenia (pozycja, skala, rotacja)

Wszystkie te przekształcenia są dostępne dla każdego z kształtów w SFML i zostały opisane w poradniku Przekształcenia kształtów.

Kolor

Aby zmienić kolor wewnątrz obiektu musisz użyć setFillColor.

Kontur

Możesz ustawić także dowolny kolor konturu figury oraz jego grubość.

 

W przypadku gdy ustawimy grubość konturu > 0 to figura się powiększa o jej grubość np jeżeli mamy koło o promieniu 10 i dodamy kontur o grubości 5 to cała figura będzie miała promień o długości 15. Jeżeli chcesz aby kontur był rysowany wewnątrz figury, czyli żeby figura nie powiększała się ustaw grubość konturu na ujemną.

 

Tekstura

Tak jak sprite’y figury mogą korzystać z tektur:

Kontur nie jest teksturowany, aby odwołać używanie tektury użyj setTexture(NULL).

 

Rysowanie kształtów


Odbywa się ono w tradycyjny sposób dla wszystkich figur.

 

Wbudowane klasy kształtów


Prostokąt

Aby narysować prostokąt musisz użyć sf::RectangleShape. Konstruktor przyjmuje jako argument jego wielkość (szerokość i wysokość).

Koło

Ta figura jest reprezentowana przez sf::CircleShape. Posiada dwa atrybuty: promień oraz opcjonalny który decyduje o tym z ilu punktów się się składa. Decyduje to o „jakości” okręgu. Im większy kształt tym więcej boków będzie potrzebował.

Wielokąty foremne

Wielokąty foremne charakteryzują się tym że powstają na okręgu i ich boki są równej długości, dzięki temu je także robimy za pomocą sf::CircleShape.

Figury wypukłe

Do rysowania figur wypukłych służy klasa sf::ConvexShape. Co musisz pamiętać przy ich rysowaniu to aby rozmieszczać kolejne punkty figury w kierunku zgodnym lub przeciwnym do wskazówek zegara, nie rób tego w sposób losowy.

SFML nie posiada klasy do rysowania figur wklęsłych, ale możesz się w tym celu posłużyć tą klasą o ile pierwszym punktem będzie środek ciężkości figury.

Linie

Linie nie mają swojego odpowiednika w SFML z prostego powodu. Jeżeli twoja linia ma jakąś grubość to jest prostokątem, jeżeli nie może być narysowana za pomocą prymitywów.

Oraz linia-prymityw:

Aby dowiedzieć się więcej o prymitywach zapraszam do poradnika o VertexArray.

 

Tworzenie własnego kształtu-klasy


Możesz poszerzyć typy kształtów o te napisane przez siebie. Aby to zrobić musisz dziedziczyć po sf::Shape i napisać 2 funkcje:

  • getPointCount do pobrania liczby punktów
  • getPoint do pobrania punktu o określonym indexie

Musisz także używać klasy update() do zaktualizowania stanu swojej figury. Poniżej jest gotowy przykład gotowej klasy rysującej elipsę.

Antyaliasing w kształtach


Czyli wygładzenie krawędzi, które możemy ustawić w ustawieniach okna

Nie każda karta graficzna może go obsługiwać, więc efekt nie musi być widoczny na każdym komputerze.

Oryginalny artykuł