Taktik untuk memindahkan logika render dari kelas GameObject

10

Saat membuat game, Anda sering membuat objek game berikut dari mana semua entitas mewarisi:

public class GameObject{
    abstract void Update(...);
    abstract void Draw(...);
}

Jadi saat Anda memutakhirkan loop, Anda mengulangi semua objek game dan memberi mereka kesempatan untuk mengubah status, kemudian pada loop undian berikutnya Anda mengulangi lagi semua objek game dan memberi mereka kesempatan untuk menggambar sendiri.

Meskipun ini bekerja dengan cukup baik dalam game sederhana dengan renderer maju sederhana, ini sering mengarah ke beberapa objek game raksasa yang perlu menyimpan model mereka, beberapa tekstur dan yang terburuk dari semua metode penarikan lemak yang menciptakan hubungan yang erat antara objek game, strategi render saat ini dan setiap kelas terkait rendering.

Jika saya mengubah strategi render dari maju ke ditangguhkan saya harus memperbarui banyak objek game. Dan objek game yang saya buat tidak dapat digunakan kembali sebagaimana mestinya. Tentu saja warisan dan / atau komposisi dapat membantu saya melawan duplikasi kode dan membuatnya sedikit lebih mudah untuk mengubah implementasi tetapi masih terasa kurang.

Cara yang lebih baik, mungkin, akan menghapus metode Draw dari kelas GameObject sekaligus dan membuat kelas Renderer. GameObject masih perlu memuat beberapa data tentang visualnya, seperti apa model untuk mewakilinya dan tekstur apa yang harus dicat pada model, tetapi bagaimana ini dilakukan akan diserahkan kepada penyaji. Namun sering ada banyak kasus perbatasan dalam rendering sehingga meskipun ini akan menghapus kopling ketat dari GameObject ke Renderer, Renderer masih harus semua mengetahui tentang semua objek game yang akan membuatnya gemuk, semua yang tahu dan berpasangan erat. Ini akan melanggar beberapa praktik baik. Mungkin Data-Oriented-Design bisa melakukan trik. Objek game tentu saja data, tetapi bagaimana penyaji akan didorong oleh ini? Saya tidak yakin.

Jadi saya bingung dan tidak bisa memikirkan solusi yang baik. Saya sudah mencoba menggunakan prinsip-prinsip MVC dan di masa lalu saya punya beberapa ide tentang bagaimana menggunakannya dalam game, tetapi baru-baru ini sepertinya tidak berlaku seperti yang saya pikirkan. Saya ingin tahu bagaimana Anda semua mengatasi masalah ini.

Pokoknya mari kita rekap, saya tertarik pada bagaimana tujuan desain berikut dapat dicapai.

  • Tidak ada render logika dalam objek game
  • Kopling longgar antara objek game dan render engine
  • Tidak semua penyaji yang tahu
  • Lebih disukai runtime switching antara mesin render

Pengaturan proyek yang ideal adalah 'logika permainan' yang terpisah dan proyek logika render yang tidak perlu saling rujukan.

Train pemikiran ini semua dimulai ketika saya mendengar John Carmack mengatakan di twitter bahwa ia memiliki sistem yang sangat fleksibel sehingga ia dapat menukar mesin render pada saat run time dan bahkan dapat memberi tahu sistemnya untuk menggunakan kedua renderer (renderer perangkat lunak dan renderer yang dipercepat perangkat keras) pada saat yang sama sehingga dia dapat memeriksa perbedaan. Sistem yang saya programkan sejauh ini bahkan tidak sedekat itu fleksibel

Roy T.
sumber

Jawaban:

7

Langkah cepat pertama untuk melepaskan:

Objek permainan mereferensikan pengidentifikasi dari apa visual mereka tetapi bukan data, katakanlah sesuatu yang sederhana seperti string. Contoh: "human_male"

Renderer bertanggung jawab untuk memuat dan memelihara referensi "human_male" dan meneruskan kembali ke objek yang akan digunakan pegangan.

Contoh dalam kodesemu yang mengerikan:

GameObject( initialization parameters )
  me.render_handle = Renderer_Create( parameters.render_string )

- elsewhere
Renderer_Create( string )

  new data handle = Resources_Load( string );
  return new data handle

- some time later
GameObject( something happens to me parameters )
  me.state = something.what_happens
  Renderer_ApplyState( me.render_handle, me.state.effect_type )

- some time later
Renderer_Render()
  for each renderable thing
    for each rendering back end
        setup graphics for thing.effect
        render it

- finally
GameObject_Destroy()
  Renderer_Destroy( me.render_handle )

Maaf untuk kekacauan itu, bagaimanapun kondisi Anda dipenuhi dengan perubahan sederhana dari OOP murni berdasarkan pada melihat hal-hal seperti objek dunia nyata dan menjadi OOP berdasarkan tanggung jawab.

  • Tidak ada render logika dalam objek game (selesai, semua objek tahu adalah pegangan sehingga dapat menerapkan efek pada dirinya sendiri)
  • Kopling longgar antara objek game dan mesin render (selesai, semua kontak adalah melalui pegangan abstrak, status yang dapat diterapkan dan bukan apa yang harus dilakukan dengan status tersebut)
  • Tidak semua renderer yang tahu (selesai, hanya tahu tentang dirinya sendiri)
  • Lebih disukai runtime beralih antara mesin render (ini dilakukan pada tahap Renderer_Render (), Anda harus menulis kedua ujung belakang sekalipun)

Kata kunci yang dapat Anda cari untuk melampaui kelas refactoring sederhana adalah "entitas / komponen sistem" dan "injeksi ketergantungan" dan pola GUI "MVC" yang berpotensi hanya untuk membuat roda gigi otak lama berputar.

Patrick Hughes
sumber
Ini sangat berbeda dari apa pun yang telah saya lakukan sebelumnya, sepertinya ada sedikit potensi. Untungnya saya tidak dibatasi oleh mesin yang ada jadi saya hanya bisa bermain-main. Saya juga akan mencari istilah yang Anda sebutkan, meskipun injeksi ketergantungan selalu membuat otak saya sakit: P.
Roy T.
2

Apa yang saya lakukan untuk mesin saya sendiri adalah mengelompokkan semuanya menjadi modul. Jadi saya memiliki GameObjectkelas saya dan memegang pegangan untuk:

  • ModuleSprite - menggambar sprite
  • ModuleWeapon - senjata tembak
  • ModuleScriptingBase - scripting
  • ModuleParticles - efek partikel
  • ModuleCollision - deteksi dan respons tabrakan

Jadi saya punya Playerkelas dan Bulletkelas. Keduanya berasal dari GameObjectdan ditambahkan ke Scene. Tetapi Playermemiliki modul-modul berikut:

  • ModuleSprite
  • ModuleWeapon
  • Modul Partikel
  • ModuleCollision

Dan Bulletmemiliki modul-modul ini:

  • ModuleSprite
  • ModuleCollision

Cara mengatur hal-hal ini menghindari "Diamond of Death" di mana Anda memiliki Vehicle, VehicleLanddan VehicleWaterdan sekarang Anda menginginkannya VehicleAmphibious. Sebaliknya Anda memiliki Vehicledan dapat memiliki ModuleWaterdan ModuleLand.

Bonus tambahan: Anda dapat membuat objek menggunakan serangkaian properti. Yang perlu Anda ketahui adalah tipe dasar (Player, Musuh, Bullet, dll.) Dan kemudian buat pegangan untuk modul yang Anda butuhkan untuk tipe itu.

Dalam Adegan saya, saya melakukan hal berikut:

  • Panggil Updateuntuk semua GameObjectpegangan.
  • Lakukan pemeriksaan tabrakan dan respons tabrakan untuk mereka yang memiliki ModuleCollisionpegangan.
  • Panggil UpdatePostsemua GameObjectpegangan untuk memberi tahu posisi akhir mereka setelah fisika.
  • Hancurkan objek yang memiliki set bendera mereka.
  • Tambahkan objek baru dari m_ObjectsCreateddaftar ke m_Objectsdaftar.

Dan saya bisa mengaturnya lebih lanjut: dengan modul bukan dengan objek. Lalu saya akan membuat daftar ModuleSprite, memperbarui banyak ModuleScriptingBasedan melakukan tabrakan dengan daftar ModuleCollision.

knight666
sumber
Kedengarannya seperti komposisi hingga maksimal! Sangat bagus. Saya tidak melihat banyak render tips khusus di sini. Bagaimana Anda mengatasinya, hanya dengan menambahkan modul yang berbeda?
Roy T.
Oh ya. Itulah kelemahan sistem ini: jika Anda memiliki persyaratan khusus untuk GameObject(misalnya cara untuk membuat "ular" Sprite), Anda harus membuat anak ModuleSpriteuntuk fungsionalitas tertentu ( ModuleSpriteSnake) atau menambahkan modul baru sama sekali ( ModuleSnake). Untungnya mereka hanya petunjuk, tetapi saya telah melihat kode di mana GameObjectbenar-benar semua yang dapat dilakukan objek.
knight666