Трудная часть ответа на ваш вопрос заключается в том, что вы спрашиваете: «что мне делать сейчас, для Понга» и «что я должен делать позже, в какой-нибудь общей игре».
Чтобы сделать Понг, вам даже не нужны классы 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, когда вы пишете код игрового процесса, и это ваш собственный внутренний код).
Теперь вы можете пойти еще дальше и иметь несколько интерфейсов. Например: один для рендеринга, один для скриптов и ИИ, один для физики и т. Д. Затем есть несколько реализаций, которые можно объединить в объекты.
Это подробно описано в этой статье . И у меня есть простой пример в этом ответе . Это подходящий следующий шаг, если вы начинаете находить, что ваш интерфейс с одним актером начинает превращаться в нечто большее в «дерево» абстрактных классов.