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

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

16 голосов
/ 28 марта 2011

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

Подход более высокого уровня - Сделать общедоступными свойства весла и мяча и проверить Game.Update на наличие столкновений?

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

Я предполагаю, что мой вопрос в более общем виде:

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

или

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

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

Ответы [ 4 ]

46 голосов
/ 28 марта 2011

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


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

Vector2 ballPosition, ballVelocity;
float leftPaddlePosition, rightPaddlePosition;

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


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

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

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


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

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

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();
}

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

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

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

Актеры также могут захотеть узнать, какие другие актеры существуют по разным причинам. Так что дайте каждому актеру ссылку на Game и сделайте список игроков публичным в Game (нет необходимости быть очень строгим в отношении public / private, когда вы пишете код игрового процесса, и это ваш собственный внутренний код).


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

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

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

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

Ball и Paddle, оба являются объектами в игре и, в данном случае, Renderable, Подвижные объекты. Весло имеет следующие критерии

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

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

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

Не может выйти за границы экрана

  1. Может быть оказано
  2. В зависимости от того, где его ударили по веслу, вы можете управлять им косвенно (немного простой физики)
  3. Если он идет за веслом, раунд заканчивается
  4. Когда игра начинается, мяч обычно прикрепляется к веслу проигравшего.

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

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(...);
}

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

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

Знай свой домен и запиши свои идеи. Отказ от планирования означает сбой

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

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

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

0 голосов
/ 28 марта 2011

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

...