Во многих местах я читал, что DrawableGameComponents следует сохранять для таких вещей, как «уровни» или какие-то менеджеры, а не использовать их, например, для персонажей или плиток (как этот парень говорит здесь ). Но я не понимаю, почему это так. Я прочитал этот пост, и он имел для меня большой смысл, но это меньшинство.
Я обычно не обращал бы слишком много внимания на подобные вещи, но в этом случае я хотел бы знать, почему очевидное большинство считает, что это не тот путь. Может быть, я что-то упустил.
xna
game-component
Кенджи Кина
источник
источник
DrawableGameComponent
блокирует вас в едином наборе сигнатур методов и конкретной модели сохраняемых данных. Обычно это неправильный выбор изначально, а блокировка существенно препятствует развитию кода в будущем. «Но позвольте мне рассказать вам, что здесь происходит ...DrawableGameComponent
является частью основного API XNA (опять же: в основном по историческим причинам). Начинающие программисты используют его, потому что он «там» и «работает», и они не знают ничего лучше. Они видят, что мой ответ говорит им, что они " не правы " и - из-за природы человеческой психологии - отвергают это и выбирают другую "сторону". Который, оказывается, является спорным не ответом Веры Шекл; который набрал обороты, потому что я не вызвал его сразу (см. эти комментарии). Я чувствую, что мое возможное опровержение там остается достаточным.Ответы:
«Игровые компоненты» были очень ранней частью дизайна XNA 1.0. Они должны были работать как элементы управления для WinForms. Идея заключалась в том, что вы сможете перетаскивать их в свою игру с помощью графического интерфейса пользователя (вроде как для добавления невизуальных элементов управления, таких как таймеры и т. Д.) И устанавливать для них свойства.
Таким образом, у вас могут быть такие компоненты, как камера или счетчик FPS? ... или ...?
Вы видите? К сожалению, эта идея на самом деле не работает для реальных игр. Компоненты, которые вы хотите для игры, на самом деле не пригодны для повторного использования. У вас может быть компонент «игрок», но он будет сильно отличаться от компонента «игрок» любой другой игры.
Я полагаю, что именно по этой причине и из-за простой нехватки времени GUI был загружен до XNA 1.0.
Но API все еще можно использовать ... Вот почему вы не должны использовать его:
В основном это сводится к тому, что проще всего писать, читать, отлаживать и поддерживать как разработчика игр. Делая что-то вроде этого в вашем коде загрузки:
Это гораздо менее понятно, чем просто делать это:
Особенно, когда в реальной игре вы можете получить что-то вроде этого:
(По общему признанию Вы могли бы разделить камеру и spriteBatch, используя подобную
Services
архитектуру. Но это - также плохая идея по той же самой причине.)Эта архитектура - или ее отсутствие - намного легче и гибче!
Я могу легко изменить порядок отрисовки или добавить несколько видовых экранов или добавить целевой эффект рендеринга, просто изменив несколько линий. Чтобы сделать это в другой системе, мне нужно подумать о том, что делают все эти компоненты, распределенные по нескольким файлам, и в каком порядке.
Это вроде как разница между декларативным и императивным программированием. Оказывается, для разработки игр императивное программирование гораздо предпочтительнее.
источник
DrawableGameComponent
.player.DrawOrder = 100;
метод над императивным для порядка прорисовки. Я заканчиваю игру Flixel, которая рисует объекты в том порядке, в котором вы добавляете их на сцену. Система FlxGroup облегчает это, но было бы неплохо иметь встроенную сортировку по Z-индексу.Хммм. Я боюсь, что это вызов для баланса здесь.
Кажется, в ответе Эндрю 5 обвинений, поэтому я постараюсь разобраться с каждым по очереди:
1) Компоненты - устаревший и заброшенный дизайн.
Идея шаблона проектирования компонентов существовала задолго до XNA - по сути, это просто решение создавать объекты, а не чрезмерно изогнутый игровой объект, как дом их собственного поведения Update и Draw. Для объектов в своей коллекции компонентов игра будет вызывать эти методы (и несколько других) автоматически в соответствующее время - главное, чтобы игровой объект сам не должен был «знать» этот код. Если вы хотите повторно использовать свои игровые классы в разных играх (и, следовательно, в разных игровых объектах), такое разъединение объектов все еще является хорошей идеей. Беглый взгляд на последние (профессионально произведенные) демонстрационные версии XNA4.0 из AppHub подтверждает мое впечатление, что Microsoft все еще (на мой взгляд, вполне справедливо) стоит за этим.
2) Эндрю не может придумать более двух повторно используемых игровых классов.
Я могу. На самом деле я могу думать о нагрузках! Я только что проверил свою текущую разделяемую библиотеку, и она содержит более 80 повторно используемых компонентов и сервисов. Чтобы придать вам вкус, вы можете иметь:
Любая конкретная игровая идея потребует как минимум 5-10 вещей из этого набора - гарантировано - и они могут быть полностью функциональными в совершенно новой игре с одной строкой кода.
3) Управление порядком прорисовки по порядку явных вызовов отрисовки предпочтительнее, чем использование свойства DrawOrder компонента.
Ну, я в основном согласен с Эндрю в этом. НО, если вы оставите все свойства DrawOrder вашего компонента по умолчанию, вы все равно сможете явно управлять их порядком рисования в том порядке, в котором вы добавляете их в коллекцию. В терминах «видимости» это действительно идентично явному упорядочению их ручных вызовов отрисовки.
4) Вызов загрузки методов Draw объекта самостоятельно является более «легким», чем вызов их с помощью существующего метода фреймворка.
Зачем? Кроме того, во всех, кроме самых тривиальных проектов, ваша логика обновления и код Draw полностью затенят вызов вашего метода с точки зрения снижения производительности в любом случае.
5) Размещение кода в одном файле предпочтительнее при отладке, чем размещение его в файле отдельного объекта.
Ну, во-первых, когда я отлаживаю в Visual Studio, местоположение точки останова с точки зрения файлов на диске в наши дни почти невидимо - IDE работает с этим местоположением без каких-либо проблем. Но также подумайте, что у вас возникли проблемы с чертежом Skybox. Неужели переход к методу Draw в Skybox действительно более обременителен, чем поиск кода для рисования скайбокса в (предположительно очень продолжительном) методе мастер- рисования в объекте Game? Нет, не совсем - на самом деле я бы сказал, что все наоборот.
Итак, в заключение - пожалуйста, внимательно посмотрите на аргументы Эндрю здесь. Я не верю, что они действительно дают существенные основания для написания запутанного игрового кода. Достоинства развязанного шаблона компонентов (используются разумно) огромны.
источник
Drawable
)GameComponent
для обеспечения вашей игровой архитектуры из-за потери явного контроля над порядком рисования / обновления (с которым вы согласны в # 3) и жесткостью их интерфейсов (которые вы не обращаетесь).Я немного не согласен с Эндрю, но я также думаю, что есть время и место для всего. Я бы не стал использовать
Drawable(GameComponent)
для чего-то такого простого, как Спрайт или Враг. Тем не менее, я бы использовал его дляSpriteManager
илиEnemyManager
.Возвращаясь к тому, что сказал Эндрю о порядке прорисовки и обновления, я думаю, что
Sprite.DrawOrder
это будет сбивать с толку многих спрайтов. Кроме того, его относительно легко настроитьDrawOrder
иUpdateOrder
для небольшого (или разумного) количества менеджеров, которые(Drawable)GameComponents
.Я думаю, что Microsoft покончила бы с ними, если бы они действительно были такими жесткими, и никто их не использовал. Но они этого не делают, потому что работают отлично, если роль правильная. Я сам использую их для разработки
Game.Services
, которые обновляются в фоновом режиме.источник
Я тоже думал об этом, и мне пришло в голову: например, чтобы украсть пример по вашей ссылке, в игре RTS вы можете сделать каждый юнит экземпляром игрового компонента. Хорошо.
Но вы также можете создать компонент UnitManager, который хранит список юнитов, рисует и обновляет их по мере необходимости. Я полагаю, что причина, по которой вы хотите превратить свои модули в игровые компоненты, заключается в разделении кода на части, поэтому будет ли это решение адекватно достигать этой цели?
источник
В комментариях я разместил сокращенную версию моего ответа старого ответа:
Использование
DrawableGameComponent
фиксирует вас в едином наборе сигнатур методов и конкретной сохраненной модели данных. Обычно это изначально неправильный выбор, и блокировка значительно препятствует развитию кода в будущем.Здесь я попытаюсь проиллюстрировать, почему это так, разместив некоторый код из моего текущего проекта.
Корневой объект, поддерживающий состояние игрового мира, имеет
Update
метод, который выглядит следующим образом:Существует несколько точек входа в
Update
зависимости от того, запущена игра в сети или нет. Например, возможно, чтобы игровое состояние было возвращено к предыдущему моменту времени, а затем повторно имитировано.Есть также некоторые дополнительные методы, подобные обновлениям, такие как:
В игровом состоянии есть список актеров, которые будут обновлены. Но это больше, чем простой
Update
звонок. Есть дополнительные проходы до и после, чтобы справиться с физикой. Есть также некоторые интересные вещи для отложенного появления / уничтожения актеров, а также переходы уровней.За пределами игрового состояния существует система пользовательского интерфейса, которой необходимо управлять независимо. Но некоторые экраны в пользовательском интерфейсе также должны работать в сетевом контексте.
Для рисования, из-за характера игры, существует специальная система сортировки и отбора, которая принимает данные от следующих методов:
И тогда, когда он выясняет, что рисовать, автоматически вызывает:
tag
Параметр необходим , поскольку некоторые участники могут держать на другие актер - и поэтому должны быть в состоянии сделать как выше , так и ниже задержанный актер. Система сортировки гарантирует, что это происходит в правильном порядке.Существует также система меню для рисования. И иерархическая система для рисования пользовательского интерфейса, вокруг HUD, вокруг игры.
Теперь сравните эти требования с тем, что
DrawableGameComponent
обеспечивает:Нет разумного способа получить доступ от системы
DrawableGameComponent
к системе, которую я описал выше. (И это не необычные требования для игры даже скромной сложности).Кроме того, очень важно отметить, что эти требования возникли не просто в начале проекта. Они развивались в течение многих месяцев разработки.
То, что люди обычно делают, если они начинают по
DrawableGameComponent
маршруту, они начинают строить на взломах:Вместо того, чтобы добавлять методы, они делают некрасивое приведение к своему собственному базовому типу (почему бы просто не использовать это напрямую?).
Вместо того, чтобы заменить код для сортировки и вызова
Draw
/Update
, они делают очень медленные вещи, чтобы изменить порядковые номера на лету.И что хуже всего: вместо добавления параметров к методам они начинают «передавать» «параметры» в ячейки памяти, которые не являются стеком (использование
Services
для этого популярно). Это запутанное, подверженное ошибкам, медленное, хрупкое, редко поточнобезопасное и может стать проблемой, если вы добавите сеть, создадите внешние инструменты и так далее.Это также делает код повторно использовать сложнее , потому что теперь ваша зависимость скрыта внутри «компоненты», а не опубликована на границе API.
Или они почти видят, как все это безумно, и начинают делать «менеджеров»,
DrawableGameComponent
чтобы обойти эти проблемы. За исключением того, что у них все еще есть эти проблемы на границах "менеджера".Эти «менеджеры» всегда можно легко заменить серией вызовов методов в нужном порядке. Все остальное слишком сложно.
Конечно, как только вы начнете использовать эти хаки, вам потребуется реальная работа, чтобы отменить эти хаки, как только вы поймете, что вам нужно больше, чем то, что
DrawableGameComponent
обеспечивает. Вы становитесь " запертыми ". И часто возникает искушение продолжать накапливать взломы - что на практике вас утомит и ограничит виды игр, которые вы можете сделать, относительно упрощенными.Многие люди, кажется, достигают этой точки и имеют внутреннюю реакцию - возможно, потому, что они используют
DrawableGameComponent
и не хотят признавать себя «неправильными». Таким образом, они фиксируют тот факт, что, по крайней мере, можно заставить его «работать».Конечно, это можно сделать, чтобы «работать»! Мы программисты - заставить вещи работать под необычными ограничениями - это практически наша работа. Но эта сдержанность не нужна, полезна или рациональна ...
Что особенно flabbergasting о том , что это поразительно тривиальной боковой шаг весь этот вопрос. Здесь я даже дам вам код:
(Это просто добавить в сортировку согласно
DrawOrder
иUpdateOrder
. Один простой способ - сохранить два списка и использоватьList<T>.Sort()
. Это также тривиально для обработкиLoad
/UnloadContent
.)Это не важно , что этот код на самом деле является . Важно то, что я могу тривиально изменить его .
Когда я понимаю, что мне нужна другая система синхронизации
gameTime
, или мне нужно больше параметров (или целыйUpdateContext
объект, содержащий около 15 вещей), или мне нужен совершенно другой порядок операций для рисования, или мне нужны некоторые дополнительные методы для разных этапов обновления я могу просто пойти дальше и сделать это .И я могу сделать это немедленно. Мне не нужно браться за выбор
DrawableGameComponent
из моего кода. Или еще хуже: взломать его каким-либо образом, что сделает его еще сложнее, когда в следующий раз возникнет такая необходимость.На самом деле есть только один сценарий, в котором это хороший выбор
DrawableGameComponent
: если вы буквально создаете и публикуете промежуточное программное обеспечение в стиле перетаскивания для XNA. Что было его первоначальной целью. Который - добавлю - никто не делает!(Точно так же это имеет смысл использовать
Services
при создании пользовательских типов контента дляContentManager
.)источник
DrawableGameComponent
для определенных компонентов, особенно для таких вещей, как экраны меню (меню, множество кнопок, опции и прочее) или оверлеи FPS / debug ты упомянул. В этих случаях вам обычно не требуется детальный контроль над порядком прорисовки, и в итоге получается меньше кода, который нужно писать вручную (что хорошо, если вам не нужно писать этот код). Но я думаю, что это в значительной степени сценарий перетаскивания, о котором вы упоминали.