2012-02-18 5 views
6

Это для небольшого игрового проекта с SDL на MinGW/Windows.Наследование/интерфейс решения для физического движка

Я работаю на физический движок, и моя идея состояла в том, чтобы иметь Physics::Object, что все физические объекты должны выводим из и регистрирует себя с глобальным Physics::System класса (это модель monostate), так что пользователь не необходимо отслеживать, какие объекты включены в физические вычисления, и просто нужно вызвать функцию типа Physics::System::PerformTimestepCalculation(double dt).

Это прекрасно работает, и я даже реализовал его с использованием одного производного класса Physics::Circle, который представляет собой 2-й круг. Я был очень доволен прогностическим обнаружением столкновения, хотя мне все еще нужно его оптимизировать.

В любом случае, я столкнулся с проблемой, когда я начал добавлять другие примитивы для включения в расчет, например. линия. Physics::System::PerformTimestepCalculation(double dt) был завален звонками Object::GetID() или аналогичными функциями (может ли избежать dynamic_cast <>), но я чувствую себя грязным.

Я сделал немного reading и понял, что мои элементы моей иерархии не подменяются (т. Е. Столкновение между двумя кругами очень сильно отличается от столкновения двух строк).

Мне нравится, как мой Physics::Objects «самостоятельно регистрируется» с классом System, поэтому они автоматически включаются в вычисления, и я действительно не хочу этого потерять.

Должны быть какие-то другие разумные пути проектирования. Как я могу лучше переделать вещи, чтобы не подменяемые объекты не мешали?

Edit FYI: В конце концов я оторвались сущность и формы свойства, подобно тому, как это было описано в принятом ответе, и аналогичные модели сущность-компонентной системы. Это означает, что у меня все еще есть логика yuk «это круг или линия, и это линия или круг?», Но я больше не притворяюсь, что полиморфизм помогает мне здесь. Это также означает, что я использую какой-то завод и могу иметь сразу несколько вычислительных миров!

+1

Вы имеете в виду у вас есть проблемы [двойной доставки] (http://en.wikipedia.org/wiki/Double_dispatch)? Если я правильно помню, [Modern C++ Design] (http: //www.amazon.ca/Modern-Design-Generic-Programming-Patterns/dp/0201704315) содержит главу о методах реализации, и это может вас заинтересовать. –

ответ

5

Самые успешные общедоступные физические двигатели не очень тяжелы для «шаблонов» или «объектно-ориентированного дизайна».

Вот краткое изложение, чтобы поддержать мое, правда, смелое, утверждение:

Chipmunk - написано на С, достаточно сказал.

Box2d - Написанный на C++, и здесь есть некоторый полиморфизм. существует иерархия форм (базовый класс b2Shape) с несколькими виртуальными функциями. Тем не менее, эта абстракция просачивается, как сито, и вы найдете множество отливок, чтобы листать классы по всему исходному коду. Существует также иерархия «контактов», которая оказывается более успешной, хотя с помощью одной виртуальной функции было бы тривиально переписать это без полиморфизма (как я полагаю, для чирикающего используется указатель на функцию). b2Body - это класс, используемый для представления твердых тел, и он не является виртуальным.

Bullet - Написано на C++, используется в тоннах игр. Тонны функций, тонны кода (относительно двух других). На самом деле есть базовый класс, который распространяется на жесткие тела и мягкие тела, но лишь небольшая часть кода может его использовать.Большая часть виртуальной функции базового класса связана с сериализацией (сохранение/загрузка состояния ядра), из двух оставшихся виртуальных функций, мягкое тело не может реализовать одно с TODO, информирующее нас о том, что некоторые хаки необходимо очистить. Не совсем озвучивание одобрения полиморфизма в физических двигателях.

Это много слов, и я даже не начал отвечать на ваш вопрос. Все, что я хочу забить, - это то, что полиморфизм - это не то, что эффективно применяется в существующих физических двигателях. И это, вероятно, не потому, что авторы не «получили» OO.

Так или иначе, мой совет: полиморфизм канава для вашего класса сущности. У вас не будет 100 различных типов, которые вы не сможете реорганизовать позже, данные формы вашего физического двигателя будут достаточно однородными (выпуклые полисы, коробки, сферы и т. Д.), И ваши данные сущности, вероятно, будут еще более однородные (вероятно, только твердые тела, чтобы начать с).

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

Учитывая это, самый чистый «шаблон», который следует соблюдать, будет фабричным образцом. Когда пользователи хотят, чтобы создать твердое тело, они должны сказать физику :: системе (действующую как завод), чтобы сделать это для них, так что в вашей физике :: Система:

// returning a smart pointer would not be unreasonable, but I'm returning a raw pointer for simplicity: 
rigid_body_t* AddBody(body_params_t const& body_params); 

А в коде клиента :

circle_params_t circle(1.f /*radius*/); 
body_params_t b(1.f /*mass*/, &circle /*shape params*/, xform /*system transform*/); 
rigid_body_t* body = physics_system.AddBody(b); 

Anyhoo, своего рода напыщенная речь. Надеюсь, это полезно. По крайней мере, я хочу указать вам на box2d. Он написан на довольно простом диалекте C++, и применяемые в нем шаблоны будут иметь отношение к вашему движку, будь то 3D или 2D.

+0

+1 для «никто не делает так». Я думаю, что это полезно. Я думаю, если бы я пошел с заводским рисунком, тогда у меня могли бы быть отдельные списки для каждого типа объектов ... хммм. – Bingo

+0

Принято, как вы говорили больше всего, и дал самый полезный совет по разработке программного обеспечения, и это мой вопрос. – Bingo

3

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

Другим решением, которое может быть использовано, является с маркированным соединением один, наилучшим образом воплощенный boost::variant.

Идея заключается в том, чтобы создать объект, который может содержать один экземпляр данного типа (в том числе предварительно выбранного списка) в любой момент времени:

typedef boost::variant<Ellipsis, Polygon, Blob> Shape; 

И тогда вы можете обеспечить функциональность путем переключения список типов:

struct AreaComputer: boost::static_visitor<double> { 
    template <typename T> 
    double operator()(T const& e) { return area(a); } 
}; 

void area(Shape const& s) { 
    AreaComputer ac; 
    return boost::apply_visitor(s, ac); 
} 

производительность такой же, как виртуальная отправка (так что не слишком много, нормально), но вы получите большую гибкость:

void func(boost::variant<Ellipsis, Blob> const& eb); 
void bar(boost::variant<Ellipsis, Polygon> const& ep); 
// ... 

Вы можете предоставить только функции , если релевантно.

А по теме бинарного посещения:

struct CollisionComputer: boost::static_visitor<CollisionResult> { 
    CollisionResult operator()(Circle const& left, Circle const& right); 
    CollisionResult operator()(Line const& left, Line const& right); 

    CollisionResult operator()(Circle const& left, Line const& right); 
    CollisionResult operator()(Line const& left, Circle const& right); 
}; 

CollisionResult collide(Shape const& left, Shape const& right) { 
    return boost::apply_visitor(CollisionComputer(), left, right); 
} 
+0

Спасибо за ваш ответ. Я не слишком хорошо знаком с посетителями или вариантами. Я думаю, что я получаю пример области, но это кажется тривиальным, потому что это легко сделать с помощью виртуального вызова, верно? Можете ли вы расширить раздел большей гибкости и, возможно, показать что-то вроде «double TimeOfCollision (Shape * s1, Shape * s2)» для различных видов фигур? (например, Circle + Circle, Circle + Line, Line + Line и т. д.) – Bingo

+0

@Bingo: гибкость, о которой я говорил, не связана с посещением (на самом деле), а скорее тем фактом, что вы можете упаковать элементы по своему усмотрению (в зависимости от потребности), тогда как с наследованием вы застреваете, если у них нет общего родителя, и это не так здорово, если их общий родитель имеет другие производные классы, которые вам не нужны ... –