Renuncia: Yo sé lo que un patrón de sistema entidad es y estoy no usarlo.
He leído mucho sobre separar objetos y renderizar. Sobre el hecho de que la lógica del juego debe ser independiente del motor de renderizado subyacente. Eso está muy bien y tiene mucho sentido, pero también causa muchos otros dolores:
- necesidad de sincronización entre el objeto lógico y el objeto de representación (el que mantiene el estado de la animación, los sprites, etc.)
- necesita abrir el objeto lógico al público para que el objeto de representación lea el estado real del objeto lógico (a menudo, lo que hace que el objeto lógico se transforme fácilmente en un objeto getter y setter tonto)
Esto no me parece una buena solución. Por otro lado, es muy intuitivo imaginar un objeto como su representación en 3D (o 2d) y también es muy fácil de mantener (y posiblemente también mucho más encapsulado).
¿Hay alguna manera de mantener la representación gráfica y la lógica del juego juntas (evitando problemas de sincronización) pero abstrayendo el motor de renderizado? ¿O hay una manera de separar la lógica del juego y el renderizado que no causa los inconvenientes anteriores?
(posiblemente con ejemplos, no soy muy bueno para entender conversaciones abstractas)
fuente
Respuestas:
Supongamos que tiene una escena compuesta de un mundo , un jugador y un jefe. Ah, y este es un juego en tercera persona, así que también tienes una cámara .
Entonces su escena se ve así:
(Al menos, esos son los datos básicos . La forma en que contenga los datos depende de usted).
Solo quieres actualizar y renderizar la escena cuando estás jugando, no cuando estás en pausa o en el menú principal ... ¡así que lo adjuntas al estado del juego!
Ahora tu estado de juego tiene una escena. A continuación, desea ejecutar la lógica en la escena y renderizar la escena. Para la lógica, solo ejecuta una función de actualización.
De esa manera puedes mantener toda la lógica del juego en la
Scene
clase. Y solo por el bien de referencia, un sistema de componentes de entidad podría hacerlo así:De todos modos, ahora has logrado actualizar tu escena. ¡Ahora quieres mostrarlo! Para lo cual hacemos algo similar a lo anterior:
Ahí tienes. El renderSystem lee la información de la escena y muestra la imagen adecuada. Simplificado, el método para renderizar la escena podría verse así:
Realmente simplificado, aún necesitarías, por ejemplo, aplicar una rotación y traducción en función de dónde está tu jugador y dónde está mirando. (Mi ejemplo es un juego en 3D, si vas con 2D, será un paseo por el parque).
Espero que esto sea lo que estabas buscando? Como se puede recordar de lo anterior, al sistema de render no le importa la lógica del juego . Solo usa el estado actual de la escena para renderizar, es decir, extrae la información necesaria para renderizar. ¿Y la lógica del juego? No le importa lo que haga el renderizador. ¡Diablos, no le importa si se muestra en absoluto!
Y tampoco necesita adjuntar información de representación a la escena. Debería ser suficiente que el renderizador sepa que necesita representar un orco. Ya habrá cargado un modelo orco, que el renderizador sabe mostrar.
Esto debería satisfacer sus requisitos. La representación gráfica y la lógica están acopladas , porque ambas usan los mismos datos. Sin embargo, están separados , ¡porque ninguno de los dos depende del otro!
EDITAR: ¿Y solo para responder por qué uno lo haría así? Porque es más fácil es la razón más simple. No necesita pensar en "tal y tal sucedió, ahora debería actualizar los gráficos". En cambio, haces que las cosas sucedan, y cada cuadro del juego mira lo que está sucediendo actualmente, y lo interpreta de alguna manera, dándote un resultado en pantalla.
fuente
Su título hace una pregunta diferente al contenido de su cuerpo. En el título, pregunta por qué la lógica y el renderizado deberían estar separados, pero en el cuerpo pregunta por implementaciones de sistemas de lógica / gráficos / renderizado.
La segunda pregunta se ha abordado anteriormente , por lo que me centraré en la primera pregunta.
Razones para separar la lógica y la representación:
En una configuración de OOP, crear instancias de nuevos objetos tiene un costo, pero en mi experiencia, el costo de los recursos del sistema es un pequeño precio a pagar por la capacidad de pensar e implementar las cosas específicas que necesito hacer.
fuente
Esta respuesta es solo para construir una intuición de por qué es importante separar la representación y la lógica, en lugar de sugerir directamente ejemplos prácticos.
Supongamos que tenemos un elefante grande , nadie en la habitación puede ver el elefante completo. tal vez todos no estén de acuerdo en lo que realmente es. Porque todos ven una parte diferente del elefante y solo pueden lidiar con esa parte. Pero al final esto no cambia el hecho de que es un gran elefante.
El elefante representa el objeto del juego con todos sus detalles. Pero en realidad nadie necesita saber todo sobre el elefante (objeto del juego) para poder hacer su funcionalidad.
Acoplando la lógica del juego y el renderizado es en realidad como hacer que todos vean al elefante completo. Si algo cambió, todos deben saberlo. Si bien en la mayoría de los casos solo necesitan ver la parte en la que solo están interesados. Si algo cambió a la persona que lo conoce, solo necesita decirle a la otra persona sobre el resultado de ese cambio, eso es lo que solo es importante para él (piense en esto como comunicación a través de mensajes o interfaces).
Los puntos que mencionó no son inconvenientes, solo son inconvenientes si hubiera más dependencias de las que debería haber en el motor, en otras palabras, los sistemas ven partes del elefante más de lo que deberían. Y esto significa que el motor no fue diseñado "correctamente".
Solo necesita sincronización con su definición formal si está utilizando un motor de subprocesos múltiples donde coloca la lógica y la representación en dos subprocesos diferentes, e incluso un motor que necesita mucha sincronización entre sistemas no está especialmente diseñado.
De lo contrario, la forma natural de lidiar con este caso es diseñar el sistema como entrada / salida. La actualización hace la lógica y genera el resultado. El renderizado solo se alimenta con los resultados de la actualización. Realmente no necesitas exponer todo. Solo expone una interfaz que se comunica entre las dos etapas. La comunicación entre diferentes partes del motor debe ser a través de abstracciones (interfaces) y / o mensajes. No debe exponerse ninguna lógica interna o estados.
Tomemos un ejemplo de gráfico de escena simple para explicar la idea.
La actualización generalmente se realiza a través de un solo bucle llamado bucle de juego (o posiblemente a través de múltiples bucles de juego, cada uno de los cuales se ejecuta en un hilo separado). Una vez que el bucle se actualizó alguna vez objeto del juego Solo necesita decir a través de mensajes o interfaces que los objetos 1 y 2 se actualizaron y alimentarlo con la transformación final.
El sistema de representación solo toma la transformación final y no sabe qué cambió realmente sobre el objeto (por ejemplo, ocurrió una colisión específica, etc.). Ahora para renderizar ese objeto solo necesita la ID de ese objeto y la transformación final. Después de eso, el renderizador alimentará la API de renderizado con la malla y la transformación final sin saber nada más.
fuente