Apa kontra menggunakan DrawableGameComponent untuk setiap instance objek game?

25

Saya telah membaca di banyak tempat bahwa DrawableGameComponents harus disimpan untuk hal-hal seperti "level" atau semacam manajer alih-alih menggunakannya, misalnya, untuk karakter atau ubin (Seperti yang dikatakan orang ini di sini ). Tapi saya tidak mengerti mengapa demikian. Saya membaca posting ini dan itu masuk akal bagi saya, tetapi ini adalah minoritas.

Saya biasanya tidak akan terlalu memperhatikan hal-hal seperti ini, tetapi dalam hal ini saya ingin tahu mengapa mayoritas orang percaya ini bukan jalan yang harus ditempuh. Mungkin saya melewatkan sesuatu.

Kenji Kina
sumber
Saya ingin tahu mengapa, dua tahun kemudian, Anda telah menghapus tanda "diterima" dari jawaban saya ? Biasanya saya tidak akan benar-benar peduli - tetapi dalam kasus ini menyebabkan VeraShackle ini menyebalkan keliru jawaban saya untuk gelembung ke atas :(
Andrew Russell
@AndrewRussell Saya membaca utas ini belum lama (karena beberapa tanggapan dan suara) dan berpikir saya tidak memenuhi syarat untuk memberikan pendapat tentang apakah jawaban Anda adalah yang benar atau tidak. Seperti yang Anda katakan, saya kira jawaban VeraShackle bukanlah jawaban yang paling tepat, jadi saya menandai jawaban Anda sebagai benar lagi.
Kenji Kina
1
Saya dapat memadatkan jawaban saya di sini sebagai berikut: " Menggunakan DrawableGameComponentkunci Anda menjadi satu set tanda tangan metode dan model data tertentu yang dipertahankan. Ini biasanya merupakan pilihan yang salah pada awalnya, dan kunci-in secara signifikan menghambat evolusi kode di masa mendatang. "Tetapi izinkan saya memberi tahu Anda apa yang terjadi di sini ...
Andrew Russell
1
DrawableGameComponentadalah bagian dari inti XNA API (sekali lagi: sebagian besar karena alasan historis). Pemrogram pemula menggunakannya karena "ada" dan "berfungsi" dan mereka tidak tahu yang lebih baik. Mereka melihat jawaban saya mengatakan bahwa mereka " salah " dan - karena sifat psikologi manusia - menolaknya dan memilih "sisi" yang lain. Yang kebetulan adalah argumentasi non-jawaban VeraShackle; yang kebetulan mengambil momentum karena saya tidak segera menyebutnya (lihat komentar itu). Saya merasa bahwa bantahan akhirnya masih cukup.
Andrew Russell
1
Jika ada yang tertarik untuk mempertanyakan legitimasi jawaban saya atas dasar upvotes: sementara memang benar bahwa (GDSE sedang dibumbui dengan novis yang disebutkan sebelumnya) jawaban VeraShackle telah berhasil mendapatkan beberapa upvotes lebih banyak daripada saya dalam beberapa tahun terakhir, jangan lupa bahwa saya memiliki ribuan lebih banyak upvotes di seluruh jaringan, sebagian besar di XNA. Saya telah mengimplementasikan XNA API pada berbagai platform. Dan saya adalah pengembang game profesional menggunakan XNA. Silsilah jawaban saya sangat sempurna.
Andrew Russell

Jawaban:

22

"Komponen Game" adalah bagian paling awal dari desain XNA 1.0. Mereka seharusnya bekerja seperti kontrol untuk WinForms. Idenya adalah Anda dapat menyeret-dan-menjatuhkannya ke dalam game Anda menggunakan GUI (semacam bit untuk menambahkan kontrol non-visual, seperti timer, dll) dan mengatur properti pada mereka.

Jadi Anda dapat memiliki komponen seperti mungkin Kamera atau ... penghitung FPS? ... atau ...?

Kamu melihat? Sayangnya ide ini tidak benar-benar berfungsi untuk game dunia nyata. Jenis komponen yang Anda inginkan untuk gim tidak benar-benar dapat digunakan kembali. Anda mungkin memiliki komponen "pemain", tetapi itu akan sangat berbeda dengan komponen "pemain" dari setiap permainan lainnya.

Saya membayangkan itu karena alasan ini, dan karena kurangnya waktu, GUI ditarik sebelum XNA 1.0.

Tetapi API masih dapat digunakan ... Inilah mengapa Anda tidak harus menggunakannya:

Pada dasarnya itu adalah apa yang paling mudah untuk menulis dan membaca dan men-debug dan memelihara sebagai pengembang game. Melakukan sesuatu seperti ini di kode pemuatan Anda:

player.DrawOrder = 100;
enemy.DrawOrder = 200;

Jauh lebih tidak jelas daripada hanya melakukan ini:

virtual void Draw(GameTime gameTime)
{
    player.Draw();
    enemy.Draw();
}

Terutama ketika, dalam game dunia nyata, Anda mungkin berakhir dengan sesuatu seperti ini:

GraphicsDevice.Viewport = cameraOne.Viewport;
player.Draw(cameraOne, spriteBatch);
enemy.Draw(cameraOne, spriteBatch);

GraphicsDevice.Viewport = cameraTwo.Viewport;
player.Draw(cameraTwo, spriteBatch);
enemy.Draw(cameraTwo, spriteBatch);

(Memang Anda bisa berbagi kamera dan spriteBatch menggunakan Servicesarsitektur yang sama . Tapi itu juga ide yang buruk untuk alasan yang sama.)

Arsitektur ini - atau ketiadaan - jauh lebih ringan dan fleksibel!

Saya dapat dengan mudah mengubah urutan pengundian atau menambahkan beberapa viewports atau menambahkan efek target render, hanya dengan mengubah beberapa baris. Untuk melakukannya di sistem lain mengharuskan saya untuk berpikir tentang apa yang dilakukan semua komponen - menyebar ke banyak file - dan dalam urutan apa.

Ini semacam perbedaan antara pemrograman deklaratif vs imperatif. Ternyata, untuk pengembangan game, pemrograman imperatif jauh lebih disukai.

Andrew Russell
sumber
2
Saya mendapat kesan mereka untuk pemrograman berbasis Komponen , yang tampaknya banyak digunakan untuk pemrograman game.
BlueRaja - Danny Pflughoeft
3
Kelas komponen game XNA tidak benar-benar langsung berlaku untuk pola itu. Pola itu adalah tentang menyusun objek dari berbagai komponen. Kelas-kelas XNA diarahkan untuk satu-komponen-per-objek. Jika mereka sangat cocok dengan desain Anda, maka tentu saja menggunakannya. Tetapi Anda tidak harus membangun desain Anda di sekitar mereka! Lihat juga jawaban saya di sini - lihat di mana saya sebutkan DrawableGameComponent.
Andrew Russell
1
Saya setuju bahwa arsitektur GameComponent / Service tidak terlalu berguna dan terdengar seperti konsep aplikasi Office atau suka dipaksa ke dalam kerangka kerja game. Tapi saya lebih suka menggunakan player.DrawOrder = 100;metode ini daripada yang penting untuk menggambar. Saya sedang menyelesaikan permainan Flixel sekarang, yang menarik objek sesuai urutan Anda menambahkannya ke tempat kejadian. Sistem FlxGroup mengurangi beberapa hal ini, tetapi memiliki semacam penyortiran Z-index akan lebih baik.
michael.bartnett
@ michael.bartnett Perhatikan bahwa Flixel adalah API mode-tertahan, sedangkan XNA (kecuali, jelas, sistem komponen permainan) adalah API mode langsung. Jika Anda menggunakan retained-mode API, atau mengimplementasikan API Anda sendiri , kemampuan untuk mengubah draw-order dengan cepat sangat penting. Faktanya, kebutuhan untuk mengganti draw-order dengan cepat adalah alasan yang bagus untuk membuat sesuatu tetap dipertahankan (contoh dari sistem windowing muncul di pikiran). Tetapi jika Anda dapat menulis sesuatu sebagai mode langsung, jika platform API (seperti XNA) dibangun dengan cara itu, maka IMO Anda harus melakukannya.
Andrew Russell
Untuk referensi lebih lanjut gamedev.stackexchange.com/a/112664/56
Kenji Kina
26

Hmmm. Saya mendapat tantangan yang satu ini demi sedikit keseimbangan di sini. Saya khawatir.

Tampaknya ada 5 tuduhan dalam balasan Andrew, jadi saya akan mencoba dan menangani masing-masing secara bergantian:

1) Komponen adalah desain yang ketinggalan zaman dan ditinggalkan.

Ide pola desain komponen sudah ada jauh sebelum XNA - pada dasarnya itu hanyalah keputusan untuk membuat objek, bukan objek permainan yang melengkung, rumah dari perilaku Update and Draw mereka sendiri. Untuk objek dalam koleksi komponennya, gim akan memanggil metode ini (dan beberapa lainnya) secara otomatis pada waktu yang tepat - yang krusial objek gim itu sendiri tidak harus 'mengetahui' kode ini. Jika Anda ingin menggunakan kembali kelas gim Anda di gim yang berbeda (dan dengan demikian obyek gim yang berbeda), penggandaan objek ini secara fundamental masih merupakan ide bagus. Melihat sekilas demo XNA4.0 terbaru (yang diproduksi secara profesional) dari AppHub menegaskan kesan saya bahwa Microsoft masih (cukup benar menurut saya) di belakang ini.

2) Andrew tidak dapat memikirkan lebih dari 2 kelas permainan yang dapat digunakan kembali.

Saya bisa. Sebenarnya saya bisa memikirkan banyak! Saya baru saja memeriksa lib bersama saya saat ini, dan terdiri lebih dari 80 komponen dan layanan yang dapat digunakan kembali . Untuk memberi Anda rasa, Anda bisa memiliki:

  • Manajer Status Game / Layar
  • Pemisah partikel
  • Primitif yang dapat digambar
  • Pengendali AI
  • Pengontrol input
  • Pengontrol animasi
  • Baliho
  • Manajer Fisika & Tabrakan
  • Kamera

Setiap ide gim tertentu membutuhkan setidaknya 5-10 hal dari set ini - dijamin - dan mereka dapat berfungsi penuh dalam gim yang sama sekali baru dengan satu baris kode.

3) Mengontrol urutan undian dengan urutan panggilan undian eksplisit lebih disukai daripada menggunakan properti DrawOrder komponen.

Yah, pada dasarnya saya setuju dengan Andrew yang satu ini. TETAPI, jika Anda membiarkan semua properti DrawOrder komponen Anda sebagai default, Anda masih dapat secara eksplisit mengontrol urutan pengundiannya dengan urutan di mana Anda menambahkannya ke koleksi. Ini benar-benar identik dalam istilah 'visibilitas' dengan urutan eksplisit panggilan draw manual mereka.

4) Memanggil banyak metode Draw objek sendiri lebih 'ringan' daripada memanggil mereka dengan metode framework yang ada.

Mengapa? Ditambah lagi, dalam semua hal kecuali proyek yang paling sepele, logika Pembaruan dan Kode Draw Anda akan benar-benar menaungi metode yang Anda panggil dalam hal kinerja yang baik.

5) Menempatkan kode Anda dalam satu file lebih disukai, ketika debugging, daripada memilikinya di file objek individu.

Yah, pertama, ketika saya sedang debug di Visual Studio lokasi breakpoint dalam hal file pada disk hampir tidak terlihat hari ini - IDE berurusan dengan lokasi tanpa drama. Tetapi juga pertimbangkan sejenak bahwa Anda memiliki masalah dengan gambar Skybox Anda. Apakah pergi ke metode Draw Skybox benar-benar lebih berat daripada menemukan kode menggambar skybox dalam metode master draw (mungkin sangat panjang) dalam objek Game? Tidak, tidak juga - sebenarnya saya berpendapat bahwa itu sangat berlawanan.

Jadi, dalam ringkasan - mohon perhatikan argumen Andrew di sini. Saya tidak percaya mereka benar-benar memberikan alasan substantif untuk menulis kode permainan terjerat. Sisi buruk dari pola komponen yang dipisahkan (digunakan secara bijaksana) sangat besar.

VeraShackle
sumber
2
Saya baru saja memperhatikan posting ini, dan saya harus mengeluarkan bantahan yang kuat: Saya tidak punya masalah dengan kelas yang dapat digunakan kembali! (Yang mungkin memiliki metode menggambar dan memperbarui, dan sebagainya.) Saya mengambil masalah khusus dengan menggunakan ( Drawable) GameComponentuntuk memberikan arsitektur game Anda, karena hilangnya kontrol eksplisit atas urutan pengundian / pembaruan (yang Anda sepakati di # 3) kekakuan antarmuka mereka (yang Anda tidak alamat).
Andrew Russell
1
Poin Anda # 4 mengambil penggunaan kata "ringan" untuk kinerja, di mana saya sebenarnya berarti fleksibilitas arsitektur. Poin Anda yang tersisa # 1, # 2, dan terutama # 5 tampaknya membuat orang -jerami yang tidak masuk akal yang saya pikir sebagian besar / semua kode harus dimasukkan ke dalam kelas permainan utama Anda! Baca jawaban saya di sini untuk beberapa pemikiran lebih rinci saya tentang arsitektur.
Andrew Russell
5
Akhirnya: jika Anda akan mengirim jawaban untuk jawaban saya, mengatakan bahwa saya salah, akan sopan juga menambahkan balasan ke jawaban saya sehingga saya akan melihat pemberitahuan tentang itu, daripada harus menemukan jawaban itu dua bulan kemudian.
Andrew Russell
Saya mungkin salah paham, tetapi bagaimana ini menjawab pertanyaan? Haruskah saya menggunakan GameComponent untuk sprite saya atau tidak?
Biasa
Bantahan lebih lanjut: gamedev.stackexchange.com/a/112664/56
Kenji Kina
4

Saya sedikit tidak setuju dengan Andrew, tetapi saya juga berpikir ada waktu dan tempat untuk semuanya. Saya tidak akan menggunakan Drawable(GameComponent)untuk sesuatu yang sederhana seperti Sprite atau Musuh. Namun, saya akan menggunakannya untuk SpriteManageratau EnemyManager.

Kembali ke apa yang dikatakan Andrew tentang menggambar dan memperbarui ketertiban, saya pikir Sprite.DrawOrderakan sangat membingungkan bagi banyak sprite. Selain itu, relatif mudah untuk mengatur DrawOrderdan UpdateOrderuntuk sejumlah kecil (atau masuk akal) Manajer yang (Drawable)GameComponents.

Saya pikir Microsoft akan menyingkirkan mereka jika mereka benar-benar kaku dan tidak ada yang menggunakannya. Tetapi mereka tidak melakukannya karena mereka bekerja dengan sangat baik, jika perannya benar. Saya sendiri menggunakannya untuk mengembangkan Game.Servicesyang diperbarui di latar belakang.

Amble Lighthertz
sumber
2

Saya juga memikirkan hal ini, dan terlintas di benak saya: Misalnya, untuk mencuri contoh di tautan Anda, dalam permainan RTS Anda bisa menjadikan setiap unit komponen komponen permainan. Baik.

Tetapi Anda juga bisa membuat komponen UnitManager, yang menyimpan daftar unit, menggambar dan memperbaruinya sesuai kebutuhan. Saya kira alasan Anda ingin membuat unit Anda menjadi komponen game di tempat pertama adalah untuk mengkotak-kotakkan kode, jadi apakah solusi ini cukup mencapai tujuan itu?

Hebat
sumber
2

Dalam komentar, saya memposting versi kental dari jawaban lama saya:

Menggunakan DrawableGameComponentmengunci Anda ke dalam satu set tanda tangan metode dan model data tertentu yang dipertahankan. Ini biasanya merupakan pilihan yang salah pada awalnya, dan penguncian secara signifikan menghambat evolusi kode di masa depan.

Di sini saya akan mencoba mengilustrasikan mengapa hal ini terjadi dengan memposting beberapa kode dari proyek saya saat ini.


Objek root yang mempertahankan keadaan dunia game memiliki Updatemetode yang terlihat seperti ini:

void Update(UpdateContext updateContext, MultiInputState currentInput)

Ada sejumlah titik masuk ke Update, tergantung pada apakah game berjalan di jaringan atau tidak. Mungkin, misalnya, untuk keadaan gim digulung kembali ke titik waktu sebelumnya dan kemudian disimulasikan kembali.

Ada juga beberapa metode tambahan seperti pembaruan, seperti:

void PlayerJoin(int playerIndex, string playerName, UpdateContext updateContext)

Dalam kondisi permainan ada daftar aktor yang akan diperbarui. Tapi ini lebih dari sekadar Updatepanggilan. Ada melewati tambahan sebelum dan sesudah untuk menangani fisika. Ada juga beberapa barang mewah untuk pemijahan yang ditangguhkan / penghancuran aktor, serta transisi tingkat.

Di luar kondisi permainan, ada sistem UI yang perlu dikelola secara mandiri. Tetapi beberapa layar di UI juga harus dapat berjalan dalam konteks jaringan.


Untuk menggambar, karena sifat permainannya, ada sistem penyortiran dan pemusnahan khusus yang mengambil input dari metode ini:

virtual void RegisterToDraw(SortedDrawList sortedDrawList, Definitions definitions)
virtual void RegisterShadow(ShadowCasterList shadowCasterList, Definitions definitions)

Dan kemudian, setelah mengetahui apa yang akan menggambar, secara otomatis memanggil:

virtual void Draw(DrawContext drawContext, int tag)

The tagparameter diperlukan karena beberapa pelaku dapat memegang aktor lain - dan begitu perlu untuk dapat menarik baik di atas dan di bawah aktor diadakan. Sistem penyortiran memastikan bahwa ini terjadi dalam urutan yang benar.

Ada juga sistem menu untuk menggambar. Dan sistem hierarkis untuk menggambar UI, sekitar HUD, di sekitar game.


Sekarang bandingkan persyaratan itu dengan yang DrawableGameComponentmenyediakan:

public class DrawableGameComponent
{
    public virtual void Update(GameTime gameTime);
    public virtual void Draw(GameTime gameTime);
    public int UpdateOrder { get; set; }
    public int DrawOrder { get; set; }
}

Tidak ada cara yang masuk akal untuk menggunakan sistem menggunakan DrawableGameComponentsistem yang saya jelaskan di atas. (Dan itu bukan persyaratan yang tidak biasa untuk permainan kompleksitas yang bahkan sederhana).

Juga, sangat penting untuk dicatat bahwa persyaratan itu tidak muncul begitu saja pada awal proyek. Mereka berkembang selama berbulan-bulan pekerjaan pembangunan.


Apa yang biasanya dilakukan orang, jika mereka memulai DrawableGameComponentrute, adalah mereka mulai membangun dari peretasan:

  • Alih-alih menambahkan metode, mereka melakukan beberapa down-casting jelek ke tipe basis mereka sendiri (mengapa tidak menggunakannya secara langsung?).

  • Alih-alih mengganti kode untuk menyortir dan memohon Draw/ Update, mereka melakukan beberapa hal yang sangat lambat untuk mengubah nomor pemesanan on-the-fly.

  • Dan yang terburuk: Alih-alih menambahkan parameter ke metode, mereka mulai "melewati" "parameter" di lokasi memori yang bukan tumpukan (penggunaan Servicesuntuk ini populer). Ini berbelit-belit, rawan kesalahan, lambat, rapuh, jarang aman, dan cenderung menjadi masalah jika Anda menambahkan jaringan, membuat alat eksternal, dan sebagainya.

    Ini juga membuat kode digunakan kembali lebih sulit , karena sekarang dependensi Anda tersembunyi di dalam "komponen", alih-alih diterbitkan pada batas API.

  • Atau, mereka hampir melihat betapa gilanya ini semua, dan mulai melakukan "manajer" DrawableGameComponentuntuk mengatasi masalah ini. Kecuali mereka masih memiliki masalah ini di batas "manajer".

    Biasanya "manajer" ini dapat dengan mudah diganti dengan serangkaian panggilan metode dalam urutan yang diinginkan. Ada lagi yang terlalu rumit.

Tentu saja, setelah Anda mulai menggunakan peretasan itu, maka dibutuhkan kerja nyata untuk membatalkan peretasan itu setelah Anda menyadari bahwa Anda membutuhkan lebih dari yang DrawableGameComponentdisediakan. Anda menjadi " terkunci ". Dan godaan seringkali adalah untuk terus menumpuk di atas peretasan - yang dalam praktiknya akan merongrong Anda dan membatasi jenis permainan yang dapat Anda buat untuk yang relatif sederhana.


Banyak orang tampaknya mencapai titik ini dan memiliki reaksi mendalam - mungkin karena mereka menggunakan DrawableGameComponentdan tidak mau menerima menjadi "salah". Jadi mereka mengaitkan pada fakta bahwa setidaknya mungkin untuk membuatnya "berfungsi".

Tentu saja bisa dibuat untuk "bekerja"! Kami adalah programmer - membuat segala sesuatunya bekerja di bawah pengekangan yang tidak biasa adalah tugas kami. Tetapi pengekangan ini tidak perlu, berguna atau rasional ...


Apa yang terutama flabbergasting tentang adalah bahwa itu adalah menakjubkan sepele untuk sisi-langkah seluruh masalah ini. Di sini, saya bahkan akan memberi Anda kode:

public class Actor
{
    public virtual void Update(GameTime gameTime);
    public virtual void Draw(GameTime gameTime);
}

List<Actor> actors = new List<Actor>();

foreach(var actor in actors)
    actor.Update(gameTime);

foreach(var actor in actors)
    actor.Draw(gameTime);

(Sangat mudah untuk menambahkan dalam penyortiran sesuai DrawOrderdan UpdateOrder. Salah satu cara sederhana adalah mempertahankan dua daftar, dan menggunakannya List<T>.Sort(). Ini juga mudah untuk menangani Load/ UnloadContent.)

Hal ini tidak penting apa kode yang benar-benar adalah . Yang penting adalah saya bisa dengan sepele memodifikasinya .

Ketika saya menyadari bahwa saya memerlukan sistem pengaturan waktu yang berbeda gameTime, atau saya memerlukan lebih banyak parameter (atau seluruh UpdateContextobjek dengan sekitar 15 hal di dalamnya), atau saya memerlukan urutan operasi yang sama sekali berbeda untuk menggambar, atau saya memerlukan beberapa metode tambahan untuk berbagai pembaruan pembaruan, saya bisa langsung saja melakukannya .

Dan saya bisa segera melakukannya. Saya tidak perlu bingung tentang cara mengambil DrawableGameComponentkode saya. Atau lebih buruk: meretasnya dalam beberapa cara yang akan membuatnya lebih sulit saat berikutnya kebutuhan semacam itu muncul.


Sebenarnya hanya ada satu skenario di mana itu adalah pilihan yang baik untuk digunakan DrawableGameComponent: dan itu adalah jika Anda benar-benar membuat dan menerbitkan middleware gaya-tarik-dan-jatuhkan-komponen untuk XNA. Yang merupakan tujuan aslinya. Yang - saya akan tambahkan - tidak ada yang melakukan!

(Demikian pula, itu hanya masuk akal untuk digunakanServices saat membuat jenis konten khusus untuk ContentManager.)

Andrew Russell
sumber
Meskipun saya setuju dengan poin Anda, saya juga tidak melihat ada yang salah dalam menggunakan hal-hal yang diwarisi dari DrawableGameComponentkomponen tertentu, terutama untuk hal-hal seperti layar menu (menu, banyak tombol, opsi dan hal-hal lain), atau overlay FPS / debug kamu menyebutkan. Dalam kasus ini, Anda biasanya tidak perlu kontrol berbutir halus atas urutan undian dan Anda berakhir dengan lebih sedikit kode yang perlu Anda tulis secara manual (yang merupakan hal yang baik, kecuali jika Anda perlu menulis kode itu). Tapi saya kira itu skenario seret-dan-jatuhkan yang Anda sebutkan.
Groo