¿Cómo puedo hacer que los objetos interactúen y se comuniquen entre sí sin forzar una jerarquía?

9

Espero que estas divagaciones aclaren mi pregunta: sin embargo, entendería totalmente si no lo harían, así que avíseme si ese es el caso e intentaré aclararme.

Conoce a BoxPong , un juego muy simple que hice para familiarizarme con el desarrollo de juegos orientado a objetos. Arrastra el cuadro para controlar el balón y recoge las cosas amarillas.
Hacer BoxPong me ayudó a formular, entre otras cosas, una pregunta fundamental: ¿cómo puedo tener objetos que interactúen entre sí sin tener que "pertenecer" el uno al otro? En otras palabras, ¿hay alguna forma de que los objetos no sean jerárquicos, sino que coexistan? (Voy a entrar en más detalles a continuación).

Sospecho que el problema de los objetos que coexisten es común, así que espero que haya una forma establecida de resolverlo. No quiero reinventar la rueda cuadrada, así que supongo que la respuesta ideal que estoy buscando es "aquí hay un patrón de diseño que se usa comúnmente para resolver su tipo de problema".

Especialmente en juegos simples como BoxPong, está claro que hay, o debería haber, un puñado de objetos que coexisten en el mismo nivel. Hay una caja, hay una pelota, hay un coleccionable. Sin embargo, todo lo que puedo expresar en lenguajes orientados a objetos, o al menos eso parece, son relaciones estrictas HAS-A . Eso se hace a través de las variables miembro. No puedo comenzar bally dejar que haga lo suyo, necesito que pertenezca permanentemente a otro objeto. He configurarlo de manera que el objeto principal del juego tiene una caja, y la caja a su vez tiene una bola, y tiene un contador de puntuación. Cada objeto también tiene unupdate()método, que calcula la posición, dirección, etc., y sigo un camino similar allí: llamo al método de actualización del objeto principal del juego, que llama a los métodos de actualización de todos sus hijos, y ellos a su vez llaman a los métodos de actualización de todos sus hijos. Esta es la única forma en que puedo ver para hacer un juego orientado a objetos, pero siento que no es la forma ideal. Después de todo, no pensaría exactamente que la pelota pertenece a la caja, sino que está en el mismo nivel e interactuando con ella. Supongo que eso se puede lograr convirtiendo todos los objetos del juego en variables miembro del objeto principal del juego, pero no veo que eso resuelva nada. Quiero decir ... dejando de lado el desorden obvio, ¿cómo habría una manera para que la pelota y la caja se conocieran , es decir, interactuaran?

También está el problema de los objetos que necesitan pasar información entre ellos. Tengo bastante experiencia escribiendo código para SNES, donde tienes acceso a prácticamente toda la RAM todo el tiempo. Digamos que está haciendo un enemigo personalizado para Super Mario World , y desea que elimine todas las monedas de Mario, luego simplemente almacene cero para abordar $ 0DBF, no hay problema. No hay limitaciones para decir que los enemigos no pueden acceder al estado del jugador. Supongo que esta libertad me ha echado a perder, porque con C ++ y similares a menudo me pregunto cómo hacer que un valor sea accesible para otros objetos (o incluso globales).
Usando el ejemplo de BoxPong, ¿y si quisiera que la pelota rebotara en los bordes de la pantalla? widthy heightson propiedades de la Gameclase,balltener acceso a ellos. Podría transmitir este tipo de valores (ya sea a través de los constructores o los métodos donde se necesitan), pero eso me grita una mala práctica.

Supongo que mi principal problema es que necesito que los objetos se conozcan, pero la única forma en que puedo hacerlo es mediante una jerarquía estricta, que es fea y poco práctica.

He oído hablar de "clases de amigos" en C ++ y sé cómo funcionan, pero si son la solución final, entonces, ¿cómo es que no veo friendpalabras clave vertidas en todos los proyectos de C ++ y cómo es que Qué concepto no existe en todos los lenguajes OOP? (Lo mismo ocurre con los punteros de función, de los que acabo de enterarme recientemente).

Gracias de antemano por las respuestas de cualquier tipo, y nuevamente, si hay una parte que no tiene sentido para usted, hágamelo saber.

vvye
fuente
2
Gran parte de la industria del juego se ha movido hacia la arquitectura Entity-Component-System y sus variaciones. Es una mentalidad diferente de los enfoques OO tradicionales, pero funciona bien y tiene sentido una vez que el concepto se asimila. Unity lo utiliza. En realidad, Unity solo usa la parte Entity-Component pero se basa en ECS.
Dunk
El problema de permitir que las clases colaboren entre sí sin conocimiento mutuo se resuelve mediante el patrón de diseño del Mediador. ¿Lo has visto?
Fuhrmanator

Respuestas:

13

En general, resulta muy malo si los objetos del mismo nivel se conocen entre sí. Una vez que los objetos se conocen, están atados o acoplados entre sí. Esto los hace difíciles de cambiar, difíciles de probar, difíciles de mantener.

Funciona mucho mejor si hay algún objeto "arriba" que conoce los dos y puede establecer las interacciones entre ellos. El objeto que conoce a los dos pares puede vincularlos mediante la inyección de dependencia o mediante eventos o mediante el paso de mensajes (o cualquier otro mecanismo de desacoplamiento). Sí, eso lleva a un poco de jerarquía artificial, pero es mucho mejor que el desorden de espagueti que se produce cuando las cosas simplemente interactúan de manera involuntaria. Eso es solo más importante en C ++, ya que también necesita algo para poseer la vida útil de los objetos.

En resumen, puede hacerlo simplemente teniendo objetos uno al lado del otro unidos por acceso ad hoc, pero es una mala idea. La jerarquía proporciona orden y propiedad clara. Lo principal a recordar es que los objetos en el código no son necesariamente objetos en la vida real (o incluso en el juego). Si los objetos en el juego no forman una buena jerarquía, una abstracción diferente puede ser mejor.

Telastyn
fuente
2

Usando el ejemplo de BoxPong, ¿y si quisiera que la pelota rebotara en los bordes de la pantalla? ancho y alto son propiedades de la clase Juego, y necesitaría que la pelota tenga acceso a ellos.

¡No!

Creo que el problema principal que tienes es que estás tomando la "Programación Orientada a Objetos" demasiado literalmente. En OOP, un objeto no representa una "cosa" sino una "idea" que significa que una "Bola", "Juego", "Física", "Matemáticas", "Fecha", etc. Todos son objetos válidos. Tampoco se exige que los objetos "sepan" nada. Por ejemplo, le Date.Now().getTommorrow()preguntaría a la computadora qué día es hoy, aplique reglas de fecha arcana para determinar la fecha de mañana y se la devolverá a la persona que llama. El Dateobjeto no sabe nada más, solo necesita solicitar información según sea necesario del sistema. Además, Math.SquareRoot(number)no necesita saber nada más que la lógica de cómo calcular una raíz cuadrada.

Entonces, en su ejemplo que cité, la "Bola" no debería saber nada sobre la "Caja". Las cajas y las bolas son ideas completamente diferentes y no tienen derecho a hablar entre ellas. Pero un motor de Física sí sabe qué es un Box and Ball (o al menos, ThreeDShape), y sabe dónde están y qué debería estar pasando con ellos. Entonces, si la bola se encoge porque hace frío, el motor de física le diría a esa instancia de bola que ahora es más pequeña.

Es como construir un auto. Un chip de computadora no sabe nada sobre el motor de un automóvil, pero un automóvil puede usar un chip de computadora para controlar un motor. La simple idea de usar cosas pequeñas y simples juntas para crear una cosa un poco más grande y más compleja, que en sí misma es reutilizable como un componente de otras partes más complejas.

Y en tu ejemplo de Mario, ¿qué pasa si estás en una sala de desafío donde tocar a un enemigo no agota las monedas de Marios, sino que simplemente lo expulsa de esa sala? Está fuera del espacio de ideas de Mario o del enemigo que Mario debe perder monedas al tocar a un enemigo (de hecho, si Mario tiene una estrella invulnerabilidad, mata al enemigo en su lugar). Entonces, cualquier objeto (dominio / idea) que sea responsable de lo que sucede cuando Mario toca a un enemigo es el único que necesita saber acerca de cualquiera de ellos, y debe hacer lo que sea por cualquiera de ellos (en la medida en que ese objeto permita cambios externos impulsados )

Además, con sus afirmaciones sobre los objetos que llaman niños Update(), eso es extremadamente propenso a los errores, ¿y si Updatese llama varias veces por fotograma de diferentes padres? (incluso si captas esto, eso es tiempo de CPU perdido que puede ralentizar tu juego) Todos deberían tocar lo que necesitan, cuando lo necesitan. Si está utilizando Update (), debería estar utilizando algún tipo de patrón de suscripción para asegurarse de que todas las Update se llamen una vez por cuadro (si esto no se maneja en Unity)

Aprender a definir sus ideas de dominio en bloques claros, aislados, bien definidos y fáciles de usar será el factor más importante en la forma en que puede utilizar OOP.

Tezra
fuente
1

Conoce BoxPong, un juego muy simple que hice para familiarizarme con el desarrollo de juegos orientado a objetos.

Hacer BoxPong me ayudó a formular, entre otras cosas, una pregunta fundamental: ¿cómo puedo tener objetos que interactúen entre sí sin tener que "pertenecer" el uno al otro?

Tengo bastante experiencia escribiendo código para SNES, donde tienes acceso a prácticamente toda la RAM todo el tiempo. Digamos que está haciendo un enemigo personalizado para Super Mario World, y desea que elimine todas las monedas de Mario, luego simplemente almacene cero para abordar $ 0DBF, no hay problema.

Parece que le falta el punto de la programación orientada a objetos.

La programación orientada a objetos se trata de administrar dependencias mediante la inversión selectiva de ciertas dependencias clave en su arquitectura para que pueda evitar la rigidez, la fragilidad y la no reutilización.

¿Qué es la dependencia? La dependencia es la dependencia de otra cosa. Cuando almacena cero para abordar $ 0DBF, se basa en el hecho de que esa dirección es donde se encuentran las monedas de Mario y que las monedas se representan como un número entero. Su código de Enemigo personalizado depende del código que implementa Mario y sus monedas. Si realiza un cambio en el lugar donde Mario almacena sus monedas en la memoria, debe actualizar manualmente todo el código que hace referencia a la ubicación de la memoria.

El código orientado a objetos se trata de hacer que su código dependa de abstracciones y no de detalles. Entonces en lugar de

class Mario
{
    public:
        int coins;
}

tu escribirias

class Mario
{
    public:
        void LoseCoins();

    private:
        int coins;
}

Ahora, si desea cambiar la forma en que Mario almacena sus monedas de un int a un largo o doble o almacenarlo en la red o almacenarlo en una base de datos o iniciar algún otro proceso largo, realice el cambio en un solo lugar: el Mario, y todos los demás códigos siguen funcionando sin cambios.

Por lo tanto, cuando preguntas

¿Cómo puedo tener objetos que interactúan entre sí sin tener que "pertenecer" el uno al otro?

realmente estás preguntando:

¿Cómo puedo tener un código que depende directamente el uno del otro sin ninguna abstracción?

que no es programación orientada a objetos.

Le sugiero que comience leyendo todo aquí: http://objectmentor.com/omSolutions/oops_what.html y luego busque en YouTube todo por Robert Martin y mire todo.

Mis respuestas se derivan de él, y algunas de ellas se citan directamente de él.

Mark Murfin
fuente
Gracias por la respuesta (y la página a la que enlazaste; parece interesante). De hecho, sé de abstracción y reutilización, pero supongo que no lo puse muy bien en mi respuesta. Sin embargo, a partir del código de ejemplo que proporcionó, ¡ahora puedo ilustrar mejor mi punto! Básicamente estás diciendo que el objeto enemigo no debería hacerlo mario.coins = 0;, pero mario.loseCoins();, lo cual está bien y es cierto, pero mi punto es, ¿cómo puede el enemigo tener acceso al marioobjeto de todos modos? marioser miembro variable de enemyno me parece correcto.
vvye
Bueno, la respuesta simple es pasar a Mario como argumento para una función en Enemy. Es posible que tenga una función como marioNearby () o attackMario () que tomaría un Mario como argumento. Entonces, cuando se active la lógica detrás de cuándo un Enemigo y un Mario deben interactuar, llamarías enemigo.marioNearby (mario) que llamaría mario.loseCoins (); Más adelante en el camino, puede decidir que hay una clase de enemigos que hacen que Mario pierda solo una moneda o incluso gane monedas. Ahora tiene un lugar para hacer ese cambio que no causa cambios en los efectos secundarios a otro código.
Mark Murfin
Al pasar a Mario a un enemigo, los acabas de acoplar. Mario y Enemy no deberían saber que el otro es una cosa. Es por eso que creamos objetos de orden superior para gestionar cómo juntar los objetos simples.
Tezra el
@Tezra Pero entonces, ¿no son estos objetos de orden superior no reutilizables en absoluto? Parece que estos objetos actúan como funciones, solo existen para ser el procedimiento que exhiben.
Steve Chamaillard
@SteveChamaillard Cada programa tendrá al menos un poco de lógica específica que no tiene sentido en ningún otro programa, pero la idea es mantener esta lógica aislada en algunas clases de alto orden. Si tienes un Mario, un enemigo y una clase de nivel, puedes reutilizar Mario y un enemigo en otros juegos. Si atas al enemigo y a Mario directamente el uno al otro, entonces cualquier juego que necesite uno también tiene que atraer al otro.
Tezra
0

Puede habilitar el acoplamiento suelto aplicando el patrón de mediador. Implementarlo requiere que tenga una referencia a un componente mediador que conozca todos los componentes receptores.

Se da cuenta del tipo de pensamiento "maestro del juego, por favor deja que esto y aquello suceda".

Un patrón generalizado es el patrón de publicación-suscripción. Es apropiado si el mediador no debe contener mucha lógica. De lo contrario, use un mediador hecho a mano que sepa dónde enrutar todas las llamadas y tal vez incluso modificarlas.

Hay variantes síncronas y asíncronas que generalmente se denominan bus de eventos, bus de mensajes o cola de mensajes. Búsquelos para determinar si son apropiados en su caso particular.

Hero Wanders
fuente