Должны ли актеры в игре быть ответственными за рисование себя?

41

Я очень новичок в разработке игр, но не в программировании.

Я (снова) играю с игрой типа Понг, используя canvasэлемент JavaScript .

Я создал Paddleобъект, который имеет следующие свойства ...

  • width
  • height
  • x
  • y
  • colour

У меня также есть Pongобъект, который имеет такие свойства, как ...

  • width
  • height
  • backgroundColour
  • draw(),

draw()Метод в настоящее время Переустановка canvasи что , когда встал вопрос.

В случае , если Paddleобъект есть draw()метод отвечает за его чертеж, или если draw()на Pongобъекте будет отвечать за разработку своих актеров (я предполагаю , что это правильный термин, пожалуйста , поправьте меня , если я неправильно).

Я подумал, что было бы выгодно Paddleрисовать себя, когда я создаю экземпляры двух объектов, Playerи Enemy. Если бы это было не в Pongdraw(), я должен был бы написать подобный код дважды.

Какова лучшая практика здесь?

Спасибо.

Алекс
источник
Аналогичный вопрос: gamedev.stackexchange.com/questions/13492/…
MichaelHouse

Ответы:

49

Рисовать актеров - не очень хороший дизайн по двум основным причинам:

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

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

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

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

Затем вы можете просто выполнить итерацию каждого видимого актера, добавить описание рендера в рендер и позволить ему делать свое дело (в основном; вы можете обобщить это еще дальше).

Джош
источник
14
+1 за «код рисования рендера», но -1 за принцип единой ответственности, который принес программистам больше вреда, чем пользы . У него правильная идея (разделение интересов) , но способ, которым это заявлено («каждый класс должен иметь только одну ответственность и / или только одну причину для изменения») , просто неверен .
BlueRaja - Дэнни Пфлюгофт
2
-1 за 1), как указала BlueRaja, этот принцип идеалистический, но не практический. -1 за 2) потому что это не делает расширение значительно более сложным. Если вам нужно изменить весь движок рендеринга, у вас будет намного больше кода для изменения, чем то, что мало у вас в коде актера. Я бы предпочел, actor.draw(renderer,x,y)чем renderer.draw(actor.getAttribs(),x,y).
CorsiKa
1
Хороший ответ! Что ж, «Вы не должны нарушать принцип единой ответственности» не входит в мой декалог, но все же это хороший принцип, который нужно принимать во внимание
FxIII
@glowcoder Это не главное, вы можете поддерживать actor.draw (), который определяется как {renderer.draw (mesh, attribs, transform); }. В OpenGL я поддерживаю struct RenderDetail { glVAO* vao; int shader; int texture; Matrix4f* transform; }более или менее для моего рендерера. Таким образом, средство визуализации не заботится о том, что представляют данные, оно просто обрабатывает их. На основании этой информации я также могу решить, следует ли сортировать порядок вызовов отрисовки, чтобы уменьшить общее количество вызовов графического процессора.
замедленная
16

Шаблон посетитель может быть полезным здесь.

То, что вы можете сделать, это иметь интерфейс Renderer, который знает, как рисовать каждый объект, и метод «себе сам» в каждом действующем субъекте, который определяет, какой (конкретный) метод визуализации вызывать, например

interface Renderer {
    void drawPaddle(Player owner, Rectangle position);
    // Note: the Renderer chooses the Color based on which player the paddle belongs to

    // Also drawBackground, drawBall etc.
}

interface Actor {
    void draw(Renderer renderer);
}

class Paddle implements Actor {
    void draw(Renderer renderer) {
        renderer.drawPaddle(this.owner, this.getBounds());
    }
}

Таким образом, все еще легко портировать на другую графическую библиотеку или фреймворк (однажды я портировал игру с Swing / Java2D на LWJGL и был очень рад, что использовал этот шаблон вместо прохождения Graphics2Dвокруг).

Есть еще одно преимущество: рендерер можно тестировать отдельно от любого кода актера.

finnw
источник
2

В вашем примере вы бы хотели, чтобы объекты Pong реализовывали draw () и отображали сами себя.

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

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

Тогда у вас будет класс PongRenderer (), который имеет ссылку на объект Pong, и он затем берет на себя ответственность за рендеринг класса Pong (), это может включать рендеринг Paddles или наличие класса PaddleRenderer, чтобы позаботиться об этом.

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

Майкл
источник
-1

Если бы вы делали это в Pongdraw (), вам не нужно было бы дублировать код - просто вызовите Paddleфункцию draw () для каждого из ваших манипуляторов. Таким образом, в вашей Pongничьей () у вас будет

humanPaddle.draw();
compPaddle.draw();
Benixo
источник
-1

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

class X
{
  public void Draw( )
  {
     // Do something 
  } 
}

class Y
{
  public void Draw( )
   { // Do Something 
   } 
}

class Game
{
  X objX;
  Y objY;

  @Override
  public void OnDraw( )
  {
      objX.Draw( );
      objY.Draw( ); 
  } 
}

Это не код, а просто чтобы показать, как рисовать объекты.

user43609
источник
2
Это не «Как рисовать», а единственный способ сделать это. Как упоминалось в предыдущих ответах, этот метод имеет некоторые недостатки и мало преимуществ, в то время как другие шаблоны имеют значительные преимущества и гибкие возможности.
Тройсеф
Я ценю, но так как нужно было дать простой пример различных способов рисования актеров на сцене, я упомянул об этом.
user43609