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ł

Sprite’y i tekstury

Na początku lekcji warto sobie uzmysłowić czym jest tekstura oraz czym jest sprite. Tekstura to jakiś obraz (np wczytany z pliku), a nazywamy go teksturą (Texture), a nie obrazem (Image) ponieważ my go będziemy mapowali do obiektów 2D. Sprite to nic innego jak „teksturowany” kwadrat.

W SFML do przechowywania tekstur służy sf::Texture, a do sprite’ów sf::Sprite.

 

Wczytywanie tekstur


Możemy wczytywać tekstury na 3 sposoby: z pliku (loadFromFile), z pamięci (loadFromMemory) oraz z jakiegoś napisanego przez nas strumienia danych (loadFromStream). Jednak my podczas tego tutoriala zajmiemy się najbardziej popularnym wczytywaniem z pliku.

W przypadku gdy nie uda nam się wczytać pliku (np. jest nieprawidłowe rozszerzenie lub zła nazwa pliku) funkcja zwraca false. Listę wspieranych przez SFML formatów obrazów znajdziesz w dokumentacji.

Wszystkie funkcje do wczytywania posidają także opcjonalny argument, który pozwala nam wczytać fragment tekstury.

sf::IntRect to klasa, która w matematyczny sposób reprezentuje kwadrat. Pierwsze 2 argumenty to pozycja na której się zaczyna, kolejne dwa to szerokość i wysokość.

 

Jeżeli nie chcesz wczytywać tekstury, a raczej wolisz wypełnić ją bezpośrednio z jakiejś tablicy pikseli możesz to zrobić. Na początku musisz stworzyć teksturę, którą możesz wypełnić później.

Pamiętaj, że teraz zawartość tekstury jest niezdefiniowana. Aby ją uzupełnić możesz to zrobić na kilka sposobów:

Te sposoby przedstawiają sposób zaktualizowania całej tektury, możesz także zaktualizować fragment, ale o tym informacje znajdziesz w dokumentacji.

Istnieją 2 właściwości, które decydują o tym w jaki sposób będzie renderowana tekstura.

Pierwszą z nich jest wygładzenie (smooth), dzięki któremu piksele są mniej widoczne. Można to włączyć używając:

Druga właściwość to powtarzanie, które działa tylko wtedy gdy obrazek który ma być wyświetlany jest większy niż tekstura, czyli np wyświetlany kwadrat o wymiarach 100×100, a nasza tekstura ma wymiary 25×25, wtedy może zostać powtórzona.

Sprite’y


Skopiowanie tekstury do sprite’a jest proste. Możemy podać teksturę w konstruktorze sprite’a lub poprzez użycie metody:

sf::Sprite sprite(texture);
sprite.setTexture(texture);

Rysowanie wygląda w ten sposób:

Możesz wczytać także fragment tekstury:

Możesz także zmieniać kolor sprite’a, ewentualnie możesz też tego użyć do zmiany przezroczystości.

Sprite’y poniżej mają tę samą teksturę, lecz różne kolory:

Sprite’y możesz także przekształcać:

Standardowo przekształcenia pokazane powyżej są dokonywane względem lewego górnego rogu, jeżeli chcesz je wykonywać względem innego punktu np środa sprite’a musisz skorzystać z:

 

Problem białego kwadratu


Jeżeli ci się zdarzyło, że mimo wczytania tekstury bezproblemowo i ustawienia jej sprite’owi, po wyświetleniu masz biały kwadrat to ten paragraf poradnika jest skierowany dla ciebie.

Jest to bardzo często spotykany błąd, problem leży w tym, że gdzieś w kodzie przestał istnieć wskaźnik do twojej tekstury, bo sprite nie kopiuje jej lecz pobiera wskaźnik do niej. W momencie gdy tekstura jest niszczona wskaźnik wskazuje na puste bity w pamięci. Prawdopodobnie twoja funkcja wygląda mniej więcej tak:

Musisz zawsze posiadać swoją teksturę w pamięci inaczej ten błąd będzie się powtarzał.

 

Używaj jak najmniej tekstur


Staraj się używać jak najmniejszej liczby tekstur jak to możliwe, pamiętaj że zmiana tekstury to ciężka operacja dla karty graficznej. Rysowanie wielu sprite’ów używających tej samej tekstury przynosi lepsze efekty. Dlatego lepiej jest korzytsać np. z tzw. tilesets (atlasów tekstur). Jest to zdecydowanie wydajniejsze.

Oryginalny artykuł

Rysowanie obiektów 2D

Z tego poradnika dowiesz się jakie obiekty można narysować obiekty, a także jak napisać własne klasy, które będziesz mógł narysować.

 

Rysowanie


Aby narysować obiekty w łatwy sposób potrzebujesz do tego specjalnej klasy sf::RenderWindow, która dziedziczy po sf::Window, co oznacza, że wszystko co się nauczyłeś jest wciąż aktualne i wygląda w ten sam sposób.

RenderWindow zawiera bardziej zaawansowane funkcje, które pozwalają nam manipulować oknem. Dzisiaj zajmiemy się 2 z nich: clear i draw. Robią dokładnie to jak się nazywają: clear czyści ekran, draw zajmuje się rysowaniem obiektów.

Wywoływanie metody clear jest ważne i musi odbyć się przed rysowaniem wszelkich obiektów, jeżeli jej nie wywołasz to mogą pozostać piksele z poprzedniego rysowania obiektów. Jeżeli byś poruszał wtedy postacią moglibyśmy uzyskać taki efekt:

(Win 7,8,8.1 straciły ten feature, ale na szczęście Win10 go odzyskał ;] )

Wywołanie display także jest konieczne to prawidłowego działania, ponieważ podczas wywoływania draw wszystkie obiekty są kopiowane do bufora (o którym porozmawiamy za chwilę), a wywołanie display powoduje uwolnienie tych obiektów z bufora i wyświetlenie ich na ekranie.

Powinno się wywoływać te metody wg kolejności:

  • clear – do czyszczenia ekranu
  • draw – do rysowania obiektów
  • display – do wyświetlenia obiektów

 

Jakie obiekty można rysować?


W SFML jest przygotowanych kilka typów obiektów, sprite’y (rysowanie obrazków), tekst (wiadomo) oraz kształty. Niektóre kształty są przygotowane, a także mamy klasę, która pozwala nam rysować własne wielokąty (VertexArray).

 

Rysowanie własnej klasy


Generalnie możemy narysować każdy obiekt, który dziedziczy po sf::Drawable. Zrobienie tego nie jest trudne, pokaże to na hipotetycznej klasie, w której mamy Sprite’a, który reprezentuje naszą postać w grze oraz kilka cech typowych dla gier:

To co musi mieć klasa dziedziczona po Drawable to metoda prywatna draw, tylko wtedy możemy wywołać narysowanie naszego obiektu poprzez window.draw(nasz_obiekt). Możesz powiedzieć, że to jest mało przydatne przecież można w tym wypadku podać sprite’a naszej klasy poprzez postac.sprite i narysować go bez bawienia się w dziedziczenie. Masz rację można, tylko że nie zawsze podajemy naszą teksturę jako publiczną oraz nie zawsze nasza postać składa się z jednego elementu. Załóżmy, że chcemy mieć super zaawansowaną kolizję, która będzie obejmowała trafienie w nogi, ręce, głowę i tors, które zrobimy z odpowiednio, linii, linii, koła i prostokąta:

Schematyczna postać składająca się z różnych elementów

A nasz kod rysujący by wyglądał tak:

Wtedy dziedziczenie nabiera sensu bo te wszystkie linie wywołasz poprzesz window.draw(postac), a gdy cię najdzie ochota na stworzenie kolejnej takiej postaci po prostu wywołasz window.draw(postac_2), a  nie kolejną masę tych samych linii. To tyle jeżeli chodzi o mój wywód „dlaczego warto dziedziczyć klasę Drawable”.

 

Rysowanie off-screen


Zanim przejdziemy dalej, to może uświadomimy sobie czym właściwie jest rysowanie off-screen (niestety polskiego określenia na to nie kojarzę). Jest to skopiowanie do pamięci następnej wyświetlonej klatki, tzn podczas rysowania obecnej klatki już w chwili obecnej jest tworzona kopia obrazu do klatki następnej. Najlepiej to prezentuje obrazek poniżej:

 

Przedstawiony tutaj sposób prezentuje narysowanie elementu na texture zamiast bezpośrednio do okna. Używamy tutaj sf::RenderTexture zamiast sf::RenderWindow.

 

Rysowanie z różnych wątków


Jeżeli korzystasz z wielu wątków, powinieneś wiedzieć jak napisać poprawnie wątek rysujący wszystkie elementy. Warto zapamiętać, że powinieneś zdezaktywować okno zanim zaczniesz je używać w innych wątkach, ponieważ okno nie może być aktywne w wieli wątkach jednocześnie.

Jak widać nie musisz włączać okna w wątku renderującym, SFML robi to za ciebie. Przypominam o tym, że najbezpieczniej jest tworzenie okna i przechwytywania eventów w głównym wątku.

Oryginalny artykuł