Hari ini kita akan berbicara tentang cara menyimpan, menerima dan mengirimkan data di dalam game. Tentang hal luar biasa yang disebut ScriptableObject, dan mengapa itu indah. Mari kita bicara sedikit tentang manfaat singleton ketika mengatur adegan dan transisi di antara mereka.
Artikel ini menjelaskan cara yang panjang dan menyakitkan untuk mengembangkan game, berbagai pendekatan yang digunakan dalam proses. Kemungkinan besar, akan ada banyak informasi berguna untuk pemula dan tidak ada yang baru untuk "veteran".
Tautan antara skrip dan objek
Pertanyaan pertama yang dihadapi pengembang pemula adalah bagaimana menghubungkan semua kelas tertulis bersama dan mengkonfigurasi interaksi di antara mereka.
Cara termudah adalah dengan menentukan tautan ke kelas secara langsung:
public class MyScript : MonoBehaviour { public OtherScript otherScript; }
Dan kemudian - secara manual mengikat skrip melalui inspektur.
Pendekatan ini memiliki setidaknya satu kelemahan signifikan - ketika jumlah skrip melebihi beberapa puluh, dan masing-masing membutuhkan dua atau tiga tautan satu sama lain, permainan dengan cepat berubah menjadi web. Satu lirikan padanya sudah cukup untuk menyebabkan sakit kepala.
Jauh lebih baik (menurut saya) untuk mengatur sistem pesan dan langganan, di mana objek kita akan menerima informasi yang mereka butuhkan - dan hanya itu! - tanpa memerlukan setengah lusin tautan satu sama lain.
Namun, setelah menyelidiki topik tersebut, saya menemukan bahwa solusi yang sudah jadi di Unity dimarahi oleh semua orang yang tidak malas. Bagi saya itu adalah tugas nontrivial untuk menulis sistem seperti itu dari awal, dan karena itu saya akan mencari solusi yang lebih sederhana.
Objek skrip
Pada dasarnya ada dua hal yang perlu diketahui tentang ScriptableObject:
- Mereka adalah bagian dari fungsionalitas yang diterapkan di dalam Unity, seperti MonoBehaviour.
- Tidak seperti MonoBehaviour, mereka tidak terikat pada objek adegan, tetapi ada sebagai aset yang terpisah dan dapat menyimpan dan mentransfer data di antara sesi permainan .
Saya langsung jatuh cinta dengan cinta panas mereka. Mereka, dengan cara tertentu, telah menjadi obat mujarab saya untuk masalah apa pun:
- Perlu menyimpan pengaturan game? ScriptableObject!
- Buat inventaris? ScriptableObject!
- Menulis AI? ScriptableObject!
- Catat informasi tentang karakter, musuh, barang? ScriptableObject tidak akan pernah mengecewakan Anda!
Tanpa berpikir dua kali, saya membuat beberapa kelas tipe ScriptableObject, dan kemudian repositori untuk mereka:
public class Database: ScriptableObject { public PlayerData playerData; public GameSettings gameSettings; public SpellController spellController; }
Masing-masing menyimpan sendiri semua informasi yang berguna dan, mungkin, tautan ke objek lain. Masing-masing cukup mengikat sekali melalui inspektur - mereka tidak akan pergi ke tempat lain.
Sekarang saya tidak perlu menentukan jumlah tautan di antara skrip! Untuk setiap skrip, saya
pernah dapat menentukan tautan ke repositori saya - dan itu akan menerima semua informasi dari sana.
Dengan demikian, perhitungan kecepatan karakter mengambil tampilan yang sangat elegan:
Dan jika, katakanlah, jebakan seharusnya hanya menembak karakter yang sedang berjalan:
if (database.playerData.isSprinting) Activate();
Selain itu, karakter tidak perlu tahu apa-apa tentang mantra atau jebakan. Itu hanya mengambil data dari penyimpanan. Tidak buruk? Tidak buruk.
Tapi segera saya mengalami masalah. ScriptableOnjects tidak dapat menyimpan tautan ke objek pemandangan secara langsung. Dengan kata lain, saya tidak dapat membuat tautan ke pemain, mengikatnya melalui inspektur dan melupakan pertanyaan tentang koordinat pemain selamanya.
Dan jika Anda memikirkannya, itu masuk akal! Aset ada di luar panggung dan dapat diakses di salah satu layar. Dan apa yang terjadi jika Anda meninggalkan tautan ke objek yang terletak di adegan lain di dalam aset?
Tidak ada yang baik.
Untuk waktu yang lama, sebuah penopang bekerja untuk saya: tautan publik dibuat di repositori, dan kemudian setiap objek, tautan yang ingin Anda ingat, mengisi tautan ini:
public class PlayerController : MonoBehaviour { void Awake() { database.playerData.player = this.gameObject; } }
Dengan demikian, terlepas dari adegannya, repositori saya pertama-tama menerima tautan ke pemain dan mengingatnya. Sekarang siapa pun, katakanlah, musuh tidak boleh menyimpan tautan ke pemain, tidak boleh mencarinya melalui FindWithTag () (yang merupakan proses intensif sumber daya). Yang dia lakukan hanyalah mengakses repositori:
public Database database; Vector3 destination; void Update () { destination = database.playerData.player.transform.position; }
Tampaknya: sistemnya sempurna! Tapi tidak. Kami masih memiliki 2 masalah.
- Saya masih harus secara manual menentukan tautan ke repositori untuk setiap skrip.
- Canggung untuk menetapkan referensi ke objek pemandangan di dalam ScriptableObject.
Tentang yang kedua lebih terinci. Misalkan seorang pemain memiliki mantra cahaya. Pemain melemparkannya, dan permainan berkata ke toko: lampu dilemparkan!
database.spellController.light.CastSpell();
Dan ini menimbulkan serangkaian reaksi:
- Lampu objek game baru (atau lama) dibuat pada titik kursor.
- Modul GUI diluncurkan, memberi tahu kami bahwa lampu aktif.
- Musuh mendapatkan, katakanlah, bonus sementara untuk menemukan pemain.
Bagaimana cara melakukan semua ini?
Dimungkinkan untuk setiap objek yang tertarik pada cahaya, langsung di Perbarui () dan menulis, kata mereka, dengan cara ini dan itu, setiap frame mengikuti cahaya (jika (database.spellController.light.isActive)), dan ketika menyala, bereaksi! Dan tidak peduli bahwa 90% dari waktu pemeriksaan ini akan bekerja menganggur. Pada beberapa ratus benda.
Atau atur semua ini dalam bentuk tautan yang disiapkan. Ternyata fungsi CastSpell () yang sederhana harus memiliki akses ke tautan ke pemain, ke cahaya, dan ke daftar musuh. Dan ini yang terbaik. Banyak tautan, ya?
Anda dapat, tentu saja, menyimpan semua yang penting dalam repositori kami ketika adegan dimulai, menyebarkan tautan pada aset, yang, secara umum, tidak dimaksudkan untuk ini ... Tetapi sekali lagi, saya melanggar prinsip repositori tunggal, mengubahnya menjadi web tautan.
Singleton
Di sinilah singleton berperan. Bahkan, ini adalah objek yang ada (dan bisa ada) hanya dalam satu salinan.
public class GameController : MonoBehaviour { public static GameController Instance;
Saya pasang ke objek pemandangan kosong. Sebut saja GameController.
Jadi, saya memiliki objek dalam adegan yang menyimpan
semua informasi tentang permainan. Selain itu, ia dapat bergerak di antara adegan, menghancurkan gandanya (jika sudah ada GameController lain di adegan baru), mentransfer data antar adegan, dan, jika diinginkan, menerapkan save / load game.
Dari semua skrip yang sudah ditulis, Anda dapat menghapus tautan ke gudang data. Lagi pula, sekarang saya tidak perlu mengkonfigurasi secara manual. Semua referensi ke objek adegan dihapus dari toko dan ditransfer ke GameController kami (kami kemungkinan besar akan membutuhkannya untuk menyimpan keadaan adegan ketika kami keluar dari game). Dan kemudian saya mengisinya dengan semua informasi yang diperlukan dengan cara yang nyaman bagi saya. Misalnya, pada Sedarlah () pemain dan musuh (dan objek penting dari adegan), itu diresepkan untuk menambahkan tautan ke diri mereka sendiri di GameController. Karena saya sekarang bekerja dengan Monobehaviour, referensi ke objek dalam adegan sangat cocok secara organik.
Apa yang kita dapatkan
Objek
apa pun dapat memperoleh informasi
apa pun tentang game yang ia butuhkan:
if (GameController.Instance.database.playerData.isSprinting) ActivateTrap();
Dalam hal ini, sama sekali tidak perlu mengkonfigurasi tautan antara objek, semuanya disimpan di GameController kami.
Sekarang tidak akan ada yang rumit dalam mempertahankan keadaan tempat kejadian. Bagaimanapun, kami sudah memiliki semua informasi yang diperlukan: musuh, item, posisi pemain, gudang data. Cukup dengan memilih informasi tentang adegan yang ingin Anda simpan, dan menulisnya ke file menggunakan FileStream saat keluar dari adegan.
Bahaya
Jika Anda membaca sampai titik ini, saya harus memperingatkan Anda tentang bahaya dari pendekatan ini.
Situasi yang sangat buruk adalah ketika banyak skrip merujuk satu variabel di dalam ScriptableObject kami. Tidak ada yang salah dengan mendapatkan nilai, tetapi ketika mereka mulai mempengaruhi variabel dari tempat yang berbeda, ini merupakan ancaman potensial.
Jika kita memiliki variabel playerSpeed ββyang disimpan, dan kita membutuhkan pemain untuk bergerak dengan kecepatan yang berbeda, kita seharusnya tidak mengubah playerSpeed ββdi penyimpanan, kita harus mendapatkannya, simpan dalam variabel sementara dan sudah menerapkan pengubah kecepatan untuk itu.
Poin kedua - jika benda apa pun memiliki akses ke apa pun - ini adalah kekuatan besar. Dan tanggung jawab besar. Dan Anda perlu mendekatinya dengan hati-hati agar beberapa skrip jamur secara tidak sengaja tidak menghancurkan semua musuh AI Anda. Enkapsulasi yang dikonfigurasi dengan benar akan mengurangi risiko, tetapi tidak akan menyelamatkan Anda dari mereka.
Juga, jangan lupa bahwa singletone adalah makhluk yang lembut. Jangan menyalahgunakan mereka.
Itu saja untuk hari ini.
Banyak yang telah diperoleh dari tutorial resmi Unity, beberapa dari yang tidak resmi. Saya harus mendapatkan sesuatu sendiri. Jadi, pendekatan di atas mungkin memiliki bahaya dan kerugian, yang saya lewatkan.
Dan karena itu - diskusi ini disambut baik!