Saya telah membuat game RTS sederhana, yang berisi ratusan karakter seperti Crusader Kings 2, di Unity. Untuk menyimpannya, pilihan termudah adalah menggunakan objek yang dapat skrip, tetapi itu bukan solusi yang baik karena Anda tidak dapat membuat yang baru saat runtime.
Jadi saya membuat kelas C # yang disebut "Karakter" yang berisi semua data. Semuanya berfungsi dengan baik, tetapi saat game disimulasikan, ia terus-menerus menciptakan karakter baru dan membunuh beberapa karakter (saat peristiwa dalam game terjadi). Saat gim terus-menerus disimulasikan, ia menciptakan ribuan karakter. Saya menambahkan cek sederhana untuk memastikan karakter "Alive" saat memproses fungsinya. Jadi itu membantu kinerja, tetapi saya tidak dapat menghapus "Karakter" jika dia sudah mati, karena saya memerlukan informasinya saat membuat silsilah keluarga.
Apakah daftar adalah cara terbaik untuk menyimpan data untuk game saya? Atau itu akan memberikan masalah setelah ada 10.000 karakter yang dibuat? Salah satu solusi yang mungkin adalah membuat daftar lain setelah daftar mencapai jumlah tertentu dan memindahkan semua karakter mati di dalamnya.
GameObjects
. Di CK2, jumlah karakter maksimum yang saya lihat pada saat yang sama adalah ketika saya melihat ke pengadilan dan melihat 10-15 orang di sana (saya tidak bermain terlalu jauh). Sama halnya, pasukan dengan 3000 tentara hanya satuGameObject
, menampilkan angka "3000".Jawaban:
Ada tiga hal yang harus Anda pertimbangkan:
Apakah ini benar - benar menyebabkan masalah kinerja? 1000-an sebenarnya tidak banyak. Komputer modern sangat cepat dan dapat menangani banyak hal. Pantau berapa banyak waktu yang dibutuhkan pemrosesan karakter dan lihat apakah itu benar-benar akan menimbulkan masalah sebelum terlalu khawatir.
Kesetiaan karakter yang saat ini minim aktif. Kesalahan yang sering terjadi dari Pemrogram Game pemula adalah terobsesi dengan memperbarui karakter di luar layar secara tepat dengan cara yang sama seperti di layar. Ini adalah kesalahan, tidak ada yang peduli. Alih-alih, Anda perlu mencari kesan bahwa karakter di luar layar masih berakting. Dengan mengurangi jumlah karakter pembaruan yang diterima di luar layar, Anda dapat secara dramatis mengurangi waktu pemrosesan.
Pertimbangkan Data-Oriented-Design. Alih-alih memiliki 1000 objek karakter dan memanggil fungsi yang sama untuk masing-masing, memiliki larik data untuk 1000 karakter dan memiliki satu loop fungsi lebih dari 1000 karakter yang memperbarui masing-masing secara bergantian. Optimalisasi semacam ini dapat secara dramatis meningkatkan kinerja.
sumber
Dalam situasi ini, saya sarankan menggunakan Komposisi :
Dalam hal ini, sepertinya
Character
kelas Anda telah menjadi seperti dewa , dan berisi semua detail tentang bagaimana karakter beroperasi pada semua tahap siklus hidupnya.Misalnya, Anda perhatikan bahwa karakter mati masih diperlukan - seperti yang digunakan di pohon keluarga. Namun, kecil kemungkinan bahwa semua informasi dan fungsionalitas karakter hidup Anda, masih diperlukan hanya untuk menampilkannya di pohon keluarga. Mereka mungkin misalnya, hanya perlu nama, tanggal lahir dan ikon potret.
Solusinya, adalah untuk membagi bagian-bagian yang terpisah dari Anda
Character
ke dalam sub-kelas, yangCharacter
memiliki instance dari. Sebagai contoh:CharacterInfo
dapat berupa struktur data sederhana dengan nama, tanggal lahir, tanggal kematian dan fraksi,Equipment
mungkin memiliki semua item yang dimiliki karakter Anda, atau aset mereka saat ini. Mungkin juga memiliki logika yang mengatur fungsi-fungsi ini.CharacterAI
atauCharacterController
mungkin memiliki semua informasi yang diperlukan tentang tujuan karakter saat ini, fungsi utilitas mereka dll. Dan mungkin juga memiliki logika pembaruan aktual yang mengkoordinasikan pengambilan keputusan / interaksi antara bagian-bagian individu itu.Setelah Anda membagi karakter, Anda tidak perlu lagi memeriksa bendera Alive / Dead di loop pembaruan.
Sebagai gantinya, Anda cukup membuat
AliveCharacterObject
yang memilikiCharacterController
,CharacterEquipment
danCharacterInfo
skrip terlampir. Untuk "membunuh" karakter, Anda cukup menghapus bagian-bagian yang tidak lagi relevan (sepertiCharacterController
) - sekarang tidak akan membuang-buang memori, atau waktu pemrosesan.Perhatikan, bagaimana
CharacterInfo
kemungkinan satu-satunya data yang benar-benar dibutuhkan untuk silsilah keluarga. Dengan mendekomposisi kelas Anda menjadi bagian-bagian kecil dari fungsionalitas - Anda dapat lebih mudah menyimpan objek data kecil itu setelah kematian, tanpa perlu menjaga seluruh karakter yang digerakkan AI.Patut disebutkan bahwa paradigma ini adalah satu-satunya yang dibangun Unity untuk digunakan - dan itulah sebabnya ia menangani banyak hal dengan banyak skrip terpisah. Membangun objek dewa besar jarang merupakan cara terbaik untuk menangani data Anda di Unity.
sumber
Ketika Anda memiliki sejumlah besar data untuk ditangani dan tidak setiap titik data diwakili oleh objek game yang sebenarnya, maka biasanya bukan ide yang buruk untuk melepaskan kelas khusus Unity dan hanya pergi dengan objek C # biasa. Dengan begitu Anda meminimalkan overhead. Jadi Anda tampaknya berada di jalur yang benar di sini.
Menyimpan semua karakter, hidup atau mati, dalam satu Daftar ( atau array ) dapat berguna karena indeks dalam daftar itu dapat berfungsi sebagai ID karakter kanonik. Mengakses posisi daftar berdasarkan indeks adalah operasi yang sangat cepat. Tetapi mungkin berguna untuk menyimpan daftar ID yang terpisah dari semua karakter yang hidup, karena Anda mungkin perlu mengulangi yang jauh lebih sering daripada Anda membutuhkan karakter yang mati.
Saat implementasi mekanika game Anda membuat kemajuan, Anda mungkin juga ingin melihat jenis pencarian apa yang paling banyak Anda lakukan. Seperti "semua karakter yang hidup di lokasi tertentu" atau "semua leluhur yang hidup atau mati dari karakter tertentu". Mungkin bermanfaat untuk membuat beberapa struktur data sekunder yang dioptimalkan untuk jenis pertanyaan ini. Ingatlah bahwa masing-masing dari mereka harus tetap up-to-date. Ini membutuhkan pemrograman tambahan dan akan menjadi sumber bug tambahan. Jadi lakukan saja jika Anda mengharapkan peningkatan kinerja yang penting.
CKII " memangkas " karakter dari basis datanya ketika dianggap tidak penting untuk menghemat sumber daya. Jika tumpukan karakter Anda yang mati menghabiskan terlalu banyak sumber daya dalam game yang sudah berjalan lama, maka Anda mungkin ingin melakukan hal serupa (saya tidak ingin menyebut ini "pengumpulan sampah". Mungkin "incremator terhormat"?).
Jika Anda benar-benar memiliki objek game untuk setiap karakter dalam game, maka sistem Unity ECS dan Jobs yang baru mungkin berguna bagi Anda. Ini dioptimalkan untuk menangani sejumlah besar objek game yang sangat mirip dengan cara yang performan. Tapi itu memaksa arsitektur perangkat lunak Anda menjadi beberapa pola yang sangat kaku.
Ngomong-ngomong, saya sangat suka CKII dan cara itu mensimulasikan dunia dengan ribuan karakter yang dikontrol AI yang unik, jadi saya berharap untuk bermain dalam genre Anda.
sumber
Like "all living characters in a specific location" or "all living or dead ancestors of a specific character". It might be beneficial to create some more secondary data-structures optimized for these kinds of queries.
Dari pengalaman saya dengan modding CK2, ini dekat dengan bagaimana CK2 menangani data. CK2 tampaknya menggunakan indeks yang pada dasarnya adalah indeks basis data, yang membuatnya lebih cepat untuk menemukan karakter untuk situasi tertentu. Alih-alih memiliki daftar karakter, tampaknya memiliki database karakter internal, dengan semua kelemahan dan manfaat yang menyertainya.Anda tidak perlu mensimulasikan / memperbarui ribuan karakter ketika hanya beberapa yang berada di dekat pemain. Anda hanya perlu memperbarui apa yang sebenarnya bisa dilihat oleh pemain pada saat ini, sehingga karakter yang jauh dari pemain harus ditangguhkan sampai pemain lebih dekat dengan mereka.
Jika ini tidak berhasil karena mekanik permainan Anda membutuhkan karakter yang jauh untuk menunjukkan berlalunya waktu, Anda dapat memperbaruinya dalam satu pembaruan "besar" ketika pemain semakin dekat. Jika mekanik gim Anda mengharuskan setiap karakter untuk benar-benar merespons peristiwa dalam gim saat hal itu terjadi, di mana pun karakter tersebut terkait dengan pemain atau acara, maka itu mungkin berhasil mengurangi frekuensi di mana karakter lebih jauh dari pemain berada. diperbarui (yaitu mereka masih diperbarui secara sinkron dengan sisa permainan, tetapi tidak sering, sehingga akan ada sedikit keterlambatan sebelum karakter yang jauh merespons suatu peristiwa tetapi ini tidak mungkin menyebabkan masalah atau bahkan diperhatikan oleh pemain) ). Atau Anda mungkin ingin menggunakan pendekatan hybrid,
sumber
Klarifikasi pertanyaan
Dalam jawaban ini, saya mengasumsikan bahwa seluruh permainan seharusnya seperti CK2, bukan hanya bagian tentang memiliki banyak karakter. Semua yang Anda lihat di layar dalam CK2 mudah dilakukan dan tidak akan membahayakan kinerja Anda atau menjadi rumit untuk diterapkan di Unity. Data di baliknya, di situlah menjadi kompleks.
Character
Kelas tanpa fungsiBaik, karena karakter dalam permainan Anda adalah hanya data. Apa yang Anda lihat di layar hanyalah representasi dari data itu.
Character
Kelas - kelas ini adalah jantung dari permainan dan karena itu dalam bahaya menjadi " objek dewa ". Jadi saya akan menyarankan langkah-langkah ekstrem terhadap itu: Hapus semua fungsi dari kelas-kelas itu. MetodeGetFullName()
yang menggabungkan nama depan dan belakang, OK, tetapi tidak ada kode yang benar-benar "melakukan sesuatu". Masukan yang kode ke dalam kelas khusus yang melakukan satu tindakan; misalnya kelasBirther
dengan metodeCharacter CreateCharacter(Character father, Character mother)
akan menjadi jauh lebih bersih daripada memiliki fungsi itu diCharacter
kelas.Jangan menyimpan data dalam kode
Tidak. Simpan dalam format JSON, menggunakan Unity's JsonUtility. Dengan
Character
kelas tanpa fungsi itu, hal itu seharusnya sepele untuk dilakukan. Itu akan bekerja untuk pengaturan awal gim serta menyimpannya di savegames. Namun, itu masih hal yang membosankan untuk dilakukan, jadi saya hanya memberikan opsi termudah dalam situasi Anda. Anda juga bisa menggunakan XML atau YAML atau format apa pun, asalkan masih bisa dibaca oleh manusia ketika disimpan dalam file teks. CK2 melakukan hal yang sama, sebenarnya sebagian besar permainan melakukannya. Ini juga merupakan pengaturan yang sangat baik untuk memungkinkan orang mengubah permainan Anda, tetapi itu adalah pemikiran untuk nanti.Berpikir abstrak
Yang ini lebih mudah diucapkan daripada dilakukan, karena sering bertabrakan dengan cara berpikir yang wajar. Anda berpikir dengan cara "alami", tentang "karakter". Namun, dalam hal gim Anda, sepertinya ada setidaknya 2 jenis data berbeda yang "adalah karakter": Saya akan menyebutnya
ActingCharacter
danFamilyTreeEntry
. Karakter matiFamilyTreeEntry
tidak perlu diperbarui dan mungkin membutuhkan data jauh lebih sedikit daripada yang aktifActingCharacter
.sumber
Saya akan berbicara dari sedikit pengalaman, beralih dari desain OO yang kaku ke desain Entity-Component-System (ECS).
Beberapa waktu yang lalu saya sama seperti Anda , saya memiliki banyak jenis benda yang memiliki sifat serupa dan saya membangun berbagai benda dan mencoba menggunakan warisan untuk menyelesaikannya. Orang yang sangat pintar bilang tidak melakukannya, dan sebagai gantinya, gunakan Entity-Component-System.
Sekarang, ECS adalah konsep besar, dan sulit untuk melakukannya dengan benar. Ada banyak pekerjaan yang dilakukan di dalamnya, membangun entitas, komponen, dan sistem dengan benar. Namun, sebelum kita dapat melakukan itu, kita perlu mendefinisikan persyaratannya.
Jadi, di sinilah saya akan pergi dengan ini:
Pertama dan terpenting, buat
ID
untuk karakter Anda. Anint
,,Guid
terserah kamu. Ini adalah "Entitas".Kedua, mulailah berpikir tentang berbagai perilaku yang telah Anda lakukan. Hal-hal seperti "Family Tree" - itu adalah perilaku. Alih-alih memodelkan itu sebagai atribut pada entitas, membangun sistem yang menampung semua informasi itu . Sistem kemudian dapat memutuskan apa yang harus dilakukan dengannya.
Demikian juga, kami ingin membangun sistem untuk "Apakah karakternya hidup atau mati?" Ini adalah salah satu sistem terpenting dalam desain Anda, karena memengaruhi semua yang lain. Beberapa sistem dapat menghapus karakter "mati" (seperti sistem "sprite"), sistem lain secara internal dapat mengatur ulang hal-hal untuk lebih mendukung status baru.
Anda akan membangun sistem "Sprite" atau "Drawing" atau "Rendering", misalnya. Sistem ini akan memiliki tanggung jawab untuk menentukan karakter apa yang perlu ditampilkan dengan sprite, dan bagaimana cara menampilkannya. Kemudian, ketika sebuah karakter mati, hapus mereka.
Selain itu, sistem "AI" yang dapat memberi tahu karakter apa yang harus dilakukan, ke mana harus pergi, dll. Ini harus berinteraksi dengan banyak sistem lain, dan membuat keputusan berdasarkan pada mereka. Sekali lagi, karakter mati mungkin dapat dihapus dari sistem ini, karena mereka tidak benar-benar melakukan apa-apa lagi.
Sistem "Nama" dan sistem "Pohon Keluarga" Anda mungkin harus menyimpan karakter (hidup atau mati) di memori. Sistem ini perlu mengingat kembali info itu, apa pun keadaan karakternya. (Jim masih Jim, bahkan setelah kami menguburkannya.)
Ini juga memberi Anda manfaat mengubah ketika sistem bereaksi lebih efisien: sistem memiliki timer sendiri. Beberapa sistem perlu menyala dengan cepat, beberapa tidak. Di sinilah kita mulai masuk ke apa yang membuat permainan berjalan efisien. Kita tidak perlu menghitung ulang cuaca setiap milidetik, kita mungkin bisa melakukannya setiap 5 atau lebih.
Ini juga memberi Anda pengungkitan yang lebih kreatif: Anda dapat membangun sistem "Pathfinder" yang dapat menangani perhitungan lintasan dari A-ke-B, dan dapat memperbarui seperlunya, memungkinkan sistem Gerakan untuk mengatakan "di mana saya harus pergi selanjutnya? " Kami sekarang dapat sepenuhnya memisahkan masalah ini, dan alasan tentang mereka lebih efektif. Gerakan tidak perlu menemukan jalan, hanya perlu membawa Anda ke sana.
Anda akan ingin mengekspos beberapa bagian dari sistem ke luar. Di
Pathfinder
sistem Anda, Anda mungkin menginginkanVector2 NextPosition(int entity)
. Dengan cara ini, Anda dapat menyimpan elemen-elemen itu dalam array atau daftar yang dikontrol ketat. Anda dapat menggunakanstruct
tipe yang lebih kecil, yang dapat membantu Anda menjaga komponen dalam blok memori yang lebih kecil dan bersebelahan, yang dapat membuat pembaruan sistem lebih cepat. (Terutama jika pengaruh eksternal ke sistem minimal, sekarang hanya perlu peduli dengan keadaan internal, sepertiName
.)Tapi, dan saya tidak bisa cukup menekankan hal ini, sekarang
Entity
ini hanyalah sebuahID
, termasuk ubin, objek, dll. Jika suatu entitas tidak termasuk dalam suatu sistem, maka sistem tidak akan melacaknya. Ini berarti kita dapat membuat objek "Pohon", menyimpannya di dalamSprite
danMovement
sistem (pohon tidak akan bergerak, tetapi mereka memiliki komponen "Posisi"), dan menjauhkan mereka dari sistem lain. Kita tidak lagi memerlukan daftar khusus untuk pohon, karena memberikan pohon tidak berbeda dengan karakter, selain dari penggantungan kertas. (YangSprite
dapat dikontrol sistem, atauPaperdoll
sistem yang dapat dikontrol.) Sekarang kitaNextPosition
dapat ditulis ulang:,Vector2? NextPosition(int entity)
dan dapat mengembalikannull
posisi untuk entitas yang tidak dipedulikannya. Kami juga menerapkan ini pada kamiNameSystem.GetName(int entity)
, ia mengembalikannull
pohon dan batu.Saya akan mengakhiri ini, tetapi idenya di sini adalah untuk memberi Anda beberapa latar belakang tentang ECS, dan bagaimana Anda benar - benar dapat memanfaatkannya untuk memberi Anda desain yang lebih baik pada gim Anda. Anda dapat meningkatkan kinerja, memisahkan elemen-elemen yang tidak terkait, dan menjaga hal-hal dalam cara yang lebih terorganisir. (Ini juga berpasangan dengan baik dengan bahasa / pengaturan fungsional, seperti F # dan LINQ, yang saya sangat merekomendasikan memeriksa F # jika Anda belum melakukannya, itu berpasangan sangat baik dengan C # ketika Anda menggunakannya bersama-sama.)
sumber
GameObject
yang tidak berbuat banyak tetapi memiliki daftarComponent
kelas yang melakukan pekerjaan yang sebenarnya. Ada perubahan paradigma tentang bundaran ECS satu dekade lalu , karena menempatkan kode akting di kelas sistem yang terpisah lebih bersih. Persatuan baru-baru ini menerapkan sistem seperti itu juga, tetapiGameObject
sistem mereka selalu ECS. OP sudah menggunakan ECS.Ketika Anda melakukan ini di Unity, pendekatan termudah adalah ini:
Dalam kode Anda, Anda dapat menyimpan referensi ke objek dalam sesuatu seperti Daftar agar Anda tidak menggunakan Find () dan variasinya setiap saat. Anda berdagang siklus CPU untuk memori, tetapi daftar petunjuk cukup kecil sehingga bahkan dengan beberapa ribu objek di dalamnya yang seharusnya tidak menjadi masalah.
Saat Anda maju melalui gim, Anda akan menemukan bahwa memiliki objek gim individual memberi Anda banyak keuntungan, termasuk navigasi dan AI.
sumber