Tworzenie gier w Javie – Wyświetlanie tekstur

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):

Współrzędne obiektów w oknie
Układ współrzędnych i pozycja obiektów świata gry w oknie

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.
Jeszcze raz wspomnę: końcowe punkty naszego okna mają współrzędne X i Y mniejsze o 1 od szerokości i wysokości okna, ponieważ układ współrzędnych zaczynamy od punktu 0, 0, a nie 1, 1.

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.

rozdzial-05\wyswietlanie-tekstur\core\src\com\kursjava\gamedev\DrawingTexturesExample.java
@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:

Rysowanie tekstury w środku i rogach okna
Tekstura rysowana w rogach i środku okna

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):

Kilka obrazów w jednej teksturze
Tekstura zawierająca cztery obrazy

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:

rozdzial-05\uzywanie-texture-region\core\src\com\kursjava\gamedev\TextureRegionExample.java
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:

Wyświetlanie regionu tekstury
Region tekstury wyświetlony jako pojedynczy obraz

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:

Współrzędne regionów tekstury
Współrzędne wierzchołków wszystkich czterech regionów tekstury

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:

Wyświetlanie wszystkich regionów tekstury
Wszystkie regiony tekstury narysowane na środku okna

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.

Klasa TextureRegion ma kilka przydatnych metod, np. getRegionWidth oraz getRegionHeight, które zwracają szerokość i wysokość regionu opisywanego przez obiekt tej klasy. Są to te same wartości, które przekazujemy do konstruktora klasy TextureRegion. Skorzystamy z tych metod, gdy będziemy pisali naszą pierwszą grę w rozdziale ósmym.

Dodaj komentarz

Twój adres email nie zostanie opublikowany. Nie musisz podawać swojego imienia, e-mailu, ani strony www, aby opublikować komentarz. Komentarze muszą zostać zatwierdzone, aby były widoczne na stronie.