Spis treści
Obrazy wyświetlane w naszych grach będziemy przechowywać w obiektach klasy Texture. Jak widzieliśmy w pierwszym programie, wczytywanie tekstury jest bardzo proste. Wystarczy podać ścieżkę do pliku jako argument do konstruktora klasy Texture:
Texture img = new Texture("nazwa_pliku.png");
Obrazy graficzne powinniśmy umieszczać w katalog core/assets naszego programu, ponieważ to od tego katalogu LibGDX będzie rozpoczynał poszukiwania podanego pliku.
Gdy wczytany obraz nie jest nam już potrzebny, powinniśmy powiadomić LibGDX, że związane z nim zasoby mogą zostać zwolnione. W tym celu należy wywołać metodę dispose obiektu typu Texture, np. w metodzie dispose klasy głównej naszego programu:
@Override public void dispose () { // zwalnianie innych obiektow // ... img.dispose(); }
Pozycjonowanie tekstur w oknie¶
Jak widzieliśmy w jednym z poprzednich rozdziałów, początek układu współrzędnych w oknie zaczyna się od jego lewego, dolnego rogu. Innymi słowy, pozycję obrazów, które będziemy chcieli narysować na ekranie, będziemy podawali względem lewego, dolnego rogu okna naszej gry.
Tak samo ustalamy położenie rysowanej tekstury – podane przez nas współrzędne X oraz Y, gdzie tekstura ma zostać narysowana, wyznaczają jej lewy, dolny róg.
Z racji tego, że początek układu współrzędnych to punkt 0, 0, to ostatni punkt widoczny na ekranie ma współrzędne szerokość_okna - 1 oraz wysokość_okna - 1. Musimy odjąć 1, ponieważ początek układu współrzędnych zaczynamy liczyć od punktu 0, 0, a nie 1, 1.
Poniższy zrzut ekranu z przykładu do tego rozdziału obrazuje powyższe zagadnienia (rozmiar okna ustawiłem na 500x400 pikseli w klasie DesktopLauncher):
W oknie 500x400 pikseli rysujemy teksturę o rozmiarze 50x50 pikseli – rozmiary okna oraz tekstury prezentują nawiasy klamrowe { }.
Początek rysowania tekstury ustaliłem na punkt o współrzędnych 200, 100 w metodzie render. Tło ustawiłem na jasnoniebieskie, aby dokładnie widać było wnętrze okna, które stanowi obszar, na którym rysowane przez nas obiekty są widoczne:
@Override public void render () { Gdx.gl.glClearColor(0.741f, 0.874f, 0.976f, 1); Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT); batch.begin(); batch.draw(catImg, 200, 100); batch.end(); }
Jak już wspominałem, wskazany punkt 200, 100, w którym tekstura ma być narysowana, wyznacza jej lewy, dolny róg – zaznaczyłem ten punkt na powyższy rysunku. Z racji tego, że tekstura ma rozmiary 50x50 pikseli, to jej prawy, górny róg znajduje się w lokalizacji:
pozycja_tekstury_x + szerokość_tekstury = 200 + 50 = 250 pozycja_tekstury_y + wysokość_tekstury = 100 + 50 = 150
Ten punkt to 250, 100 – także zaznaczyłem go powyżej.
Oznaczyłem także cztery rogi okna:
- lewy, dolny róg, mający współrzędne 0, 0, to początek układu współrzędnych w naszym oknie,
- prawy, górny róg, ma współrzędne 499, 399 – jest to ostatni widoczny punkt naszego okna o rozmiarze 500x400 pikseli,
- lewy, górny róg, oraz prawy, dolny, róg, mają współrzędne, odpowiednio, 0, 399 oraz 499, 0.
Klasa Texture udostępnia dwie przydatne metody, które zaraz użyjemy: getWidth oraz getHeight, które zwracają szerokość i wysokość wczytanego obrazu graficznego.
Do tego samego przykładu dodałem jeszcze kilka wywołań batch.draw, aby narysować teksturę w każdym z czterech rogów okna, natomiast tekstura rysowana uprzednio w lokalizacji 200, 100 teraz rysowana jest w środku okna. Zauważ, z jakich wzorów skorzystałem, aby obliczyć każdy z punktów, od którego tekstury mają być rysowane, aby znaleźć się w konkretnym miejscu w oknie. Stałe SCREEN_WIDTH i SCREEN_HEIGHT używane poniżej to stałe zdefiniowane przeze mnie – przechowują one rozmiar okna.
@Override public void render () { Gdx.gl.glClearColor(0.741f, 0.874f, 0.976f, 1); Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT); batch.begin(); // obraz w lewym, dolnym rogu batch.draw(catImg, 0, 0); // obraz w prawym, gornym rogu batch.draw( catImg, SCREEN_WIDTH - catImg.getWidth(), SCREEN_HEIGHT - catImg.getHeight() ); // obraz w lewym, gornym rogu batch.draw(catImg, 0, SCREEN_HEIGHT – catImg.getHeight()); // obraz w prawym, dolnym rogu batch.draw(catImg, SCREEN_WIDTH - catImg.getWidth(), 0); // obraz w srodku okna batch.draw( catImg, SCREEN_WIDTH / 2 - catImg.getWidth() / 2, SCREEN_HEIGHT / 2 - catImg.getHeight() / 2 ); batch.end(); }
Okno tego przykładu wygląda teraz następująco:
Aby narysować obraz w konkretnym miejscu, musimy wziąć pod uwagę, że podane współrzędne X oraz Y wyznaczają lewy, dolny róg rysowanej tekstury. Dlatego musimy odejmować szerokość i/lub wysokość tekstury, aby została ona narysowana w całości w odpowiednim miejscu.
W przypadku środka okna musimy od połowy szerokości i wysokości okna odjąć także połowę szerokości i wysokości tekstury – w przeciwnym razie, środek tekstury nie pokrywałby się ze środkiem okna – zamiast tego, byłby tam lewy, dolny wierzchołek tekstury.
Rysowanie fragmentów tekstury za pomocą TextureRegion¶
W grach bardzo często umieszcza się wiele obrazów graficznych w jednym pliku. Dzięki temu nie musimy wczytywać dużej liczby plików, a dodatkowo wyświetlanie obrazów będących fragmentami tej samej tekstury jest wydajniejsze, ponieważ silnik graficzny musi wykonać mniej zmian aktualnie wykorzystywanej tekstury.
Aby wskazać, który fragment tekstury powinien zostać narysowany, możemy skorzystać z obiektu klasy TextureRegion. Klasa ta przechowuje początek fragmentu obrazu w skojarzonym z nim obiekcie klasy Texture, oraz szerokość i wysokość tego fragmentu. Możemy następnie przekazać obiekt tej klasy do metody batch.draw, co spowoduje narysowanie nie całej tekstury, lecz opisanego za pomocą obiektu klasy TextureRegion fragmentu.
Spójrzmy na przykładowy obrazek, który zawiera cztery obrazy o rozmiarze 50x50 pikseli (więc cała tekstura ma rozmiar 100x100 pikseli):
Niestety, w przypadku ustalania pozycji fragmentu tekstury za pomocą klasy TextureRegion, początek tekstury znajduje się w lewym, górnym rogu, a nie w lewym, dolnym rogu, jak to ma miejsce przy ustalaniu pozycji rysowanego obrazu w oknie gry. Oznacza to, że utworzenie następującego obiektu TextureRegion spowoduje opisanie fragmentu zawierającego gwiazdkę, a nie romb:
TextureRegion star = new TextureRegion(multiTexture, 0, 0, 50, 50);
Pierwszy argument konstruktora klasy TextureRegion to obiekt klasy Texture, którego fragment chcemy opisać za pomocą kolejnych czterech argumentów. Są nimi pozycja X i Y lewego, górnego wierzchołka fragmentu tekstury oraz szerokość i wysokość tego fragmentu.
Powyższy obiekt star opisuje fragment zaczynający się w lewym, górnym rogu tekstury przechowywanej w obiekcie multiTexture. Fragment ten ma mieć 50 pikseli szerokości i 50 pikseli wysokości.
Spójrzmy na przykład rysowania tego regionu tekstury:
package com.kursjava.gamedev; import com.badlogic.gdx.ApplicationAdapter; import com.badlogic.gdx.Gdx; import com.badlogic.gdx.graphics.GL20; import com.badlogic.gdx.graphics.Texture; import com.badlogic.gdx.graphics.g2d.SpriteBatch; import com.badlogic.gdx.graphics.g2d.TextureRegion; public class TextureRegionExample extends ApplicationAdapter { private SpriteBatch batch; private Texture multiTexture; private TextureRegion star; @Override public void create () { batch = new SpriteBatch(); multiTexture = new Texture("kilka_obrazow.png"); star = new TextureRegion(multiTexture, 0, 0, 50, 50); } @Override public void render () { Gdx.gl.glClearColor(1, 1, 1, 1); Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT); batch.begin(); // cala tekstura batch.draw(multiTexture, 0, 0); // fragment tekstury batch.draw(star, 225, 200); batch.end(); } @Override public void dispose () { batch.dispose(); multiTexture.dispose(); } }
Zauważ, że na obiektach klasy TextureRegion nie musisz wywoływać metody dispose. Dodatkowo, aby używać klasy TextureRegion, musisz ją zaimportować:
import com.badlogic.gdx.graphics.g2d.TextureRegion;
W metodzie create utworzyłem obiekt klasy TextureRegion, który następnie przekazuję do metody batch.draw, aby narysować fragment tekstury przez ten obiekt opisywany w pozycji 225, 200 w oknie programu:
// fragment tekstury batch.draw(star, 225, 200);
Wynik działania tego programu:
Możemy w ten sposób wyznaczyć regiony pozostałych obrazów:
star = new TextureRegion(multiTexture, 0, 0, 50, 50); lightning = new TextureRegion(multiTexture, 50, 0, 50, 50); diamond = new TextureRegion(multiTexture, 0, 50, 50, 50); triangle = new TextureRegion(multiTexture, 50, 50, 50, 50);
Dwa ostatnie parametry pozostają takie same, ponieważ rozmiar każdego regionu jest taki sam i wynosi 50x50 pikseli. Zmieniają się tylko wartości początkowe regionu X i Y:
Narysujemy teraz powyższe regiony, jeden pod drugim:
// fragment tekstury batch.draw(star, 225, 200); batch.draw(lightning, 225, 150); batch.draw(diamond, 225, 100); batch.draw(triangle, 225, 50);
Wynik na ekranie:
Za pomocą obiektów typu TextureRegion i klasy Animation, którą poznamy wkrótce, będziemy mogli w prosty sposób tworzyć animacje w naszych grach.