Saya merasa sulit untuk menemukan cara untuk mengatur objek permainan sehingga mereka polimorfik tetapi pada saat yang sama tidak polimorfik.
Berikut ini sebuah contoh: dengan asumsi bahwa kita ingin semua objek kita update()
dan draw()
. Untuk melakukan itu kita perlu mendefinisikan kelas dasar GameObject
yang memiliki dua metode murni virtual dan membiarkan polimorfisme muncul:
class World {
private:
std::vector<GameObject*> objects;
public:
// ...
update() {
for (auto& o : objects) o->update();
for (auto& o : objects) o->draw(window);
}
};
Metode pembaruan seharusnya menjaga keadaan apa pun yang perlu diperbarui oleh objek kelas tertentu. Faktanya adalah bahwa setiap objek perlu tahu tentang dunia di sekitar mereka. Sebagai contoh:
- Tambang perlu diketahui jika seseorang bertabrakan dengannya
- Seorang prajurit harus tahu apakah tentara tim lain berada dalam jarak dekat
- Seorang zombie harus tahu di mana otak terdekat, dalam radius, adalah
Untuk interaksi pasif (seperti yang pertama) saya berpikir bahwa deteksi tabrakan dapat mendelegasikan apa yang harus dilakukan dalam kasus spesifik tabrakan ke objek itu sendiri dengan a on_collide(GameObject*)
.
Sebagian besar informasi lainnya (seperti dua contoh lainnya) hanya dapat ditanyakan oleh dunia game yang diteruskan ke update
metode. Sekarang dunia tidak membedakan objek berdasarkan tipenya (ia menyimpan semua objek dalam wadah polimorfik tunggal), jadi apa yang sebenarnya akan kembali dengan ideal world.entities_in(center, radius)
adalah wadah GameObject*
. Tapi tentu saja prajurit itu tidak ingin menyerang prajurit lain dari timnya dan zombie tidak peduli dengan zombie lainnya. Jadi kita perlu membedakan perilaku. Sebuah solusi bisa jadi sebagai berikut:
void TeamASoldier::update(const World& world) {
auto list = world.entities_in(position, eye_sight);
for (const auto& e : list)
if (auto enemy = dynamic_cast<TeamBSoldier*>(e))
// shoot towards enemy
}
void Zombie::update(const World& world) {
auto list = world.entities_in(position, eye_sight);
for (const auto& e : list)
if (auto enemy = dynamic_cast<Human*>(e))
// go and eat brain
}
tapi tentu saja jumlah dynamic_cast<>
per frame bisa sangat tinggi, dan kita semua tahu betapa lambatnya dynamic_cast
bisa. Masalah yang sama juga berlaku untuk on_collide(GameObject*)
delegasi yang telah kita bahas sebelumnya.
Jadi apa itu cara ideal untuk mengatur kode sehingga objek dapat mengetahui objek lain dan dapat mengabaikannya atau mengambil tindakan berdasarkan tipenya?
sumber
dynamic_cast<Human*>
, mengimplementasikan sesuatu sepertibool GameObject::IsHuman()
, yang mengembalikanfalse
secara default tetapi diganti untuk kembalitrue
diHuman
kelas.IsA
overrides terbukti hanya sedikit lebih baik daripada casting dinamis dalam praktek untuk saya. Hal terbaik yang harus dilakukan adalah bagi pengguna untuk, jika memungkinkan, mengurutkan daftar data alih-alih mengulangi secara membabi buta seluruh kumpulan entitas.TeamASoldier
danTeamBSoldier
benar-benar identik - menembak siapa pun di tim lain. Semua yang dibutuhkan oleh entitas lain adalahGetTeam()
metode yang paling spesifik dan, dengan contoh congusbongus, yang dapat diabstraksi lebih jauh menjadiIsEnemyOf(this)
semacam antarmuka. Kode tidak perlu peduli dengan klasifikasi taksonomi prajurit, zombie, pemain, dll. Fokus pada interaksi, bukan tipe.Jawaban:
Alih-alih mengimplementasikan pengambilan keputusan masing-masing entitas itu sendiri, Anda juga bisa menggunakan pola pengontrol. Anda akan memiliki kelas pengontrol pusat yang menyadari semua objek (yang penting bagi mereka) dan mengontrol perilaku mereka.
MovementController akan menangani pergerakan semua objek yang dapat bergerak (lakukan pencarian rute, perbarui posisi berdasarkan vektor pergerakan saat ini).
MineBehaviorController akan memeriksa semua ranjau dan semua prajurit, dan memerintahkan ranjau untuk meledak ketika seorang prajurit terlalu dekat.
ZombieBehaviorController akan memeriksa semua zombie dan tentara di sekitarnya, memilih target terbaik untuk setiap zombie, dan memerintahkannya untuk bergerak ke sana dan menyerangnya (langkah itu sendiri ditangani oleh MovementController).
Seorang SoldierBehaviorController akan menganalisis seluruh situasi dan kemudian datang dengan instruksi taktis untuk semua tentara (Anda pindah ke sana, Anda menembak ini, Anda menyembuhkan orang itu ...). Eksekusi sebenarnya dari perintah tingkat yang lebih tinggi ini juga akan ditangani oleh pengontrol tingkat yang lebih rendah. Ketika Anda berupaya, Anda bisa membuat AI mampu membuat keputusan kerja sama yang cukup cerdas.
sumber
std::map
s dan entitas hanya ID dan kemudian kita harus membuat semacam sistem tipe (mungkin dengan komponen tag, karena renderer akan perlu tahu apa yang harus menggambar); dan jika kita tidak ingin melakukan itu kita akan perlu memiliki komponen gambar: tetapi perlu komponen posisi untuk mengetahui di mana harus ditarik, jadi kami membuat dependensi antara komponen yang kami pecahkan dengan sistem pesan yang sangat kompleks. Apakah ini yang Anda sarankan?Pertama-tama, cobalah untuk mengimplementasikan fitur-fitur agar objek tetap independen satu sama lain, jika memungkinkan. Terutama Anda ingin melakukan itu untuk multi threading. Dalam contoh kode pertama Anda, set semua objek dapat dibagi dalam set yang sesuai dengan jumlah core CPU dan diperbarui dengan sangat efisien.
Tetapi seperti yang Anda katakan, interaksi dengan objek lain diperlukan untuk beberapa fitur. Itu berarti bahwa keadaan semua objek harus disinkronkan di beberapa titik. Dengan kata lain, aplikasi Anda harus menunggu semua tugas paralel selesai terlebih dahulu, dan kemudian menerapkan perhitungan yang melibatkan interaksi. Adalah baik untuk mengurangi jumlah titik sinkronisasi ini, karena selalu menyiratkan bahwa beberapa utas harus menunggu yang lain selesai.
Oleh karena itu, saya menyarankan buffering informasi tentang objek yang diperlukan dari dalam objek lain. Dengan adanya buffer global, Anda dapat memperbarui semua objek yang tidak tergantung satu sama lain, tetapi hanya bergantung pada diri mereka sendiri dan buffer global, yang lebih cepat dan lebih mudah dirawat. Pada stempel waktu tetap, katakan setelah setiap frame, perbarui buffer dengan status objek saat ini.
Jadi apa yang Anda lakukan sekali per frame adalah 1. buffer negara objek saat ini secara global, 2. perbarui semua objek berdasarkan diri mereka sendiri dan buffer, 3. gambar objek Anda dan mulai lagi dengan memperbarui buffer.
sumber
Gunakan sistem berbasis komponen, di mana Anda memiliki barebones GameObject yang berisi 1 atau lebih komponen yang menentukan perilaku mereka.
Misalnya, beberapa objek seharusnya bergerak ke kiri dan ke kanan sepanjang waktu (platform), Anda dapat membuat komponen seperti itu dan melampirkannya ke GameObject.
Sekarang katakanlah objek game seharusnya perlahan-lahan berputar sepanjang waktu, Anda bisa membuat komponen terpisah yang melakukan hal itu dan melampirkannya ke GameObject.
Bagaimana jika Anda ingin memiliki platform bergerak yang juga diputar, dalam hirarki kelas tradisional yang menjadi sulit dilakukan tanpa duplikasi kode.
Keindahan dari sistem ini, adalah bahwa alih-alih memiliki kelas Rotatable atau MovingPlatform, Anda melampirkan kedua komponen tersebut ke GameObject dan sekarang Anda memiliki MovingPlatform yang AutoRotates.
Semua komponen memiliki properti, 'membutuhkan Pembaruan' yang sementara benar, GameObject akan memanggil metode 'pembaruan' pada komponen tersebut. Misalnya, Anda memiliki komponen Draggable, komponen ini pada tetikus-ke-bawah (jika melebihi GameObject) dapat mengatur 'requireUpdate' menjadi true, dan kemudian pada tetikus-atas tetapkan ke false. Mengizinkannya mengikuti mouse hanya saat mouse turun.
Salah satu pengembang Tony Hawk Pro Skater memiliki defacto yang menuliskannya, dan layak dibaca: http://cowboyprogramming.com/2007/01/05/evolve-your-heirachy/
sumber
Komposisi nikmat daripada warisan.
Saran terkuat saya selain dari ini adalah: Jangan terjebak dalam pola pikir "Saya ingin ini menjadi sangat fleksibel". Fleksibilitas memang bagus, tetapi ingat bahwa pada tingkat tertentu, dalam sistem apa pun yang terbatas seperti permainan, ada bagian atom yang digunakan untuk membangun keseluruhan. Dengan satu atau lain cara, pemrosesan Anda bergantung pada tipe atom yang telah ditentukan sebelumnya. Dengan kata lain, melayani jenis data "apa saja" (jika itu mungkin) tidak akan membantu Anda dalam jangka panjang, jika Anda tidak memiliki kode untuk memprosesnya. Pada dasarnya, semua kode harus mem-parsing / memproses data berdasarkan spesifikasi yang diketahui ... yang berarti sekumpulan tipe yang telah ditentukan. Seberapa besar set itu? Terserah kamu.
Artikel ini menawarkan wawasan tentang prinsip Komposisi atas Warisan dalam pengembangan game melalui arsitektur entitas-komponen yang kuat dan berkinerja.
Dengan membangun entitas dari (berbeda) himpunan bagian dari superset dari komponen yang telah ditentukan, Anda menawarkan AI Anda cara-cara konkret, sedikit demi sedikit untuk memahami dunia dan para aktor di sekitarnya, dengan membaca keadaan 'komponen' aktor-aktor tersebut.
sumber
Secara pribadi saya merekomendasikan menjaga fungsi draw dari kelas Object itu sendiri. Saya bahkan merekomendasikan menjaga lokasi Objek / koordinat keluar dari Objek itu sendiri.
Metode draw () akan berurusan dengan rendering API tingkat rendah baik OpenGL, OpenGL ES, Direct3D, layer pembungkus Anda pada API tersebut atau API engine. Mungkin Anda harus menukar antara waktu itu (Jika Anda ingin mendukung OpenGL + OpenGL ES + Direct3D misalnya.
GameObject itu seharusnya hanya berisi informasi dasar tentang penampilan visualnya seperti Mesh atau mungkin bundel yang lebih besar termasuk input shader, keadaan animasi, dan sebagainya.
Anda juga akan menginginkan pipa grafis yang fleksibel. Apa yang terjadi jika Anda ingin memesan objek berdasarkan jaraknya ke kamera. Atau jenis bahannya. Apa yang terjadi jika Anda ingin menggambar objek yang 'dipilih' dengan warna yang berbeda. Bagaimana jika alih-alih benar-benar rending sama seperti ketika Anda memanggil fungsi draw pada suatu objek, alih-alih memasukkannya ke dalam daftar perintah tindakan yang harus diambil render (mungkin diperlukan untuk threading). Anda dapat melakukan hal semacam itu dengan sistem lain tetapi itu adalah PITA.
Apa yang saya sarankan adalah daripada menggambar secara langsung, Anda mengikat semua objek yang Anda inginkan ke struktur data lain. Penjilidan itu hanya benar-benar perlu memiliki referensi ke lokasi objek dan informasi render.
Level / chunk / area / peta / hub / wholeworld Anda / apa pun yang diberikan indeks spasial, ini berisi objek dan mengembalikannya berdasarkan permintaan koordinat dan bisa berupa daftar sederhana atau sesuatu seperti Oktree. Itu juga bisa menjadi pembungkus untuk sesuatu yang diimplementasikan oleh mesin fisika pihak ke-3 sebagai adegan fisika. Hal ini memungkinkan Anda untuk melakukan hal-hal seperti "Permintaan semua objek yang ada dalam pandangan kamera dengan beberapa area tambahan di sekitar mereka", atau untuk permainan yang lebih sederhana di mana Anda bisa membuat semuanya mengambil seluruh daftar.
Indeks spasial tidak harus mengandung informasi posisi yang sebenarnya. Mereka bekerja dengan menyimpan objek dalam struktur pohon dalam kaitannya dengan lokasi objek lain. Mereka dapat dianggap sebagai semacam cache lossy yang memungkinkan pencarian cepat objek berdasarkan posisinya. Tidak perlu menduplikasi koordinat X, Y, Z Anda yang sebenarnya. Setelah mengatakan bahwa Anda bisa jika Anda ingin tetap
Bahkan objek game Anda bahkan tidak perlu mengandung informasi lokasi mereka sendiri. Misalnya objek yang belum dimasukkan ke level seharusnya tidak memiliki koordinat x, y, z, itu tidak masuk akal. Anda dapat memuatnya di indeks khusus. Jika Anda perlu mencari koordinat objek berdasarkan referensi aktualnya maka Anda ingin memiliki ikatan antara objek dan grafik adegan (grafik adegan adalah untuk mengembalikan objek berdasarkan koordinat tetapi lambat mengembalikan koordinat berdasarkan objek) .
Ketika Anda menambahkan Obyek ke Tingkat. Ini akan melakukan hal berikut:
1) Buat Struktur Lokasi:
Ini juga bisa menjadi referensi ke objek dalam mesin fisika pihak ke-3. Atau bisa juga merupakan koordinat offset dengan referensi ke lokasi lain (untuk kamera pelacakan atau objek atau contoh yang dilampirkan). Dengan polimorfisme itu bisa tergantung pada apakah itu objek statis atau dinamis. Dengan menyimpan referensi ke indeks spasial di sini ketika koordinat diperbarui indeks spasial juga bisa.
Jika Anda khawatir tentang alokasi memori dinamis, gunakan kumpulan memori.
2) Ikatan / tautan antara objek Anda, lokasi dan grafik adegan.
3) Binding ditambahkan ke indeks spasial di dalam level pada titik yang sesuai.
Saat Anda bersiap untuk membuat.
1) Dapatkan kamera (Ini hanya akan menjadi objek lain, kecuali lokasi itu akan melacak karakter pemain dan penyaji Anda akan memiliki referensi khusus untuk itu, pada kenyataannya hanya itu yang benar-benar dibutuhkan).
2) Dapatkan SpacialBinding kamera.
3) Dapatkan indeks spasial dari pengikatan.
4) Permintaan objek yang (mungkin) terlihat oleh kamera.
5A) Anda perlu memproses informasi visual. Tekstur diunggah ke GPU dan sebagainya. Ini akan lebih baik dilakukan di muka (seperti pada beban level) tetapi mungkin bisa dilakukan saat runtime (untuk dunia terbuka, Anda dapat memuat barang saat Anda mendekati bongkahan tetapi harus tetap dilakukan di muka).
5B) Secara opsional membangun pohon render cache, jika Anda ingin kedalaman / mengurutkan bahan atau melacak objek di dekatnya yang mungkin terlihat di lain waktu. Kalau tidak, Anda bisa menanyakan indeks spasial setiap kali itu tergantung pada persyaratan permainan / kinerja Anda.
Penyaji Anda kemungkinan akan membutuhkan objek RenderBinding yang akan menautkan antara Objek, koordinat
Kemudian ketika Anda membuat, jalankan saja daftar.
Saya telah menggunakan referensi di atas tetapi bisa berupa pointer pintar, pointer mentah, pegangan objek dan sebagainya.
EDIT:
Adapun hal-hal yang 'disadari' satu sama lain. Itu deteksi tabrakan. Mungkin akan diterapkan di Octree. Anda perlu memberikan beberapa panggilan balik di objek utama Anda. Hal ini paling baik ditangani oleh mesin fisika yang tepat seperti Bullet. Dalam hal ini, ganti saja Octree dengan PhysicsScene dan Position dengan tautan ke sesuatu seperti CollisionMesh.getPosition ().
sumber