Bagaimana saya bisa menghindari kelas pemain raksasa?


46

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?


26
Beberapa file atau satu file, kodenya harus pergi ke suatu tempat. Game itu kompleks. Untuk menemukan yang Anda butuhkan, tulis nama metode yang baik dan komentar deskriptif. Jangan takut untuk melakukan perubahan - cukup tes. Dan buat cadangan pekerjaan Anda :)
Chris McFarland

7
Saya mendapatkannya harus pergi ke suatu tempat tetapi desain kode penting dalam fleksibilitas dan pemeliharaan. Memiliki kelas atau kelompok kode yang ribuan baris tidak mengejutkan saya juga.
user441521

17
@ChrisMcFarland tidak menyarankan untuk membuat cadangan, menyarankan untuk kode versi XD.
GameDeveloper

1
@ChrisMcFarland Saya setuju dengan GameDeveloper. Memiliki kontrol versi seperti Git, svn, TFS, ... membuat pengembangan menjadi jauh lebih mudah karena dapat membatalkan perubahan besar dengan lebih mudah dan dapat dengan mudah memulihkan dari hal-hal seperti tidak sengaja menghapus proyek Anda, kegagalan perangkat keras atau korupsi file.
Nzall

3
@ TylerH: Saya sangat tidak setuju. Cadangan tidak mengizinkan penggabungan banyak perubahan eksplorasi bersama-sama, juga tidak mengikat metadata yang berguna sebanyak yang diperlukan untuk perubahan, juga tidak memungkinkan alur kerja multi-pengembang yang waras. Anda dapat menggunakan kontrol versi seperti sistem cadangan point-in-time yang sangat kuat, tetapi itu kehilangan banyak potensi dari alat-alat itu.
Phoshi

Jawaban:


67

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.


Komentar bukan untuk diskusi panjang; percakapan ini telah dipindahkan ke obrolan .
MichaelHouse

8
Meskipun saya memahami komentar bergerak yang perlu dipindahkan, jangan gerakkan komentar yang menantang keakuratan jawabannya Itu harus jelas, bukan?
bug-a-lot

20

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 a class Inventory. Ini adalah salah satu anggota class Player, tetapi secara internal class Inventorydapat membungkus banyak kode.

Contoh lain: karakter pemain mungkin memiliki hubungan dengan NPC, jadi Anda mungkin memiliki class Relationreferensi baik Playerobjek maupun NPCobjek, tetapi tidak memiliki keduanya.


Ya, saya hanya mencari ide tentang cara melakukan ini. Apa pola pikir itu karena ada banyak fungsionalitas potongan-potongan kecil sehingga sementara pengkodean itu tidak alami, bagi saya, untuk memecahkan fungsionalitas potongan-potongan kecil itu. Namun menjadi jelas bahwa semua potongan kecil fungsionalitas mulai membuat kelas pemain besar.
user441521

1
Orang biasanya mengatakan sesuatu adalah kelas dewa atau objek dewa, ketika berisi dan mengelola setiap kelas / objek lain dalam permainan.
Bálint

11

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. IStatetelah dalam kasus kami memiliki 4 metode hanya sebagai contoh.Loot() Run() Walk() Attack()

Juga, kami memiliki class InputControllertempat kami memeriksa setiap Input pengguna.

Sekarang untuk contoh nyata: di InputControllerkami memeriksa apakah pemain menekan salah satu WASD or arrowsdan kemudian apakah dia juga menekan Shift. Jika dia menekan hanya WASDmaka kita memanggil _currentPlayerState.Walk();ketika ini terjadi dan kita harus currentPlayerStatesama dengan WalkStatekemudian 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+ Shiftmenekan? Tapi keadaan kita sebelumnya adalah WalkState. Dalam hal ini Run()akan dipanggil InputController(jangan campur ini, Run()dipanggil karena kami memiliki WASD+ Shiftcheck in InputControllerbukan karena WalkState). Ketika kita sebut _currentPlayerState.Run();di WalkState- kita tahu bahwa kita harus beralih _currentPlayerStateke RunStatedan kami melakukannya di Run()dari WalkStatedan 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 LootStateketika pemain tidak bisa berjalan atau berlari sampai dia melepaskan tombol? Nah dalam hal ini ketika kita mulai menjarah, misalnya ketika tombol Editekan kita panggil _currentPlayerState.Loot();kita beralih ke LootStatedan 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 : IStateyang menerapkan semua metode metode default ini, tetapi memilikinya virtualsehingga Anda dapat overridemelakukannya dalam class LootState : BaseStatejenis 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 Buildingakan 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. IDamagabletentu 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.


Saya akan menggali nanti malam tapi FYI saya tidak menggunakan persatuan jadi saya harus menyesuaikan beberapa yang baik-baik saja.
user441521

Oh, sry aku pikir di sini ada label Unity, salahku. Satu-satunya adalah MonoBehavior - itu hanya kelas dasar untuk setiap Mesin Virtual di tempat kejadian di editor Unity. Adapun Physics.OverlapSphere () - ini adalah metode yang menciptakan collider sphere selama bingkai dan memeriksa apa yang disentuhnya. Coroutine seperti Pembaruan palsu, panggilan mereka dapat dikurangi menjadi jumlah yang lebih kecil dari fps pada pemain PC - bagus untuk kinerja. Mulai () - hanya metode yang dipanggil sekali saat Mesin Virtual dibuat. Segala sesuatu yang lain harus berlaku di tempat lain. Bagian selanjutnya saya tidak akan menggunakan apa pun dengan Unity. Sry. Semoga ini menjelaskan sesuatu.
Candid Moon _Max_

Saya telah menggunakan Unity sebelumnya jadi saya mengerti idenya. Saya menggunakan Lua yang memiliki coroutine juga sehingga hal-hal harus diterjemahkan dengan cukup baik.
user441521

Jawaban ini tampaknya agak terlalu spesifik Unity mengingat kurangnya tag Unity. Jika Anda membuatnya lebih umum dan menjadikan hal kesatuan lebih sebagai contoh, ini akan menjadi jawaban yang jauh lebih baik.
Pharap

@ CalidMoon Ya, itu lebih baik.
Pharap

4

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 Playerkelas 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 dll

Cara 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 Controllerkelas yang digunakan untuk mengontrol Entitys. Dengan melakukan ini, semua logika berat berakhir di controller dan semua data dan kesamaan disimpan di entitas.

Selain itu, dengan mensubklasifikasikan Controllerke dalam InputControllerdan AIController, ini memungkinkan pemain untuk secara efektif mengontrol apa pun Entitydi dalam ruangan. Pendekatan ini juga membantu multiplayer dengan memiliki kelas RemoteControlleratau NetworkControlleryang beroperasi melalui perintah dari aliran jaringan.

Ini bisa menghasilkan banyak logika yang disepelekan menjadi satu Controllerjika Anda tidak hati-hati. Cara untuk menghindari itu adalah dengan memiliki Controlleryang terdiri dari yang lain Controller, atau membuat Controllerfungsi tergantung pada berbagai properti dari Controller. Misalnya, AIControllerakan memiliki DecisionTreemelekat padanya, dan PlayerCharacterControllerdapat terdiri dari berbagai lainnya Controllerseperti MovementController, a JumpController(berisi mesin negara dengan negara OnGround, Ascending dan Descending), sebuah InventoryUIController. Manfaat tambahan dari hal ini adalah bahwa yang baru Controllerdapat ditambahkan ketika fitur baru ditambahkan - jika permainan dimulai tanpa sistem inventaris dan satu ditambahkan, pengontrol untuk itu dapat ditempelkan nanti.


Saya suka ide ini tetapi tampaknya telah mentransfer semua kode ke kelas controller meninggalkan saya dengan masalah yang sama.
user441521

@ user441521 Baru menyadari ada paragraf tambahan yang akan saya tambahkan tetapi saya kehilangannya ketika browser saya mogok. Saya akan menambahkannya sekarang. Pada dasarnya, Anda dapat memiliki pengontrol yang berbeda yang dapat menyusunnya menjadi pengontrol agregat sehingga setiap pengontrol menangani hal yang berbeda. mis. AggregateController.Controllers = {JumpController (keybinds), MoveController (keybinds), InventoryUIController (keybinds, uisystem)}
Pharap
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.