Lobby loomine

Lobby süsteemi tegemine pole keeruline. Põhimõtteliselt seisneb see selles, et kliendi saadetud info suunatakse üldisesse klassi, kust edasi vaadatakse millise kindla mänguga peab edasi tegelema.

See näide on toodud selleks, et tekiks arusaam, kuidas lobby peaks töötama.

Lobby süsteemi tegemiseks on 3 põhiklassi, kuhu asjad käivad:

  • Lobby klass, kus on list mängijatest. See on siis ootamisala enne päris mängu algust.

  • Game klass, kus on list mängijatest ja mängu ID (võib olla ka teisi asju), see on hetkel aktiivne mäng.

  • Server klass, kus on list aktiivsetest mängudest. Selles võetakse vastu kliendi poolt saadetud infot.

Mängu klassi tegemine

Siin näites on meie mängu klass selline:

package ee.taltech.iti0301;


import java.util.ArrayList;
import java.util.List;

public class Game {

     // game ID
    private final int gameId;

     // List of players currently in the game
    private final List<Server.GameConnection> players = new ArrayList<>();

    public Game(int gameId) {
        this.gameId = gameId;
    }
    public int getGameId() {
        return this.gameId;
    }
    public List<Server.GameConnection> getPlayers() {
        return this.players;
    }
}

Lobby klassi tegemine

Siin on meie lobby class, kus tegelikult ei olegi muud peale listi mängijatest, kes seal sees on.

Siin näites on meil ainult 1 lobby, mis läheb tühjaks kui mängu alustatakse, kuid kui soovite teha mitu lobby, siis peate lisama siia veel lobby ID’d, et saaks kontrollida, mis lobby’st info tuleb.

package ee.taltech.iti0301;

import java.util.ArrayList;
import java.util.List;

public class Lobby {

    /*
        List of players in the lobby.
     */
    private final List<Server.GameConnection> peers = new ArrayList<>();

    public List<Server.GameConnection> getPeers() {
        return peers;
    }
    public void clearPeers() {
        this.peers.clear();
    }
    public void addPeer(Server.GameConnection peer) {
        this.peers.add(peer);
    }
}

Serveri klassi tegemine

See on serveri klass, kuhu tulevad klientide saadetud paketid. Siin peab vaatama, kust pakett tuli ning seejärel leidma vastava mängu ja seal vastavalt paketi sisule tegutsema. Selle jaoks hoitakse selles klassis listi kõikidest mängudest.

Siin tehakse kõike ühes meetodis, kuid saab ka teha väiksemad abimeetodid.

package ee.taltech.iti0301;

import com.esotericsoftware.kryonet.Connection;
import com.esotericsoftware.kryonet.Listener;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;

public class Server {

     // Game ID counter
    private int gameId = 1;

     // List of all currently running games
    private final List<Game> games = new ArrayList<>();

     // This is a variable to use for setting player health upon starting a game.
    private static final int PLAYER_BASE_HEALTH = 100;

    private final Random random = new Random();

    public Server(int port) throws IOException {
        Lobby lobby = new Lobby();
        com.esotericsoftware.kryonet.Server serverObject = new com.esotericsoftware.kryonet.Server() {
            @Override
            protected Connection newConnection() {
                return new GameConnection();
            }
        };

        Network.register(serverObject);

         /*
             We create a listener, which is what picks up the packets coming from clients
             and sees what to do with them
         */
        serverObject.addListener(new Listener() {
            @Override
            public void received(Connection c, Object object) {
                GameConnection player = (GameConnection)c;

                /*
                    GameId is 0 when the game has not started/they are in the lobby waiting.
                 */
                if (player.gameId == 0) {

                    /*
                        Checks if the gotten packet (object) is an 'OnStartGame' which is the packet starting the game.
                     */
                    if (object instanceof Network.OnStartGame packet) {
                        /*
                            Here we would do everything that's needed when the game starts (spawning players, clearing the lobby etc).
                         */
                        Game game = new Game(gameId);
                        packet.gameId = gameId;
                        if (packet.starterId == player.getID() && player.isLobbyHost) {
                             // loop through all players in the lobby and sending them the packet to start a game
                            for (GameConnection peer : lobby.getPeers()) {
                                int randomX = random.nextInt(10);
                                int randomY = random.nextInt(10);
                                 // Setting player attributes like health and spawn locations, as well as game ID for the player.
                                peer.health = PLAYER_BASE_HEALTH;
                                peer.x = randomX;
                                peer.y = randomY;
                                peer.gameId = gameId;

                                 // Now sending back the packet to start a game to the client.
                                peer.sendTCP(packet);
                                game.getPlayers().add(peer);
                            }
                        }
                        lobby.clearPeers();
                        games.add(game);
                        gameId++;
                    }
                    /*
                        Here's what happens when the server receives a packet for a player joining the lobby
                     */
                    if (object instanceof Network.PlayerJoinPacket joinedPlayer && (joinedPlayer.id == -1)) {
                         // If the lobby is empty, we set the player who joined as the host
                        if (lobby.getPeers().size() == 0) player.isLobbyHost = true;

                        player.userName = joinedPlayer.userName;

                         // List of players in the lobby
                        ArrayList<Network.OnLobbyJoin> lobbyPlayers = new ArrayList<>();

                         // We make a class to send to players already in the lobby
                        Network.OnLobbyJoin joinedPeer = new Network.OnLobbyJoin();
                        joinedPeer.id = player.getID();
                        joinedPeer.name = player.userName;
                        joinedPeer.isHost = player.isLobbyHost;

                         // loop through players to so we could send everyone the packet for a new player joining,
                         // but we also make packets of everyone already in the game and add them to a list
                         // to show the new player who is already in the lobby.
                        for (GameConnection peer : lobby.getPeers()) {
                            if (peer.gameId == 0) {
                                peer.sendTCP(joinedPeer);
                                Network.OnLobbyJoin join2 = new Network.OnLobbyJoin();
                                join2.id = peer.getID();
                                join2.name = peer.userName;
                                join2.isHost = peer.isLobbyHost;
                                lobbyPlayers.add(join2);
                            }
                        }
                        /*
                            Making a class, to send to the new person, of players already in the lobby.
                         */
                        Network.OnLobbyList list = new Network.OnLobbyList();
                        list.netId = player.getID();
                        list.isHost = player.isLobbyHost;
                        list.peers = lobbyPlayers;

                         // Sending the lobby list to the player who joined.
                        player.sendTCP(list);

                         // Adding the new player to a list of players in the lobby class.
                        lobby.addPeer(player);
                    }
                }
                else {
                    /*
                        Here's what happens if the packet came from an active game.
                        Firstly we find the game it came from.
                     */
                    Game currentGame = null;
                    for (Game game : games) {
                        if (game.getGameId() == player.gameId) {
                            currentGame = game;
                            break;
                        }
                    }
                    if (currentGame == null) return;

                    /*
                         Now when a Movement packet comes. We would update all players in the current game.
                         This part can vary depending on how the movement system is made.
                     */
                    if (object instanceof Network.Movement movementPacket) {
                        float x = movementPacket.x;
                        float y = movementPacket.y;
                        player.x = x;
                        player.y = y;

                         // Loop through all players in the game so we could update the positions for other players.
                        for (GameConnection currentPeer : currentGame.getPlayers()) {
                            if (currentPeer != player) {
                                Network.Movement mov = new Network.Movement();
                                mov.id = player.getID();
                                mov.x = x;
                                mov.y = y;
                                currentPeer.sendTCP(mov);
                            }
                        }
                    }
                }
            }
        });
        serverObject.start();
        serverObject.bind(port);
        serverObject.run();
    }

    /*
        A simple class for player connection.
     */
     public static class GameConnection extends Connection {
        public String name;
        public int health;
        public float x;
        public float y;
        public String userName;
        public int gameId;
        public boolean isLobbyHost;
    }
}

P. S. Selleks, et oleks võimalik serverit käivida, saab luua serveri objekti Main klassis.

Pakettide klasside tegemine

Selles näites kasutame info saatmiseks selliseid klasse:

// Class for when a player joins.
 public static class PlayerJoinPacket {
    public boolean test;
    public int id;
    public float x;
    public float y;
    public String userName;
}

// Class for movement updates
public static class Movement {
    public int id;
    public float x;
    public float y;
    public int state;
}

// Class for player joining a lobby
public static class OnLobbyJoin {
    public int id;
    public String name;
    public boolean isHost;
}

// Class for sending a new player a list of people already in the lobby
public static class OnLobbyList {
    public int netId;
    public boolean isHost;
    public List<OnLobbyJoin> peers;
}

// Class that's used to tell the server or client to start a game
public static class OnStartGame {
    public int starterId;
    public int gameId;
}

Need klassid tuleks lisada Network'i klassi.