Рефакторинг: сделать игровой движок более модульным - PullRequest
7 голосов
/ 01 марта 2011

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

Вот некоторые примеры: Базовый модуль, управляющий окном и отвечающий на события ОС, менеджер сущностей, менеджер Lua, менеджер физики.

В настоящее время эти модули организованы как пространства имен, и ихсостояние определяется с помощью локальных переменных в соответствующих исходных файлах.Каждое из пространств имен имеет функции Open (), Close () и Update ().

Теперь мне больше не нравится решение с пространствами имен .

  • Это недостаточно гибко

  • Даже если в действительности это может и не понадобиться, наличие простой возможности создания нескольких экземпляров модуля кажется правильным

  • Похоже, я здесь не использую ООП - базовый класс модуля с виртуальной функцией-членом Update () звучит более разумно

  • Этотруднее обеспечить, чтобы при закрытии и повторном открытии модуля все переменные тоже были сброшены (класс с конструкторами и деструкторами был бы проще)

  • Вы не можете правильно получитьмодули управляются без явного вызова Open (), Close () и Update ()

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

Но решение с ООП поднимает проблему о том, как модули должны взаимодействовать.Прямо сейчас базовый модуль велел консольному модулю печатать что-либо с помощью console::print()

  • Как обойти эту проблему без необходимости использовать что-то вроде g_ModuleManager.GetConsoleModule()->print()?

  • Как этот менеджер модулей мог бы работать подробно?

И мой последний вопрос:

  • У вас есть какие-нибудьдополнительные советы для меня по теме написания модульного игрового движка на C ++ с ООП?

  • Существуют ли какие-либо шаблоны проектирования, которые могут помочь мне в этой ситуации, или, возможно, даже конкретный материал для чтения?

Ответы [ 3 ]

2 голосов
/ 02 марта 2011

Пространства имен очень быстро становятся очень негибкими.
Один из способов сохранить слабую связь - использовать обмен сообщениями через центральный диспетчер сообщений; вместо того, чтобы говорить console::print, вы бы сказали messenger->sendMessage(PRINT_MESSAGE, stuffToPrint).
Консоль будет регистрироваться как слушатель PRINT_MESSAGE s и действовать так, как она хочет. Отправителю не нужно заботиться о том, слушает ли его кто-то, и каждое сообщение может даже иметь несколько слушателей (полезно для отладки).

Что касается материалов для чтения, "Game Engine Architecture" Джейсона Грегори довольно хорош, обсуждая плюсы и минусы нескольких архитектур.

1 голос
/ 01 марта 2011

Во-первых, в качестве общего напоминания, просто не забудьте использовать наследование для подстановки, а не повторное использование кода. Примерно это означает, что все, что наследуется от вашего базового модуля, должно поддерживать (в основном) одни и те же операции, и что если пользователь скажет open, close или update в любом модуле, это обеспечит ожидаемые результаты.

Вы действительно хотите менеджер модулей? Вместо этого может быть лучше нарисовать диаграмму отношений между вашими различными классами / модулями и создать там связь, а не менеджер высшего уровня. Тогда основной цикл приложения будет знать об одном или нескольких модулях высокого уровня и напрямую вызывать соответствующие методы для них. При желании может быть даже модуль высокого уровня для управления этим высоким уровнем делегирования.

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

0 голосов
/ 02 марта 2011
g_ModuleManager.GetConsoleModule()->print()

просто, как уже говорилось до меня, вам, скорее всего, придется реорганизовать многие вещи, чтобы воспользоваться преимуществами ООП. Вы хотите, чтобы у каждого класса были особые отношения, а не у всех на равных. Тот, кто говорит ConsoleModule о печати, должен иметь свою собственную функцию печати, которая сообщает ConsoleModule для печати. Таким образом, вы просто говорите print()

также думать в терминах «имеет» для состава и «является» для наследования. Я бы прочитал весь раздел 8: http://www.learncpp.com/cpp-tutorial/81-welcome-to-object-oriented-programming/

В качестве примера моего текущего проекта: у меня есть доска, которая "имеет" beatles, walls и apples. Каждый из них "есть" icon

...