-
Notifications
You must be signed in to change notification settings - Fork 37
Protocolo (Networking) Ejemplo
Tanto el cliente como el servidor envian paquetes usando KryoNet. Esto quiere decir que una vez declaradas las clases que se van a usar en la clase NetworkDirectory
, podemos enviarlas a través de la red y de ambos lados van a saber serializar y deserializar dicho objeto. Todas estas clases estan en el modulo shared
ya que estan tanto del lado del cliente como del servidor. Por ejemplo:
public NetworkDictionary() {
registerAll(
// Game Requests
MovementRequest.class,
AttackRequest.class,
MeditateRequest.class,
TalkRequest.class,
ItemActionRequest.class,
TakeItemRequest.class,
SpellCastRequest.class,
TimeSyncRequest.class,
// Game Responses
MovementResponse.class,
TimeSyncResponse.class,
...
)
}
Cada una de estas clases debería implementar/extender de alguna de las siguientes: IRequest
, IResponse
o INotification
. Esto es porque tanto el cliente como el servidor tienen "procesadores" por cada uno de estos (IResponseProcessor, IRequestProcessor, INotificationProcessor)
Entonces al recibir un paquete, si este es instancia de alguna de estas interfaces, se procesara como corresponda, en caso de no extender de ninguno, el paquete se ignora.
ClientResponseProcessor
GameNotificationProcessor
Este sistema es el que se encarga de recibir objetos y procesarlos.
public void received(int connectionId, Object object) {
Gdx.app.postRunnable(() -> {
Log.info(object.toString());
if (object instanceof IResponse) {
((IResponse) object).accept(responseProcessor);
} else if (object instanceof INotification) {
((INotification) object).accept(notificationProcessor);
}
});
}
Hoy en dia tenemos un servidor que hace de Lobby (la clase se llama Finsiterra) y por cada sala/juego, tenemos un "Server"
-
FinisterraRequestProcessor
: procesa y tiene la logica de todo lo relacionado al Lobby o las salas. -
ServerRequestProcessor
: recibira todo lo relacionado a una partida. -
ServerNotificationProcessor
: algunas notificaciones de parte de algun cliente, como cosas relacionadas a los items, inventarios, etc.
Utiliza una cola para guardar los paquetes, a medida que los recibe los agrega a la cola (concurrente) y en el proximo ciclo del servidor, se procesan.
@Override
public void received(int connectionId, Object object) {
netQueue.add(new NetworkJob(connectionId, object));
}
private void processJob(NetworkJob job) {
int connectionId = job.connectionId;
Object object = job.receivedObject;
if (object instanceof IRequest) {
((IRequest) object).accept(requestProcessor, connectionId);
} else if (object instanceof INotification) {
((INotification) object).accept(notificationProcessor);
}
}
@Override
protected void processSystem() {
super.processSystem();
while (netQueue.peek() != null) {
processJob(netQueue.poll());
}
}
El objetivo es que el server notifique a un cliente cuando tiene que reproducir un sonido. Lo primero que tenemos que hacer es definir un paquete, por ejemplo, creamos una clase que implemente INotification:
package shared.network.sound;
import shared.network.interfaces.INotification;
import shared.network.interfaces.INotificationProcessor;
public class SoundNotification implements INotification {
private int soundNumber;
// Constructor vacio necesario para que Kryo pueda recrear el objeto
public SoundNotification() {}
public SoundNotification(int soundNumber) {
this.soundNumber = soundNumber;
}
public int getSoundNumber() {
return soundNumber;
}
public void setSoundNumber(int soundNumber) {
this.soundNumber = soundNumber;
}
@Override
public void accept(INotificationProcessor processor) {
// hay que crear el metodo para esta clase en la interfaz INotificationProcessor
processor.processNotification(this);
}
}
Ahora nos falta enviar este paquete por parte del servidor, y del lado del cliente, reproducirlo luego de recibir dicho paquete:
- Digamos que, por ejemplo, queremos reproducir el sonido del rechazo de un ataque con un escudo. En la metodo de la clase
PhysicalCombatSystem::canAttack
se encarga de calcular la probabilidad de que el usuario rechace con escudo. Primero tenemos que pensar en quien va a escuchar el sonido, en este caso, todos los que esten cerca, para esto podemos usar el metodoWorldManager::notifyUpdate
que lo que hace es, enviar un paquete al usuario, y a todos los que esten en un rango de "15" tiles. En caso de querer que se entere solo un cliente, podemos usarWorldManager::sendEntityUpdate
.
// shield evasion
if (E(targetId.get()).hasShield() && ThreadLocalRandom.current().nextInt(101) <= prob) {
notifyCombat(targetId.get(), SHIELD_DEFENSE);
notifyCombat(userId, format(DEFENDED_WITH_SHIELD, getName(targetId.get())));
// TODO send sound
getWorldManager().notifyUpdate(targetId.get(), new SoundNotification(10));
} else {
...
}
- El procesador (
GameNotificationProcessor
) de notificaciones del cliente debe implementar el metodo:
@Override
public void processNotification(SoundNotification soundNotification) {
int soundNumber = soundNotification.getSoundNumber();
// TODO obtener el sonido a reproducir
// TODO reproducir
}
- IntelliJ
- Windows JDK
- Linux JDK
- Run client and server localhost
- Host a public server
- Import project to IntelliJ