Terima kasih kepada Vincent Quarles untuk membantu rakan -rakan mengkaji artikel ini.
Dalam tutorial ini, kami akan menyelesaikan pelaksanaan fungsi Simpan dan Beban dalam permainan kami. Dalam tutorial sebelumnya mengenai penjimatan dan memuatkan data permainan pemain dalam perpaduan, kami berjaya menyelamatkan dan memuatkan data yang berkaitan dengan pemain seperti statistik dan inventori, tetapi sekarang kami akan menangani bahagian yang paling sukar-objek dunia. Sistem akhir harus mengingatkan permainan Elder Scrolls - setiap objek yang disimpan tepat di mana ia berada, untuk jumlah masa yang tidak terbatas.
dipilih (
desported ), dan kita perlu menyimpan dan memuatkan keadaan mereka dengan betul. Versi siap projek (dengan sistem simpan sepenuhnya dilaksanakan) boleh didapati di bahagian bawah artikel ini.
Projek Zip Muat turun
Objek Tahap objek yang akan bertelur dan menghina objek. Ia perlu menanam objek yang disimpan di peringkat (jika kita memuatkan tahap dan tidak memulakan lagi), Despawn mengambil objek, memberitahu objek yang mereka perlukan untuk menyelamatkan diri, dan menguruskan senarai objek. Bunyi seperti banyak, jadi mari kita masukkan ke dalam carta aliran:
Pada dasarnya, keseluruhan logik menyimpan senarai objek ke cakera keras - yang, pada masa akan datang tahap dimuatkan, akan dilalui, dan semua objek daripadanya menghasilkan ketika kita mula bermain. Kedengarannya mudah, tetapi syaitan berada dalam butiran: bagaimana kita tahu objek mana yang hendak disimpan dan bagaimana kita menanamnya kembali?
Dalam artikel sebelumnya, saya nyatakan kami akan menggunakan sistem perwakilan-peristiwa untuk memberitahu objek yang mereka perlukan untuk menyelamatkan diri. Mari kita terlebih dahulu jelaskan apa perwakilan dan peristiwa.
Anda boleh membaca dokumentasi perwakilan rasmi dan dokumentasi acara rasmi. Tetapi jangan risau: walaupun saya tidak faham banyak Technobabble dalam dokumentasi rasmi, jadi saya akan meletakkannya dalam bahasa Inggeris biasa:
Anda boleh memikirkan perwakilan sebagai pelan fungsi fungsi. Ia menerangkan apa fungsi yang sepatutnya kelihatan: apakah jenis pulangannya dan apa argumen yang diterima. Contohnya:
public delegate void SaveDelegate(object sender, EventArgs args);
Jadi bagaimana ikatan ini dengan peristiwa?
peristiwa
. Ia hanya menerima fungsi yang sepadan dengan perwakilan (cetak biru), dan anda boleh meletakkan dan mengeluarkan fungsi daripadanya semasa runtime namun anda mahu.
public event SaveDelegate SaveEvent;
melanggan dan berhenti berlangganan
Potondroppable yang perlu melanggan peristiwa:
//In PotionDroppable's Start or Awake function: GlobalObject.Instance.SaveEvent += SaveFunction; //In PotionDroppable's OnDestroy() function: GlobalObject.Instance.SaveEvent -= SaveFunction; //[...] public void SaveFunction (object sender, EventArgs args) { //Here is code that saves this instance of an object. }
Apabila kita memilikinya, kita hanya meletakkan fungsi itu di dalam kotak pada permulaan atau terjaga skrip dan mengeluarkannya apabila ia dimusnahkan. (Berhenti melanggan adalah sangat penting: jika anda cuba memanggil fungsi objek yang dimusnahkan, anda akan mendapat pengecualian rujukan null pada masa runtime). Kami melakukan ini dengan mengakses objek acara yang diisytiharkan, dan menggunakan pengendali tambahan diikuti dengan nama fungsi.
Nota: Kami tidak memanggil fungsi menggunakan kurungan atau argumen; Kami hanya menggunakan nama fungsi, tidak ada yang lain.
jadi mari kita jelaskan apa yang akhirnya dilakukan dalam beberapa contoh aliran permainan.
Mari kita anggap bahawa, melalui aliran permainan, tindakan pemain telah menghasilkan dua pedang dan dua ramuan di dunia (contohnya, pemain membuka dada dengan rampasan).
Empat objek ini mendaftarkan fungsi mereka dalam acara SAVE:
Sekarang mari kita anggap pemain mengambil satu pedang dan satu ramuan dari dunia. Memandangkan objek 'diambil', mereka berkesan mencetuskan perubahan dalam inventori pemain dan kemudian memusnahkan diri mereka (jenis runtuhan sihir, saya tahu):
Dan kemudian, anggap pemain memutuskan untuk menyelamatkan permainan - mungkin mereka benar -benar perlu menjawab telefon yang berbunyi tiga kali sekarang (hei, anda membuat permainan yang hebat):
kod
mari kita mula -mula pergi ke projek sedia ada dan membiasakan diri dengan apa yang ada di dalamnya.
Seperti yang anda lihat, kami mempunyai kotak -kotak yang direka dengan indah yang bertindak sebagai spawners untuk kedua -dua objek yang telah kami sebutkan. Terdapat pasangan dalam setiap dua adegan. Anda boleh melihat masalahnya dengan segera: Jika anda beralih ke tempat kejadian atau gunakan
F5/F9 untuk menyimpan/memuatkan, objek yang ditanam akan hilang.
Kotak spawners dan objek yang dilahirkan sendiri menggunakan mekanik antara muka yang mudah diinterakkan yang memberikan kita keupayaan untuk mengenali raycast kepada objek tersebut, menulis teks pada skrin, dan berinteraksi dengan mereka menggunakan kekunci [e].
tidak banyak lagi yang ada. Tugas kami di sini adalah:
seperti yang anda lihat, ini tidak betul -betul seperti yang remeh kerana seseorang mungkin berharap fungsi asas seperti itu. Malah, tiada enjin permainan yang ada di luar sana (CryEngine, UDK, Unreal Engine 4, yang lain) benar -benar mempunyai pelaksanaan fungsi Simpan/Beban yang mudah untuk pergi. Ini kerana, seperti yang anda bayangkan, simpan mekanik benar -benar spesifik untuk setiap permainan. Terdapat lebih banyak lagi kelas objek yang biasanya memerlukan penjimatan; Ia adalah negara -negara dunia seperti pencarian yang lengkap/aktif, keramahan/permusuhan puak, walaupun keadaan cuaca semasa dalam beberapa permainan. Ia menjadi agak rumit, tetapi dengan asas yang tepat dari mekanik penjimatan, ia mudah untuk hanya menaik tarafnya dengan lebih banyak fungsi.
mari kita mulakan dengan perkara mudah terlebih dahulu - senarai objek. Data pemain kami disimpan dan dimuatkan melalui perwakilan mudah data dalam kelas Serializable.
untuk menyusun kod kami dengan baik, kami akan bermula dengan kelas mudah pada akhir serializable kami:
public delegate void SaveDelegate(object sender, EventArgs args);
Sekarang, anda mungkin perasan saya menggunakan istilah
droppable banyak. Ini kerana kita perlu membezakan antara objek droppable (boleh ditanam), dan objek yang boleh diletakkan, yang mengikuti peraturan penjimatan dan pemijahan yang berlainan. Kami akan sampai ke sana kemudian.
public delegate void SaveDelegate(object sender, EventArgs args);
Tempat terbaik untuk membuat contoh senarai ini akan menjadi kelas GlobalControl kami:
public event SaveDelegate SaveEvent;
senarai kami cukup baik untuk pergi sekarang: kami akan mengaksesnya dari objek levelmaster kemudian apabila kami perlu menanam barang, dan menyimpan/beban dari cakera keras dari dalam GlobalControl, seperti yang telah kami lakukan dengan data pemain .
ah, akhirnya. Mari kita melaksanakan perkara acara yang terkenal.
dalam GlobalControl:
//In PotionDroppable's Start or Awake function: GlobalObject.Instance.SaveEvent += SaveFunction; //In PotionDroppable's OnDestroy() function: GlobalObject.Instance.SaveEvent -= SaveFunction; //[...] public void SaveFunction (object sender, EventArgs args) { //Here is code that saves this instance of an object. }
seperti yang anda lihat, kami membuat acara rujukan statik, jadi lebih logik dan lebih mudah untuk bekerja dengan kemudian.
Satu nota akhir mengenai pelaksanaan acara: Hanya kelas yang mengandungi perisytiharan peristiwa boleh membakar peristiwa. Sesiapa sahaja boleh melanggannya dengan mengakses GlobalControl.SaveEvent = ..., tetapi hanya kelas GlobalControl yang boleh membakarnya menggunakan SaveEvent (null, null);. Cuba menggunakan GlobalControl.SaveEvent (null, null); Dari tempat lain akan mengakibatkan ralat pengkompil!
Dan itu sahaja untuk pelaksanaan acara! Mari kita langgan beberapa perkara untuk itu!
Sekarang bahawa kita mempunyai acara kita, objek kita perlu melanggannya, atau, dengan kata lain, mula mendengar
ke acara dan bertindak balas apabila ia api.
kita memerlukan fungsi yang akan dijalankan apabila kebakaran peristiwa - untuk setiap objek. Mari kita pergi ke skrip Potondropplable dalam folder pickups
. Nota: Pedang tidak mempunyai skripnya lagi; Kami akan membuatnya seketika!
di potondropplable, tambahkan ini:
[Serializable] public class SavedDroppablePotion { public float PositionX, PositionY, PositionZ; } [Serializable] public class SavedDroppableSword { public float PositionX, PositionY, PositionZ; }
[Serializable] public class SavedDroppableList { public int SceneID; public List<SavedDroppablePotion> SavedPotions; public List<SavedDroppableSword> SavedSword; public SavedDroppableList(int newSceneID) { this.SceneID = newSceneID; this.SavedPotions = new List<SavedDroppablePotion>(); this.SavedSword = new List<SavedDroppableSword>(); } }
public List<SavedDroppableList> SavedLists = new List<SavedDroppableList>();
public delegate void SaveDelegate(object sender, EventArgs args); public static event SaveDelegate SaveEvent;
Ini adalah di mana semua salutan gula sintaksis kami benar -benar bersinar. Ini sangat mudah dibaca, mudah difahami dan mudah diubah untuk keperluan anda sendiri apabila anda memerlukannya! Singkatnya, kami membuat perwakilan 'ramuan' baru, dan menyimpannya dalam senarai sebenar.
Pertama, sedikit penyediaan. Di dalam projek kami yang sedia ada, kami mempunyai pemboleh ubah global yang memberitahu kami jika tempat kejadian dimuatkan. Tetapi kita tidak mempunyai pemboleh ubah sedemikian untuk memberitahu kita jika adegan sedang peralihan dengan menggunakan pintu. Kami mengharapkan semua objek yang jatuh untuk masih berada di sana ketika kami kembali ke bilik sebelumnya, walaupun kami tidak menyimpan/memuatkan permainan kami di mana saja di antara.
untuk berbuat demikian, kita perlu membuat perubahan kecil kepada kawalan global:
public delegate void SaveDelegate(object sender, EventArgs args);
dalam TransitionScript:
public event SaveDelegate SaveEvent;
Kami bersedia untuk membuat objek levelmaster yang akan berfungsi secara normal.
Sekarang kita hanya perlu membaca senarai dan menanam objek dari mereka apabila kita memuatkan permainan. Inilah yang akan dilakukan oleh Objek Tahap Tahap. Mari buat skrip baru dan panggilnya levelmaster
:
//In PotionDroppable's Start or Awake function: GlobalObject.Instance.SaveEvent += SaveFunction; //In PotionDroppable's OnDestroy() function: GlobalObject.Instance.SaveEvent -= SaveFunction; //[...] public void SaveFunction (object sender, EventArgs args) { //Here is code that saves this instance of an object. }
itu banyak kod, jadi mari kita pecahkan.
Kod berjalan hanya pada permulaan, yang kami gunakan untuk memulakan senarai yang disimpan dalam GlobalControl jika diperlukan. Kemudian, kami meminta GlobalControl jika kami memuatkan atau memindahkan adegan. Sekiranya kita memulakan adegan baru (seperti permainan baru atau sebagainya), tidak penting - kita tidak menelan objek.
Jika kita memuatkan adegan, kita perlu mengambil salinan tempatan kita senarai objek yang disimpan (hanya untuk menjimatkan sedikit prestasi pada mengakses GlobalControl yang berulang, dan untuk menjadikan sintaks lebih mudah dibaca).
Seterusnya, kami hanya melintasi senarai dan menanam semua objek ramuan di dalamnya. Sintaks yang tepat untuk pemijahan pada dasarnya adalah salah satu daripada kaedah instantiate yang berlebihan. Kita mesti membuang hasil kaedah instantiate ke dalam GameObject
(atas sebab tertentu, jenis pulangan lalai adalah objek mudah) supaya kita dapat mengakses transformasi, dan mengubah kedudukannya.
kita perlu meletakkan tuan tahap kami di setiap tempat kejadian dan menetapkan prefabs yang sah kepadanya:
Sekarang kita hanya kehilangan satu sekeping penting: kita perlu benar -benar membakar acara itu, bersiri senarai ke cakera keras dan membaca dari mereka. Kami hanya akan melakukannya dalam fungsi simpanan dan beban sedia ada kami dalam GlobalControl:
Ini juga kelihatan banyak kod, tetapi majoriti itu sudah ada. (Jika anda mengikuti tutorial terdahulu saya, anda akan mengenali perintah siriisasi binari; satu -satunya perkara baru di sini ialah fungsi FireSaveEvent, dan satu fail tambahan yang menjimatkan senarai kami.
[Serializable] public class SavedDroppablePotion { public float PositionX, PositionY, PositionZ; } [Serializable] public class SavedDroppableSword { public float PositionX, PositionY, PositionZ; }
Jika anda menjalankan projek sekarang, objek ramuan akan disimpan dengan betul dan dimuatkan setiap kali anda memukul
f9 , atau berjalan melalui pintu (dan sebarang kombinasi seperti). Walau bagaimanapun, ada satu lagi masalah untuk diselesaikan: kami tidak menyelamatkan pedang.
ini hanya untuk menunjukkan cara menambah objek baru
yang boleh diselamatkan ke projek anda sebaik sahaja anda mempunyai asas yang sama dibina.jadi katakan anda sudah mempunyai sistem objek-objek baru di tempat-seperti yang sudah kita lakukan dengan objek pedang. Mereka kini tidak berinteraksi dengan banyak (di luar fizik asas), jadi kita perlu menulis skrip yang serupa dengan ramuan yang membolehkan kita 'mengambil' pedang dan menjadikannya menyimpan dengan betul.
Prefab of the Sword yang kini ditanam boleh didapati dalam folder Aset> Prefabs.
mari kita lakukan. Pergi ke Aset> Skrip> Pickups dan di sana anda akan melihat
PoTiondRoppable
public delegate void SaveDelegate(object sender, EventArgs args);
Jangan lupa pelaksanaan antara muka 'Interactable'. Ia sangat penting: tanpa pedang anda tidak akan diiktiraf oleh kamera Raycast dan akan tetap tidak dapat ditarik. Juga, semak semula bahawa prefab pedang tergolong dalam lapisan item. Jika tidak, ia akan sekali lagi diabaikan oleh Raycast. Sekarang tambahkan skrip ini kepada anak pertama Prefab Pedang (yang sebenarnya mempunyai penerima mesh dan komponen lain):
Sekarang, kita perlu menanamnya. Di peringkat tuan, di bawah kami untuk gelung yang memancarkan ramuan:
public event SaveDelegate SaveEvent;
... dan itu sahaja. Bila -bila masa anda memerlukan jenis item baru yang disimpan:
Buat masa ini, sistem ini agak kasar: terdapat banyak fail simpan pada cakera keras, putaran objek tidak disimpan (pedang, pemain, dll), dan logik untuk meletakkan pemain semasa peralihan tempat kejadian (tetapi tidak Memuatkan) agak unik.
Ini adalah masalah kecil sekarang untuk diselesaikan sebaik sahaja sistem pepejal disediakan, dan saya menjemput anda untuk cuba mengikat dengan tutorial ini dan projek selesai untuk melihat sama ada anda dapat memperbaiki sistem.
Tetapi walaupun begitu, ia sudah menjadi kaedah yang boleh dipercayai dan kukuh untuk melakukan mekanik menyimpan/memuatkan dalam permainan anda - namun banyak yang mungkin berbeza dari contoh
projek.
Seperti yang dijanjikan, inilah projek siap, sekiranya anda memerlukannya untuk rujukan, atau kerana anda terjebak di suatu tempat. Sistem simpan dilaksanakan mengikut arahan tutorial ini dan dengan skim penamaan yang sama.
Bagaimana saya boleh menyimpan dan memuatkan struktur data kompleks dalam perpaduan 5? Struktur data kompleks dalam Perpaduan 5 dapat dicapai menggunakan siri JSON atau pemformatan binari. Serialization JSON membolehkan anda menukar struktur data kompleks ke dalam format rentetan yang boleh disimpan dan dimuatkan dengan mudah. Pemformatan binari adalah kaedah yang lebih selamat yang mengubah data anda menjadi format binari.
Bagaimana saya boleh mengoptimumkan fungsi menyimpan dan memuatkan dalam Perpaduan 5 untuk prestasi yang lebih baik? dicapai dengan meminimumkan jumlah data yang anda simpan dan memuatkan, menggunakan struktur data yang cekap, dan memastikan skrip anda dioptimumkan dengan baik. 5?
Atas ialah kandungan terperinci Menguasai Fungsi Simpan dan Beban dalam Perpaduan 5. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!