Обычно это делается с помощью сообщений. Вы можете найти много деталей в других вопросах на этом сайте, как здесь или там .
Чтобы ответить на ваш конкретный пример, можно определить небольшой Message
класс, который могут обрабатывать ваши объекты, например:
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);
}
}
Таким образом, вы не «загрязняете» Obj
интерфейс вашего класса компонентными методами. Некоторые компоненты могут выбрать обработку сообщения, некоторые могут просто игнорировать его.
Вы можете начать с вызова этого метода непосредственно из другого объекта:
Message msg(obj1, "EmitForce(5.0,0.0,0.0)");
obj2.ProcessMessage(msg);
В этом случае obj2
s Physics
выберет сообщение и выполнит любую необходимую ему обработку. Когда это будет сделано, оно будет либо:
- Отправьте сообщение «SetPosition» себе, что
Position
выберет компонент;
- Или напрямую получить доступ к
Position
компоненту для модификаций (что совершенно неверно для дизайна, основанного исключительно Position
на Position
компонентах , поскольку нельзя предполагать, что каждый объект имеет компонент, но компонент может быть требованиемPhysics
).
Как правило, рекомендуется отложить фактическую обработку сообщения до обновления следующего компонента. Его немедленная обработка может означать отправку сообщений другим компонентам других объектов, поэтому отправка только одного сообщения может быстро привести к неразрывному стеку спагетти.
Возможно, позже вам придется перейти на более продвинутую систему: асинхронные очереди сообщений, отправка сообщений в группу объектов, регистрация / отмена регистрации для каждого компонента и т. Д.
Message
Класс может быть универсальным контейнером для простой строки , как показано выше, но обработка строк во время выполнения не очень эффективные. Вы можете использовать контейнер общих значений: строки, целые числа, числа с плавающей запятой ... С именем или, еще лучше, идентификатором, чтобы различать различные типы сообщений. Или вы также можете получить базовый класс для удовлетворения конкретных потребностей. В вашем случае вы можете представить себе, EmitForceMessage
что происходит от Message
и добавляет желаемый вектор силы, но остерегайтесь затрат времени выполнения RTTI, если вы это сделаете.
dynamic_cast
могут стать узким местом, но я пока не буду об этом беспокоиться. Вы все еще можете оптимизировать это позже, если это станет проблемой. Основанные на CRC идентификаторы классов работают как шарм.То, что я сделал, чтобы решить проблему, похожую на ту, которую вы показываете, это добавить некоторые специфические обработчики компонентов и добавить какую-то систему разрешения событий.
Таким образом, в случае вашего объекта «Физика», когда он инициализируется, он добавляет себя в центральный менеджер объектов Физики. В игровом цикле менеджеры такого типа имеют свой собственный шаг обновления, поэтому при обновлении этого PhysicsManager он вычисляет все физические взаимодействия и добавляет их в очередь событий.
После того, как вы создадите все свои события, вы можете разрешить свою очередь событий, просто проверив, что произошло, и предприняв соответствующие действия; в вашем случае должно быть событие, говорящее о том, что объекты A и B как-то взаимодействовали, поэтому вы вызываете свой метод emitForceOn.
Плюсы этого метода:
Минусы:
Надеюсь, это поможет.
PS: Если у кого-то есть более чистый / лучший способ решить эту проблему, я бы очень хотел это услышать.
источник
Несколько вещей, чтобы отметить на этом дизайне:
источник