Haruskah aktor dalam permainan bertanggung jawab untuk menggambar diri mereka sendiri?

41

Saya sangat baru dalam pengembangan game, tetapi tidak untuk pemrograman.

Saya (lagi-lagi) bermain-main dengan game tipe Pong menggunakan canvaselemen JavaScript .

Saya telah membuat Paddleobjek yang memiliki properti berikut ...

  • width
  • height
  • x
  • y
  • colour

Saya juga punya Pongobjek yang memiliki properti seperti ...

  • width
  • height
  • backgroundColour
  • draw().

The draw()Metode saat ini ulang canvasdan yang mana pertanyaan muncul.

Haruskah Paddleobjek memiliki draw()metode yang bertanggung jawab untuk menggambar nya, atau harus draw()dari Pongobjek bertanggung jawab untuk menggambar aktor nya (saya berasumsi bahwa adalah istilah yang benar, perbaiki saya jika saya salah).

Saya pikir itu akan menguntungkan bagi Paddleuntuk menggambar sendiri, karena saya instantiate dua objek, Playerdan Enemy. Jika tidak ada di Pong's draw(), saya harus menulis kode yang sama dua kali.

Apa praktik terbaik di sini?

Terima kasih.

alex
sumber
Pertanyaan serupa: gamedev.stackexchange.com/questions/13492/…
MichaelHouse

Jawaban:

49

Membuat aktor menggambar sendiri bukanlah desain yang baik, karena dua alasan utama:

1) itu melanggar prinsip tanggung jawab tunggal , karena aktor-aktor itu mungkin memiliki pekerjaan lain yang harus dilakukan sebelum Anda memasukkan kode render ke dalamnya.

2) itu membuat ekstensi sulit; jika setiap jenis aktor mengimplementasikan gambarnya sendiri, dan Anda perlu mengubah cara Anda menggambar secara umum , Anda mungkin harus memodifikasi banyak kode. Menghindari penggunaan warisan secara berlebihan dapat meringankan hal ini sampai batas tertentu, tetapi tidak sepenuhnya.

Lebih baik bagi penyaji Anda untuk menangani gambar. Lagi pula, itulah artinya menjadi penyaji. Metode menggambar renderer harus mengambil objek "deskripsi render", yang berisi semua yang Anda butuhkan untuk membuat sesuatu. Referensi ke (mungkin dibagi) data geometri, transformasi spesifik-instance atau sifat material seperti warna, dan lain-lain. Itu kemudian menarik itu, dan tidak peduli apa yang membuat deskripsi seharusnya "menjadi."

Aktor Anda kemudian dapat berpegang pada deskripsi render yang mereka buat sendiri. Karena aktor biasanya adalah tipe pemrosesan logika, mereka dapat mendorong perubahan status ke deskripsi render sesuai kebutuhan - misalnya, saat aktor mengalami kerusakan, aktor dapat mengatur warna deskripsi rendernya menjadi merah untuk mengindikasikan hal ini.

Kemudian Anda dapat dengan mudah mengulangi setiap aktor yang terlihat, meminta deskripsi render mereka ke dalam renderer, dan membiarkannya melakukan hal tersebut (pada dasarnya; Anda dapat menggeneralisasi ini lebih jauh).

Josh
sumber
14
+1 untuk "kode gambar penyaji," tetapi -1 untuk prinsip tanggung jawab tunggal, yang telah merugikan programmer lebih baik daripada baik . Itu punya ide yang tepat (pemisahan keprihatinan) , tetapi cara itu dinyatakan ("setiap kelas harus hanya memiliki satu tanggung jawab, dan / atau hanya satu alasan untuk berubah") adalah salah .
BlueRaja - Danny Pflughoeft
2
-1 untuk 1), seperti yang ditunjukkan BlueRaja, prinsip ini idealis, tetapi tidak praktis. -1 untuk 2) karena itu tidak membuat ekstensi secara signifikan lebih sulit. Jika Anda harus mengubah seluruh mesin rendering Anda, Anda akan memiliki lebih banyak kode untuk diubah daripada sedikit yang Anda miliki dalam kode aktor Anda. Saya lebih suka actor.draw(renderer,x,y)daripada renderer.draw(actor.getAttribs(),x,y).
corsiKa
1
Jawaban yang bagus! Ya, "Kamu tidak boleh melanggar prinsip tanggung jawab tunggal" tidak ada di dalam dekalog saya, tetapi masih merupakan prinsip yang baik untuk diperhitungkan
FxIII
@ glowcoder Bukan itu intinya, Anda dapat mempertahankan actor.draw (), yang didefinisikan sebagai {renderer.draw (mesh, attribs, transform); }. Di OpenGL, saya mempertahankan struct RenderDetail { glVAO* vao; int shader; int texture; Matrix4f* transform; }lebih atau kurang untuk renderer saya. Dengan begitu renderer tidak peduli dengan data yang diwakilinya, itu hanya memprosesnya. Berdasarkan informasi ini, saya kemudian dapat memutuskan apakah akan mengurutkan urutan panggilan undian untuk mengurangi panggilan GPU secara keseluruhan.
deceleratedcaviar
16

The Pola Pengunjung dapat berguna di sini.

Apa yang dapat Anda lakukan adalah memiliki antarmuka Renderer yang tahu cara menggambar setiap objek dan metode "gambar sendiri" di setiap aktor yang menentukan metode penyaji (spesifik) mana yang harus dipanggil, misalnya

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

Dengan cara ini masih mudah untuk port ke perpustakaan grafis atau kerangka kerja lain (saya pernah mem-porting sebuah game dari Swing / Java2D ke LWJGL dan sangat senang saya telah menggunakan pola ini alih-alih Graphics2Dberkeliling.)

Ada keuntungan lain: renderer dapat diuji secara terpisah dari kode aktor apa pun

menemukan
sumber
2

Dalam contoh Anda, Anda ingin memiliki objek Pong mengimplementasikan draw () dan membuat sendiri.

Meskipun Anda tidak akan melihat adanya keuntungan signifikan dalam proyek sebesar ini, umumnya memisahkan logika game dan representasi visualnya (rendering) adalah kegiatan yang bermanfaat.

Maksud saya ini, Anda akan memiliki objek game yang dapat diperbarui () tetapi mereka tidak tahu bagaimana mereka dirender, mereka hanya peduli dengan simulasi.

Maka Anda akan memiliki kelas PongRenderer () yang memiliki referensi ke objek Pong, dan kemudian bertanggung jawab untuk memberikan kelas Pong (), ini bisa melibatkan rendering Paddles, atau memiliki kelas PaddleRenderer untuk mengurusnya.

Pemisahan masalah dalam hal ini adalah hal yang cukup alami yang berarti kelas Anda bisa kurang membengkak, dan lebih mudah untuk memodifikasi bagaimana hal-hal yang diberikan, itu tidak lagi harus mengikuti hierarki yang dilakukan simulasi Anda.

Michael
sumber
-1

Jika Anda melakukannya Pongdi draw (), Anda tidak perlu menduplikasi kode - cukup panggil fungsi Paddledraw () untuk setiap dayung Anda. Jadi, dalam Pongundian Anda () Anda akan

humanPaddle.draw();
compPaddle.draw();
Benixo
sumber
-1

Setiap objek harus mengandung fungsi draw-nya sendiri yang harus dipanggil oleh kode pembaruan game atau kode draw. Seperti

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

Ini bukan kode tetapi hanya untuk menunjukkan, bagaimana menggambar objek harus dilakukan.

pengguna43609
sumber
2
Ini bukan "Bagaimana menggambar harus dilakukan" tetapi satu cara melakukannya. Seperti disebutkan dalam jawaban sebelumnya, metode ini memiliki beberapa kelemahan dan sedikit manfaat, sementara pola lain memiliki manfaat dan fleksibilitas yang signifikan.
Troyseph
Saya menghargai tetapi karena contoh sederhana akan diberikan tentang berbagai cara menggambar aktor dalam adegan, saya menyebutkan cara ini.
user43609