Lobby (uus)¶
1. Sissejuhatus¶
1.1. Kirjeldus¶
See juhend kirjeldab kuidas luua lihtsama lobbi kasutades LibGDX ja Kryonet'i
Kui tekkivad probleemid, siis saab avada originaalkoodi ja otsida mis läks valesti.
1.2. Milleks on lobbi vaja?¶
Lobbi on oluline osa multiplayer'i loomiseks mängudes. Lobbi annab meile võimalust, et teha mitu mängi instantsi, et kõik mängijad saaksid mängida eraldi teistest mängijatest
1.3. Lisa info¶
- Kasulikud materjalid:
2. Mängija registreerimine¶

2.1. Loo vormi mängija loomiseks¶
Esialguks kliendi kaustas on vaja luua uue pakki screen
ja lisada uue klassi MainScreen
.
See ekraan on mõeldud mängijate loomiseks.
Loo sinna uue stage'i ja lisa 3 toimijad (Label
, TextField
ja TextButton
)
MainScreen.java
@Override
public void show() {
Stage stage = new Stage();
Gdx.input.setInputProcessor(stage);
Label label = new Label("Write your name:", skin);
TextField field = new TextField("", skin);
TextButton button = new TextButton("START", skin);
button.addListener(new RegisterPlayerClickListener(field.getName()));
Table table = new Table();
table.setFillParent(true);
table.defaults().space(10);
table.add(label).uniform().fillX();
table.row();
table.add(field);
table.row();
table.add(button).uniform().fillX();
stage.addActor(table);
}
@Override
public void render(float delta) {
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
Gdx.gl.glClearColor(0.157f, 0.196f, 0.522f, 1);
stage.act(delta);
stage.draw();
}
2.2. Saada UDP päringu serverile¶
Enne päringu saatmist peame serveriga ühenduma.
Selleks loome uue ClientLauncher
klassi
2.2.1 ClientLauncher¶
ClientLauncher.java
public class ClientLauncher extends Client {
private static ClientLauncher instance;
private ClientLauncher() {
KryoHelper.registerClasses(getKryo());
connectToServer();
}
public static ClientLauncher getInstance() {
if (instance == null) {
instance = new ClientLauncher();
}
return instance;
}
public void connectToServer() {
start();
try {
connect(DEFAULT_CONNECTION_TO_SERVER_TIMEOUT, DEFAULT_HOST, DEFAULT_TCP_PORT, DEFAULT_UDP_PORT);
} catch (IOException e) {
System.out.println(e);
}
}
}
2.2.2. Constants¶
Lisa Constants
klassi shared
kausta, et hoida konstantseid väärtusi
Constants.java
public class Constants {
public static final int DEFAULT_CONNECTION_TO_SERVER_TIMEOUT = 5000;
public static final int DEFAULT_TCP_PORT = 8080;
public static final int DEFAULT_UDP_PORT = 8081;
public static final String DEFAULT_HOST = "localhost";
}
2.3. Töötle nuppu vajutamist¶
Nüüd lisa uue klassi RegisterPlayerClickListener
, mis pärib ClickListener
ja hoiab endas TextField field
.
RegisterPlayerClickListener.java
@AllArgsConstructor
public class RegisterPlayerClickListener extends ClickListener {
private TextField field;
@Override
public void clicked(InputEvent event, float x, float y) {
String username = field.getText();
if (username.isBlank()) return;
ClientLauncher.getInstance().sendUDP(new RegisterPlayerPacket(username));
}
}
Kui ClientLauncher.getInstance()
on kasutatud esimest korda, siis luuakse uue ClientLauncher
klassist objekti ja kohe ühendatakse serveriga ja saadatakse päringu.
2.4. Registeeri klassid kasutades Kryo¶
Kui praegu proovida seda koodi, siis tuleb viga. Selleks, et seda parandada peab Kryonet'i registreerima RegisterPlayerPacket
.
Loome uue KryoHelper
klassi shared
kausta.
KryoHelper.java
public class KryoHelper {
public static void registerClasses(Kryo kryo) {
kryo.register(RegisterPlayerPacket.class);
// Register more classes here.
}
}
*Kõik klassid, mis saadetakse kliendilt serverile ja serverilt kliendile peavad olema registreeritud.
Kasuta see meetod serveril ja kliendil.
ClientLauncher.java
private ClientLauncher() {
KryoHelper.registerClasses(getKryo());
...
}
ServerLauncher.java
public ServerLauncher() {
KryoHelper.registerClasses(getKryo());
...
}
Nüüd klassid registreeritakse nii serveril, kui ka kliendil.
2.5. Lombok¶
Selles juhendis hakkan kasutama Lombok
Selleks, et lisada Lombok ava build.gradle
faili ja lisa dependencies
alla
build.gradle
annotationProcessor "org.projectlombok:lombok:1.18.34"
compileOnly "org.projectlombok:lombok:1.18.34"
*Võid versiooni muuta uueks, kui on vajadus
2.6. Päringu töötlemine serveris¶
Lisame uue ServerListener
klassi.
ServerListener.java
public class ServerListener implements Listener {
@Override
public void received(Connection connection, Object object) {
switch (object) {
case RegisterPlayerPacket packet -> // if object is instance of RegisterPlayerPacket
ServerLauncher.getInstance().registerPlayer(connection.getID(), packet.getName());
default ->
// leave it like this
System.out.println("PACKET SKIPPED");
}
}
}
ServerLauncher.java
public ServerLauncher() {
...
addListener(new ServerListener());
}
Ja lisa shared
kausta mängija klassi.
Player.java
@AllArgsConstructor
@NoArgsConstructor
@Getter
public class Player {
private int id;
private String name;
}
2.7. Vastuse töötlemine kliendis¶
Lisame uue ClientListener
klassi.
ClientListener.java
public class ClientListener implements Listener {
@Override
public void received(Connection connection, Object object) {
switch (object) {
case RegisterPlayerPacket packet ->
Main.getInstance().createPlayer(connection.getID(), packet.getName());
default -> System.out.println("Skipped package");
}
}
}
ClientLauncher.java
private ClientLauncher() {
addListener(new ClientListener());
...
}
Nüüd salvestame mängija ja muudame ekraani.
Main.java
private Player currentPlayer;
public void createPlayer(int id, String username) {
currentPlayer = new Player(id, username);
Gdx.app.postRunnable(new SetScreenRunnable(new LobbiesListScreen()));
}
2.8. Kasuta Runnable ekraanide muutmiseks
SetScreenRunnable.java
@AllArgsConstructor
public class SetScreenRunnable implements Runnable {
private Screen screen;
@Override
public void run() {
Main.getInstance().setScreen(screen);
}
}
*Runnable kasutamine on kohustuslik, sest mäng ja kryonet töötavad erinevatel Thread'idel
3. Loo uus lobbi¶
Järgmiseks etappiks realiseerime lobbide loomine.

3.1. Lisa lobbi class¶
Loo lobby
klass shared
kaustas.
Lobby.java
@Getter
@NoArgsConstructor
public class Lobby {
private static int nextId = 0;
private int id;
private String name;
private Map<Integer, Player> players;
public Lobby(Player player) {
this.id = nextId++;
this.name = generateLobbyName(player);
this.players = new HashMap<>();
joinLobby(player);
}
public void joinLobby(Player player) {
players.put(player.getId(), player);
}
private String generateLobbyName(Player player) {
return String.format("%s's lobby", player.getName());
}
}
3.2. Töötle nuppu vajutamist¶
LobbiesListScreen.java
@Override
protected void createInterface() {
...
TextButton addLobbyButton = new TextButton("Add Lobby", skin);
addLobbyButton.addListener(new CreateLobbyClickListener());
...
}
CreateLobbyClickListener.java
public class CreateLobbyClickListener extends ClickListener {
@Override
public void clicked(InputEvent event, float x, float y) {
ClientLauncher.getInstance().sendUDP(new CreateLobbyPacket());
}
}
Saadame lihtsalt tühja CreateLobbyPacket
, sest meile polegi midagi vaja välja arvatud mängija ID, mida me saame koos päringuga vaikimisi.
CreateLobbyPacket.java
@AllArgsConstructor
@NoArgsConstructor
@Getter
public class CreateLobbyPacket {
private Lobby lobby;
}
*Hoiame lobby muutuja vastuseks serverilt. Kuid päringuks saadame tühja paketti
3.3. Päringu töötlemine serveris¶
ServerListener.java
@Override
public void received(Connection connection, Object object) {
switch (object) {
...
case CreateLobbyPacket packet ->
ServerLauncher.getInstance().getGame().createLobby(connection.getID());
...
}
}
Lisame uue kujutise lobbideks, et hoida neid serveril.
Game.java
private Map<Integer, Lobby> lobbies = new HashMap<>();
public void createLobby(int id) {
Player player = players.get(id);
Lobby lobby = new Lobby(player);
lobbies.put(lobby.getId(), lobby);
ServerLauncher.getInstance().sendToUDP(id, new CreateLobbyPacket(lobby));
}
3.4. Vastuse töötlemine kliendis¶
ClientListener.java
@Override
public void received(Connection connection, Object object) {
switch (object) {
...
case CreateLobbyPacket packet ->
Main.getInstance().joinLobby(Main.getInstance().getCurrentPlayer(), packet.getLobby());
...
}
}
Main.java
public void joinLobby(Lobby lobby) {
currentLobby = lobby;
Gdx.app.postRunnable(new SetScreenRunnable(new LobbyScreen(currentLobby)));
}
4. Lobbist väljumine¶
Lobbist väljumise implementatsioon on peaaegu sama nagu lobbi loomine

4.1. Töötle nuppu vajutamist¶
LobbyScreen.java
@Override
protected void createInterface() {
...
TextButton backButton = new TextButton("", skin);
backButton.addListener(new LeaveLobbyClickListener(lobby.getId()));
...
}
LeaveLobbyClickListener.java
@AllArgsConstructor
public class LeaveLobbyClickListener extends ClickListener {
private int id;
@Override
public void clicked(InputEvent event, float x, float y) {
ClientLauncher.getInstance().sendUDP(new LeaveLobbyPacket(id));
}
}
4.2. Töötle nuppu vajutamist¶
Main.java
public void removePlayer(int id) {
currentLobby = null;
Gdx.app.postRunnable(new SetScreenRunnable(new LobbiesListScreen()));
}
4.3. Päringu töötlemine serveris¶
ServerListener.java
@Override
public void received(Connection connection, Object object) {
switch (object) {
...
case LeaveLobbyPacket packet ->
ServerLauncher.getInstance().getGame().leaveLobby(connection.getID(), packet.getId());
...
}
}
Game.java
public void leaveLobby(int playerId, int lobbyId) {
Lobby lobby = lobbies.get(lobbyId);
lobby.kickPlayer(playerId);
if (lobby.getPlayersNumber() == 0) {
lobbies.remove(lobbyId);
}
ServerLauncher.getInstance().sendToUDP(playerId, new LeaveLobbyPacket(playerId));
}
4.4. Lobbi klassi värskendamine¶
Lobby.java
public void kickPlayer(int playerId) {
players.remove(playerId);
}
public int getPlayersNumber() {
return players.keySet().size();
}
*See pole kohustuslik, et saata midagi tagasi kliendile, sest lobbi väljumist me saame töötelda ka kliendis.
5. Näita lobbid¶

5.1. Hoia muutujad¶
Selleks, et näidata lobbid, peame hoidma järgmised muutujaid
LobbiesListScreen.java
private Table lobbies;
private Map<Integer, Table> lobbyActors = new HashMap<>();
* lobbies
tabel hakkab hoidma endas toimijad. Ja lobbyActors
kujutis on selleks, et oleks lihtsam leida need toimijaid ja kustutada neid.
5.2. Küsi lobbid¶
Pärast seda, kui sa said LobbiesListScreen
, küsi serverilt näidata lobbid
LobbiesListScreen.java
@Override
protected void createInterface() {
...
ClientLauncher.getInstance().sendUDP(new GetLobbiesPacket());
}
GetLobbiesPacket.java
@AllArgsConstructor
@NoArgsConstructor
@Getter
public class GetLobbiesPacket implements Packet {
private Map<Integer, Lobby> lobbies;
}
*Kui me saadame serverile päringut, siis me saadame tühja paketti, vastuseks saame sama paketti, aga juba koos lobbidega
5.3. Päringu töötlemine serveris¶
ServerListener.java
@Override
public void received(Connection connection, Object object) {
switch (object) {
...
case GetLobbiesPacket packet ->
ServerLauncher.getInstance().getGame().getLobbies(connection.getID());
...
}
}
Game.java
public void getLobbies(int id) {
ServerLauncher.getInstance().sendToUDP(id, new GetLobbiesPacket(lobbies));
}
5.4. Koodi värskendamine¶
Nüüd on vaja natuke muuta meie koodi.
Täpsemalt, värskendame createLobby
koodi.
Pärast lobbi loomist saadame kõikidele lobbi otsijatele uue lobbi.
public void createLobby(int id) {
...
ServerLauncher.getInstance().sendToAllExceptUDP(id, new GetLobbiesPacket(lobbies));
}
5.5. Optimiseerimine¶
Praegu saadetakse paketti kõigidele mängijatele välja arvatud mängija kes lõi uue lobbi.
Selleks, et seda optimiseerida, saaks lisada mängijale uue muutuja, mis hoiaks tema oleku
ja kui olek oleks näiteks State.SEARCH
siis saata temale paketti.
5.6. Vastuse töötlemine kliendil¶
ClientListener.java
@Override
public void received(Connection connection, Object object) {
switch (object) {
...
case GetLobbiesPacket packet ->
Main.getInstance().updateLobbies(packet.getLobbies());
...
}
}
Esialgus vaatame kas mängija on praegu LobbiesListScreen ekraanil, et vältida vigu ja siis lisame uue lobbi ekraanile.
Main.java
public void updateLobbies(Map<Integer, Lobby> lobbies) {
if (getScreen() instanceof LobbiesListScreen lobbiesListScreen) {
lobbiesListScreen.clearLobbies();
for (Lobby lobby : lobbies.values()) {
lobbiesListScreen.addLobby(lobby);
}
}
}
LobbiesListScreen.java
private Table lobbies;
private final Map<Integer, Table> lobbyActors = new HashMap<>();
public void addLobby(Lobby lobby) {
TextButton lobbyButton = new TextButton(lobby.getName(), skin);
lobbies.add(lobbyButton).expandX().fillX();
lobbies.row();
lobbyActors.put(lobby.getId(), lobbyButton);
}
public void removeLobby(int lobbyId) {
Table lobby = lobbyActors.remove(lobbyId);
lobbies.removeActor(lobby);
}
public void clearLobbies() {
lobbies.clear();
lobbyActors.clear();
}
6. Lobbi eemaldamine ekraanilt¶
6.1 Koodi värskendamine¶
Kui lobbis on 0 mängijad, siis on vaja seda kustutada ekraanilt.
Game.java
public void leaveLobby(int playerId, int lobbyId) {
...
if (lobby.getPlayersNumber() == 0) {
...
ServerLauncher.getInstance().sendToAllExceptUDP(playerId, new DeleteLobbyPacket(lobbyId));
}
}
6.2. Vastuse töötlemine kliendis¶
ClientListener.java
@Override
public void received(Connection connection, Object object) {
switch (object) {
...
case DeleteLobbyPacket packet ->
Main.getInstance().deleteLobby(packet.getLobbyId());
...
}
}
Main.java
public void deleteLobby(int lobbyId) {
if (getScreen() instanceof LobbiesListScreen lobbiesListScreen) {
lobbiesListScreen.removeLobby(lobbyId);
}
}
Nüüd lobbi kaob ekraanilt, kui seda kustutakse.
7. Lobbi sisenemine¶

7.1. Töötle ekraanis oleva lobbi vajutamist¶
JoinLobbyClickListener.java
@AllArgsConstructor
public class JoinLobbyClickListener extends ClickListener {
private final int lobbyId;
@Override
public void clicked(InputEvent event, float x, float y) {
ClientLauncher.getInstance().sendUDP(new JoinLobbyPacket(lobbyId));
}
}
Lisame see listener
iga lobbile.
LobbiesListScreen.java
public void addLobby(Lobby lobby) {
TextButton lobbyButton = new TextButton(lobby.getName(), skin);
lobbyButton.addListener(new JoinLobbyClickListener(lobby.getId()));
...
}
7.2. Päringu töötlemine serveris¶
@Override
public void received(Connection connection, Object object) {
switch (object) {
...
case JoinLobbyPacket packet ->
ServerLauncher.getInstance().getGame().joinLobby(connection.getID(), packet.getLobbyId());
...
}
}
Lisame mängija lobbisse ja saadame kõigidele mängijatele lobbis, et uus mängija sisenes.
Game.java
public void joinLobby(int id, int lobbyId) {
Player player = players.get(id);
Lobby lobby = lobbies.get(lobbyId);
lobby.joinLobby(player);
for (Player currentPlayer : lobby.getPlayers().values()) {
ServerLauncher.getInstance().sendToUDP(currentPlayer.getId(), new PlayerJoinedLobbyPacket(player, lobby));
}
}
PlayerJoinedLobbyPacket.java
@AllArgsConstructor
@NoArgsConstructor
@Getter
public class PlayerJoinedLobbyPacket {
private Player player;
private Lobby lobby;
}
7.3. Vastuse töötlemine kliendis¶
7.3.1 Töötle vastust¶
ClientListener.java
@Override
public void received(Connection connection, Object object) {
switch (object) {
....
case PlayerJoinedLobbyPacket packet ->
Main.getInstance().joinLobby(packet.getPlayer(), packet.getLobby());
...
}
}
7.3.2 Koodi värskendamine¶
Main.java
public void joinLobby(Player player, Lobby lobby) {
if (player.getId() == currentPlayer.getId()) {
currentLobby = lobby;
Gdx.app.postRunnable(new SetScreenRunnable(new LobbyScreen(currentLobby)));
} else {
if (getScreen() instanceof LobbyScreen lobbyScreen) {
lobbyScreen.addPlayer(player);
}
}
}
LobbyScreen.java
private final Map<Integer, Actor> players = new HashMap<>();
public void addPlayer(Player player) {
Label playerNameLabel = new Label(player.getName(), skin);
playersTable.add(playerNameLabel).expandX().fillX();
players.put(player.getId(), playerNameLabel);
playersTable.row();
}
Ja kui mängija sisenes lobbisse, siis lisame teda kõigi mängijate ekraanidele.
LobbyScreen.java
@Override
protected void createInterface() {
...
for (Player player : lobby.getPlayers().values()) {
addPlayer(player);
}
}
8. Väljunud mängija eemaldamine ekraanilt¶
8.1. Koodi värskendamine¶
Game.java
public void leaveLobby(int playerId, int lobbyId) {
...
if (lobby.getPlayersNumber() == 0) {
...
}
ServerLauncher.getInstance().sendToAllUDP(new LeaveLobbyPacket(playerId));
}
Nüüd, kui mängija väljub lobbist, siis saadetakse sellest info teistele lobbis asuvatele mängijatele.
8.2. Vastuse töötlemine kliendis¶
ClientLauncher.java
@Override
public void received(Connection connection, Object object) {
switch (object) {
...
case LeaveLobbyPacket packet ->
Main.getInstance().removePlayer(packet.getId());
...
}
}
Main.java
public void removePlayer(int id) {
if (id == ClientLauncher.getInstance().getID()) {
currentLobby = null;
Gdx.app.postRunnable(new SetScreenRunnable(new LobbiesListScreen()));
} else {
if (getScreen() instanceof LobbyScreen lobbyScreen) {
lobbyScreen.removePlayer(id);
}
}
}
LobbyScreen.java
public void removePlayer(int id) {
Actor player = players.remove(id);
playersTable.removeActor(player);
}
Lõpuks peaks olema täiesti töötav lobbi, kuhu saavad mängijad siseneda ja väljuda sellest.
Kui tekkivad probleemid, siis saab avada originaalkoodi ja otsida mis läks valesti.
