¿Cómo conectar en red este sistema de entidades?

33

He diseñado un sistema de entidad para un FPS. Básicamente funciona así:

Tenemos un objeto "mundial", llamado GameWorld. Esto contiene una matriz de GameObject, así como una matriz de ComponentManager.

GameObject contiene una matriz de componentes. También proporciona un mecanismo de eventos que es realmente simple. Los componentes mismos pueden enviar un evento a la entidad, que se transmite a todos los componentes.

El componente es básicamente algo que le da a GameObject ciertas propiedades, y dado que GameObject es en realidad solo un contenedor de ellos, todo lo que tiene que ver con un objeto de juego ocurre en los Componentes. Los ejemplos incluyen ViewComponent, PhysicsComponent y LogicComponent. Si se necesita la comunicación entre ellos, eso se puede hacer mediante el uso de eventos.

ComponentManager solo una interfaz como Component, y para cada clase de Componente, generalmente debería haber una clase de ComponentManager. Estos administradores de componentes son responsables de crear los componentes e inicializarlos con propiedades leídas de algo así como un archivo XML.

ComponentManager también se encarga de las actualizaciones masivas de componentes, como el PhysicsComponent, donde utilizaré una biblioteca externa (que hace todo en el mundo a la vez).

Para la configurabilidad, utilizaré una fábrica para las entidades que leerán un archivo XML o un script, crearán los componentes especificados en el archivo (que también agrega una referencia en el administrador de componentes adecuado para actualizaciones masivas) y luego inyectarlos en un objeto GameObject.

Ahora viene mi problema: voy a tratar de usar esto para juegos multijugador. No tengo idea de cómo abordar esto.

Primero: ¿Qué entidades deberían tener los clientes desde el principio? Debo comenzar explicando cómo un motor para un solo jugador determinaría qué entidades crear.

En el editor de niveles puede crear "pinceles" y "entidades". Los pinceles son para paredes, pisos y techos, básicamente formas simples. Las entidades son el GameObject del que te hablé. Al crear entidades en el editor de niveles, puede especificar propiedades para cada uno de sus componentes. Estas propiedades se pasan directamente a algo como un constructor en el script de la entidad.

Cuando guarda el nivel para que se cargue el motor, se descompone en una lista de entidades y sus propiedades asociadas. Los pinceles se convierten en una entidad "engendro del mundo".

Cuando carga ese nivel, solo instancia todas las entidades. Suena simple, ¿eh?

Ahora, para las redes de las entidades me encuentro con numerosos problemas. Primero, ¿qué entidades deberían existir en el cliente desde el principio? Suponiendo que tanto el servidor como el cliente tienen el archivo de nivel, el cliente también podría instanciar todas las entidades en el nivel, incluso si están allí solo a los fines de las reglas del juego en el servidor.

Otra posibilidad es que el cliente instancia una entidad tan pronto como el servidor envía información al respecto, y eso significa que el cliente solo tendrá entidades que necesita.

Otro problema es cómo enviar la información. Creo que el servidor podría usar la compresión delta, lo que significa que solo envía nueva información cuando algo cambia, en lugar de enviar una instantánea al cliente en cada cuadro. Aunque eso significa que el servidor debe realizar un seguimiento de lo que cada cliente sabe en este momento.

Y finalmente, ¿cómo deberían inyectarse las redes en el motor? Estoy pensando en un componente, NetworkComponent, que se inyecta en cada entidad que se supone que está conectada en red. Pero, ¿cómo debe saber el componente de red qué variables red y cómo? conectar acceder a ellas, y finalmente cómo el componente de red correspondiente en el cliente debe saber cómo cambiar las variables en red?

Estoy teniendo grandes problemas para abordar esto. Realmente agradecería que me ayudaras en el camino. También estoy abierto a consejos sobre cómo mejorar el diseño del sistema de componentes, así que no tenga miedo de sugerir eso.

Carretero
fuente

Respuestas:

13

Esta es una maldita bestia de una pregunta con muchos detalles +1 allí. Definitivamente suficiente para ayudar a las personas que se topan con él.

¡Solo quería agregar mis 2 centavos por no enviar datos de física! Sinceramente, no puedo enfatizar esto lo suficiente. Incluso si lo tiene optimizado hasta el punto de que prácticamente puede enviar 40 esferas que rebotan con micro colisión y podría ir a toda velocidad en una sala de sacudidas que ni siquiera disminuye la velocidad de fotogramas. Me refiero a realizar su "compresión / codificación delta", también conocida como diferenciación de datos, que usted mencionó. Es bastante similar a lo que iba a mencionar.

Dead Reckoning VS Data Difference: son lo suficientemente diferentes y realmente no ocupan los mismos métodos, lo que significa que puede implementar ambos para aumentar aún más la optimización. Nota: No los he usado juntos, pero he trabajado con ambos.

Codificación Delta o diferenciación de datos: el servidor transporta datos sobre lo que los clientes saben y envía solo las diferencias entre los datos antiguos y lo que se debe cambiar al cliente. por ejemplo, pseudo-> en un ejemplo, puede enviar los datos "315 435 222 3546 33" cuando los datos ya son "310 435 210 4000 40" ¡Algunos solo se modifican ligeramente y uno no se modifica en absoluto! En lugar de eso, enviaría (en delta) "5 0 12 -454 -7", que es considerablemente más corto.

Mejores ejemplos podrían ser algo que cambia mucho más que eso, por ejemplo, supongamos que tengo una lista vinculada con 45 objetos vinculados en este momento. Quiero matar a 30 de ellos, así que hago eso, y luego envío a todos cuál es el nuevo paquete de datos, lo que ralentizaría el servidor si aún no estaba construido para hacer cosas como esta, y sucedió porque estaba intentando para corregirse por ejemplo. En la codificación delta, simplemente pondría (pseudo) "list.kill 30 at 5" y eliminaría 30 objetos de la lista después del quinto y luego autenticaría los datos, pero en cada cliente en lugar del servidor.

Pros: (Solo puedo pensar en uno de cada uno en este momento)

  1. Velocidad: Obviamente en mi último ejemplo que describí. Sería una diferencia mucho mayor que el ejemplo anterior. En general, no puedo decir honestamente por experiencia cuál de ellos sería más común, ya que trabajo mucho más con los cálculos muertos

Contras:

  1. Si está actualizando su sistema y desea agregar más datos que deberían editarse a través del delta, ¡tendrá que crear nuevas funciones para cambiar esos datos! (por ejemplo, como antes "list.kill 30 at 5" ¡Oh, mierda, necesito un método de deshacer agregado al cliente! "list.kill undo")

Cálculo muerto: En pocas palabras, aquí hay una analogía. Estoy escribiendo un mapa para alguien sobre cómo llegar a una ubicación, y solo incluyo los puntos de dónde ir en general, porque es lo suficientemente bueno (pare en el edificio, gire a la izquierda). Alguien más mapa incluye los nombres de las calles y también cuántos grados para girar a la izquierda, ¿es eso necesario? (No...)

El cálculo muerto es donde cada cliente tiene un algoritmo que es constante por cliente. Los datos se modifican prácticamente al decir qué datos se deben modificar y cómo hacerlo. El cliente cambia los datos por su cuenta. Un ejemplo es que si tengo un personaje que no es mi jugador, pero está siendo movido por otra persona que juega conmigo, ¡no debería tener que actualizar los datos en cada cuadro porque muchos de los datos son consistentes!

Digamos que mi personaje se mueve en alguna dirección, muchos servidores enviarán datos a los clientes que dicen (casi por cuadro) dónde está el jugador, y que se está moviendo (por motivos de animación). ¡Son tantos datos innecesarios! ¿Por qué demonios necesito actualizar cada cuadro, dónde está la unidad y en qué dirección está mirando y que se está moviendo? En pocas palabras: no lo hago. ¡Actualiza los clientes solo cuando cambia la dirección, cuando cambia el verbo (isMoving = true?) Y cuál es el objeto! Luego, cada cliente moverá el objeto en consecuencia.

Personalmente, esta es una táctica de sentido común. Es algo que pensé que era inteligente al inventar hace mucho tiempo, que resultó ser utilizado todo el tiempo.

Respuestas

Para ser franco, lea la publicación de James y lea lo que dije sobre los datos. Sí, definitivamente deberías usar la codificación delta, pero piensa también en usar el cálculo muerto.

Personalmente, instanciaría los datos en el cliente, cuando reciba información sobre ellos desde el servidor (algo que sugirió).

Solo los objetos que pueden cambiar deben notarse como editables en primer lugar, ¿verdad? ¡Me gusta su idea de incluir que un objeto debe tener datos de red, a través de su sistema de componentes y entidades! Es inteligente y debería funcionar bien. Pero nunca debe dar a los pinceles (o cualquier información que sea absolutamente consistente) ningún método de red. No lo necesitan, ya que es algo que ni siquiera puede cambiar (cliente a cliente).

Si es algo así como una puerta, le daría datos de red pero solo un booleano sobre si está abierto o no, entonces, obviamente, qué tipo de objeto es. El cliente debe saber cómo modificarlo, por ejemplo, está abierto, ciérrelo, cada cliente recibe que todos deben cerrarlo, por lo que debe cambiar los datos booleanos y luego animar la puerta para que se cierre.

En cuanto a cómo debería saber qué variables conectar en red, podría tener un componente que sea realmente un SUB-objeto, y darle componentes que le gustaría conectar en red. Otra idea es no solo tener, AddComponent("whatever")sino también AddNetComponent("and what have you")porque suena más inteligente personalmente.

Joshua Hedges
fuente
¡Esta es una respuesta ridículamente larga! Lo siento mucho por eso. Como tenía la intención de proporcionar solo una pequeña cantidad de conocimiento y luego mis 2 centavos sobre algunas cosas. Así que entiendo que mucho de esto puede ser un poco innecesario de notar.
Joshua Hedges
3

Iba a escribir un comentario, pero decidió que podría ser suficiente información para una respuesta.

Primero, +1 para una pregunta tan bien escrita con toneladas de detalles para juzgar la respuesta.

Para la carga de datos, el cliente debería cargar el mundo desde el archivo mundial. Si sus entidades tienen identificadores que provienen del archivo de datos, también los cargaría de forma predeterminada para que su sistema de red pueda referirse a ellos para saber de qué objetos está hablando. Todos los que carguen los mismos datos iniciales deberían significar que todos tienen los mismos ID para esos objetos.

En segundo lugar, no cree un componente NetworkComponent ya que esto no haría nada más que replicar datos en otros componentes existentes (la física, la animación y similares son algunas cosas comunes para enviar). Para usar su propio nombre, es posible que desee crear un NetworkComponentManager. Esto estaría un poco alejado de la otra relación Component to ComponentManager que tenga, pero esto podría ser instanciado cuando inicie un juego en red y tenga cualquier tipo de componentes que tengan un aspecto de red para que entregue sus datos al administrador para que pueda empaquetarlo y enviarlo Aquí es donde su funcionalidad de Guardar / Cargar podría usarse si tiene algún tipo de mecanismo de serialización / deserialización que también podría usar para empaquetar datos para, como se mencionó,

Dada su pregunta y nivel de información, no creo que necesite entrar en más detalles, pero si algo no está claro, publique un comentario y actualizaré la respuesta para abordarlo.

Espero que esto ayude.

James
fuente
Entonces, ¿lo que está diciendo es que los componentes que deberían conectarse en red deberían implementar algún tipo de interfaz como esta ?: void SetNetworkedVariable (nombre de cadena, valor NetworkedVariable); NetworkedVariable GetNetworkedVariable (nombre de cadena); Donde NetworkedVariable se usa con el propósito de interpolar y otras cosas de la red. Sin embargo, no sé cómo identificar qué componentes implementan esto. Podría usar identificación de tipo de tiempo de ejecución, pero eso me parece feo.
Carter