UE4 | Equipo para multijugador # 5 | Transferencia de información entre servidor y cliente


En este artículo consideraremos la transferencia de datos entre el Servidor y el Cliente en Unreal Engine 4 implementado en C ++ . Al principio, para una persona que no tiene una educación especializada, esto parece ser algo incomprensiblemente complicado. A pesar de la gran cantidad de ejemplos y análisis, personalmente fue muy difícil para mí reunir una imagen completa de este proceso. Pero, cuando se alcanzó la cantidad crítica de información obtenida a través de la lectura y las pruebas realizadas, se comprendió cómo funciona todo esto.




En primer lugar, debes comprender claramente que todos los objetos del juego (con raras excepciones) pueden tener múltiples copias. El objeto original se encuentra en el servidor. El original siempre está ahí. Pueden existir copias en los Clientes, pero no en absoluto, es decir pueden no serlo. Todos los eventos importantes ocurren en el Servidor, y es él quien decide quién necesita saberlo y quién no.

Todo lo que sucede en el Cliente no lo conoce nadie, excepto el Cliente.

Ejemplo

Comenzamos el juego con dos clientes. Hay un cubo en el escenario. El original de este cubo se encuentra en el servidor. El es lo más importante. La primera copia se ubicará en el primer Cliente y la segunda, en el segundo Cliente. Si hacemos algo con una copia del objeto en cualquiera de sus clientes, entonces el original no sufrirá . El cambio será puramente local, solo para este Cliente. Si cambia el original, entonces hay 2 escenarios principales:


  1. Las copias de los clientes permanecerán sin cambios.
  2. Las copias de los clientes se sincronizarán con el original.



Ahora que las reglas básicas del juego son claras, podemos considerar qué opciones están disponibles para transmitir información entre el Servidor y el Cliente. Conozco 3 formas, pero consideraremos solo las dos primeras, porque el tercero le permite transferir cualquier cosa y en cualquier lugar, y solo se aplica si está limitado por las limitaciones de los dos primeros.


  1. Replicación
  2. RPC ( Llamadas a procedimiento remoto ).
  3. TCP


    Replicación


Lo primero que debe aceptar incondicionalmente:

La replicación es un camino unidireccional y solo funciona desde el Servidor hasta el Cliente.
Lo segundo que debes saber:
Solo se pueden replicar objetos o variables de clase.
Y tercero, una condición importante:
La replicación ocurre solo cuando se produce un cambio en el servidor.

Si en Blueprint simplemente marcamos los lugares correctos, entonces C ++ no es mucho más complicado. Lo principal es no olvidar incluir #include "UnrealNetwork.h" .

Primero, considere la replicación de objetos.
En el constructor, escribimos:


bReplicates = true; 

Si queremos replicar el movimiento:


 bReplicateMovement = true; 

Si necesita replicar el componente conectado:


 Component->SetReplicates(true); 

Una descripción completa se puede encontrar aquí .


Con la replicación variable, las cosas son un poco más interesantes.
Comencemos con el archivo de encabezado .h .
Simplemente puede replicar la variable:


 UPROPERTY(Replicated) bool bMyReplicatedVariable; 

O puede ejecutar alguna función en el lado del Cliente si la variable se ha replicado. No importa qué valor tome la variable. El hecho de su cambio es importante.


 UPROPERTY(ReplicatedUsing = OnRep_MySomeFunction) TArray<float> MyReplicatedArray; UFUNCTION() void OnRep_MySomeFunction(); 

Ejecutar una función es solo una de las formas de enviar comandos desde el Servidor. Es muy importante saber que la replicación de matrices no ocurre completamente, sino solo la parte modificada. Por lo tanto, con solo una variable, puede enviar muchos comandos desde el servidor, analizando la matriz en elementos y elementos en bits.


Ahora pasemos a .cpp
Escribimos condiciones de replicación:


 void AMySuperActor::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const { Super::GetLifetimeReplicatedProps(OutLifetimeProps); DOREPLIFETIME(AMySuperActor, bMyReplicatedVariable); DOREPLIFETIME_CONDITION(AMySuperActor, MyReplicatedArray, COND_OwnerOnly); } 

La primera variable bMyReplicatedVariable se replicó incondicionalmente a todos los Clientes a la vez, mientras que la segunda, MyReplicatedArray , se actualizó solo para el Cliente que posee el objeto AMySuperActor , a menos que, por supuesto, se haya declarado uno.


Una lista completa de posibles condiciones se puede encontrar aquí .




RPC ( llamadas a procedimiento remoto )


Este método de transferencia de datos, a diferencia de la replicación, funciona en ambos sentidos, pero es más costoso. Para usarlo, solo necesita conectar #include "UnrealNetwork.h" .

Una característica importante es que puede transferir variables utilizando el método RPC .
Primero, debe decirse que los RPC vienen con acuse de recibo de Confiable y sin Confiable . Si en el primer caso el remitente no se calma hasta que esté convencido de que el paquete ha sido entregado (y lo enviará una y otra vez si no hay noticias recíprocas), en el segundo, al remitente no le importa si alguien recibió el paquete o no. Enviado y olvidado.
Donde sea posible, use No confiable . Por lo general, este método es adecuado para información no muy importante o para datos actualizados con frecuencia. Donde es imposible, en el caso de enviar desde el Servidor al Cliente, tratamos de hacer la replicación con una llamada a la función, como se muestra arriba.

Entonces, para enviar nuestro paquete desde el Cliente al Servidor, debe registrar tres funciones:


 UFUNCTION(Reliable, Server, WithValidation) void ServerTestFunction(float MyVariable); void ServerTestFunction_Implementation(float MyVariable); bool ServerTestFunction_Validate(float MyVariable); 

Fiable : un paquete con acuse de recibo.
Servidor : envío del Cliente al Servidor.
WithValidation : el destinatario abre el paquete solo si se cumplen las condiciones descritas en la función bool ServerTestFunction_Validate (float MyVariable) . Es decir, si la función devuelve verdadero . Este parámetro solo es necesario para las funciones que se ejecutan en el servidor.
ServerTestFunction (float MyVariable) : el cliente llama a esta función si desea enviar algo al servidor. En general, ni siquiera es necesario describirlo en .cpp .
ServerTestFunction_Implementation (float MyVariable) : esta función se invocará directamente en el servidor solo si ...
ServerTestFunction_Validate (float MyVariable) : esta función se ejecuta en el servidor y, si se devuelve true , se llamará a ServerTestFunction_Implementation (float MyVariable) .


Para enviar un paquete del Servidor al Cliente, si no estamos categóricamente satisfechos con el uso de la replicación, en esencia solo el Servidor cambia al Cliente :


 UFUNCTION(Reliable, Client, WithValidation) 

Los nombres de las funciones, en principio, pueden ser arbitrarios, pero generalmente deben reflejar hacia dónde va el paquete. Para nuestra conveniencia.


 UFUNCTION(Reliable, Client, WithValidation) void ClientTestFunction(float MyVariable); void ClientTestFunction_Implementation(float MyVariable); bool ClientTestFunction_Validate(float MyVariable); 

De lo contrario, funciona exactamente de la misma manera que en el ejemplo anterior, teniendo en cuenta el hecho de que esta vez el paquete va del servidor al cliente.


Hay otra opción para enviar desde el servidor, cuando el paquete va a todos los clientes a la vez.


 UFUNCTION(Reliable, NetMulticast, WithValidation) void NetMulticastTestFunction(); void NetMulticastTestFunction_Implementation(); bool NetMulticastTestFunction_Validate(); 

No abuses de esta opción. Piense en cómo podría administrar la replicación.

Para Client y NetMulticast, la validación es opcional



Ejemplo de implementación de solicitud del servidor
 /*       ,     */ void ADreampaxActor::DoSomethingWithOtherActor(ADreampaxOtherActor * SomeOtherActor) { /*  ,      */ if (Role < ROLE_Authority) { /*    ,    ,      */ ServerDoSomethingWithOtherActor(SomeOtherActor); /*   ,        */ return; } /*         */ SomeOtherActor->Destroy(true); } /*      ,    ServerDoSomethingWithOtherActor(SomeOtherActor)     */ void ADreampaxCharacter::ServerDoSomethingWithOtherActor_Implementation(ADreampaxOtherActor * SomeOtherActor) { /*   ,       */ DoSomethingWithOtherActor(SomeOtherActor); } /*     ,    ServerDoSomethingWithOtherActor_Implementation(ADreampaxOtherActor * SomeOtherActor) */ bool ADreampaxCharacter::ServerDoSomethingWithOtherActor_Validate(ADreampaxOtherActor * SomeOtherActor) { /*      true,   ,   -    fasle.       */ return true; } 

Y finalmente, un enlace al manual, que debe leerse.
Compendio de red 'Unreal Engine 4'


Eso es todo lo que necesita saber sobre la comunicación entre el Servidor y el Cliente para pasar a la siguiente sección, donde anotaremos la replicación del inventario y consideraremos cómo realizar cambios en él correctamente.


PD: si observa imprecisiones o errores, escriba los comentarios.

Source: https://habr.com/ru/post/es420233/


All Articles