Проектирование игровых объектов - PullRequest
10 голосов
/ 07 октября 2008

Я недавно начал работать над небольшой игрой для собственного удовольствия, используя 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 не нужны для создания объекта, и все они могут быть изменены позже с помощью вызовов методов.

Ответы [ 11 ]

7 голосов
/ 08 октября 2008

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

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

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

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

Опять же, незначительные моменты. Ваш дизайн в порядке.

5 голосов
/ 08 октября 2008

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

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

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

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

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

  • Означает знать его положение относительно экрана. Это возможно благодаря модели его объекта в мире и что-то вроде модели игрового мира!
  • Он должен знать растровое изображение, отображаемое для его модели и его размеров.
  • Он должен знать, должно ли что-либо мешать рисованию, если это не обрабатывается механизмом рисования (то есть другим объектом, скрывающим его).
5 голосов
/ 08 октября 2008

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

Если я напишу:

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

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

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

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

Есть много способов сделать это.

4 голосов
/ 08 октября 2008

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

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

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

3 голосов
/ 08 октября 2008

Мне очень интересен ваш метод хранения для поворота в векторе. Как это работает? Если вы сохраняете углы осей X, Y, Z (так называемые углы Эйлера), вам следует переосмыслить эту идею, если вы хотите создать 3D-игру, так как вы встретитесь вскоре после первого рендеринга зла ЗАДВИЖНОЙ ЗАМК

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

2 голосов
/ 08 октября 2008

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

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

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

1 голос
/ 08 октября 2008

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

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

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

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

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

1 голос
/ 08 октября 2008

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

1 голос
/ 08 октября 2008

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

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

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

1 голос
/ 08 октября 2008

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...