Игровая архитектура

У меня вопрос о игре XNA, которую я делаю, но это также общий вопрос для будущих игр. Я делаю игру в понг, и я точно не знаю, что обновлять там, поэтому я лучше объясню, что я имею в виду. У меня есть класс Game, Paddle и Ball, и, например, я хочу проверить столкновения между мячом с ограничениями экрана или веслами, но для этого я сталкиваюсь с двумя подходами:

Подход на более высоком уровне. Создайте свойства paddle и ball public и в Game.Update проверьте наличие конфликтов?

Подход на более низком уровне. Я предоставляю всю необходимую информацию (ограничения экрана и информацию о paddles) для класса шара (по параметру или в общедоступном статическом классе) и на Ball.Update Я проверяю наличие конфликтов?

Я думаю, мой вопрос более общим образом:

Должен ли объект знать, как обновлять и рисовать сам, даже имея зависимости от более высоких уровней, которые каким-то образом предоставляются им?

или

Лучше обрабатывать его на более высоких уровнях в Game.Update или Game.Draw или с помощью Менеджеров для упрощения кода?

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

Ответы

Ответ 1

Трудная часть ответа на ваш вопрос заключается в том, что вы задаете оба вопроса: "что мне теперь делать, для понга" и "что мне делать дальше, в какой-то общей игре".


Чтобы создать понг, вам даже не нужны классы Ball и Paddle, потому что они в основном являются просто позициями. Просто вставьте что-то подобное в свой класс игры:

Vector2 ballPosition, ballVelocity;
float leftPaddlePosition, rightPaddlePosition;

Затем просто обновляйте и рисуйте их в любом порядке, который вам подходит в функциях Game Update и Draw. Легко!


Но, скажем, вы хотите создать несколько шаров, а шары имеют много свойств (положение, скорость, поворот, цвет и т.д.). Возможно, вы захотите создать класс или структуру Ball, которые вы можете использовать (например, весла). Вы можете даже перенести некоторые функции в этот класс, где они автономны (a Draw - хороший пример).

Но сохраняйте концепцию дизайна одинаковой - все взаимодействие с объектом-объектом (т.е. игровой процесс) происходит в вашем классе Game.

Все это прекрасно, если у вас есть два или три разных элемента игры (или классы).


Однако пусть постулирует более сложную игру. Пусть возьмите основную игру в понг, добавьте некоторые элементы пинбола, такие как мантии и мячи, контролируемые игроком. Давайте добавим некоторые элементы из Змеи, скажем, у нас есть "змея", контролируемая AI, а также некоторые объекты пикапа, которые могут поразить шары или змея. И для хорошей меры позвольте сказать, что весла также могут стрелять лазерами, как в Space Invaders, и лазерные болты делают разные вещи в зависимости от того, что они попали.

Голли, это огромный беспорядок взаимодействия! Как мы с этим справимся? Мы не можем поместить все это в игру!

Simple! Мы создаем интерфейс (или абстрактный класс или виртуальный класс), из которого выйдет каждая "вещь" (или "актер" ) в нашем игровом мире. Вот пример:

interface IActor
{
    void LoadContent(ContentManager content);
    void UnloadContent();

    void Think(float seconds);
    void UpdatePhysics(float seconds);

    void Draw(SpriteBatch spriteBatch);

    void Touched(IActor by);

    Vector2 Position { get; }
    Rectangle BoundingBox { get; }
}

(Это всего лишь пример. Не существует "одного истинного актерского интерфейса", который будет работать для каждой игры, вам нужно будет создать свой собственный. Вот почему мне не нравится DrawableGameComponent.)

Наличие общего интерфейса позволяет Game просто говорить об актерах - вместо того, чтобы знать о каждом типе в вашей игре. Остается только делать вещи, общие для каждого типа: обнаружение, рисование, обновление, загрузка, разгрузка и т.д.

Как только вы входите в актера, вы можете начать беспокоиться о конкретных типах актеров. Например, это может быть метод в Paddle:

void Touched(IActor by)
{
    if(by is Ball)
         ((Ball)by).BounceOff(this.BoundingBox);
    if(by is Snake)
         ((Snake)by).Kill();
}

Теперь мне нравится, чтобы мяч отскочил от Паддла, но это действительно вопрос вкуса. Вы могли бы сделать это наоборот.

В конце концов, вы сможете удержать всех своих актеров в большом списке, который вы можете просто пропустить в игре.

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

Актеры также могут захотеть запросить, что другие участники существуют по разным причинам. Поэтому дайте каждому актеру ссылку на Игру и сделайте список актеров общедоступным в игре (нет необходимости быть супер-строгим в отношении публичного/частного, когда вы пишете код геймплея, и это ваш собственный внутренний код.)


Теперь вы можете пойти еще дальше и иметь несколько интерфейсов. Например: один для рендеринга, один для сценариев и AI, один для физики и т.д. Затем есть несколько реализаций, которые могут быть скомпонованы в объекты.

Это подробно описано в в этой статье. И у меня есть простой пример в этом ответе. Это подходящий следующий шаг, если вы начнете обнаруживать, что ваш интерфейс одного актера начинает превращаться в больше "дерева" абстрактных классов.

Ответ 2

Вы также можете начать думать о том, как разные компоненты игры должны разговаривать друг с другом.

Ball and Paddle, оба являются объектами в игре, и в этом случае Renderable, Movable objects. Paddle имеет следующие критерии:

Он может двигаться только вверх и вниз

  • Весло фиксируется на одной стороне экрана или снизу
  • Паддл может управляться пользователем (1 против компьютера или 1 против 1)
  • Весы могут отображаться
  • Весло может перемещаться только снизу или в верхней части экрана, оно не может проходить через границы.

Мяч имеет следующие критерии

Он не может покинуть границы экрана

  • Он может отображаться
  • В зависимости от того, где он попадает на весло, вы можете косвенно управлять им (некоторая простая физика)
  • Если он идет за веслом, раунд завершен.
  • Когда игра начинается, мяч обычно прикрепляется к Паддлу потерявшего человека.

Идентификация общих критериев вы можете извлечь интерфейс

public interface IRenderableGameObject
{
   Vector3 Position { get; set; }
   Color Color { get; set; }
   float Speed { get; set; }
   float Angle { get; set; }
}

У вас также есть GamePhysics

public interface IPhysics
{
    bool HasHitBoundaries(Window window, Ball ball);
    bool HasHit(Paddle paddle, Ball ball);
    float CalculateNewAngle(Paddle paddleThatWasHit, Ball ball);
}

Тогда существует некоторая игровая логика

public interface IGameLogic
{
   bool HasLostRound(...);
   bool HasLostGame(...);
}

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

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

Знай свой домен и напиши свои идеи. Неспособность запланировать планирует провалиться

Ответ 3

Вы могли видеть свой шар и весло как компонент вашей игры, а XNA дает вам базовый класс GameComponent, который имеет Update(GameTime gameTime) вы можете переопределить логику. Кроме того, существует класс DrawableGameComponent, который поставляется со своим Draw для переопределения. Каждый класс GameComponent также имеет свойство Game, которое содержит объект игры, который их создал. Там вы можете добавить некоторые Сервисы, которые ваш компонент может использовать для получения информации самостоятельно.

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

Ответ 4

Я согласен с тем, что сказал Эндрю. Я просто изучаю XNA, а также в своих классах, например, в вашем классе. У меня был бы метод обновления (gametime) и метод Draw(). Обычно инициализируются(), Load(). Затем из основного игрового класса я буду называть эти методы их соответствующими кузенами. Это было до того, как я узнал об GameComponent. Вот хорошая статья о том, следует ли вам это использовать. http://www.nuclex.org/blog/gamedev/100-to-gamecomponent-or-not-to-gamecomponent