У меня есть игровой движок, в котором я отделяю физическое моделирование от функциональности игрового объекта. Итак, у меня есть чистый виртуальный класс для физического тела
class Body
из которого я буду выводить различные реализации физического моделирования. Тогда мой класс игрового объекта выглядит как
class GameObject {
public:
// ...
private:
Body *m_pBody;
};
и я могу подключить любую реализацию, которая мне нужна для этой конкретной игры. Но мне может понадобиться доступ ко всем функциям Body, когда у меня есть только GameObject. Я обнаружил, что пишу кучу таких вещей, как
Vector GameObject::GetPosition() const { return m_pBody->GetPosition(); }
У меня есть искушение поцарапать их все и просто сделать что-то вроде
pObject->GetBody()->GetPosition();
но это кажется неправильным (т.е. нарушает закон Деметры). Кроме того, он просто подталкивает многословие от реализации к использованию. Поэтому я ищу другой способ сделать это.





Один из подходов, который вы можете использовать, - это разделить интерфейс Body на несколько интерфейсов, каждый из которых имеет разные цели, и предоставить GameObject право собственности только на те интерфейсы, которые он должен предоставлять.
class Positionable;
class Movable;
class Collidable;
//etc.
Конкретные реализации Body, вероятно, будут реализовывать все интерфейсы, но GameObject, которому нужно только раскрыть свою позицию, будет ссылаться (через внедрение зависимостей) только на интерфейс Positionable:
class BodyA : public Positionable, Movable, Collidable {
// ...
};
class GameObjectA {
private:
Positionable *m_p;
public:
GameObjectA(Positionable *p) { m_p = p; }
Positionable *getPosition() { return m_p; }
};
BodyA bodyA;
GameObjectA objA(&bodyA);
objA->getPosition()->getX();
Насколько я понимаю, идея состоит в том, чтобы полностью избавиться от m_pBody. Вместо этого у вас есть интерфейс Positionable, предоставляющий GetPosition () и связанные с ним методы. Затем вы реализуете эти методы в GameBody. Суть Закона Деметры - убедиться, что вещи, которые следует спрятать, есть.
Но в чем разница между m_pBody и m_p? (На самом деле, большинство вещей, которые я хотел бы сделать для / чтения из Body, я бы также хотел сделать то же самое для GameObject. Вся эта вещь Positionable / Movable / Collidable, кажется, просто обходит проблему.
Игровые иерархии не должны включать в себя большое количество наследований. Я не могу указать вам на какие-либо веб-страницы, но это ощущение, которое я почерпнул из нескольких источников, в первую очередь из серии игровых драгоценных камней.
У вас могут быть иерархии, такие как корабль-> tie_fighter, корабль-> x_wing. Но не PlaysSound-> tie_fighter. Ваш класс tie_fighter должен состоять из объектов, которые он должен представлять. Часть физики, часть графики и т. д. Вы должны предоставить минимальный интерфейс для взаимодействия с вашими игровыми объектами. Реализуйте как можно больше физической логики в движке или в физической части.
При таком подходе ваши игровые объекты становятся коллекциями более основных игровых компонентов.
При этом вы захотите иметь возможность устанавливать физическое состояние игровых объектов во время игровых событий. Таким образом, вы столкнетесь с проблемой, которую вы описали для настройки различных частей состояния. Это просто неприглядно, но это лучшее решение, которое я нашел до сих пор.
Недавно я попытался сделать функции состояния более высокого уровня, используя идеи из Box2D. Есть функция SetXForm для установки позиций и т. д. Еще одна для SetDXForm для скоростей и угловой скорости. Эти функции принимают прокси-объекты в качестве параметров, которые представляют различные части физического состояния. Используя подобные методы, вы можете уменьшить количество методов, которые вам понадобятся для установки состояния, но в конечном итоге вы, вероятно, все равно будете реализовывать более мелкие, и прокси-объекты будут больше работать, чем вы бы сэкономили, пропустив по нескольким методам.
Так что я не особо помог. Это было скорее опровержением предыдущего ответа.
Подводя итог, я бы рекомендовал вам придерживаться подхода, основанного на множестве методов. Между игровыми объектами и физическими объектами не всегда может быть простое отношение один к одному. Мы столкнулись с тем, что было намного проще иметь один игровой объект, представляющий все частицы от взрыва. Если бы мы уступили и просто выставили указатель тела, мы не смогли бы упростить задачу.
Я могу просто оставить все повторяющиеся методы. Но это довольно неприятно.
Правильно ли я понимаю, что вы отделяете физику чего-то от его игрового представления?
то есть, вы бы увидели что-то вроде этого:
class CompanionCube
{
private:
Body* m_pPhysicsBody;
};
?
Если так, мне это плохо пахнет. Технически ваш GameObject является является физическим объектом, поэтому он должен быть производным от Body.
Похоже, вы планируете поменять местами физические модели, и именно поэтому вы пытаетесь сделать это с помощью агрегации, и если это так, я бы спросил: «Планируете ли вы менять типы физики во время выполнения или во время компиляции? ? ".
Если время компиляции - ваш ответ, я бы получил ваши игровые объекты из Body и сделал бы Body как typedef для любого физического тела, которое вы хотите использовать по умолчанию.
Если это среда выполнения, вам придется написать класс Body, который выполняет это внутреннее переключение, что может быть неплохой идеей, если ваша цель - поиграть с другой физикой.
В качестве альтернативы вы, вероятно, обнаружите, что у вас будут разные «родительские» классы для Body в зависимости от типа игрового объекта (вода, твердое тело и т. д.), Поэтому вы можете просто сделать это явным в своем производном.
В любом случае, я перестану болтать, так как этот ответ основан на многих предположениях. ;) Дайте знать, если я не на базе, и я удалю свой ответ.
Это своего рода микс. Я не могу получить игровые объекты из Body, потому что некоторые объекты могут использовать разные типы Body (как вы предложили); но это не так жестко, как «Вода должна происходить от WaterBody», поскольку нам может понадобиться WaterBody1 и WaterBody2, и мы можем выбирать между ними либо во время выполнения, либо во время компиляции.
Кроме того, проблема с написанием класса Body, который переключается внутри, заключается в том, что он подталкивает ту же проблему к выполнение Body (с тех пор у нас будут аналогично производные классы, скажем, от BodyImpl, и мы захотим переслать все BodyImpl выполняет функции Body).
Идея закона Деметры заключается в том, что ваш GameObject не должен иметь таких функций, как GetPosition(). Вместо этого он должен иметь функции MoveForward(int) или TurnLeft(), которые могут вызывать GetPosition() (вместе с другими функциями) изнутри. По сути, они переводят один интерфейс в другой.
Если ваша логика требует функции GetPosition(), имеет смысл превратить ее в интерфейс a la Ates Goral. В противном случае вам придется переосмыслить, почему вы так глубоко захватываете объект, чтобы вызвать методы его подобъектов.
GameObject определенно нуждается в таких вещах, как GetPosition (), поскольку это может быть, скажем, мяч (для которого MoveForward не имеет смысла).
В этом случае «ваша логика требует функции GetPosition ()», и поэтому «имеет смысл превратить ее в интерфейс a la Ates Goral». Но вам следует «переосмыслить, почему вы захватываете ... объект» (т.е. определить, нужно ли вам изменить дизайн, скажем, с помощью ImmovableGameObjects и MovableGameObjects).
Как будет работать этот редизайн? Также см. Мой комментарий к Атесу Горалу.
Я передумал - на самом деле я к этому и собираюсь. Что меня раньше смущало, так это то, что MoveForward и TurnLeft не имеют смысла в моем контексте, но другие функции (с аналогичной идеей) имеют.
Похоже, это просто переносит проблему на Positionable - нам по-прежнему приходится использовать двойную цепочку для получения любой релевантной информации.