Как моделировать рендеринг и поведение игровых объектов по модульному принципу? - PullRequest
8 голосов
/ 11 февраля 2010

Я делаю Java-игру «Стреляй им» для телефонов Android. У меня в игре 20 странных врагов, у каждого из которых есть несколько уникальных поведений, но большинство из них используют определенные виды поведения. Мне нужно смоделировать пули, взрывы, астероиды и т. Д. И другие вещи, которые тоже действуют немного как враги. Мой текущий дизайн предпочитает композицию наследованию и представляет игровые объекты примерно так:

// Generic game object
class Entity
{
  // Current position
  Vector2d position;

  // Regular frame updates behaviour
  Behaviour updateBehaviour;
  // Collision behaviour
  Behaviour collideBehaviour;

  // What the entity looks like
  Image image;
  // How to display the entity
  Renderer renderer;

  // If the entity is dead and should be deleted
  int dead;
}

abstract class Renderer { abstract void draw(Canvas c); }

abstract class Behaviour { abstract void update(Entity e); }

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

class SimpleRenderer extends Renderer
{
  void draw(Canvas c)
  {
    // just draw the image
  }
}

Чтобы заставить объект беспорядочно летать в каждом кадре, просто добавьте такое поведение:

class RandomlyMoveBehaviour extends Behaviour
{
  void update(Entity e)
  {
    // Add random direction vector to e.position
  }
}

Или добавьте более сложное поведение, например, ожидание, пока игрок не приблизится, прежде чем войти в систему:

class SleepAndHomeBehaviour extends Behaviour
{
  Entity target;
  boolean homing;

  void init(Entity t) { target = t; }

  void update(Entity e)
  {
    if (/* distance between t and e < 50 pixels */)
    {
      homing = true;
      // move towards t...
    }
    else
    {
      homing = false;
    }
  }
}

Пока я действительно доволен этим дизайном. Это приятно и гибко, что вы можете, например, Модульный последний класс, чтобы вы могли предоставить поведение «сна» и «пробуждение», чтобы вы могли сказать что-то вроде нового WaitUntilCloseBehaviour (player, 50 / пикселей /, new MoveRandomlyBehaviour (), new HomingBehaviour () ). Это позволяет легко создавать новых врагов.

Единственная часть, которая беспокоит меня, это то, как взаимодействуют поведение и рендеры. На данный момент Entity содержит объект Image, который Поведение может изменить, если решит это сделать. Например, одно поведение может изменить объект между спящим и проснувшимся изображением, а средство визуализации просто нарисует изображение. Я не уверен, как это будет масштабироваться, хотя, например ::

  • А как насчет турель-подобного врага, который стоит в определенном направлении? Полагаю, я мог бы добавить поле вращения в Entity, чтобы Behavior и Renderer могли изменять и читать.

    • А как насчет танка, в котором корпус танка и пистолет танка имеют разные направления? Теперь рендереру нужен доступ к двум поворотам откуда-то и двум изображениям для использования. Вы действительно не хотите раздувать класс Entity с этим, если есть только один танк.

    • А как насчет врага, который светится, когда его пистолет заряжается? Вы бы действительно хотели сохранить время перезарядки в объекте Behavior, но тогда класс Renderer не сможет его увидеть.

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

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

Может кто-нибудь придумать какие-нибудь альтернативы? Я действительно хочу простоты. Поскольку это игра, эффективность может быть проблемой, если, например, Рисование одного изображения врага 5x5, когда у меня 50 врагов, летающих со скоростью 60 кадров в секунду, включает в себя множество слоев вызовов функций.

Ответы [ 2 ]

1 голос
/ 18 февраля 2010

Дизайн, с которым ты идешь, выглядит хорошо для меня. Эта глава о компонентах может вам помочь.

1 голос
/ 12 февраля 2010

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

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

Несколько проблем:

  • если Renderer запрашивает данные, которые не были установлены в поведении. В нашем случае событие регистрируется, и используются значения по умолчанию (определенные в рендерере).
  • Немного сложнее заранее проверить необходимую информацию (т. Е. Какие данные должны быть в пакете данных для рендерера A? Какие данные задаются поведением B?). Мы стараемся поддерживать документ в актуальном состоянии, но мы думаем о записи набора / получения классами и создании страницы документа ...

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

Также в нашем случае мы выбрали набор специализированных средств визуализации. Например, если у сущности есть не пустые данные щита, ShieldRenderer отображает представление ... В вашем случае, танк может иметь два рендерера, связанных с двумя (определяемыми инициализацией) данными:

Renderer renderer1 = new RotatedImage("Tank.png", "TankRotation");
Renderer enderer2 = new RotatedImage("Turret.png", "TurretRotation");

с "TankRotation" и "TurretRotation", установленными поведением. и средство рендеринга просто поворачивает изображение перед тем, как отобразить его в позиции.

  image.rotate (entity.databag.getData(variable));

Надеюсь, эта помощь

Привет
Гийом

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