Czy aktorzy w grze powinni być odpowiedzialni za losowanie?

41

Jestem bardzo nowy w tworzeniu gier, ale nie w programowaniu.

Gram (ponownie) w grę typu Pong, używając canvaselementu JavaScript .

Utworzyłem Paddleobiekt, który ma następujące właściwości ...

  • width
  • height
  • x
  • y
  • colour

Mam również Pongobiekt, który ma właściwości takie jak ...

  • width
  • height
  • backgroundColour
  • draw().

draw()Metoda obecnie jest zresetowanie canvasi to jest pytanie, gdzie wpadł.

W przypadku, gdy Paddleprzedmiot posiada draw()metoda odpowiedzialna za jego rysunku, lub jeżeli draw()w Pongobiekcie jest odpowiedzialny za przygotowanie jego uczestników (zakładam, że jest poprawne określenie, proszę mnie poprawić, jeśli jestem nieprawidłowe).

Uznałem, że korzystne Paddlebyłoby rysowanie samego siebie, ponieważ tworzę instancję dwóch obiektów Playeri Enemy. Gdyby nie było w Pong„s draw(), to muszę napisać podobny kod dwukrotnie.

Jaka jest tutaj najlepsza praktyka?

Dzięki.

alex
źródło
Podobne pytanie: gamedev.stackexchange.com/questions/13492/…
MichaelHouse

Odpowiedzi:

49

Nakłanianie aktorów do rysowania się nie jest dobrym projektem z dwóch głównych powodów:

1) narusza zasadę pojedynczej odpowiedzialności , ponieważ ci aktorzy prawdopodobnie mieli przed sobą inną pracę, zanim wrzuciliście do nich kod renderujący.

2) utrudnia przedłużenie; jeśli każdy typ aktora implementuje własny rysunek i konieczna jest ogólna zmiana sposobu rysowania , może być konieczne zmodyfikowanie dużej ilości kodu. Unikanie nadmiernego dziedziczenia może złagodzić to w pewnym stopniu, ale nie całkowicie.

Lepiej, żeby Twój renderer obsługiwał rysunek. W końcu to właśnie znaczy być rendererem. Metoda losowania renderera powinna przyjmować obiekt „renderuj opis”, który zawiera wszystko, czego potrzebujesz do renderowania rzeczy. Odniesienia do (prawdopodobnie udostępnionych) danych geometrii, transformacji specyficznych dla instancji lub właściwości materiału, takich jak kolor itp. Następnie rysuje to i nie przejmuje się tym, czym powinien być ten opis renderowania.

Twoi aktorzy mogą następnie trzymać się opisu renderowania, który sami tworzą. Ponieważ aktorzy są zwykle typami przetwarzania logicznego, mogą w razie potrzeby przesuwać zmiany stanu do opisu renderowania - na przykład, gdy aktor odniesie uszkodzenie, może ustawić kolor opisu renderowania na czerwony, aby to wskazać.

Następnie możesz po prostu iterować każdego widocznego aktora, sprawdzać jego opisy renderowania w rendererze i pozwolić mu działać (w zasadzie; możesz to uogólnić jeszcze bardziej).

Josh
źródło
14
+1 dla „kodu rysującego renderera”, ale -1 dla zasady pojedynczej odpowiedzialności, która wyrządziła programistom więcej szkody niż pożytku . Ma właściwy pomysł (rozdzielenie obaw) , ale sposób, w jaki jest stwierdzony („każda klasa powinna mieć tylko jedną odpowiedzialność i / lub tylko jeden powód do zmiany”) jest po prostu zły .
BlueRaja - Danny Pflughoeft
2
-1 za 1), jak wskazał BlueRaja, zasada ta jest idealistyczna, ale niepraktyczna. -1 za 2), ponieważ nie utrudnia to znacznie rozszerzenia. Jeśli musisz zmienić cały silnik renderowania, będziesz mieć o wiele więcej kodu do zmiany niż niewiele w kodzie aktora. Wolałbym dużo actor.draw(renderer,x,y)niż renderer.draw(actor.getAttribs(),x,y).
corsiKa
1
Dobra odpowiedź! Cóż, „Nie naruszysz zasady pojedynczej odpowiedzialności” nie jest w moim dekalogu, ale nadal jest dobrą zasadą, którą należy wziąć pod uwagę
FxIII
@glowcoder Nie o to chodzi, możesz zachować actor.draw (), który jest zdefiniowany jako {renderer.draw (mesh, attribs, transform); }. W OpenGL zachowuję struct RenderDetail { glVAO* vao; int shader; int texture; Matrix4f* transform; }mniej więcej mniej więcej mój renderer. W ten sposób mechanizm renderujący nie dba o to, co reprezentują dane, po prostu je przetwarza. Na podstawie tych informacji mogę również zdecydować, czy posortować kolejność połączeń losujących, aby zmniejszyć ogólne połączenia GPU.
spowolniłaviar
16

Odwiedzający mogą tu być użyteczne.

To, co możesz zrobić, to mieć interfejs Renderera, który wie, jak narysować każdy obiekt oraz metodę „narysuj siebie” w każdym z aktorów, która określa, którą (konkretną) metodę renderera wywołać, np.

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());
    }
}

W ten sposób nadal łatwo jest przenieść do innej biblioteki graficznej lub frameworka (kiedyś przeniosłem grę ze Swing / Java2D do LWJGL i bardzo się ucieszyłem, że użyłem tego wzorca zamiast przekazywać Graphics2D.)

Jest jeszcze jedna zaleta: renderer może być testowany niezależnie od dowolnego kodu aktora

finnw
źródło
2

W twoim przykładzie chciałbyś, aby obiekty Pong implementowały funkcję draw () i renderowały się.

Chociaż nie zauważysz żadnych znaczących korzyści w projekcie o tej wielkości, warto oddzielić logikę gry od ich wizualnej reprezentacji (renderowania).

Rozumiem przez to, że masz obiekty gry, które można aktualizować (), ale nie mają pojęcia o tym, jak są renderowane, zajmują się tylko symulacją.

Wtedy miałbyś klasę PongRenderer (), która ma odwołanie do obiektu Pong, a następnie zajmuje się renderowaniem klasy Pong (), może to obejmować renderowanie Paddles lub posiadanie klasy PaddleRenderer, która się tym zajmie.

Rozdzielenie problemów w tym przypadku jest dość naturalne, co oznacza, że ​​twoje klasy mogą być mniej rozdęte, i łatwiej jest modyfikować sposób renderowania rzeczy, nie musi już podążać za hierarchią, którą wykonuje twoja symulacja.

Michał
źródło
-1

Jeśli miałbyś to zrobić w funkcji Pongdraw (), nie musiałbyś duplikować kodu - wystarczy wywołać funkcję Paddledraw () dla każdej z łyżek. Więc w swoim Pongdraw () miałbyś

humanPaddle.draw();
compPaddle.draw();
Benixo
źródło
-1

Każdy obiekt powinien zawierać własną funkcję losowania, którą powinien wywołać kod aktualizacji gry lub kod losowania. Lubić

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( ); 
  } 
}

To nie jest kod, ale tylko pokazanie, jak należy rysować obiekty.

użytkownik43609
źródło
2
To nie jest „Jak zrobić rysunek”, ale jeden sposób na zrobienie tego. Jak wspomniano w poprzednich odpowiedziach, ta metoda ma pewne wady i kilka korzyści, podczas gdy inne wzorce mają znaczące zalety i elastyczność.
Troyseph,
Doceniam to, ale ponieważ podałem prosty przykład różnych sposobów rysowania aktorów na scenie, wspomniałem o tym w ten sposób.
user43609,