Я хочу избавиться от своего шаблона дизайна «сделай все, статично и глобально», но как?

11

Я делаю небольшую гусеницу в космосе, и мне хотелось бы услышать несколько советов о том, как сделать бэкэнд двигателя более приятным. По сути, в настоящее время все основано на куче менеджеров:

  • BackgroundManager: есть AddBackground(image, parallax)метод создания крутых фоновых эффектов.
  • ConfigManager: читает / создает файл конфигурации, а также содержит данные, считанные из этого файла конфигурации.
  • DrawManager: имеет Draw(sprite)метод для рисования материала на экране, и тому подобное SetView(view), GetView()и т. Д.
  • EntityManager: содержит все активные сущности и имеет методы для добавления, удаления и поиска сущностей.
  • DungeonManager (на самом деле называется GridManager, но это для простоты): имеет такие методы, как GenerateDungeon(), PlaceEnemies()и PlaceFinish()т. Д.

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

Теперь я подумал о том, чтобы сделать менеджеры не статичными, и дать моему ScreenMainGame экземпляр всех игровых менеджеров. Но это делает вызов / получение чего-либо, связанного с менеджерами, огромным беспорядком ... например, если бы я хотел вытащить свое подземелье из любого места, где его нет ScreenMainGame.Draw(), я должен был бы сделать это ...

((ScreenMainGame)ScreenManager.CurrentScreen).GridManager.Draw()

Это действительно некрасиво.

Итак, коллеги-разработчики игр, у кого-нибудь из вас есть идея, как решить эту проблему? Я был бы признателен!

Dlaor
источник
Я не уверен, как именно вам помочь, но я бы порекомендовал почитать хорошую книгу по шаблонам проектирования. Я читал Head First Design Patterns, и это очень легко понять. Примеры приведены на Java, но я думаю, что это будет достаточно близко к C #, чтобы помочь вам. Извините, если мое предложение слишком новичок.
Amplify91
Что вы используете для взаимодействия с видеокартой? XNA? SlimDX?
BlueRaja - Дэнни Пфлюгофт
@BlueRaja: я использую SFML.NET.
Длаор
В этом случае вы должны просто использовать обычные методы решения этих проблем в C #: см. Мою последнюю ссылку ниже, а также Что такое внедрение зависимостей?
BlueRaja - Дэнни Пфлюгофт

Ответы:

10

Как насчет компонентного движка ?

У вас будет основной класс с именем Engine, который будет хранить список GameScreens, который будет содержать список Components.

Двигатель имеет Updateи Drawметод , и как вызов на GameScreen«S Updateи Drawметоды, которые сами по себе пройти через каждый компонент и вызов Updateи Draw.

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

Также гораздо проще поддерживать такой код, поскольку вы просто проходите большую иерархию классов и вам не нужно искать BackgroundManagerвсе специфические фоны. Вы просто есть ScrollingBackground, ParallaxBackground, StaticBackgroundи т.д. , которые все вытекают из Backgroundкласса.

В конечном итоге вы создадите довольно солидный движок, который вы сможете повторно использовать во всех своих проектах со множеством часто используемых компонентов и вспомогательных методов (например, FrameRateDisplayerв качестве утилиты отладки, Spriteкласса в качестве базового спрайта с текстурой и методов расширения для векторов и генерация случайных чисел).

У вас больше не будет BackgroundManagerкласса, но есть Backgroundкласс, который будет управлять собой.

Когда ваша игра начинается, все, что вам нужно сделать, это в основном:

// when declaring variables:
Engine engine;

// when initializing:
engine = new Engine();
engine.Initialize();
engine.LoadContent();
engine.AddGameScreen(new MainMenuScreen());

// when updating:
engine.Update();

// when drawing:
engine.Draw();

И это все для кода запуска вашей игры.

Затем для экрана главного меню:

class MainMenuScreen : MenuScreen // where MenuScreen derives from the GameScreen class
{
    const int ENEMY_COUNT = 10;

    StaticBackground background;
    Player player;
    List<Enemy> enemies;

    public override void Initialize()
    {
        background = new StaticBackground();
        player = new Player();
        enemies = new List<Enemy>();

        base.AddComponent(background); // defined within the GameScreen class
        base.AddComponent(player);
        for (int i = 0; i < ENEMY_COUNT; ++i)
        {
            Enemy newEnemy = new Enemy();
            enemies.Add(newEnemy);
            base.AddComponent(newEnemy);
        }
    }
}

Вы получите общую идею.

Вы также должны сохранить ссылку на Engineвсе ваши GameScreenклассы, чтобы иметь возможность добавлять новые экраны даже внутри GameScreenкласса (например, когда пользователь нажимает кнопку StartGame , находясь внутри вашего MainMenuScreen, вы можете перейти к GameplayScreen).

То же самое относится и к Componentклассу: он должен содержать ссылку на своего родителя GameScreen, чтобы иметь доступ как к Engineклассу, так и к его родителю GameScreenдля добавления новых компонентов (например, вы можете создать класс, связанный с HUD, DrawableButtonкоторый называется DrawableTextкомпонентом и StaticBackgroundкомпонентом).

После этого вы даже можете применять другие шаблоны проектирования, например, «шаблон проектирования служб» (не уверен в точном названии), где вы можете хранить различные полезные службы в своем Engineклассе (вы просто храните список IServices и позволяете другим классам добавлять службы самостоятельно. ). Например, я бы оставил Camera2Dкомпонент над всем моим проектом как сервис, чтобы применить его преобразование при рисовании других компонентов. Это избавляет от необходимости передавать его как параметр везде.

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

Джесси Эмонд
источник
1
XNA поддерживает все это из коробки через GameComponents и сервисы. Нет необходимости в отдельном Engineклассе.
BlueRaja - Дэнни Пфлюгофт
+1 за этот ответ. Кроме того, имеет ли смысл помещать рисунок в отдельную ветку из ветки обновления игры?
f20k
1
@BlueRaja - DannyPflughoeft: Действительно, но я предположил, что XNA не использовался, поэтому я объяснил немного больше о том, как это сделать.
Джесси Эмонд
@ f20k: Да, почти так же, как это делает XNA. Я не знаю, как это реализовано, хотя. = / Где-то в интернете должна быть статья о том, как это сделать. :)
Джесси Эмонд
Да, я не использую XNA, но это похоже на хорошую альтернативу тому, что я делаю. В настоящее время я читаю книгу «Head First Design Patterns», чтобы узнать, есть ли еще какие-либо альтернативы. Ура!
Длаор
1

То, что вы хотите сделать, это разделить ваш код на компоненты и сервисы. XNA уже имеет встроенную поддержку для них.

Смотрите эту статью на GameComponentsи услуги. Затем посмотрите этот ответ об использовании служб в XNA и более общий ответ об услугах на любом языке.

BlueRaja - Дэнни Пфлугхофт
источник
-3

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

Бо Бьюкенен
источник
4
Синглтон - это просто фасад для основной проблемы, которую ОП пытается здесь решить.