2008-10-07 3 views
9

Недавно я начал работать над небольшой игрой для собственного развлечения, используя Microsoft XNA и C#. Мой вопрос касается проектирования игрового объекта и объектов, которые его наследуют. Я собираюсь определить игровой объект как что-то, что можно отобразить на экране. Поэтому для этого я решил создать базовый класс, который все остальные объекты, которые нужно будет отображать, наследуют, называемые GameObject. Код ниже класс я сделал:Проектирование игровых объектов

class GameObject 
{ 
    private Model model = null; 
    private float scale = 1f; 
    private Vector3 position = Vector3.Zero; 
    private Vector3 rotation = Vector3.Zero; 
    private Vector3 velocity = Vector3.Zero; 
    private bool alive = false; 
    protected ContentManager content; 

    #region Constructors 
    public GameObject(ContentManager content, string modelResource) 
    { 
     this.content = content; 
     model = content.Load<Model>(modelResource); 
    } 
    public GameObject(ContentManager content, string modelResource, bool alive) 
     : this(content, modelResource) 
    { 
     this.alive = alive; 
    } 
    public GameObject(ContentManager content, string modelResource, bool alive, float scale) 
     : this(content, modelResource, alive) 
    { 
     this.scale = scale; 
    } 
    public GameObject(ContentManager content, string modelResource, bool alive, float scale, Vector3 position) 
     : this(content, modelResource, alive, scale) 
    { 
     this.position = position; 
    } 
    public GameObject(ContentManager content, string modelResource, bool alive, float scale, Vector3 position, Vector3 rotation) 
     : this(content, modelResource, alive, scale, position) 
    { 
     this.rotation = rotation; 
    } 
    public GameObject(ContentManager content, string modelResource, bool alive, float scale, Vector3 position, Vector3 rotation, Vector3 velocity) 
     : this(content, modelResource, alive, scale, position, rotation) 
    { 
     this.velocity = velocity; 
    } 
    #endregion 
} 

Я оставил из дополнительных методов, которые делают такие вещи, как поворот, перемещение, и нарисовать объект. Теперь, если бы я хотел создать другой объект, например корабль, я бы создал класс Ship, который наследует GameObject. Пример кода ниже:

class Ship : GameObject 
{ 
    private int num_missiles = 20; // the number of missiles this ship can have alive at any given time 
    private Missile[] missiles; 
    private float max_missile_distance = 3000f; // max distance a missile can be from the ship before it dies 

    #region Constructors 
    public Ship(ContentManager content, string modelResource) 
     : base(content, modelResource) 
    { 
     InitShip(); 
    } 
    public Ship(ContentManager content, string modelResource , bool alive) 
     : base(content, modelResource, alive) 
    { 
     InitShip(); 
    } 
    public Ship(ContentManager content, string modelResource, bool alive, float scale) 
     : base(content, modelResource, alive, scale) 
    { 
     InitShip(); 
    } 
    public Ship(ContentManager content, string modelResource, bool alive, float scale, Vector3 position) 
     : base(content, modelResource, alive, scale, position) 
    { 
     InitShip(); 
    } 
    public Ship(ContentManager content, string modelResource, bool alive, float scale, Vector3 position, Vector3 rotation) 
     : base(content, modelResource, alive, scale, position, rotation) 
    { 
     InitShip(); 
    } 
    public Ship(ContentManager content, string modelResource, bool alive, float scale, Vector3 position, Vector3 rotation, Vector3 velocity) 
     : base(content, modelResource, alive, scale, position, rotation, velocity) 
    { 
     InitShip(); 
    } 
    #endregion 
} 

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


Спасибо всем, кто оставил ответ. Они были очень полезны. Похоже, что существует общее мнение о том, что изменить его вокруг, чтобы использовать шаблон MVC, было бы лучше всего. Я еще раз посмотрю, как это сделать. Я также удалю большинство конструкторов и будет иметь только один конструктор, потому что все аргументы после modelResource не нужны для создания объекта, и все они могут быть изменены позже через вызовы методов.

ответ

4

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

  • Re-обесшкуривания игру, изменяя все художественные активы,
  • Изменение поведения многих различных в -имя объектов, например правила, которые определяют, жив или нет объект.
  • Изменение на другой спрайт-движок.

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

6

Лично я нахожу огромное количество конструкторов у вас есть немного offputting, но это не ваш выбор, ничего принципиально плохого с ним :)

Что касается общей стратегии вывода игровых объектов с общей базой, это довольно распространенный способ сделать что-то. Стандартный, ровный. Я, как правило, начал использовать нечто гораздо более похожее на MVC, с невероятно легкими «образными» игровыми объектами, которые содержат только данные.

Другие общие подходы, которые я видел в XNA включать/вещи, которые вы можете рассмотреть следующие вопросы:

  • Внедрение IRenderable интерфейса, а не делать каких-либо сделать код в базе или производных классов. Например, вы можете захотеть, чтобы игровые объекты никогда не отображались - путевые точки или некоторые из них.
  • Вы можете сделать свой базовый класс абстрактным, вы вряд ли захотите создать экземпляр GameObject.

Снова, хотя, второстепенные моменты. Ваш дизайн в порядке.

+0

@NM: +1, здесь, здесь меньше конструкторов больше свойств! – user7116 2008-10-08 03:39:11

1

В зависимости от того, как часто будут вызываться конструкторы, вы можете подумать о том, чтобы сделать все дополнительные параметры обнуляемыми и просто передать null, если у них нет значений. Messier, создающий объекты, меньше конструкторов

1

Я пошел по этому пути при первом создании игр в XNA, но в следующий раз я бы сделал больше подхода типа модели/вида. Объекты модели будут тем, что вы имеете в своем цикле обновления, и ваши представления будут использоваться в вашем цикле рисования.

У меня возникли проблемы с тем, чтобы не отделять модель от представления, когда я хотел использовать свой спрайт-объект для обработки как трехмерных, так и 2D-объектов. Это стало действительно беспорядочным.

3

Я очень заинтересован в вашем методе хранения для вращения в векторе. Как это работает? Если вы храните углы X, Y, Z-оси (так называемые углы Эйлера), вы должны переосмыслить эту идею, если хотите создать 3D-игру, так как вы встретитесь вскоре после первого рендеринга зла GIMBAL LOCK

Лично мне не так много конструкторов, так как вы можете предоставить 1 конструктор и установить все ненужные параметры в null. таким образом ваш интерфейс остается чистым.

+0

Я не знал о кардановом замке. Я храню углы X, Y, Z в векторе, как вы сказали. Я посмотрел на вашу ссылку и попытался воссоздать ее, имея X = 90, Y = 0, Z = 0 (это перевернуло модель вверх). Затем я увеличил Y и повернул, как ожидалось. Но увеличение Z показало, что оно вращается вокруг Y. – Kobol 2008-10-07 21:23:48

+0

Вам понадобится представление, которое немного более стабильно. Я знаю, что Second Life использует Quaternions для операций вращения, чтобы избежать подобных проблем. – 2008-10-07 21:35:07

1

Люди, которые говорят о разделении модели из вида, верны. Чтобы добавить его в более ориентированные на игры разработчики, у вас должны быть отдельные классы для GameObject и RenderObject. Вернется к вашему основному циклу игры:

// main loop 
while (true) { 
    ProcessInput(); // handle input events 
    UpdateGameWorld(); // update game objects 
    RenderFrame(); // each render object draws itself 
} 

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

2

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

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

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

1

В качестве альтернативы вы можете сделать STRUCT или вспомогательный класс для хранения всех данных и иметь много конструкторов в структурах, таким образом, вы только должны иметь тонны конструкторов в 1 объекте, а не в каждом классе, который наследуется от GameObject

5

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

Если я пишу:

new Ship(content, "resource", true, null, null, null, null); 

Что делает второй NULL делать?

Это делает код более удобным для чтения (но более многословный), чтобы использовать структуру для хранения ваших параметров, если список параметров выходит за пределы четырех или пяти параметров:

GameObjectParams params(content, "resource"); 
params.IsAlive = true; 
new Ship(params); 

Там много способов это можно сделать ,

1

Первая игра? Старт простой. Ваш базовый GameObject (который в вашем случае более правильно называется DrawableGameObject [обратите внимание на класс DrawableGameComponent в XNA]) должен содержать только информацию, принадлежащую всем GameObjects. Ваши живые и переменные скорости действительно не принадлежат. Как отмечали другие, вы, вероятно, не хотите смешивать логику рисования и обновления.

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

Что означает «живое», может быть очень различным в зависимости от контекста. В простом космическом шутере что-то, что не живое, вероятно, должно быть уничтожено (возможно, заменено несколькими кусками космического мусора?). Если это игрок, который восстает, вы захотите убедиться, что игрок отделен от корабля. Корабль не возрождается, корабль уничтожается, а игрок получает новый корабль (вместо старого уничтожения старого корабля вместо его удаления, а создание нового - меньшее количество циклов - вы можете просто переместить корабль в никчемный -land или полностью вытащить его из игровой сцены и вернуть обратно после респауна).

Еще одна вещь, о которой вы должны опасаться ... Не позволяйте игрокам смотреть вверх или вниз по вашей вертикальной оси, или вы столкнетесь, как упоминал Питер Паркер, проблемы с Gimbal Lock (которые вы не делаете хотите общаться, если вы занимаетесь разработкой игр для удовольствия!). Еще лучше ... начинать с 2D (или, по крайней мере, ограничивать движение только до двух измерений)! Это намного легче, когда вы новичок в разработке игр.

Я бы предложил следующее XNA Creators Club Getting Started Guides перед выполнением каких-либо дополнительных программ. Вы также должны рассмотреть возможность поиска других более общих руководств по разработке игр, чтобы получить несколько альтернативных предложений.

5

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

Игры очень поддаются Model-View-Controller pattern. Вы явно говорите о части дисплея, но об этом подумали : у вас есть векторы и т. Д., И это обычно является частью того, что вы моделируете. Подумайте об игровом мире и о предметах в нем как о моделях. У них есть положение, они могут иметь скорость и направление. Это модельная часть MVC.

Я заметил ваше желание иметь огонь корабля. Вы, вероятно, захотите, чтобы механик делал корабль в классе контроллера, например, с клавиатуры. И этот класс контроллера отправит сообщения части модели. Звук и легко думать, что вы, вероятно, захотите, это иметь некоторый метод fireAction() на модели. На вашей базовой модели это может быть переопределяемый (виртуальный, абстрактный) метод. На корабле он может добавить «пулю» в игровой мир.

Видя, как я уже писал о поведении вашей модели, вот еще одна вещь, которая мне очень помогла: используйте strategy pattern, чтобы сделать поведение классов заменяемыми. Стратегия стратегии также отличная, если вы думаете, что AI и хотите поведение для изменения в будущем. Возможно, вы не знаете этого сейчас, но через месяц вы можете использовать баллистические ракеты как стандартные ракеты, и вы можете легко изменить это позже, если бы использовали стратегию pattenr.

Так что, в конце концов, вы можете действительно сделать что-то вроде класса отображения. У него будут такие примитивы, как вы назвали: rotate, translate и т. Д. И вы хотите извлечь из него дополнительную функциональность или изменить функциональность. Но подумайте об этом, если у вас есть больше, чем корабль, особый вид корабля, еще один особый корабль, вы столкнетесь с деривацией ада , и вы повторите много кода. Опять же, используйте шаблон стратегии. И не забудьте сохранить это просто. Что должен иметь класс Displayble?

  • Средство знать его положение относительно экрана. Он может, благодаря модели своего объекта в мире и что-то вроде модели gameworld!
  • Он должен знать растровое изображение для отображения его модели и ее размеров.
  • Он должен знать, если что-либо должно препятствовать его рисованию, если это не обрабатывается механизмом рисования (т. Е. Другой объект закрывает его).
Смежные вопросы