Tworzenie gier w Javie – Obsługa myszy i klawiatury

W tym rozdziale dowiemy się jak reagować na naciskane przez użytkownika klawisze na klawiaturze oraz na ruch myszki i naciskanie jej przycisków. Zobaczymy także, jak ma się położenie kursora myszy na ekranie do współrzędnych obiektów.

Sprawdzanie naciśniętych klawiszy na klawiaturze

Aby sprawdzić, czy któryś z klawiszy na klawiaturze jest naciśnięty, korzystamy z metody isKeyPressed pola input klasy Gdx. Jeżeli metoda zwróci true, będzie to oznaczać, że klawisz jest w tym momencie naciśnięty.

Obiekt input to obiekt klasy implementującej interfejs Input. Obiekt input jest tworzony dla nas automatycznie przez LibGDX podczas uruchamiania naszego programu, więc nie musimy sami zajmować się tworzeniem tego obiektu.

Dla przykładu, w następujący sposób można sprawdzić, czy użytkownik nacisnął lewą strzałkę na klawiaturze:

if (Gdx.input.isKeyPressed(Input.Keys.LEFT)) {
  // obsluga nacisniecia strzalki w lewo
}

Jako argument do metody isKeyPressed przekazujemy kod znaku, który chcemy sprawdzić. Nie musimy na szczęście znać kodów liczbowych wszystkich klawiszy – interfejs Input zawiera zagnieżdżoną klasę o nazwie Keys. Klasa ta zawiera statyczne stałe odpowiadające odpowiednim klawiszom na klawiaturze. Aby móc odnieść się do klasy Input.Keys, należy dodać następujący import:

import com.badlogic.gdx.Input;

Przykłady kilku stałych z klasy Keys:

  • LEFT, RIGHT, UP, DOWN – strzałki,
  • A – litera a,
  • SHIFT_LEFT, SHIFT_RIGHT – lewy i prawy Shift,
  • ENTEREnter,
  • CONTROL_LEFT, CONTROL_RIGHT – lewy i prawy Ctrl,
  • SPACESpacja.
Możesz zajrzeć do klasy Keys, aby sprawdzić, jakie stałe odpowiadają pozostałym klawiszom. Jeżeli korzystasz z IntelliJ IDEA, możesz nacisnąć lewy klawisz Ctrl i kliknąć lewym przyciskiem myszy na nazwę klasy Keys – IntelliJ IDEA przeniesie Cię do kodu klasy Keys, gdzie znajdziesz wszystkie pola statyczne odpowiadające klawiszom na klawiaturze. Możesz też zajrzeć do oficjalnej dokumentacji klasy Input.Keys.

Obiekt input oferuje jeszcze jedną metodę do sprawdzania naciśniętych klawiszy – jest to metoda isKeyJustPressed.

Różnica pomiędzy metodą isKeyJustPressed oraz metodą isKeyPressed jest taka, że ta pierwsza metoda zwróci wartość true (tzn. klawisz naciśnięty) tylko w przypadku, gdy poprzednio nie był on naciśnięty. Jeżeli klawisz nie zostanie zwolniony, po ponownym wywołaniu tej metody zwróci ona wartość false. Dopiero po puszczeniu klawisza i ponownym jego naciśnięciu ta metoda zwróci true. Wcześniej poznana metoda isKeyPressed zwraca true cały czas dopóki klawisz jest naciśnięty.

Dla przykładu, jeżeli klawisz Enter nie jest naciśnięty, obie metody zwrócą false. Gdy użytkownik wciśnie klawisz Enter, obie metody zwrócą true. Jeżeli użytkownik będzie nadal trzymał wciśnięty klawisz Enter, to metoda isKeyJustPressed nie zwróci już wartości true, ale metoda isKeyPressed tak.

Kiedy stosować każdą z tych metod?

  • Jeżeli sprawdzasz, czy np. powinieneś przesunąć postać (np. po naciśnięciu strzałki), to postać powinna poruszać się dopóki użytkownik trzyma wciśnięty klawisz – w tym przypadku skorzystasz z isKeyPressed.
  • Jeżeli chcesz sprawdzić, czy użytkownik nacisnął klawisz i wykonać jednorazową akcję, to skorzystaj z metody isKeyJustPressed. Przykładem może być tutaj naciśnięcie klawisza, który pokazuje menu, pauzuje grę lub restartuje poziom gry. Każda z tych akcji powinna być wykonana jednorazowo na każde naciśnięcie klawisza. Jeżeli w Twojej grze można np. zapauzować i odpauzować grę klawiszem Spacja, to użycie isKeyPressed zamiast isKeyJustPressed powodowałoby pauzowanie i odpauzowywanie gry w kółko, dopóki użytkownik trzymałby wciśnięty klawisz Spacja.

Czas na praktykę – poniższy program znajdziesz w katalogu rozdzial-07\obsluga-klawiatury. W tym przykładzie przesuwamy po ekranie obraz za pomocą klawiszy strzałek. Korzystamy z ruchu niezależnego od FPS, poznanego w jednym z poprzednim rozdziałów. Kod klasy głównej jest następujący:

rozdzial-07\obsluga-klawiatury\core\src\com\kursjava\gamedev\HandlingKeyboardExample.java
package com.kursjava.gamedev;

import com.badlogic.gdx.ApplicationAdapter;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.Input;
import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;

public class HandlingKeyboardExample extends ApplicationAdapter {
  private SpriteBatch batch;
  private Texture cat;

  private static final int CAT_SPEED = 200;
  private float x, y;

  @Override
  public void create () {
    batch = new SpriteBatch();
    cat = new Texture("cat.png");
  }

  @Override
  public void render () {
    float deltaTime = Gdx.graphics.getDeltaTime();

    if (Gdx.input.isKeyPressed(Input.Keys.RIGHT)) {
      x += CAT_SPEED * deltaTime;
    } else if (Gdx.input.isKeyPressed(Input.Keys.LEFT)) {
      x -= CAT_SPEED * deltaTime;
    }

    if (Gdx.input.isKeyPressed(Input.Keys.UP)) {
      y += CAT_SPEED * deltaTime;
    } else if (Gdx.input.isKeyPressed(Input.Keys.DOWN)) {
      y -= CAT_SPEED * deltaTime;
    }

    Gdx.gl.glClearColor(1, 1, 1, 1);
    Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);

    batch.begin();
    batch.draw(cat, x, y);
    batch.end();
  }

  @Override
  public void dispose () {
    batch.dispose();
    cat.dispose();
  }
}

W metodzie render znajduje się poniższy kod, który sprawdza, czy klawisze strzałek są naciśnięte i przesuwa współrzędne tekstury w odpowiednią stronę:

if (Gdx.input.isKeyPressed(Input.Keys.RIGHT)) {
  x += CAT_SPEED * deltaTime;
} else if (Gdx.input.isKeyPressed(Input.Keys.LEFT)) {
  x -= CAT_SPEED * deltaTime;
}

if (Gdx.input.isKeyPressed(Input.Keys.UP)) {
  y += CAT_SPEED * deltaTime;
} else if (Gdx.input.isKeyPressed(Input.Keys.DOWN)) {
  y -= CAT_SPEED * deltaTime;
}

Strzałki w lewo i prawo zmieniają wartość x, a strzałki w górę i dół – wartość y. W wyniku działania tego programu możemy przesuwać obraz wyświetlany na ekranie:

Obsługa klawiatury: poruszanie teksturą
Obsługa klawiatury: poruszanie teksturą

Obsługa pozycji i przycisków myszki

Aby sprawdzić, czy przycisk myszki jest wciśnięty, mamy do dyspozycji dwie metody (podobnie jak ze sprawdzaniem naciśnięcia klawiszy na klawiaturze):

  • Gdx.input.isButtonPressed – zwraca true, jeżeli podany jako argument przycisk myszy jest w tej chwili naciśnięty,
  • Gdx.input.isButtonJustPressed – zwraca true tylko wtedy, gdy podany jako argument przycisk myszy został właśnie naciśnięty, tzn. nie był naciśnięty poprzednio. Różnica między tą metodą a metodą isButtonPressed jest taka sama, jak różnica pomiędzy isKeyPressed i isKeyJustPressed z rozdziału o sprawdzaniu klawiszy naciśniętych na klawiaturze.

Obie metody przyjmują liczbę, która reprezentuje przycisk myszy, który chcemy sprawdzić. Klasa Input.Buttons zawiera statyczne stałe, które powinniśmy stosować jako argumenty do powyższych metod. Input.Buttons zawiera pola dla każdego z trzech przycisków myszy:

  • LEFT – lewy przycisk myszy,
  • RIGHT – prawy przycisk myszy,
  • MIDDLE – środkowy przycisk myszy.

Dla przykładu, poniższy kod sprawdza, czy lewy przycisk myszy jest w tej chwili wciśnięty:

if (Gdx.input.isButtonPressed(Input.Buttons.LEFT)) {
  // obsluga wcisniecia lewego przycisku myszy
}

Aby sprawdzić położenie kursora myszy w oknie gry, stosujemy metody Gdx.input.getX oraz Gdx.input.getY. Pierwsza zwraca współrzędną X kursora, a druga – współrzędną Y.

Jest tylko jedno ale: współrzędne kursora myszy w oknie zaczynają się w lewym, górnym rogu okna, podczas gdy współrzędne obiektów świata gry mają swój początek współrzędnych w lewym, dolnym rogu.

Oznacza to, że współrzędna Y kursora rośnie, gdy kursor zbliża się do dolnej krawędzi okna. Z kolei współrzędna Y obiektów gry rośnie, gdy zbliżają się one do górnej krawędzi okna. Spójrz na poniższy obrazek:

Położenie kursora myszy względem okna
Położenie kursora myszy względem okna

Zauważ, że zarówno kursor myszy, jak i wyświetlana tekstura, mają takie same współrzędne. Kursor myszy znajduje się w punkcie 100, 50. Tekstura także znajduje się w punkcie 100, 50, jednak, jak widzimy, kursor i tekstura są w innych miejscach na ekranie, pomimo takich samych współrzędnych. Powodem jest to, że mają one początek układu współrzędnych w innych miejscach.

Ta rozbieżność powoduje, że musimy położenie kursora myszy „mapować” na położenie obiektów świata gry – nie jest to trudne, ale musimy o tym pamiętać. Aby to zrobić, wystarczy od wysokości, okna pomniejszonej o 1, odjąć współrzędną Y kursora myszy zwróconą przez metodę Gdx.input.getY.

Dla przykładu, powyższe okno gry ma wysokość 300 pikseli. Jeżeli od 300 odejmiemy 1 i pozycję Y kursora myszy zwracaną przez metodę Gdx.input.getY, to otrzymamy 300 - 1 - 50 = 249. Ta współrzędna Y jest teraz zgodna z początkiem układu współrzędnych w lewym, dolnym rogu okna, czyli układu współrzędnych wszystkich obiektów naszej gry.

Musimy odjąć dodatkowo liczbę 1 od wysokości okna, ponieważ układ współrzędnych zaczyna się od 0. Dla przykładu, jeżeli kursor myszki byłby na samym dole okna, to jego współrzędna Y wynosiłaby 299 (przed zmapowaniem), bo jak wiemy z rozdziału o rysowaniu tekstur, ostatni widoczny punkt okna ma współrzędne o 1 mniejsze od szerokości i wysokości okna. Powodem jest to, że układ współrzędnych rozpoczyna się w punkcie 0, 0, a nie punkcie 1, 1. W tym przypadku, gdybyśmy nie odjęli 1, to po zmapowaniu współrzędna Y kursora myszki wynosiłaby 300 - 299 = 1, zamiast 0.

Poniższy przykład używa poznanych w tym rozdziale zagadnień do przesuwania tekstury wraz z ruchem kursora myszki. Aby rozpocząć przesuwanie, użytkownik musi kliknąć lewym przyciskiem myszy na teksturę. Aby przerwać przesuwanie tekstury, należ kliknąć prawym przyciskiem myszy:

rozdzial-07\obsluga-myszki\core\src\com\kursjava\gamedev\HandlingMouseExample.java
package com.kursjava.gamedev;

import com.badlogic.gdx.ApplicationAdapter;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.Input;
import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;

public class HandlingMouseExample extends ApplicationAdapter {
  private SpriteBatch batch;
  private Texture cat;

  private static final int SCREEN_HEIGHT = 300;
  private int catX, catY;
  private boolean isCatMoving ;
  private float lastMouseX, lastMouseY;

  @Override
  public void create () {
    batch = new SpriteBatch();
    cat = new Texture("cat.png");
  }

  @Override
  public void render () {
    int currentMouseX = Gdx.input.getX();
    int currentMappedMouseY = SCREEN_HEIGHT - 1 – Gdx.input.getY();

    if (isCatMoving) {
      catX += currentMouseX - lastMouseX;
      catY += currentMappedMouseY – lastMouseY;

      lastMouseX = currentMouseX;
      lastMouseY = currentMappedMouseY;
    }

    if (Gdx.input.isButtonJustPressed(Input.Buttons.LEFT)) {
      if (currentMouseX >= catX &&
          currentMouseX < catX + cat.getWidth() &&
          currentMappedMouseY >= catY &&
          currentMappedMouseY < catY + cat.getHeight()) {
        lastMouseX = currentMouseX;
        lastMouseY = currentMappedMouseY;
        isCatMoving = true;
      }
    } else if (Gdx.input.isButtonJustPressed(Input.Buttons.RIGHT)) {
      isCatMoving = false;
    }

    Gdx.gl.glClearColor(1, 1, 1, 1);
    Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);

    batch.begin();
    batch.draw(cat, catX, catY);
    batch.end();
  }

  @Override
  public void dispose () {
    batch.dispose();
    cat.dispose();
  }
}

Na początku metody render zapisujemy w zmiennych pomocniczych currentMouseX oraz currentMappedMouseY aktualną pozycję kursora myszy. Współrzędną Y mapujemy poznanym w tym rozdziale wzorem, aby była zgodna z pozycją obiektów w oknie gry:

int currentMouseX = Gdx.input.getX();
int currentMappedMouseY = SCREEN_HEIGHT - 1 – Gdx.input.getY();

Przesuwanie współrzędnych tekstury uzależniamy od wartości zmiennej isCatMoving. Wartość tej zmiennej ustawiamy na true, jeżeli użytkownik kliknie lewym przyciskiem myszy w obszar tekstury:

if (Gdx.input.isButtonJustPressed(Input.Buttons.LEFT)) {
  if (currentMouseX >= catX &&
      currentMouseX < catX + cat.getWidth() &&
      currentMappedMouseY >= catY &&
      currentMappedMouseY < catY + cat.getHeight()) {
    lastMouseX = currentMouseX;
    lastMouseY = currentMappedMouseY;
    isCatMoving = true;
  }
} else if (Gdx.input.isButtonJustPressed(Input.Buttons.RIGHT)) {
  isCatMoving = false;
}

Aby sprawdzić, czy kursor myszy jest w obrębie tekstury, sprawdzamy, czy współrzędne kursora zawierają się w obrębie prostokąta wyznaczanego przez lewy, dolny i prawy, górny wierzchołek tekstury. Wierzchołki te mają współrzędne, odpowiednio: catX i catY oraz catX + szerokość_tekstury i catY + wysokość_tekstury. Jeżeli kursor znajduje się w obrębie tekstury, ustawiamy isCatMoving na true, a także zapamiętujemy aktualną pozycję kursora myszy w polach lastMouseX oraz lastMouseY. Te wartości będziemy używać, aby wyznaczyć, o ile kursor myszy się przesunął i o tyle samo przesuniemy teksturę na ekranie. Jeżeli użytkownik naciśnie prawy przycisk myszki, ustawiamy isCatMoving na false, aby przerwać przesuwanie tekstury.

Zmiana pozycji tekstury odbywa się w następującym fragmencie kodu:

if (isCatMoving) {
  catX += currentMouseX - lastMouseX;
  catY += currentMappedMouseY – lastMouseY;

  lastMouseX = currentMouseX;
  lastMouseY = currentMappedMouseY;
}

Zmieniamy położenie tekstury (catX oraz catY) o wartość równą różnicy aktualnego położenia kursora myszy i poprzedniego położenia. Następnie, aktualną pozycję kursora myszy zapamiętujemy w zmiennych lastMouseX i lastMouseY, aby następnym razem znów móc porównać pozycję myszki do poprzedniej pozycji.

Wszystko, czego się do tej pory nauczyliśmy, wystarczy, aby napisać naszą pierwszą grę. Do dzieła!

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.