LibGDX postRunnable liides¶
Kuidas seda kasutada ja miks see on vajalik. Näited KryoNeti kliendi põhjal.¶
Selles projektipõhises kursuses peate looma mitmikmängu, kasutades LibGDX mängumootorit ja Java programmeerimiskeelt. Kuna projekt on võrgupõhine, tekitab see kliendi (teie mängu) ja serveri vahelisel suhtlusel mõningaid huvitavaid tehnilisi olukordi.
Javas (nagu ka paljudes teistes keeltes) on kasutusel termin "Lõim" (Thread). Võite mõelda sellest kui ühest konkreetsest tegevuste jadast. Näiteks üks lõim teeb taustal keerulisi matemaatilisi arvutusi, samal ajal kui teine lõim tegeleb ekraanile pildi joonistamisega.
See on meie jaoks kriitilise tähtsusega, sest LibGDX ja KryoNet jooksevad eraldi lõimedel.
Probleem ja lahendus¶
Probleem seisneb selles, et LibGDX kasutab graafika joonistamiseks OpenGL-i. OpenGL-i kontekst on aktiivne ja turvaliselt ligipääsetav ainult pealõimes (Main thread / Rendering thread). KryoNeti võrgukuulaja (Listener) võtab aga sõnumeid vastu omaenda taustalõimes.
Kui proovite muuta LibGDX-i graafilist objekti (näiteks liigutada karakterit) otse KryoNeti kuulaja sees, proovite te ligi pääseda OpenGL-ile valest lõimest. Tulemuseks on tavaliselt veateade (Exception) ja mängu kokkujooksmine.
Lahendus on Gdx.app.postRunnable. See meetod võimaldab teil saata koodijupi ("Runnable") pealõime ootejärjekorda.
LibGDX käivitab selle koodi turvaliselt järgmise kaadri joonistamise ajal.
Näide: EnemyListener¶
Vaatame allolevat koodinäidet. Oleme loonud klassi EnemyListener, mis laiendab KryoNeti baasklassi Listener.
Sellel klassil on privaatne muutuja enemy (tüüp Character), mis on meie enda loodud klass ja baseerub LibGDX-il.
Iga KryoNeti kuulaja nõuab meetodi received ülekirjutamist (@Override). Selles näites ootab kuulaja kahte tüüpi sõnumeid: EnemySpawnedMessage ja EnemyMovedMessage.
Sõltuvalt sellest, milline sõnum saabub, peame muutma LibGDX-i objekti.
Siin tulebki appi Gdx.app.postRunnable. Kasutades seda, saame käivitada koodi LibGDX-i pealõimes ja turvaliselt muuta objekti asukohta.
public class EnemyListener extends Listener {
private Character enemy;
public EnemyListener(Character enemy) {
this.enemy = enemy;
}
@Override
public void received(Connection connection, Object object) {
if (object instanceof EnemySpawnedMessage) {
final EnemySpawnedMessage enemySpawnedMessage = (EnemySpawnedMessage) object;
// Käivitame koodi LibGDX pealõimes (UI thread)
Gdx.app.postRunnable(new Runnable() {
public void run() {
enemy.moveToNewPosition(enemySpawnedMessage.getX(), enemySpawnedMessage.getY());
System.out.println("ENEMY SPAWNED!");
}
});
} else if (object instanceof EnemyMovedMessage) {
final EnemyMovedMessage enemyMovedMessage = (EnemyMovedMessage) object;
Gdx.app.postRunnable(new Runnable() {
public void run() {
// Kontrollime, kas koordinaadid on tegelikult muutunud
if (enemyMovedMessage.getX() != enemy.getBounds().x
|| enemyMovedMessage.getY() != enemy.getBounds().y) {
enemy.moveToNewPosition(enemyMovedMessage.getX(), enemyMovedMessage.getY());
}
}
});
}
}
}
Pange tähele, et see on vaid üks viis kliendi ja serveri vahelise suhtluse haldamiseks. Selles näites muudetakse LibGDX-i objekti otse kuulaja sees (läbi postRunnable), mis on lihtsamate olukordade puhul väga mugav.
NB! Sarnane loogika kehtib ka Netty raamistiku puhul.
Näide: MovementUpdateListener¶
Selles näites uuendame peategelase asukohta serverist saadud info põhjal. See on vajalik näiteks positsiooni sünkroniseerimiseks.
/**
* MovementUpdateListener constructor. Kuulab liikumisega seotud sõnumeid
* ja uuendab mängu vastavalt.
*/
public class MovementUpdateListener extends Listener {
private Character character;
private HashMap<String, Character> otherPlayers;
private OrthographicCamera camera;
public MovementUpdateListener(Character character, HashMap<String, Character> otherPlayers, OrthographicCamera camera) {
this.character = character;
this.otherPlayers = otherPlayers;
this.camera = camera;
}
@Override
public void received(Connection connection, Object object) {
if (object instanceof MoveUpdateEvent) {
/*
* Saime serverist liikumise uuenduse.
* Uuendame kohaliku mängija positsiooni vastavalt serveri andmetele.
*/
final MoveUpdateEvent moveUpdateEvent = (MoveUpdateEvent) object;
// Salvestame debugimiseks vanad koordinaadid
character.oldX = character.getBounds().x;
character.oldY = character.getBounds().y;
// Eeldame, et server saadab uue absoluutse asukoha
character.lastMoveX = moveUpdateEvent.getNewX();
character.lastMoveY = moveUpdateEvent.getNewY();
Gdx.app.postRunnable(new Runnable() {
public void run() {
character.moveDirection = moveUpdateEvent.getMoveDirection();
// Uuendame karakteri asukohta
character.moveToNewPosition(moveUpdateEvent.getNewX(), moveUpdateEvent.getNewY());
// Uuendame tekstuuri vastavalt suunale
character.setTextureBasedOnMovedDirection();
// Liigutame kaamerat koos karakteriga
camera.position.set(character.getBounds().x, character.getBounds().y, 0);
}
});
}
}
}
Teine viis loogika ülesehitamiseks on kasutada stiili, mis sarnaneb olekumasinatele (State Machines).
Selle asemel, et muuta graafikaobjekte otse võrgukuulajas, uuendate seal vaid mängu olekut (näiteks salvestate uued koordinaadid muutujatesse). Mängu peatsüklis (mis jookseb juba pealõimes) kontrollite, kas olek on muutunud. Kui on, uuendate objekte vastavalt.