Apakah menyimpan semua objek game dalam satu daftar merupakan desain yang dapat diterima?


24

Untuk setiap game yang saya buat, saya akhirnya menempatkan semua objek game saya (peluru, mobil, pemain) dalam daftar array tunggal, yang saya lewati untuk menggambar dan memperbarui. Kode pembaruan untuk setiap entitas disimpan di dalam kelasnya.

Saya bertanya-tanya, apakah ini cara yang tepat untuk menyelesaikan berbagai hal, atau apakah cara yang lebih cepat dan lebih efisien?


4
Saya perhatikan Anda mengatakan "daftar susunan", dengan asumsi ini. Net, Anda harus meningkatkan ke Daftar <T> sebagai gantinya. Itu hampir identik, kecuali bahwa Daftar <T> adalah tipe-kuat dan karena itu, Anda tidak perlu mengetikkan gips setiap kali Anda mengakses elemen. Inilah dokumennya
John McDonald

Jawaban:


26

Tidak ada cara yang benar. Apa yang Anda gambarkan karya, dan mungkin cukup cepat sehingga tidak masalah karena Anda tampaknya bertanya karena Anda khawatir tentang desain dan bukan karena itu menyebabkan Anda masalah kinerja. Jadi itu adalah suatu cara yang benar, yakin.

Anda tentu bisa melakukannya secara berbeda, misalnya dengan membuat daftar yang homogen untuk setiap jenis objek atau dengan memastikan semua yang ada di daftar heterogen Anda dikelompokkan bersama sehingga Anda telah meningkatkan koherensi untuk kode yang ada dalam cache atau untuk memungkinkan pembaruan paralel. Apakah Anda akan melihat peningkatan kinerja yang cukup dengan melakukan hal ini sulit untuk dikatakan tanpa mengetahui ruang lingkup dan skala gim Anda.

Anda juga dapat memfaktorkan tanggung jawab entitas Anda lebih lanjut - sepertinya entitas memperbarui dan membuat diri mereka sendiri saat ini - untuk mengikuti Prinsip Tanggung Jawab Tunggal dengan lebih baik. Ini dapat meningkatkan rawatan dan fleksibilitas antarmuka individual Anda dengan memisahkannya satu sama lain. Tetapi sekali lagi, apakah Anda akan melihat manfaat dari kerja ekstra yang akan menyulitkan saya untuk mengatakan mengetahui apa yang saya ketahui tentang game Anda (yang pada dasarnya tidak ada artinya).

Saya akan merekomendasikan untuk tidak terlalu menekankan tentang hal itu, dan tetap di belakang kepala Anda bahwa itu mungkin menjadi area yang potensial untuk perbaikan jika Anda pernah melihat masalah kinerja atau pemeliharaan.


16

Seperti yang dikatakan orang lain, jika cukup cepat, maka cukup cepat. Jika Anda bertanya-tanya apakah ada cara yang lebih baik, pertanyaan untuk bertanya pada diri sendiri adalah

Apakah saya menemukan diri saya mengulangi semua objek tetapi hanya beroperasi pada subset dari mereka?

Jika Anda melakukannya, maka itu merupakan indikasi bahwa Anda mungkin ingin memiliki beberapa daftar yang hanya menyimpan referensi yang relevan. Misalnya jika Anda mengulang-ulang setiap objek game untuk merendernya tetapi hanya sebagian objek yang harus dirender, mungkin ada baiknya menyimpan daftar yang dapat diurai terpisah hanya untuk objek-objek yang perlu dirender.

Ini akan mengurangi jumlah objek yang Anda lewati dan melewatkan pemeriksaan yang tidak perlu karena Anda sudah tahu semua objek dalam daftar akan diproses.

Anda harus membuat profil untuk melihat apakah Anda mendapatkan banyak keuntungan kinerja.


Saya setuju dengan ini, saya biasanya memiliki himpunan bagian dari daftar saya untuk objek yang perlu diperbarui setiap frame, dan saya telah mempertimbangkan melakukan hal yang sama untuk objek yang membuat. Namun ketika properti tersebut dapat berubah dengan cepat, mengelola semua daftar bisa menjadi rumit. Kapan dan objek berubah dari menjadi renderable menjadi non-renderable, atau dapat diperbarui ke non-update, dan sekarang Anda menambahkan atau menghapusnya dari beberapa daftar sekaligus.
Nic Foster

8

Itu hampir seluruhnya tergantung pada kebutuhan gim spesifik Anda . Ini dapat bekerja dengan sempurna untuk gim yang sederhana, tetapi gagal pada sistem yang lebih kompleks. Jika ini berfungsi untuk gim Anda, jangan khawatir tentang hal itu atau coba merekayasa secara berlebihan hingga muncul kebutuhan.

Adapun mengapa pendekatan sederhana mungkin gagal dalam beberapa situasi, sulit untuk meringkas bahwa dalam satu posting, karena kemungkinan tidak terbatas dan sepenuhnya bergantung pada permainan itu sendiri. Jawaban lain telah menyebutkan misalnya bahwa Anda mungkin ingin mengelompokkan objek berbagi tanggung jawab bersama ke dalam daftar terpisah.

Itu salah satu perubahan paling umum yang mungkin Anda lakukan tergantung pada desain gim Anda. Saya akan mengambil kesempatan untuk menggambarkan beberapa contoh lain (lebih kompleks), tetapi ingat masih ada banyak alasan dan solusi yang mungkin.

Sebagai permulaan, saya akan menunjukkan bahwa memperbarui objek game Anda dan merendernya mungkin memiliki persyaratan yang berbeda di beberapa game, dan karena itu perlu ditangani secara terpisah. Beberapa kemungkinan masalah dengan memperbarui dan merender objek game:

Memperbarui Objek Game

Berikut adalah kutipan dari Arsitektur Mesin Game yang saya sarankan baca:

Di hadapan ketergantungan antar-objek, teknik pembaruan bertahap yang dijelaskan di atas harus disesuaikan sedikit. Ini karena dependensi antar-objek dapat menyebabkan aturan yang saling bertentangan yang mengatur urutan pembaruan.

Dengan kata lain, dalam beberapa permainan dimungkinkan objek bergantung satu sama lain dan memerlukan urutan pembaruan tertentu. Dalam skenario ini, Anda mungkin perlu merancang struktur yang lebih kompleks daripada daftar untuk menyimpan objek dan saling ketergantungannya.

masukkan deskripsi gambar di sini

Rendering Objek Game

Dalam beberapa permainan, adegan dan objek membuat hierarki simpul orangtua-anak dan harus dirender dalam urutan yang benar dan dengan transformasi relatif terhadap orangtua mereka. Dalam kasus tersebut, Anda mungkin memerlukan struktur yang lebih kompleks seperti grafik adegan (struktur pohon) alih-alih satu daftar:

masukkan deskripsi gambar di sini

Alasan lain mungkin misalnya, menggunakan struktur data partisi spasial untuk mengatur objek Anda dengan cara yang meningkatkan efisiensi melakukan pemunculan frustum tampilan.


4

Jawabannya adalah "ya, ini baik-baik saja." Ketika saya mengerjakan simulasi besar di mana kinerja tidak bisa dinegosiasikan, saya terkejut melihat semuanya dalam satu susunan global besar (BESAR dan FAT). Kemudian saya bekerja pada sistem OO dan harus membandingkan keduanya. Meskipun itu sangat jelek, versi "satu larik untuk mengatur semuanya" dalam single-threaded plain C lama yang dikompilasi dalam debug beberapa kali lebih cepat daripada sistem OO multithreaded "cantik" yang dikompilasi -O2 dalam C ++. Saya pikir sedikit itu ada hubungannya dengan lokalitas cache yang sangat baik (dalam ruang dan waktu) dalam versi array besar-lemak.

Jika Anda menginginkan kinerja, satu array global C besar yang gemuk akan menjadi batas atas (dari pengalaman).


2

Pada dasarnya yang ingin Anda lakukan adalah membuat daftar sesuai kebutuhan Anda. Jika Anda menggambar ulang objek medan sekaligus, daftar hanya benda itu bisa berguna. Tetapi untuk ini layak sementara Anda akan membutuhkan puluhan ribu dari mereka, dan 10.000 hal yang tidak terrain. Kalau tidak membaca dengan teliti daftar segala sesuatu dan menggunakan "jika" untuk mengeluarkan objek medan cukup cepat.

Saya selalu membutuhkan satu daftar master, terlepas dari berapa banyak daftar lain yang saya miliki. Biasanya saya membuatnya menjadi semacam Peta, atau tabel, atau kamus, sehingga saya dapat secara acak mengakses setiap item. Tetapi daftar sederhana jauh lebih sederhana; jika Anda tidak mengakses objek game secara acak, Anda tidak memerlukan Peta. Dan pencarian sekuensial sesekali melalui beberapa ribu entri untuk menemukan yang tepat tidak akan lama. Jadi saya katakan, apa pun yang Anda lakukan, rencanakan untuk tetap dengan daftar master. Pertimbangkan menggunakan Peta jika terlalu besar. (Meskipun jika Anda dapat menggunakan indeks alih-alih kunci, Anda dapat tetap menggunakan array dan memiliki sesuatu yang jauh lebih baik daripada Peta. Saya selalu berakhir dengan kesenjangan besar antara nomor indeks, jadi saya harus menyerah.)

Sepatah kata tentang array: mereka luar biasa cepat. Tetapi ketika mereka bangun sekitar 100.000 elemen, mereka mulai memberikan pengumpul sampah yang cocok. Jika Anda dapat mengalokasikan ruang sekali dan tidak menyentuhnya sesudahnya, baik-baik saja. Tetapi jika Anda terus memperluas array, Anda terus mengalokasikan dan membebaskan banyak memori dan cenderung untuk permainan Anda menggantung selama beberapa saat sekaligus dan bahkan gagal dengan kesalahan memori.

Jadi lebih cepat dan lebih efisien? Yakin. Peta untuk akses acak. Untuk daftar yang sangat besar, Daftar Tertaut akan mengalahkan array jika dan ketika Anda tidak membutuhkan akses acak. Beberapa peta / daftar untuk mengatur objek dengan berbagai cara, sesuai kebutuhan Anda. Anda dapat melakukan multi-utas pembaruan.

Tapi itu semua banyak pekerjaan. Array tunggal itu cepat dan sederhana. Anda dapat menambahkan daftar dan hal-hal lain hanya saat dibutuhkan, dan Anda mungkin tidak akan membutuhkannya. Sadarilah apa yang salah jika game Anda menjadi besar. Anda mungkin ingin memiliki versi uji dengan objek 10 kali lebih banyak daripada versi nyata untuk memberi Anda gambaran tentang kapan Anda menuju masalah. (Hanya lakukan ini jika permainan Anda adalah mendapatkan besar.) (Dan jangan mencoba multi-threading tanpa memberikan banyak pemikiran. Segala sesuatu yang lain adalah mudah untuk memulai dengan dan Anda dapat melakukan sebanyak atau sesedikit yang Anda seperti dan mundur kapan saja. Dengan multi-threading Anda akan membayar harga yang besar untuk masuk, iuran untuk tetap tinggi, dan sulit untuk kembali.)

Dengan kata lain, tetap lakukan apa yang Anda lakukan. Batas terbesar Anda mungkin adalah waktu Anda sendiri, dan Anda memanfaatkan sebaik mungkin hal itu.


Saya benar-benar tidak berpikir suatu Linked List akan mengungguli array pada ukuran berapa pun, kecuali Anda sering menambahkan atau menghapus hal-hal dari tengah.
Zan Lynx

@ ZanLynx: Dan bahkan kemudian. Saya pikir optimasi runtime baru banyak membantu array - bukan bahwa mereka membutuhkannya. Tetapi jika Anda mengalokasikan dan membebaskan potongan besar memori berulang kali dan cepat, seperti yang dapat terjadi dengan array besar, Anda dapat mengikat pengumpul sampah di simpul. (Jumlah total memori adalah faktor di sini juga. Masalahnya adalah fungsi dari ukuran blok, frekuensi bebas dan mengalokasikan, dan memori yang tersedia.) Kegagalan terjadi secara tiba-tiba, dengan program menggantung atau menabrak. Biasanya ada banyak memori bebas; GC tidak bisa menggunakannya. Daftar tertaut menghindari masalah ini.
RalphChapin

Di sisi lain, daftar tertaut memiliki kemungkinan cache yang lebih tinggi untuk kehilangan iterasi karena fakta bahwa node tidak bersebelahan dalam memori. Ini tidak begitu kering dan kering. Anda dapat mengatasi masalah realokasi dengan tidak melakukan itu (mengalokasikan di muka jika memungkinkan, karena sering kali demikian) dan Anda dapat mengurangi masalah koherensi cache dalam daftar yang ditautkan dengan kolam yang mengalokasikan node (meskipun Anda masih memiliki biaya mengikuti referensi , ini relatif sepele).
Josh

Ya, tetapi bahkan dalam array, bukankah Anda hanya menyimpan pointer ke objek? (Kecuali itu adalah array yang berumur pendek untuk sistem partikel atau sesuatu.) Jika demikian, maka masih akan ada banyak kesalahan cache.
Todd Lehman

1

Saya telah menggunakan metode yang agak mirip untuk permainan sederhana. Menyimpan segala sesuatu dalam satu array sangat berguna ketika kondisi berikut ini benar:

  1. Anda memiliki sejumlah kecil atau maksimum objek permainan yang diketahui
  2. Anda lebih menyukai kesederhanaan daripada kinerja (yaitu: struktur data yang lebih optimal seperti pohon tidak sepadan dengan masalahnya)

Bagaimanapun, saya mengimplementasikan solusi ini sebagai array berukuran tetap yang berisi sebuah gameObject_ptrstruct. Struct ini berisi pointer ke objek game itu sendiri, dan nilai boolean yang mewakili apakah objek itu "hidup" atau tidak.

Tujuan boolean adalah untuk mempercepat operasi pembuatan dan penghapusan. Alih-alih menghancurkan objek game ketika mereka dihapus dari simulasi, saya hanya akan mengatur bendera "hidup" false.

Saat melakukan perhitungan pada objek, kode akan mengulang melalui array, melakukan perhitungan pada objek yang ditandai sebagai "hidup", dan hanya objek yang ditandai "hidup" yang diberikan.

Optimalisasi sederhana lainnya adalah mengatur array berdasarkan tipe objek. Semua peluru, misalnya, bisa hidup di n sel terakhir dari array. Kemudian, dengan menyimpan int dengan nilai indeks atau pointer ke elemen array, tipe tertentu dapat dirujuk dengan biaya yang lebih rendah.

Tidak perlu dikatakan lagi bahwa solusi ini tidak baik untuk gim dengan jumlah data yang besar, karena lariknya akan terlalu besar. Ini juga tidak cocok untuk game dengan batasan memori yang melarang benda yang tidak terpakai dari mengambil memori. Intinya adalah bahwa jika Anda memiliki batas atas yang diketahui tentang jumlah objek game yang akan Anda miliki pada satu titik waktu, tidak ada yang salah dengan menyimpannya dalam array statis.

Ini posting pertama saya! Saya harap ini menjawab pertanyaan Anda!


Pos pertama! yay!
Tili

0

Itu dapat diterima, meskipun tidak biasa. Istilah seperti "lebih cepat" dan "lebih efisien" relatif; biasanya Anda akan mengatur objek permainan ke dalam kategori yang terpisah (jadi satu daftar untuk peluru, satu daftar untuk musuh, dll.) untuk membuat pemrograman bekerja lebih teratur (dan dengan demikian lebih efisien) tetapi tidak seperti komputer akan berputar melalui semua objek lebih cepat .


2
Cukup benar dalam hal sebagian besar game XNA, tetapi mengatur objek Anda sebenarnya dapat membuat iterasi melalui mereka lebih cepat: objek dengan tipe yang sama lebih mungkin untuk mencapai instruksi & cache data dari CPU Anda. Kehilangan cache sangat mahal, terutama pada konsol. Di Xbox 360 misalnya, kehilangan cache L2 akan dikenakan biaya ~ 600 siklus. Itu menyisakan banyak kinerja di atas meja. Ini adalah satu tempat di mana kode terkelola memiliki keunggulan dibandingkan kode asli: objek yang dialokasikan berdekatan dalam waktu juga akan dekat dalam memori, dan situasinya membaik dengan pengumpulan sampah (pemadatan).
Pemrogram Game Kronis

0

Anda mengatakan array dan objek tunggal.

Jadi (tanpa kode Anda untuk melihat) saya kira mereka semua terkait dengan 'uberGameObject'.

Jadi mungkin dua masalah.

1) Anda mungkin memiliki objek 'dewa' - mengerjakan banyak hal, tidak benar-benar tersegmentasi berdasarkan apa yang dilakukannya atau apa.

2) Anda mengulangi daftar ini dan mengetik untuk mengetik? atau unboxing masing-masing. Ini memiliki penalti performa yang besar.

pertimbangkan untuk memecah berbagai jenis (peluru, orang jahat, dll) ke dalam daftar mereka yang sangat diketik.

List<BadGuyObj> BadGuys = new List<BadGuyObj>();
List<BulletsObj> Bullets = new List<BulletObj>();

 //Game 
OnUpdate(gameticks gt)
{  foreach (BulletObj thisbullet in Bullets) {thisbullet.Update();}  // much quicker.

Tidak perlu melakukan casting tipe jika ada semacam kelas dasar atau antarmuka umum yang memiliki semua metode yang akan dipanggil dalam loop pembaruan (mis. update()).
bummzack

0

Saya dapat mengusulkan beberapa kompromi antara daftar tunggal umum dan daftar terpisah untuk setiap jenis objek.

Simpan referensi ke satu objek dalam beberapa daftar. Daftar ini ditentukan oleh perilaku dasar. Misalnya, MarineSoldierobjek akan dirujuk dalam PhysicalObjList, GraphicalObjList, UserControlObjList,HumanObjList . Jadi, ketika Anda memproses fisika, Anda mengulanginya PhysicalObjList, ketika proses perusakan dari racun - HumanObjList, jika Prajurit mati - singkirkan HumanObjListtapi tetap masuk PhysicalObjList. Untuk membuat mekanisme ini berfungsi, Anda perlu mengatur penyisipan / menghapus objek saat dibuat / dihapus.

Keuntungan dari pendekatan:

  • pengorganisasian objek yang diketik (tidak AllObjectsListdengan casting saat pembaruan)
  • daftar didasarkan pada logika, bukan pada kelas objek
  • tidak ada masalah dengan objek dengan kemampuan gabungan (MegaAirHumanUnderwaterObject akan dimasukkan ke daftar yang sesuai alih-alih membuat daftar baru untuk tipenya)

PS: Sedangkan untuk memperbarui sendiri, Anda akan menemui masalah ketika beberapa objek berinteraksi dan masing-masing tergantung pada yang lain. Untuk mengatasi ini, kita perlu memengaruhi objek secara eksternal, bukan di dalam pembaruan diri.

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.