Questo di solito viene fatto usando i messaggi. Puoi trovare molti dettagli in altre domande su questo sito, come qui o lì .
Per rispondere al tuo esempio specifico, una strada da percorrere è quella di definire una piccola Message
classe che i tuoi oggetti possano elaborare, ad esempio:
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);
}
}
In questo modo non ti "inquina" Obj
interfaccia della classe con metodi relativi ai componenti. Alcuni componenti possono scegliere di elaborare il messaggio, altri potrebbero semplicemente ignorarlo.
Puoi iniziare chiamando questo metodo direttamente da un altro oggetto:
Message msg(obj1, "EmitForce(5.0,0.0,0.0)");
obj2.ProcessMessage(msg);
In questo caso, obj2
's Physics
sceglierà il messaggio, e fare tutto ciò di trasformazione che deve fare. Al termine, sarà:
- Invia un messaggio "SetPosition" a se stesso, che il
Position
componente sceglierà;
- O accedi direttamente al
Position
componente per le modifiche (piuttosto errato per una progettazione basata su componenti puri, poiché non puoi presumere che ogni oggetto abbia un Position
componente, ma il Position
componente potrebbe essere un requisito di Physics
).
È generalmente una buona idea ritardare l'effettiva elaborazione del messaggio alla successiva all'aggiornamento del componente . Elaborarlo immediatamente potrebbe significare inviare messaggi ad altri componenti di altri oggetti, quindi inviare un solo messaggio potrebbe significare rapidamente uno stack di spaghetti inestricabile.
Probabilmente dovrai scegliere un sistema più avanzato in seguito: code di messaggi asincrone, invio di messaggi a un gruppo di oggetti, registrazione / annullamento della registrazione per componente dai messaggi, ecc.
La Message
classe può essere un contenitore generico per una stringa semplice come mostrato sopra, ma l'elaborazione delle stringhe in fase di runtime non è davvero efficiente. Puoi cercare un contenitore di valori generici: stringhe, numeri interi, float ... Con un nome o, meglio ancora, un ID, per distinguere diversi tipi di messaggi. Oppure puoi anche derivare una classe base per soddisfare esigenze specifiche. Nel tuo caso, potresti immaginare un EmitForceMessage
che deriva Message
e aggiunge il vettore di forza desiderato, ma fai attenzione al costo di runtime di RTTI se lo fai.