2010-04-22 4 views
3

Я пытаюсь выполнить какое-то «только-для-меня» игровой движок и сюжет этой проблемы идет следующим образом:C++ игры проектирование и полиморфизм вопрос

Предположит, у меня есть некоторый абстрактный интерфейс для визуализируемого объекта , например IRenderable.

И это декларируется следующим образом:

interface IRenderable { 
    // (...) 
    // Suppose that Backend is some abstract backend used 
    // for rendering, and it's implementation is not important 
    virtual void Render(Backend& backend) = 0; 
}; 

Что я делаю сейчас что-то вроде объявляя различные классы, как

class Ball : public IRenderable { 

    virtual void Render(Backend& backend) { 
    // Rendering implementation, that is specific for 
    // the Ball object 
    // (...) 
    } 
}; 

И тогда все выглядит нормально. Я легко могу сделать что-то вроде std::vector<IRenderable*> items, чтобы некоторые элементы, такие как new Ball() в этом векторе, а затем сделать похожую вызова для foreach (IRenderable* in items) { item->Render(backend); }

Хорошо, я предполагаю, что это является «полиморфный» способом, но что, если я хочу иметь различные типы объекты в моей игре и способность управлять их состоянием, где каждый объект можно манипулировать через собственный интерфейс?

я мог бы сделать что-то вроде

struct GameState { 
    Ball ball; 
    Bonus bonus; 
    // (...) 
}; 

, а затем легко изменять объекты государства через свои собственные методы, как ball.Move(...) или bonus.Activate(...), где Move(...) специфичен только для Ball и Activate(...) - только для Bonus экземпляров.

Но в этом случае я теряю возможность написать foreach IRenderable* просто потому, что я храню эти шары и бонусы как экземпляры их производных, а не базовых классов. И в этом случае процедура рендеринга превращается в беспорядок, как

ball.Render(backend); 
bonus.Render(backend); 
// (...) 

и это плохо, потому что мы на самом деле потерять наш полиморфизм этого путь (нет никакой фактической потребности для принятия Render функции виртуальной и т.д.

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

Итак, мой вопрос - есть ли какой-то (возможно) альтернативный подход к тому, что я хочу сделать, или мой текущий шаблон может быть каким-то образом изменен, так что я бы фактически хранили IRenderable* для своих игровых объектов (чтобы я мог вызывать виртуальный метод Render для каждого из них) сохраняя при этом способность легко изменять состояние этих объектов?

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

Спасибо заранее!

ответ

1

Я не программист C++, но, надеюсь, лже-код поможет ...

Класс визуализируемых имеет функцию визуализации

Класс Подвижные наследует от визуализируемых, а также обладает такими свойствами, как х, у, г и функции, как Move

Класс Болл наследует от Movable

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

Другие действия, такие как тиканье мировых часов, могут зацикливаться на поиске объектов, которые являются экземплярами Moveable, и сообщать им об обновлении.

Если ваш бонус является рендерируемым (я не мог точно сказать из вашего вопроса), то он мог бы подкласса Collectable, который подклассы Moveable.

+0

Знает ли кто-нибудь некоторый эффективный способ C++ для реализации второго цикла через эти элементы Renderable, что приводит к обнаружению подмножества подвижных объектов? Либо я просто глуп, либо это можно сделать только путем вызова динамического casting/typeid/собственной реализации RTTI ... Спасибо за ответ :) –

+1

@ Kotti просто создайте другой список из них. Например. у вас есть список Renderables, список Movables, список коллекционирования. Затем в любое время, когда вы хотите работать с любым подмножеством, просто перейдите к соответствующему списку. (Снова добавьте Renderables в отображаемый список в конструкторе Renderable.) –

3

Тот факт, что вы храните разные объекты в качестве членов класса, не должен мешать вам хранить указатели на них в векторе IRenderable*.

struct GameState { 
    Ball ball; 
    Bonus bonus; 
    vector<IRenderable*> m_items; 

    GameState() // constructor 
    { 
    m_items.push_back(&ball); 
    m_items.push_back(&bonus); 
    } 

}; 

Таким образом, вы не должны беспокоиться об освобождении указателей в m_items. Они будут автоматически уничтожены при уничтожении GameState.

Кроме того, ваш синтаксис foreach является фанки.

+0

Выглядит хорошо, спасибо. Предшествующем подобным образом можно сделать, например, с помощью BOOST_FOREACH. –

2

У вас действительно должны быть отдельные подсистемы в игровом движке. Один для рендеринга и еще один для игровой логики. В вашей графической подсистеме должен быть список указателей на объекты с Идентификатором. Ваша подсистема логической логики должна иметь свой собственный список объектов с другим интерфейсом.

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

+0

Возможно, но я думаю, что это может привести к большой головной боли, особенно IRenderable * указатель синхронизации после добавления/удаления/... игровых объектов и так далее. Я мог ошибаться, вероятно, должен попробовать. –

+0

Используйте RAII (http://en.wikipedia.org/wiki/RAII). Зарегистрируйте объект в конструкторе и удалите его деструктор. Это гарантирует, что объект будет регистрироваться и отменять регистрацию правильно. – bitc

+0

btw, видимые объекты IRenderable будут обновляться каждый раз, когда экран обновляется. Логика игры должна быть независимой от кадров в секунду и иметь собственную частоту обновления. Это еще одна причина для разделения этих обязанностей. – bitc

Смежные вопросы