Multiplayer mängu loomiseks LibGDX-iga kliendi poolel ja KryoNet-iga serveri poolel hõlmab sellist projektistruktuuri, mis eraldab kliendi ja serveri koodi, kuid võimaldab nende vahel lihtsat ja kiiret suhtlust.
Sissejuhatus ja projekti ülesehitus¶
Multiplayer mängu arendamine erineb tavalisest mängust eelkõige selle poolest, et teil on sisuliselt kaks eraldiseisvat programmi: üks, mis jookseb mängija arvutis (Klient), ja teine, mis jookseb kusagil kaugel pilves (Server).
Peamine väljakutse seisneb selles, et need kaks programmi peavad "rääkima sama keelt". Kui Klient saadab info "Liigu paremale", peab Server sellest täpselt samamoodi aru saama.

Selle probleemi lahendamiseks ei piisa vaid koodi kirjutamisest – tuleb alustada projekti kaustade ja moodulite õigest struktureerimisest.
Tüüpiline ja toimiv LibGDX + KryoNet projekt jaguneb seetõttu kolmeks peamiseks mooduliks:
Core (või Shared): See on teie projekti süda. Siia läheb kood, mis on ühine nii kliendile kui serverile. Siin asuvad tavaliselt võrgupaketid (sõnumid, mida saadetakse) ja mängu üldised objektid.
Server: Siin asub KryoNet serveri käivitus ja mängu autoriteetne loogika. Serveril on ligipääs Core moodulile, et ta teaks, milliseid objekte ta liigutama peab.
Desktop (Klient): Siin asub LibGDX graafiline pool. Ka kliendil on ligipääs Core moodulile, et ta oskaks serverilt saadud infot lahti pakkida.
Et vältida segadust failide asukohaga, on soovitatav järgida sellist kaustastruktuuri:
ManguProjekt/
├── core/ (Shared) <-- Mõlemad pooled näevad seda
│ └── src/com/mangu/nimi/
│ ├── packets/ <-- Siin asuvad klassid, mida saadetakse üle võrgu
│ │ ├── PacketLogin.java
│ │ └── PacketPlayerMove.java
│ └── entities/ <-- Ühised mängu objektid
│ └── Player.java
├── server/ <-- Ainult server näeb seda
│ └── src/com/mangu/nimi/server/
│ └── GameServer.java
└── desktop/ <-- Ainult klient näeb seda
│ └── src/com/mangu/nimi/desktop/
│ └── DesktopLauncher.java
Mängumaailm ja objektid¶
Kui projektistruktuur on paigas, tekib küsimus: kus peaks asuma mängumaailm ise? Kas iga mängija arvutis või serveris?
Kuigi maailma hoidmine kliendis (mängija arvutis) tundub alguses lihtsam ja kiirem, tekitab see mõne probleemi – mängijad võivad hakata petma (muutes oma koordinaate) ja erinevate klientide maailmad võivad minna sünkroonist välja.
Seetõttu on soovitatav hoida maailma serveris (aga ei ole kohustuslik). See tähendab, et server teab, kus mängijad tegelikult asuvad, ja kliendid tegelevad vaid pildi joonistamisega serverist saadud info põhjal.
Klasside loomisel tuleb seega rangelt eristada andmeid (mis on ühised) ja graafikat (mis on ainult kliendis) näiteks:
- Player (Core/Shared moodulis): Looge üks universaalne
Playerklass, mis sisaldab vaid andmeid (koordinaadid, elud, nimi). Kuna see asub ühises kaustas, saavad seda kasutada nii server (arvutamiseks) kui klient (info lugemiseks). NB! Ärge pange siia graafikat (Texture/Sprite), sest server jookseb ilma ekraanita ja see tekitaks vigu!
- Player (Core/Shared moodulis): Looge üks universaalne
- Screen (Desktop/Client moodulis): Graafika joonistamine jääb ainult kliendi vastutuseks.
Selle asemel, et kogu mängu loogika ühte faili kuhjata, tehke iga vaate (Menu, Lobby, Game) jaoks eraldi klass, mis laiendab LibGDX
ScreenAdapter-it. See hoiab visuaalse poole puhtana ja eraldatuna serveri loogikast.
Suhtluskanal: Packetid¶
Nüüd, kui meil on olemas ühine kaust (Core) ja mänguobjektid, on vaja viisi, kuidas info kliendi ja serveri vahel liikuma panna. Seda tehakse spetsiaalsete andmeklasside abil, mida nimetatakse Packetiteks.

Kuna KryoNet saadab objekte üle võrgu, peab Packeti klass olema täpselt identne nii saatja kui vastuvõtja jaoks. Just seetõttu asuvad kõik Packetid alati Core (Shared) moodulis (nagu näidatud ülaltoodud puus).
Üks korralik Packet järgib kolme reeglit: 1. See on eraldi fail/klass. 2. Sellel on tühi konstruktor (ilma selleta KryoNet ei tööta!). 3. See sisaldab ainult andmeid (muutujaid), mitte keerulist loogikat.
Näide korrektsest Packetist (Core moodulis):
package com.mygame.packets;
public class PacketConnectToLobby {
public String playerName;
public int playerLevel;
// 1. Tühi konstruktor on KryoNeti jaoks kohustuslik!
public PacketConnectToLobby() {
}
// 2. Mugavuskonstruktor andmete täitmiseks
public PacketConnectToLobby(String name, int level) {
this.playerName = name;
this.playerLevel = level;
}
}
Selleks, et server ja klient teineteist mõistaksid, tuleb need Packetid "tutvustada" ehk registreerida. See peab toimuma mõlemal pool täpselt samas järjekorras:
// Seda meetodit kutsuvad nii Klient kui Server oma 'start' faasis
public static void registerPackets(Kryo kryo) {
kryo.register(PacketConnectToLobby.class);
kryo.register(PacketPlayerMove.class);
// ...
}
Mängu töövoog (Flow)¶
Kui struktuur on paigas, objektid loodud ja packetid registreeritud, saab alata reaalne mäng. Kuidas näeb välja üks tsükkel nupuvajutusest kuni liikumiseni ekraanil?
See protsess peab olema kiire ja täpne. Alljärgnev skeem ja kirjeldus illustreerivad andmete liikumist:

Protsess samm-sammult:
Sisend: Mängija vajutab nuppu (nt "Liigu paremale"). Klient fikseerib vajutuse, kuid ei liiguta mängijat kohe lõplikult (või teeb seda vaid visuaalselt ennustades).
Saatmine: Klient loob
PacketPlayerInputobjekti ja saadab selle serverisse.Töötlus: Server võtab packeti vastu. Ta kontrollib, kas liikumine on legaalne (et mängija ei kõnniks läbi seina). Kui kõik on korras, uuendab Server oma maailmas mängija koordinaate.
Vastus: Server saadab kõigile ühendatud klientidele tagasi
PacketGameState(võiPacketPositionUpdate), mis sisaldab uusi koordinaate.Kuvamine: Klient saab serverilt packeti, võtab sealt uued koordinaadid ja joonistab mängijad ekraanil õigetesse kohtadesse.
Selline lähenemine tagab, et kõik mängijad näevad sama pilti ning keegi ei saa ebaõiglast eelist.