Я знаю, что одиночные игры плохие, мой старый игровой движок использовал одноэлементный объект «Game», который обрабатывает все, начиная от хранения всех данных и заканчивая реальным игровым циклом. Сейчас я делаю новый.
Проблема в том, чтобы нарисовать что-то в SFML, который вы используете window.draw(sprite)
где находится окно sf::RenderWindow
. Есть 2 варианта, которые я вижу здесь:
- Создайте одноэлементный игровой объект, который извлекает каждая сущность в игре (что я использовал раньше)
- Сделайте это конструктором для сущностей:
Entity(x, y, window, view, ...etc)
(это просто смешно и раздражает)
Каков будет правильный способ сделать это, сохранив конструктор Entity равным x и y?
Я мог бы попытаться отследить все, что я делаю в основном игровом цикле, и просто вручную нарисовать их спрайт в игровом цикле, но это тоже кажется грязным, и я также хочу получить абсолютный полный контроль над всей функцией отрисовки для сущности.
c++
design-patterns
oop
аккумуляторный
источник
источник
Ответы:
Сохраняйте только данные, необходимые для рендеринга спрайта внутри каждой сущности, затем извлекайте их из сущности и передавайте в окно для рендеринга. Нет необходимости хранить какие-либо окна или просматривать данные внутри сущностей.
У вас может быть класс Game или Engine верхнего уровня, который содержит класс Level (содержит все используемые в настоящее время сущности) и класс Renderer (содержит окно, представление и все остальное для рендеринга).
Таким образом, цикл обновления игры в вашем классе верхнего уровня может выглядеть так:
источник
Logger::getInstance().Log(...)
а не простоLog(...)
? Зачем инициализировать класс случайным образом, когда вас спрашивают, можете ли вы сделать это вручную только один раз? Глобальная функция, ссылающаяся на статические глобальные переменные, гораздо проще создавать и использовать.Простой подход состоит в том, чтобы просто сделать то, что раньше было
Singleton<T>
глобальнымT
. У глобалов тоже есть проблемы, но они не представляют собой кучу дополнительной работы и шаблонного кода для обеспечения тривиального ограничения. Это в основном единственное решение, которое не предполагает (потенциально) касания конструктора сущностей.Более сложный, но, возможно, лучший подход заключается в передаче ваших зависимостей туда, где они вам нужны . Да, это может включать передачу
Window *
объектов (например, вашей сущности) способом, который выглядит грубым. Тот факт, что он выглядит грубым, должен вам кое-что сказать: ваш дизайн может быть грубым.Причина, по которой это сложнее (помимо привлечения большего числа типов), заключается в том, что это часто приводит к рефакторингу ваших интерфейсов, так что то, что вам «нужно» передать, требуется меньшему количеству классов конечного уровня. Это делает многие уродства, присущие передаче вашего рендерера всему, исчезает, а также повышает общую удобство сопровождения вашего кода за счет уменьшения количества зависимостей и связности, степень которой вы сделали очень очевидной, принимая зависимости как параметры , Когда зависимости были единичными или глобальными, было менее очевидно, насколько взаимосвязаны ваши системы.
Но это потенциально серьезное начинание. Делать это в системе после факта может быть совершенно больно. Возможно, для вас гораздо более прагматично просто оставить свою систему наедине с одиночкой на данный момент (особенно если вы пытаетесь на самом деле выпустить игру, которая в противном случае работает просто отлично; игрокам, как правило, не будет интересно, если у вас есть). синглтон или четыре там).
Если вы действительно хотите попробовать сделать это с вашим существующим дизайном, вам может потребоваться опубликовать гораздо больше подробностей о вашей текущей реализации, поскольку на самом деле нет общего контрольного списка для внесения этих изменений. Или приходите обсудить это в чате .
Исходя из того, что вы опубликовали, я думаю, что большой шаг в направлении «без единого» будет состоять в том, чтобы избежать необходимости для ваших сущностей иметь доступ к окну или представлению. Это говорит о том, что они рисуют сами, а вам не нужно, чтобы сущности сами рисовали . Вы можете принять методологию, в которой сущности просто содержат информацию, которая позволила бы их нужно рисовать какой-то внешней системой (которая имеет ссылки на окна и представления). Сущность просто выставляет свою позицию и спрайт, который она должна использовать (или какая-то ссылка на указанный спрайт, если вы хотите кэшировать фактические спрайты в самом рендерере, чтобы избежать дублирования экземпляров). Рендереру просто приказывают нарисовать определенный список сущностей, который он просматривает, считывает данные и использует свой внутренний оконный объект для вызова
draw
со спрайтом, ищущим сущность.источник
Наследовать от sf :: RenderWindow
SFML фактически поощряет вас наследовать от своих классов.
Отсюда вы создаете функции рисования элементов для объектов рисования.
Теперь вы можете сделать это:
Вы даже можете сделать это еще дальше, если ваши сущности собираются хранить свои уникальные спрайты, заставляя сущность наследовать от sf :: Sprite.
Теперь
sf::RenderWindow
можно просто рисовать сущности, а сущности теперь имеют такие функции, какsetTexture()
иsetColor()
. Объект может даже использовать позицию спрайта в качестве своей собственной позиции, что позволяет вам использоватьsetPosition()
функцию как для перемещения Объекта, так и для его спрайта.В конце концов , это очень приятно, если у вас есть:
Ниже приведены несколько примеров реализации.
ИЛИ
источник
Вы избегаете одиночных игр в разработке игр так же, как вы избегаете их в любой другой разработке программного обеспечения: вы передаете зависимости .
При этом вы можете передавать зависимости напрямую как голые типы (например
int
,Window*
и т. Д.) Или передавать их в один или несколько пользовательских типов-оболочек (например,EntityInitializationOptions
).Первый способ может раздражать (как вы выяснили), а второй позволит вам передавать все в одном объекте и изменять поля (и даже специализировать тип параметров), не обходя и не изменяя каждый конструктор сущностей. Я думаю, что последний способ лучше.
источник
Синглтоны не плохие. Вместо этого ими легко злоупотреблять. С другой стороны, глобальные ошибки даже легче использовать, и у них больше проблем.
Единственная действительная причина заменить синглтон глобальным - это умиротворение религиозных ненавистников-синглтонов.
Проблема состоит в том, чтобы иметь дизайн, который включает классы, из которых когда-либо существует только один глобальный экземпляр, и который должен быть доступен везде. Это распадается, как только у вас появляется несколько экземпляров синглтона, например, в игре, когда вы реализуете разделенный экран, или в достаточно крупном корпоративном приложении, когда вы замечаете, что один регистратор не всегда такая хорошая идея. ,
В итоге, если у вас действительно есть класс, где у вас есть один глобальный экземпляр, который вы не можете разумно передать по ссылке , синглтон часто является одним из лучших решений в пуле неоптимальных решений.
источник
Внедрить зависимости. Преимущество этого заключается в том, что теперь вы можете создавать различные типы этих зависимостей через фабрику. К сожалению, вырывать синглетов из класса, который их использует, все равно что тянуть кошку за задние лапы по ковру. Но если вы внедряете их, вы можете менять реализации, возможно, на лету.
Теперь вы можете вводить различные типы окон. Это позволяет вам писать тесты для RenderSystem с различными типами окон, чтобы вы могли увидеть, как ваша RenderSystem сломается или будет работать. Это невозможно или более сложно, если вы используете синглтоны непосредственно внутри "RenderSystem".
Теперь он более тестируемый, модульный и также не связан с конкретной реализацией. Это зависит только от интерфейса, а не от конкретной реализации.
источник