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 satu WASD or arrows
dan kemudian apakah dia juga menekan Shift
. Jika dia menekan hanya WASD
maka kita memanggil _currentPlayerState.Walk();
ketika ini terjadi dan kita harus currentPlayerState
sama dengan WalkState
kemudian di WalkState.Walk()
kita memiliki semua komponen yang diperlukan untuk keadaan ini - dalam hal ini MovementSystem
, jadi kami membuat pemain bergerak public 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 adalah WalkState
. Dalam hal ini Run()
akan dipanggil InputController
(jangan campur ini, Run()
dipanggil karena kami memiliki WASD
+ Shift
check in InputController
bukan karena WalkState
). Ketika kita sebut _currentPlayerState.Run();
di WalkState
- kita tahu bahwa kita harus beralih _currentPlayerState
ke RunState
dan kami melakukannya di Run()
dari WalkState
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 tombol E
ditekan kita panggil _currentPlayerState.Loot();
kita beralih ke LootState
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 menekan WASD
? - _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 ke IDLEState
.
Selain itu, Anda dapat melakukan skrip lain yang disebut class BaseState : IState
yang menerapkan semua metode metode default ini, tetapi memilikinya virtual
sehingga Anda dapat override
melakukannya dalam class 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 seperti PutItemToInventory()
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()
, tetapi Building
akan mengurus sisanya, dan ketika pengguna memutuskan untuk membangun beberapa bangunan tertentu, ia memberikan semua informasi yang diperlukan kepada pemain Build(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 saja TakeDamage()
.
class LivinEntity : IDamagable {
private float _health; // For fields that are the same between Instances I would use Flyweight Pattern.
public void TakeDamage() {
....
}
}
class GameActor : LivingEntity, IGameActor {
// Here goes state machine and other attached components needed.
}
class Player : GameActor {
// Inventory, Building, Crafting.... components.
}
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.