Как реструктурировать эту иерархию кода (относящуюся к закону Деметры) - PullRequest
3 голосов
/ 22 января 2009

У меня есть игровой движок, в котором я отделяю физическое моделирование от функциональности игрового объекта. Итак, у меня есть чисто виртуальный класс для физического тела

class Body

, из которого я буду извлекать различные реализации физического моделирования. Мой класс игровых объектов выглядит как

class GameObject {
public:
   // ...
private:
   Body *m_pBody;
};

и я могу подключить любую реализацию, которая мне нужна для этой конкретной игры. Но мне может понадобиться доступ ко всем функциям Body, когда у меня есть только GameObject. Так что я обнаружил, что пишу тонны вещей вроде

Vector GameObject::GetPosition() const { return m_pBody->GetPosition(); }

Я испытываю желание поцарапать их всех и просто делать что-то вроде

pObject->GetBody()->GetPosition();

но это кажется неправильным (то есть нарушает закон Деметры). Плюс, это просто толкает многословие от реализации к использованию. Поэтому я ищу другой способ сделать это.

Ответы [ 4 ]

3 голосов
/ 22 января 2009

Идея закона Деметры заключается в том, что ваш GameObject не должен иметь такие функции, как GetPosition(). Вместо этого он должен иметь функции MoveForward(int) или TurnLeft(), которые могут вызывать GetPosition() (вместе с другими функциями) внутри. По сути, они переводят один интерфейс в другой.

Если вашей логике требуется функция GetPosition(), то имеет смысл превратить ее в интерфейс а-ля Ates Goral. В противном случае вам придется переосмыслить, почему вы так глубоко вбираете объект, чтобы вызывать методы для его подобъектов.

3 голосов
/ 22 января 2009

Один из подходов, который вы могли бы использовать, - это разделить интерфейс 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();
1 голос
/ 22 января 2009

Игровые иерархии не должны включать много наследства. Я не могу указать вам на какие-либо веб-страницы, но это чувство, которое я собрал из нескольких источников, особенно из серии игровых гемов.

Вы можете иметь иерархии, такие как ship-> tie_fighter, ship-> x_wing. Но не PlaysSound-> tie_fighter. Ваш класс tie_fighter должен состоять из объектов, которые он должен представлять сам. Физическая часть, графическая часть и т. Д. Вы должны предоставить минимальный интерфейс для взаимодействия с игровыми объектами. Реализуйте как можно больше логики физики в движке или физическом элементе.

При таком подходе ваши игровые объекты становятся коллекциями более базовых игровых компонентов.

Все это говорит о том, что вы захотите установить физическое состояние игровых объектов во время игровых событий. Таким образом, вы столкнетесь с проблемой, которую вы описали для установки различных частей состояния. Это просто неприлично, но это лучшее решение, которое я нашел до сих пор.

Я недавно пытался создавать функции состояний более высокого уровня, используя идеи из Box2D. Есть функция SetXForm для установки позиций и т. Д. Другая для SetDXForm для скоростей и угловых скоростей. Эти функции принимают прокси-объекты в качестве параметров, которые представляют различные части физического состояния. Используя такие методы, вы могли бы уменьшить количество методов, которые вам понадобятся для установки состояния, но, в конце концов, вы все равно могли бы реализовать более мелкозернистые, и прокси-объекты потребовали бы больше работы, чем вы бы сэкономили, пропустив на несколько методов.

Итак, я не сильно помог. Это было скорее опровержением предыдущего ответа.

Таким образом, я бы порекомендовал вам придерживаться подхода многих методов. Между игровыми объектами и физическими объектами не всегда могут быть простые отношения один к одному. Мы столкнулись с тем, что было гораздо проще иметь один игровой объект, представляющий все частицы от взрыва. Если бы мы сдались и просто выставили указатель тела, мы бы не смогли упростить проблему.

0 голосов
/ 22 января 2009

Правильно ли я понимаю, что вы отделяете физику чего-либо от представления игры?

То есть, вы бы увидели что-то вроде этого:

class CompanionCube
{
    private:
        Body* m_pPhysicsBody;
};

Если так, то это пахнет неправильно для меня. Технически ваш GameObject является физическим объектом, поэтому он должен быть производным от Body.

Звучит так, будто вы планируете менять физические модели, и именно поэтому вы пытаетесь сделать это с помощью агрегации, и если это так, я бы спросил: «Планируете ли вы менять физические типы во время выполнения, или время компиляции? ".

Если ответом будет время компиляции, я извлеку ваши игровые объекты из Body и сделаю Body typedef для любого физического тела, которое вы хотите иметь по умолчанию.

Если это время выполнения, вам нужно написать класс 'Body', который выполняет это переключение внутри, что может быть неплохой идеей, если ваша цель - поиграть с другой физикой.

В качестве альтернативы вы, вероятно, обнаружите, что у вас будут разные «родительские» классы для тела в зависимости от типа игрового объекта (вода, твердое тело и т. Д.), Так что вы можете просто сделать это явным при выводе.

Во всяком случае, я перестану бродить, так как этот ответ основан на многих догадках. ;) Дайте мне знать, если я не в базе, и я удалю свой ответ.

...