4. Näidismängu kliendi ja serveri suhtlus¶
4.1. Serveri ülesanned¶
Serveri ülesanded:
Kontrollida, kas kuul tabas mängijat, ning edastada teavet tabamuse kohta.
Käivitada mäng alles siis, kui on liitunud 2 mängijat.
Edastada teavet mängija1 uuest asukohast kõigile teistele mängijatele.
Edastada teavet kuuli uuest asukohast kõigile teistele mängijatele.
Kliendi ülesanded:
Edastada mängija liikumised.
Edastada info tulistatud kuulide kohta.
4.2. Serveri seaded¶
Loome Server-klassi objekti ja lisame listeneri, mis hakkab vastu võtma ja töötlema kogu serverile saadetud teabe.
Kood on võetud example-game serverist
package ee.taltech.examplegame.server;
import com.esotericsoftware.kryonet.Server;
import com.esotericsoftware.minlog.Log;
import ee.taltech.examplegame.server.listener.ServerListener;
import java.io.IOException;
import static constant.Constants.PORT_TCP;
import static constant.Constants.PORT_UDP;
import static network.KryoHelper.registerClasses;
/**
* Launches the server application.
*/
public class ServerLauncher {
public static void main(String[] args) {
try {
Server server = new Server();
// register classes that are sent over the network
// this must be done for every message that is sent over the network
registerClasses(server.getKryo());
server.start();
server.bind(PORT_TCP, PORT_UDP);
// this will listen for all connections and messages,
// that are sent to the server by the clients
// look in the ServerListener class for more information
server.addListener(new ServerListener());
} catch (IOException e) {
Log.error("Server failed to start.", e);
}
}
}
Nüüd loome ServerListener-i meetodis received sisemise mängusimulatsiooni. Seda on vaja selleks, et serveris töödelda kogu mängijate liikumist, tulistamist, kokkupõrkeid jms.
Seda teeme, kuna me ei saa usaldada klienti, et ta ise saadab teate, et ta tabas kedagi, või et ta edastab oma liikumise otse teistele mängijatele – klient võib saata manipuleeritud andmeid.
package ee.taltech.examplegame.server.listener;
import com.esotericsoftware.kryonet.Connection;
import com.esotericsoftware.kryonet.Listener;
import com.esotericsoftware.minlog.Log;
import ee.taltech.examplegame.server.game.GameInstance;
import message.GameJoinMessage;
/**
* This class listens for all connections and messages that are
* sent to the server by the clients.
* <p>
* It contains 3 methods that can be overridden to add custom logic
*/
public class ServerListener extends Listener {
private GameInstance game;
/**
* When a client connects to the server, this method is called.
* Include any logic that should be executed when a client connects to the server.
* ex - add the client to the game, etc.
*
* @param connection The connection object that is created when a client connects to the server.
*/
@Override
public void connected(Connection connection) {
Log.info("Client connected: " + connection.getRemoteAddressTCP().getAddress().getHostAddress());
super.connected(connection);
}
/**
* When a client disconnects from the server, this method is called.
* Include any logic that should be executed when a client disconnects from the server.
* ex - clean up resources, remove the client from the game, etc.
*
* @param connection The connection object of the client that disconnected.
*/
@Override
public void disconnected(Connection connection) {
Log.info("Client disconnected");
if (game != null) {
game.removeConnection(connection);
}
super.disconnected(connection);
}
/**
* When a message is received from a client, this method is called.
* ex - client sends a JoinGame message, the server will add the client to the game.
*
* @param connection The connection object of the client that sent the message.
* @param object The object that is sent by the client.
*/
@Override
public void received(Connection connection, Object object) {
Log.debug("Received message from client (" + connection.getRemoteAddressTCP().getAddress().getHostAddress() + "): " + object.toString());
// when a GameJoinMessage is received, the server will add the connection to the game instance
// if there is no active instance, a new one is created
if (object instanceof GameJoinMessage) {
if (game == null) {
game = new GameInstance(this, connection); // Create a new game instance for the first player (connection)
game.start(); // Start the Thread, which contains the main game loop
} else {
game.addConnection(connection); // Add a second player (connection) if there is enough room in the game
}
}
super.received(connection, object);
}
/**
* Disposes of the current game instance by setting the game reference to null.
*/
public void disposeGame() {
this.game = null;
}
}
GameInstance on eraldi lõim (thread), mis uuendatakse kindla sagedusega (tick rate). Tick rate on vajalik selleks, et vältida liigset koormust serverile – kui mäng uuendaks end pidevalt ilma pausideta, koormaks see protsessorit maksimaalselt, kuigi tegelikult pole nii sagedasi uuendusi vaja.
Samuti vaatleme veidi hiljem järgmisi klasse: Person, Bullet, BulletCollisionHandler ja GameStateHandler.
package ee.taltech.examplegame.server.game;
import com.esotericsoftware.kryonet.Connection;
import com.esotericsoftware.minlog.Log;
import ee.taltech.examplegame.server.game.object.Bullet;
import ee.taltech.examplegame.server.game.object.Player;
import ee.taltech.examplegame.server.listener.ServerListener;
import java.time.Duration;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import static constant.Constants.GAME_TICK_RATE;
import static constant.Constants.PLAYER_COUNT_IN_GAME;
/**
* Represents the game logic and server-side management of the game instance.
* Handles player connections, game state updates, bullet collisions, and communication with clients.
* <p>
* This class extends {@link Thread} because the game loop needs to run continuously
* in the background, independent of other server operations. By running in a separate thread,
* it ensures that the game state updates at a fixed tick rate without blocking other processes in the main server.
*/
public class GameInstance extends Thread {
private final ServerListener server;
private final BulletCollisionHandler collisionHandler = new BulletCollisionHandler();
private final GameStateHandler gameStateHandler = new GameStateHandler();
private final Set<Connection> connections = new HashSet<>(); // Avoid a connection (player) joining the game twice
private final List<Player> players = new ArrayList<>();
private List<Bullet> bullets = new ArrayList<>();
/**
* Initializes the game instance.
*
* @param server Reference to ServerListener to call dispose() when the game is finished or all players leave.
* @param firstConnection Connection of the first player.
*/
public GameInstance(ServerListener server, Connection firstConnection) {
this.server = server;
Player newPlayer = new Player(firstConnection, this);
players.add(newPlayer);
connections.add(firstConnection);
}
public void addBullet(Bullet bullet) {
this.bullets.add(bullet);
}
/**
* Check if the game has the required number of players to start.
*/
public boolean hasEnoughPlayers() {
return connections.size() == PLAYER_COUNT_IN_GAME;
}
/**
* Adds a new connection and player to the game.
* If the required number of players is reached, the game is ready to start.
*
* @param connection Connection to the client side of the player.
*/
public void addConnection(Connection connection) {
if (hasEnoughPlayers()) {
Log.info("Cannot add connection: Required number of players already connected.");
return;
}
// Add new player and connection
Player newPlayer = new Player(connection, this);
players.add(newPlayer);
connections.add(connection);
// Check if the game is ready to start
if (hasEnoughPlayers()) {
gameStateHandler.setAllPlayersHaveJoined(true);
}
}
public void removeConnection(Connection connection) {
this.connections.remove(connection);
}
/**
* Stops and disposes the current game instance, so a new one can be created with the same or new players.
*/
private void disposeGame() {
players.forEach(Player::dispose); // remove movement and shooting listeners
connections.clear();
server.disposeGame(); // Sets the active game instance in main server to null
}
/**
* Game loop. Updates the game state, checks for collisions, and sends updates to clients.
* The game loop runs until the game is stopped or no players remain.
*/
@Override
public void run() {
boolean isGameRunning = true;
while (isGameRunning) {
gameStateHandler.incrementGameTimeIfPlayersPresent();
// update bullets, check for collisions and remove out of bounds bullets
bullets.forEach(Bullet::update);
bullets = collisionHandler.handleCollisions(bullets, players);
// construct gameStateMessage
var gameStateMessage = gameStateHandler.getGameStateMessage(players, bullets);
// send the state of current game to all connected clients
connections.forEach(connection -> connection.sendUDP(gameStateMessage));
// If any player is dead, end the game
if (players.stream().anyMatch(x -> x.getLives() == 0)) {
// Use TCP to ensure that the last gameStateMessage reaches all clients
connections.forEach(connection -> connection.sendTCP(gameStateMessage));
disposeGame();
isGameRunning = false;
}
// If no players are connected, stop the game loop
if (connections.isEmpty()) {
Log.info("No players connected, stopping game loop.");
disposeGame();
isGameRunning = false;
}
try {
// We don't want to update the game state every millisecond, that would be
// too much for the server to handle. So a tick rate is used to limit the
// amount of updates per second.
Thread.sleep(Duration.ofMillis(1000 / GAME_TICK_RATE));
} catch (InterruptedException e) {
Log.error("Game loop sleep interrupted", e);
}
}
}
}
Alustame GameStateHandler-iga. Selles klassis toimuvad järgmised tegevused:
Mänguaja uuendamine.
Mängijate ja kuulide seisundite kogumine.
Kõikide objektide seisundid pakitakse GameStateMessage klassi objekti, mis saadetakse igale mängijale.
Teoorias võiks siin samuti asuda pettusevastane süsteem (anticheat), mis kontrolliks, kas uued objektiseisundid on legaalsed.
package ee.taltech.examplegame.server.game;
import ee.taltech.examplegame.server.game.object.Bullet;
import ee.taltech.examplegame.server.game.object.Player;
import lombok.Getter;
import lombok.Setter;
import message.GameStateMessage;
import message.dto.BulletState;
import message.dto.PlayerState;
import java.util.ArrayList;
import java.util.List;
import static constant.Constants.GAME_TICK_RATE;
@Getter
@Setter
public class GameStateHandler {
private boolean allPlayersHaveJoined = false;
private float gameTime = 0;
public void incrementGameTimeIfPlayersPresent() {
if (allPlayersHaveJoined) {
gameTime += 1f / GAME_TICK_RATE;
}
}
public GameStateMessage getGameStateMessage(List<Player> players, List<Bullet> bullets) {
// get the state of all players
var playerStates = new ArrayList<PlayerState>();
players.forEach(player -> playerStates.add(player.getState()));
// get state of all bullets
var bulletStates = new ArrayList<BulletState>();
bullets.forEach(bullet -> bulletStates.add(bullet.getState()));
// construct gameStateMessage
var gameStateMessage = new GameStateMessage();
gameStateMessage.setPlayerStates(playerStates);
gameStateMessage.setBulletStates(bulletStates);
gameStateMessage.setGameTime(Math.round(gameTime));
gameStateMessage.setAllPlayersHaveJoined(allPlayersHaveJoined);
return gameStateMessage;
}
}
Nüüd klass Player. Selles pole midagi erilist, lisame kaks Listener-it – movementListener, shootingListener. Samuti, sõltuvalt sellest, kuhu mängija liigub – muudame tema koordinaate, kuid kontrollime, et mängija ei läheks kaardist välja.
Samuti lisame meetodi shoot, mis saadab serveri Engine-le teate, et mängija on tulistanud, koos kogu kaasneva infoga.
Meetod getState on vajalik vaid selleks, et kogu vajalik info mängija kohta pakendada ühte objekti, et server saaks selle mugavalt töödelda ja samuti teistele osalejatele edasi anda.
package ee.taltech.examplegame.server.game.object;
import com.esotericsoftware.kryonet.Connection;
import ee.taltech.examplegame.server.game.GameInstance;
import ee.taltech.examplegame.server.listener.PlayerMovementListener;
import ee.taltech.examplegame.server.listener.PlayerShootingListener;
import lombok.Getter;
import lombok.Setter;
import message.dto.Direction;
import message.dto.PlayerState;
import static constant.Constants.ARENA_LOWER_BOUND_X;
import static constant.Constants.ARENA_LOWER_BOUND_Y;
import static constant.Constants.ARENA_UPPER_BOUND_X;
import static constant.Constants.ARENA_UPPER_BOUND_Y;
import static constant.Constants.PLAYER_HEIGHT_IN_PIXELS;
import static constant.Constants.PLAYER_LIVES_COUNT;
import static constant.Constants.PLAYER_SPEED;
import static constant.Constants.PLAYER_WIDTH_IN_PIXELS;
/**
* Server-side representation of a player in the game. This class listens for player movements or shooting actions
* and changes the player's server-side state accordingly. Lives management.
*/
@Getter
@Setter
public class Player {
private final Connection connection;
// Keep track of listener objects for each player connection, so they can be disposed when the game ends
private final PlayerMovementListener movementListener = new PlayerMovementListener(this);
private final PlayerShootingListener shootingListener = new PlayerShootingListener(this);
private final int id;
private final GameInstance game;
private float x, y = 0f;
private int lives = PLAYER_LIVES_COUNT;
/**
* Initializes a new server-side representation of a Player with a game reference and connection to client-side.
*
* @param connection Connection to client-side.
* @param game Game instance that this player is a part of.
*/
public Player(Connection connection, GameInstance game) {
this.connection = connection;
this.id = connection.getID();
this.game = game;
this.connection.addListener(movementListener);
this.connection.addListener(shootingListener);
}
/**
* Moves the player in the specified direction within the arena bounds.
*
* @param direction The direction in which the player moves.
*/
public void move(Direction direction) {
if (direction == null) return;
switch (direction) {
case UP -> y += 1 * PLAYER_SPEED;
case DOWN -> y -= 1 * PLAYER_SPEED;
case LEFT -> x -= 1 * PLAYER_SPEED;
case RIGHT -> x += 1 * PLAYER_SPEED;
}
// enforce arena bounds
x = Math.max(ARENA_LOWER_BOUND_X, Math.min(x, ARENA_UPPER_BOUND_X - PLAYER_WIDTH_IN_PIXELS));
y = Math.max(ARENA_LOWER_BOUND_Y, Math.min(y, ARENA_UPPER_BOUND_Y - PLAYER_HEIGHT_IN_PIXELS));
}
/**
* Returns the current state of the player, consisting of their position and remaining lives.
*/
public PlayerState getState() {
PlayerState playerState = new PlayerState();
playerState.setId(connection.getID());
playerState.setX(x);
playerState.setY(y);
playerState.setLives(lives);
return playerState;
}
public void shoot(Direction direction) {
// adjust bullet spawn position to be in the center of player
game.addBullet(
new Bullet(x + PLAYER_WIDTH_IN_PIXELS / 2, y + PLAYER_HEIGHT_IN_PIXELS / 2, direction, id)
);
}
public void decreaseLives() {
if (lives > 0) {
setLives(getLives() - 1);
}
}
/**
* Removes the movement and shooting listeners from the player's connection.
* This should be called when the player disconnects or the game ends.
* Disposing of the listeners prevents potential thread exceptions when reusing
* same connections for future game instances.
*/
public void dispose() {
connection.removeListener(movementListener);
connection.removeListener(shootingListener);
}
}
Klassiga Bullet on kõik täpselt samamoodi.
package ee.taltech.examplegame.server.game.object;
import lombok.Getter;
import lombok.Setter;
import message.dto.BulletState;
import message.dto.Direction;
import static constant.Constants.BULLET_SPEED;
@Getter
@Setter
public class Bullet {
private final Direction direction;
private float x;
private float y;
private int shotById;
public Bullet(float x, float y, Direction direction, int shotByPlayerWithId) {
this.x = x;
this.y = y;
this.direction = direction;
this.shotById = shotByPlayerWithId;
}
public void update() {
switch (direction) {
case UP -> y += BULLET_SPEED;
case DOWN -> y -= BULLET_SPEED;
case LEFT -> x -= BULLET_SPEED;
case RIGHT -> x += BULLET_SPEED;
}
}
public BulletState getState() {
BulletState bulletState = new BulletState();
bulletState.setX(x);
bulletState.setY(y);
bulletState.setDirection(direction);
return bulletState;
}
}
Nüüd movementListener ja shootingListener. Need on lihtsad, neil on vaid üks meetod – sõnumite kuulamiseks. Kui saame sõnumi tegevuse kohta, uuendame klassi Player eksemplare, kasutades vastavaid varem loodud meetodeid. Näiteks, player.shoot(), player.move().
package ee.taltech.examplegame.server.listener;
import com.esotericsoftware.kryonet.Connection;
import com.esotericsoftware.kryonet.Listener;
import com.esotericsoftware.minlog.Log;
import ee.taltech.examplegame.server.game.object.Player;
import message.PlayerShootingMessage;
import java.time.Duration;
import java.time.LocalTime;
import static constant.Constants.BULLET_TIMEOUT_IN_MILLIS;
public class PlayerShootingListener extends Listener {
private final Player player;
private LocalTime date = LocalTime.now();
public PlayerShootingListener(Player player) {
this.player = player;
}
@Override
public void received(Connection connection, Object object) {
if (object instanceof PlayerShootingMessage playerShootingMessage) {
Log.info("Player " + connection.getID() + " shot in direction: " + playerShootingMessage.getDirection());
// Preventing player from shooting too often
if (LocalTime.now().isAfter(date.plus(Duration.ofMillis(BULLET_TIMEOUT_IN_MILLIS)))) {
date = LocalTime.now();
} else {
return;
}
player.shoot(playerShootingMessage.getDirection());
}
super.received(connection, object);
}
}
package ee.taltech.examplegame.server.listener;
import com.esotericsoftware.kryonet.Connection;
import com.esotericsoftware.kryonet.Listener;
import com.esotericsoftware.minlog.Log;
import ee.taltech.examplegame.server.game.object.Player;
import message.PlayerMovementMessage;
public class PlayerMovementListener extends Listener {
private final Player player;
public PlayerMovementListener(Player player) {
this.player = player;
}
@Override
public void received(Connection connection, Object object) {
if (object instanceof PlayerMovementMessage playerMovementMessage) {
Log.info("Player " + connection.getID() + " moved: " + playerMovementMessage.getDirection());
player.move(playerMovementMessage.getDirection());
}
super.received(connection, object);
}
}
Viimane asi, mida serveripoolel vaadata – BulletCollisionHandler. Siin kontrollitakse kokkupõrkeid kuulide ja mängijate vahel. Samuti eemaldatakse nimekirjast need kuulid, mis on kaardilt välja lennanud. Lähemalt kuulide loogika kohta saab vaadata bullets peatükist.
package ee.taltech.examplegame.server.game;
import com.esotericsoftware.minlog.Log;
import ee.taltech.examplegame.server.game.object.Bullet;
import ee.taltech.examplegame.server.game.object.Player;
import java.awt.*;
import java.util.ArrayList;
import java.util.List;
import static constant.Constants.ARENA_LOWER_BOUND_X;
import static constant.Constants.ARENA_LOWER_BOUND_Y;
import static constant.Constants.ARENA_UPPER_BOUND_X;
import static constant.Constants.ARENA_UPPER_BOUND_Y;
import static constant.Constants.PLAYER_HEIGHT_IN_PIXELS;
import static constant.Constants.PLAYER_WIDTH_IN_PIXELS;
/**
* Handles bullet collisions with players and arena boundaries.
*/
public class BulletCollisionHandler {
/**
* Checks for collisions between bullets and players, removes bullets that hit players or moved out of bounds.
*
* @param bullets The list of bullets in the game.
* @param players The list of players to check for collisions.
* @return A list of remaining bullets after handling collisions.
*/
public List<Bullet> handleCollisions(List<Bullet> bullets, List<Player> players) {
List<Bullet> bulletsToBeRemoved = new ArrayList<>();
for (Player player : players) {
Rectangle hitBox = constructPlayerHitBox(player);
for (Bullet bullet : bullets) {
// register a hit only if the bullet was shot by a different player
if (bullet.getShotById() != player.getId() && hitBox.contains(bullet.getX(), bullet.getY())) {
player.decreaseLives();
bulletsToBeRemoved.add(bullet);
Log.info("Player with id " + player.getId() + " was hit. " + player.getLives() + " lives left.");
}
}
}
bulletsToBeRemoved.addAll(findOutOfBoundsBullets(bullets));
bullets.removeAll(bulletsToBeRemoved); // remove bullets that hit a player or move out of bounds
return bullets;
}
/**
* Finds bullets that are out of the arena bounds.
*
* @param bullets All active bullets.
* @return Bullets that are out of bounds.
*/
private List<Bullet> findOutOfBoundsBullets(List<Bullet> bullets) {
List<Bullet> outOfBoundsBullets = new ArrayList<>();
for (Bullet bullet : bullets) {
if (bullet.getX() < ARENA_LOWER_BOUND_X ||
bullet.getX() > ARENA_UPPER_BOUND_X ||
bullet.getY() < ARENA_LOWER_BOUND_Y ||
bullet.getY() > ARENA_UPPER_BOUND_Y
) {
outOfBoundsBullets.add(bullet);
}
}
return outOfBoundsBullets;
}
/**
* Constructs a rectangular hitbox for a player based on their position.
* A hitbox is essential for detecting collisions between players and bullets.
* Only bullets that visually overlap with the player's sprite register as hits.
*/
private Rectangle constructPlayerHitBox(Player player) {
return
new Rectangle(
(int) (player.getX()), // bottom left corner coordinates
(int) (player.getY()), // bottom left corner coordinates
(int) PLAYER_WIDTH_IN_PIXELS, // rectangle width
(int) PLAYER_HEIGHT_IN_PIXELS // rectangle height
);
}
}
4.3. Kliendi ülesanned¶
Kliendi ülesanded – saata serverile teavet oma praeguse oleku kohta ning vastu võtta serverilt sõnumeid mängumaailma uuenduste kohta ja neid töödelda.
Näiteks, kui teine mängija liikus – saadab server esimesele mängijale teise mängija uued koordinaadid ja esimese mängija klient uuendab need. Sama ka siis, kui esimest mängijat tabatakse – klient näitab, et teda on tabatud (efektid, tervise vähenemine jne).
4.4. Kliendi seaded¶
Kõik vajalikud klassid asuvad kaustas network. Seal on klass serveriga ühenduse loomiseks ja samuti Listener, mis kuulab serverilt saadud sõnumeid. Rohkem seal midagi ei ole, sest kõik olemasolevad sõnumitüübid asuvad kaustas shared ning neid kasutavad nii klient kui ka server. Klient kasutab neid, et saata teavet oma uue positsiooni, laskmise, ühenduse jms kohta.
Kood on võetud näidismängust
package ee.taltech.examplegame.network;
import com.badlogic.gdx.Gdx;
import com.esotericsoftware.kryonet.Client;
import static constant.Constants.*;
import static network.KryoHelper.registerClasses;
/**
* Handles the connection to the server.
* This class is a singleton, meaning that only one instance of this class can exist at a time.
* More about singletons:
* <a href="https://javadoc.pages.taltech.ee/design_patterns/creational_patterns.html#singel-singleton">...</a>
*/
public class ServerConnection {
private static ServerConnection instance;
private final Client client;
private ServerConnection() {
client = new Client();
// register classes that are sent over the network
registerClasses(client.getKryo());
client.start();
}
public static ServerConnection getInstance() {
if (instance == null) {
instance = new ServerConnection();
}
return instance;
}
public void connect() {
try {
client.connect(5000, SERVER_IP, PORT_TCP, PORT_UDP);
} catch (Exception e) {
Gdx.app.error("ServerConnection", "Failed to connect to server", e);
}
}
public Client getClient() {
return client;
}
}
package ee.taltech.examplegame.network.listener;
import com.esotericsoftware.kryonet.Connection;
import com.esotericsoftware.kryonet.Listener;
import ee.taltech.examplegame.game.GameStateManager;
import message.GameStateMessage;
/**
* Listener for handling incoming GameStateMessages from the server.
*/
public class GameStateMessageListener extends Listener {
private final GameStateManager gameStateManager;
public GameStateMessageListener(GameStateManager gameStateManager) {
this.gameStateManager = gameStateManager;
}
/**
* Processes received GameStateMessage objects.
* @param connection Connection that sent the message.
* @param object Received object (in this case GameStateMessage).
*/
@Override
public void received(Connection connection, Object object) {
super.received(connection, object);
if (object instanceof GameStateMessage gameStateMessage) {
// Update the game state
gameStateManager.setLatestGameStateMessage(gameStateMessage);
}
}
}
Edasi kliendipoolsel igal tsüklil töödeldakse vaid teavet gameStateManager.getLatestGameStateMessage() kaudu. Aga seda me enam ei hakka käsitlema.
4.5. Ühisosa¶
Ühises osas asuvad kõik edastatavad sõnumid, konstantsed väärtused, mis on ühised nii serverile kui ka kliendile, ja mis kõige tähtsam – kaustas network asub klass KryoHelper, kus tuleb ära märkida kõik varem loodud võimalikud sõnumid – ilma selleta ei tööta miski.
Allpool on toodud selle klassi kood.
Kood on võetud siit
package network;
import com.esotericsoftware.kryo.Kryo;
import message.PlayerShootingMessage;
import message.dto.BulletState;
import message.dto.Direction;
import message.dto.PlayerState;
import java.util.ArrayList;
public class KryoHelper {
public static void registerClasses(Kryo kryo) {
// all classes that you want to send over the network
// must be registered here. To make the handling of these classes
// easier they are all stored in the "messages" package
kryo.register(message.GameJoinMessage.class);
kryo.register(message.PlayerMovementMessage.class);
kryo.register(Direction.class);
kryo.register(ArrayList.class);
kryo.register(message.GameStateMessage.class);
kryo.register(PlayerState.class);
kryo.register(BulletState.class);
kryo.register(PlayerShootingMessage.class);
}
}