Eso generalmente se hace mediante mensajes. Puede encontrar muchos detalles en otras preguntas en este sitio, como aquí o allá .
Para responder a su ejemplo específico, un camino a seguir es definir una pequeña Message
clase que sus objetos puedan procesar, por ejemplo:
struct Message
{
Message(const Objt& sender, const std::string& msg)
: m_sender(&sender)
, m_msg(msg) {}
const Obj* m_sender;
std::string m_msg;
};
void Obj::Process(const Message& msg)
{
for (int i=0; i<m_components.size(); ++i)
{
// let components do some stuff with msg
m_components[i].Process(msg);
}
}
De esta manera no estás "contaminando" tu Obj
interfaz de clase con métodos relacionados con componentes. Algunos componentes pueden elegir procesar el mensaje, algunos simplemente pueden ignorarlo.
Puede comenzar llamando a este método directamente desde otro objeto:
Message msg(obj1, "EmitForce(5.0,0.0,0.0)");
obj2.ProcessMessage(msg);
En este caso, obj2
's Physics
elegirá el mensaje y realizará el procesamiento que sea necesario. Cuando termine, ya sea:
- Envíe un mensaje "SetPosition" a sí mismo, que el
Position
componente elegirá;
- O acceda directamente al
Position
componente para modificaciones (bastante incorrecto para un diseño basado en componentes puros, ya que no puede suponer que cada objeto tiene un Position
componente, pero el Position
componente podría ser un requisito Physics
).
En general, es una buena idea retrasar el procesamiento real del mensaje a la actualización del siguiente componente. Procesarlo de inmediato podría significar enviar mensajes a otros componentes de otros objetos, por lo que enviar solo un mensaje podría significar rápidamente una pila de espagueti inextricable.
Probablemente tendrá que ir a un sistema más avanzado más adelante: colas de mensajes asíncronos, envío de mensajes a un grupo de objetos, registro / anulación de registro por componente, etc.
La Message
clase puede ser un contenedor genérico para una cadena simple como se muestra arriba, pero el procesamiento de cadenas en tiempo de ejecución no es realmente eficiente. Puede buscar un contenedor de valores genéricos: cadenas, enteros, flotantes ... Con un nombre o mejor aún, un ID, para distinguir diferentes tipos de mensajes. O también puede derivar una clase base para satisfacer necesidades específicas. En su caso, podría imaginar un EmitForceMessage
derivado del Message
vector de fuerza deseado y agregarlo, pero tenga cuidado con el costo de tiempo de ejecución de RTTI si lo hace.
dynamic_cast
puede convertirse en un cuello de botella, pero no me preocuparé por ahora. Todavía puede optimizar esto más adelante si se convierte en un problema. Los identificadores de clase basados en CRC funcionan como un encanto.Lo que hice para resolver un problema similar al que muestra es agregar algunos controladores de componentes específicos y agregar algún tipo de sistema de resolución de eventos.
Entonces, en el caso de su objeto "Física", cuando se inicializa, se agregaría a un administrador central de objetos de Física. En el ciclo del juego, este tipo de gerentes tienen su propio paso de actualización, por lo que cuando este PhysicsManager se actualiza, calcula todas las interacciones físicas y las agrega a una cola de eventos.
Después de producir todos sus eventos, puede resolver su cola de eventos simplemente verificando lo que sucedió y tomando acciones según corresponda, en su caso, debería haber un evento que diga que los objetos A y B interactuaron de alguna manera, por lo que llama a su método emitForceOn.
Ventajas de este método:
Contras:
Espero que esto ayude.
PD: Si alguien tiene una forma más limpia / mejor de resolver esto, realmente me gustaría escucharlo.
fuente
Algunas cosas a tener en cuenta en este diseño:
fuente