Hampir selalu ada kelas pemain dalam game. Pemain umumnya dapat melakukan banyak hal dalam permainan yang berarti bagi saya kelas ini akhirnya menjadi besar dengan banyak variabel untuk mendukung setiap bagian dari fungsi yang dapat dilakukan pemain. Masing-masing bagian cukup kecil sendiri tetapi dikombinasikan saya berakhir dengan ribuan baris kode dan itu menjadi susah untuk menemukan apa yang Anda butuhkan dan menakutkan untuk membuat perubahan. Dengan sesuatu yang pada dasarnya merupakan kontrol umum untuk seluruh permainan, bagaimana Anda menghindari masalah ini?
architecture
pengguna441521
sumber
sumber
Jawaban:
Anda biasanya menggunakan sistem komponen entitas (Sistem komponen entitas adalah arsitektur berbasis komponen). Ini juga membuat membuat entitas lain lebih mudah, dan juga dapat membuat musuh / NPC memiliki komponen yang sama dengan pemain.
Pendekatan ini berjalan dalam arah yang berlawanan persis sebagai pendekatan berorientasi objek. Segala sesuatu dalam game adalah entitas. Entitas hanyalah kasing tanpa mekanisme permainan yang ada di dalamnya. Ini memiliki daftar komponen dan cara untuk memanipulasi mereka.
Misalnya, pemain memiliki komponen posisi, komponen animasi dan komponen input dan ketika pengguna menekan spasi, Anda ingin pemain melompat.
Anda dapat mencapai ini dengan memberikan entitas pemain komponen lompatan, yang ketika dipanggil membuat komponen animatiom berubah menjadi animasi lompat dan Anda membuat pemain memiliki kecepatan y positif dalam komponen posisi. Di komponen input Anda mendengarkan tombol spasi dan Anda memanggil komponen lompat. (Ini hanya sebuah contoh, Anda harus memiliki komponen pengontrol untuk gerakan).
Ini membantu memecah kode menjadi modul yang lebih kecil, dapat digunakan kembali, dan dapat menghasilkan proyek yang lebih terorganisir.
sumber
Game tidak unik dalam hal ini; kelas dewa adalah anti-pola di mana-mana.
Solusi umum adalah memecah kelas besar di pohon kelas yang lebih kecil. Jika pemain memiliki inventaris, jangan menjadikan manajemen inventaris sebagai bagian dari
class Player
. Sebagai gantinya, buat aclass Inventory
. Ini adalah salah satu anggotaclass Player
, tetapi secara internalclass Inventory
dapat membungkus banyak kode.Contoh lain: karakter pemain mungkin memiliki hubungan dengan NPC, jadi Anda mungkin memiliki
class Relation
referensi baikPlayer
objek maupunNPC
objek, tetapi tidak memiliki keduanya.sumber
1) Player: State-machine + arsitektur berbasis komponen.
Komponen biasa untuk Player: HealthSystem, MovementSystem, InventorySystem, ActionSystem. Itu semua kelas suka
class HealthSystem
.Saya tidak menyarankan menggunakan di
Update()
sana (Tidak masuk akal dalam kasus-kasus biasa untuk memiliki pembaruan dalam sistem kesehatan kecuali jika Anda memerlukannya untuk beberapa tindakan di sana setiap frame, ini jarang terjadi. Satu kasus Anda mungkin juga berpikir - pemain diracuni dan Anda membutuhkannya untuk kehilangan kesehatan dari waktu ke waktu - di sini saya sarankan menggunakan coroutine. Satu lagi terus-menerus memperbaharui kesehatan atau menjalankan daya, Anda hanya mengambil kesehatan atau kekuatan saat ini dan memanggil coroutine untuk mengisi ke tingkat itu ketika waktu datang. Break coroutine ketika kesehatan penuh atau dia rusak atau dia mulai berlari lagi dan seterusnya. OK itu agak offtopic tapi saya harap itu berguna) .Serikat: LootState, RunState, WalkState, AttackState, IDLEState.
Setiap negara mewarisi dari
interface IState
.IState
telah dalam kasus kami memiliki 4 metode hanya sebagai contoh.Loot() Run() Walk() Attack()
Juga, kami memiliki
class InputController
tempat kami memeriksa setiap Input pengguna.Sekarang untuk contoh nyata: di
InputController
kami memeriksa apakah pemain menekan salah satuWASD or arrows
dan kemudian apakah dia juga menekanShift
. Jika dia menekan hanyaWASD
maka kita memanggil_currentPlayerState.Walk();
ketika ini terjadi dan kita haruscurrentPlayerState
sama denganWalkState
kemudian diWalkState.Walk()
kita memiliki semua komponen yang diperlukan untuk keadaan ini - dalam hal iniMovementSystem
, jadi kami membuat pemain bergerakpublic void Walk() { _playerMovementSystem.Walk(); }
- Anda melihat apa yang kami miliki di sini? Kami memiliki perilaku lapisan kedua dan itu sangat bagus untuk pemeliharaan kode dan debug.Sekarang untuk kasus kedua: bagaimana jika kita telah
WASD
+Shift
menekan? Tapi keadaan kita sebelumnya adalahWalkState
. Dalam hal iniRun()
akan dipanggilInputController
(jangan campur ini,Run()
dipanggil karena kami memilikiWASD
+Shift
check inInputController
bukan karenaWalkState
). Ketika kita sebut_currentPlayerState.Run();
diWalkState
- kita tahu bahwa kita harus beralih_currentPlayerState
keRunState
dan kami melakukannya diRun()
dariWalkState
dan menyebutnya lagi dalam metode ini, tetapi sekarang dengan keadaan yang berbeda karena kita tidak ingin tindakan kehilangan frame ini. Dan sekarang tentu saja kita sebut_playerMovementSystem.Run();
.Tapi untuk apa
LootState
ketika pemain tidak bisa berjalan atau berlari sampai dia melepaskan tombol? Nah dalam hal ini ketika kita mulai menjarah, misalnya ketika tombolE
ditekan kita panggil_currentPlayerState.Loot();
kita beralih keLootState
dan sekarang panggil dipanggil dari sana. Di sana kita misalnya memanggil metode collsion untuk mendapatkan jika ada sesuatu untuk dijarah dalam jangkauan. Dan kita memanggil coroutine di mana kita memiliki animasi atau di mana kita memulainya dan juga memeriksa apakah pemain masih memegang tombol, jika tidak istirahat coroutine, jika ya kita memberinya jarahan di ujung coroutine. Tetapi bagaimana jika pemain menekanWASD
? -_currentPlayerState.Walk();
Disebut, tapi di sini ada hal yang cantik tentang mesin negara, diLootState.Walk()
kami memiliki metode kosong yang tidak melakukan apa-apa atau seperti yang akan saya lakukan sebagai fitur - para pemain mengatakan: "Hei, saya belum menjarah ini, bisakah Anda menunggu?". Ketika dia berakhir menjarah, kami berganti keIDLEState
.Selain itu, Anda dapat melakukan skrip lain yang disebut
class BaseState : IState
yang menerapkan semua metode metode default ini, tetapi memilikinyavirtual
sehingga Anda dapatoverride
melakukannya dalamclass LootState : BaseState
jenis kelas.Sistem berbasis komponen sangat bagus, satu-satunya hal yang menggangguku adalah Instance, banyak di antaranya. Dan itu membutuhkan lebih banyak memori dan bekerja untuk pemulung. Misalnya, jika Anda memiliki 1000 instance musuh. Semuanya memiliki 4 komponen. 4000 objek bukan 1000. Mb itu bukan masalah besar (saya belum menjalankan tes kinerja) jika kita mempertimbangkan semua komponen yang dimiliki gameobject.
2) Arsitektur berbasis warisan. Meskipun Anda akan melihat bahwa kami tidak dapat sepenuhnya menghilangkan komponen - sebenarnya tidak mungkin jika kami ingin memiliki kode yang bersih dan berfungsi. Juga, jika kita ingin menggunakan Pola Desain yang sangat direkomendasikan untuk digunakan dalam kasus-kasus yang tepat (jangan terlalu sering menggunakannya, itu disebut overengeneering).
Bayangkan kita memiliki kelas Player yang memiliki semua properti yang dibutuhkan untuk keluar dalam game. Memiliki kesehatan, mana atau energi, dapat bergerak, berlari dan menggunakan kemampuan, memiliki inventaris, dapat membuat barang, menjarah barang, bahkan dapat membangun beberapa barikade atau menara.
Pertama-tama saya akan mengatakan bahwa Inventaris, Kerajinan, Gerakan, Bangunan harus berbasis komponen karena itu bukan tanggung jawab pemain untuk memiliki metode seperti
AddItemToInventoryArray()
- meskipun pemain dapat memiliki metode sepertiPutItemToInventory()
itu akan memanggil metode yang dijelaskan sebelumnya (2 lapisan - kita dapat tambahkan beberapa kondisi tergantung pada lapisan yang berbeda).Contoh lain dengan bangunan. Pemain dapat memanggil sesuatu seperti
OpenBuildingWindow()
, tetapiBuilding
akan mengurus sisanya, dan ketika pengguna memutuskan untuk membangun beberapa bangunan tertentu, ia memberikan semua informasi yang diperlukan kepada pemainBuild(BuildingInfo someBuildingInfo)
dan pemain mulai membangunnya dengan semua animasi yang diperlukan.Prinsip SOLID - OOP. S - tanggung jawab tunggal: bahwa apa yang telah kita lihat dalam contoh sebelumnya. Ya oke, tapi di mana Warisannya?
Di sini: haruskah kesehatan dan karakteristik pemain lainnya ditangani oleh entitas lain? Saya pikir tidak. Tidak mungkin ada pemain tanpa kesehatan, jika ada, kami hanya tidak mewarisi. Sebagai contoh, kita memiliki
IDamagable
,LivingEntity
,IGameActor
,GameActor
.IDamagable
tentu sajaTakeDamage()
.Jadi di sini saya tidak bisa benar-benar membagi komponen dari warisan, tetapi kita dapat mencampurnya seperti yang Anda lihat. Kita juga dapat membuat beberapa kelas dasar untuk sistem Gedung misalnya jika kita memiliki jenis yang berbeda dan kami tidak ingin menulis kode lebih dari yang dibutuhkan. Memang kita juga dapat memiliki berbagai jenis bangunan dan sebenarnya tidak ada cara yang baik untuk melakukannya berdasarkan komponen!
OrganicBuilding : Building
,TechBuilding : Building
. Anda tidak perlu membuat 2 komponen dan menulis kode di sana dua kali untuk operasi umum atau properti bangunan. Dan kemudian menambahkannya secara berbeda, Anda dapat menggunakan kekuatan warisan dan kemudian polimorfisme dan inkapsulasi.Saya akan menyarankan menggunakan sesuatu di antaranya. Dan tidak terlalu sering menggunakan komponen.
Saya sangat merekomendasikan membaca buku ini tentang Pola Pemrograman Game - gratis di WEB.
sumber
Tidak ada peluru perak untuk masalah ini, tetapi ada berbagai pendekatan yang berbeda, hampir semuanya berputar di sekitar prinsip 'pemisahan perhatian'. Jawaban lain telah membahas pendekatan berbasis komponen yang populer, tetapi ada pendekatan lain yang dapat digunakan sebagai ganti atau sejalan dengan solusi berbasis komponen. Saya akan membahas pendekatan entitas-controller karena ini adalah salah satu solusi pilihan saya untuk masalah ini.
Pertama, ide tentang
Player
kelas adalah menyesatkan sejak awal. Banyak orang cenderung menganggap karakter pemain, karakter NPC, dan monster / musuh sebagai kelas yang berbeda, padahal sebenarnya mereka semua memiliki banyak kesamaan: mereka semua tergambar di layar, mereka semua bergerak, mereka mungkin bergerak semua memiliki persediaan dllCara berpikir ini mengarah pada pendekatan di mana karakter pemain, karakter non pemain dan monster / musuh semua diperlakukan sebagai
Entity
'daripada' diperlakukan secara berbeda. Tentu saja, mereka harus berperilaku berbeda - karakter pemain harus dikontrol melalui input dan npcs perlu ai.Solusi untuk ini adalah memiliki
Controller
kelas yang digunakan untuk mengontrolEntity
s. Dengan melakukan ini, semua logika berat berakhir di controller dan semua data dan kesamaan disimpan di entitas.Selain itu, dengan mensubklasifikasikan
Controller
ke dalamInputController
danAIController
, ini memungkinkan pemain untuk secara efektif mengontrol apa punEntity
di dalam ruangan. Pendekatan ini juga membantu multiplayer dengan memiliki kelasRemoteController
atauNetworkController
yang beroperasi melalui perintah dari aliran jaringan.Ini bisa menghasilkan banyak logika yang disepelekan menjadi satu
Controller
jika Anda tidak hati-hati. Cara untuk menghindari itu adalah dengan memilikiController
yang terdiri dari yang lainController
, atau membuatController
fungsi tergantung pada berbagai properti dariController
. Misalnya,AIController
akan memilikiDecisionTree
melekat padanya, danPlayerCharacterController
dapat terdiri dari berbagai lainnyaController
sepertiMovementController
, aJumpController
(berisi mesin negara dengan negara OnGround, Ascending dan Descending), sebuahInventoryUIController
. Manfaat tambahan dari hal ini adalah bahwa yang baruController
dapat ditambahkan ketika fitur baru ditambahkan - jika permainan dimulai tanpa sistem inventaris dan satu ditambahkan, pengontrol untuk itu dapat ditempelkan nanti.sumber