Dalam proses pemrograman entitas dalam game, situasi muncul ketika mereka harus bertindak dalam kondisi yang berbeda dengan cara yang berbeda, yang menunjukkan penggunaan 
negara .
Tetapi jika Anda memutuskan untuk menggunakan kekerasan, kode akan dengan cepat berubah menjadi kekacauan kusut dengan banyak pernyataan if-else bersarang.
Untuk solusi anggun untuk masalah ini, Anda dapat menggunakan pola desain State. Kami akan mendedikasikan tutorial ini untuknya!
Dari tutorial Anda:
- Pelajari dasar-dasar templat Negara di Unity.
- Anda akan belajar apa itu mesin negara dan kapan menggunakannya.
- Pelajari cara menggunakan konsep-konsep ini untuk mengontrol pergerakan karakter Anda.
Catatan : tutorial ini untuk pengguna tingkat lanjut; diasumsikan bahwa Anda sudah tahu cara bekerja di Unity dan memiliki tingkat rata-rata pengetahuan C #. Selain itu, tutorial ini menggunakan Unity 2019.2 dan C # 7.
Mulai bekerja
Unduh 
materi proyek . Buka 
zip file zip dan buka proyek 
starter di Unity.
Ada beberapa folder dalam proyek yang akan membantu Anda memulai. Folder 
Aset / RW berisi folder 
Animasi , 
Bahan , 
Model , 
Rak itan , 
Sumber Daya , 
Adegan , 
Skrip, dan 
Suara , dinamai sesuai dengan sumber daya yang dikandungnya.
Untuk menyelesaikan tutorial, kami hanya akan bekerja dengan 
Adegan dan 
Skrip .
Pergi ke 
RW / Adegan dan buka 
Main . Dalam mode Game, Anda akan melihat karakter di tudung di dalam kastil abad pertengahan.
Klik 
Play dan perhatikan bagaimana 
Kamera bergerak agar sesuai dengan bingkai 
Karakter . Saat ini, dalam permainan kecil kami tidak ada interaksi, kami akan mengerjakannya dalam tutorial.
Jelajahi karakter
Dalam 
hierarki, pilih 
Karakter . Periksa 
Inspektur . Anda akan melihat 
komponen dengan nama yang sama berisi logika kontrol 
karakter .
Buka 
Character.cs yang terletak di 
RW / Script .
Script melakukan banyak tindakan, tetapi sebagian besar tidak penting bagi kami. Untuk saat ini, mari kita perhatikan metode-metode berikut.
- Move: bergerak karakter, menerima nilai- speedtipe float sebagai kecepatan gerakan dan- rotationSpeed, Kecepatan sebagai kecepatan sudut.
- ResetMoveParams: metode ini mengatur ulang parameter yang digunakan untuk menghidupkan gerakan dan kecepatan sudut karakter. Ini digunakan hanya untuk membersihkan.
- SetAnimationBool: Ini mengatur- paramanimasi- paramdari tipe Bool ke nilai.
- CheckCollisionOverlap: menerima- Vector3dan mengembalikan- boolyang menentukan apakah ada colliders dalam radius yang ditentukan dari- TriggerAnimation:- TriggerAnimationparameter animasi parameter input.
- ApplyImpulse:- ApplyImpulsepulsa ke Karakter sama dengan- forceparameter input tipe- Vector3.
Di bawah ini Anda akan melihat metode ini. Dalam tutorial kami, konten dan pekerjaan internal mereka tidak penting.
Apa itu mesin negara
Mesin negara adalah konsep di mana sebuah wadah menyimpan keadaan sesuatu pada saat tertentu. Berdasarkan data input, ini dapat memberikan kesimpulan tergantung pada kondisi saat ini, meneruskan proses ini ke status baru. Mesin negara dapat direpresentasikan sebagai 
diagram keadaan . Mempersiapkan diagram keadaan memungkinkan Anda untuk memikirkan semua kondisi sistem yang mungkin dan transisi di antara mereka.
Mesin negara
Mesin negara terbatas atau 
FSM (mesin negara terbatas) adalah salah satu dari empat keluarga utama 
mesin . Automata adalah model abstrak dari mesin sederhana. Mereka dipelajari dalam kerangka 
teori automata - cabang teori ilmu komputer.
Singkatnya:
- FSM terdiri dari sejumlah kondisi yang terbatas. Pada waktu tertentu , hanya satu dari kondisi ini yang aktif .
- Setiap negara menentukan ke negara mana ia akan masuk sebagai output berdasarkan urutan yang diterima dari informasi yang masuk .
- Status output menjadi status aktif baru. Dengan kata lain, ada transisi antar negara .
Untuk lebih memahami hal ini, pertimbangkan karakter permainan platform yang ada di tanah. Karakter dalam status 
Berdiri . Ini akan menjadi 
status aktifnya hingga pemain menekan tombol sehingga karakter melompat.
Status 
Berdiri mengidentifikasi tombol yang ditekan sebagai 
input signifikan dan, sebagai 
output , beralih ke status 
Jumping .
Misalkan ada sejumlah keadaan gerak tertentu dan satu karakter hanya dapat berada di salah satu keadaan pada suatu waktu. Ini adalah contoh FSM.
Mesin negara hirarkis
Pertimbangkan platformer menggunakan FSM, di mana beberapa negara bagian berbagi logika fisika umum. Misalnya, Anda bisa bergerak dan melompat di status 
Crouching dan 
Standing . Dalam hal ini, beberapa variabel yang masuk mengarah ke perilaku yang sama dan keluaran informasi untuk dua keadaan yang berbeda.
Dalam situasi seperti itu, adalah logis untuk mendelegasikan perilaku umum ke beberapa negara lain. Untungnya, ini dapat dicapai dengan menggunakan mesin negara 
hierarkis .
Dalam FSM hirarkis, ada 
substate mendelegasikan informasi masuk 
mentah ke 
substrat mereka. Ini pada gilirannya memungkinkan Anda untuk dengan anggun mengurangi ukuran dan kompleksitas FSM, sambil mempertahankan logikanya.
Template Status
Dalam buku mereka 
Design Patterns: Elements of Reusable Object-Oriented Software, Erich Gamma, Richard Helm, Ralph Johnson dan John Vlissidis ( 
Geng Empat ) mendefinisikan 
tugas templat Negara sebagai berikut:
โDia harus membiarkan objek mengubah perilakunya ketika keadaan internalnya berubah. Dalam hal ini, akan terlihat bahwa objek telah mengubah kelasnya. "
Untuk lebih memahami ini, perhatikan contoh berikut:
- Sebuah skrip yang menerima informasi yang masuk untuk logika pergerakan dilampirkan ke entitas dalam game.
- Kelas ini menyimpan variabel status saat ini yang hanya merujuk ke instance kelas negara .
- Informasi yang masuk didelegasikan ke keadaan saat ini, yang memprosesnya dan menciptakan perilaku yang didefinisikan dalam dirinya sendiri. Ini juga menangani transisi negara yang diperlukan.
Oleh karena itu, karena fakta bahwa pada waktu yang berbeda, variabel 
state saat ini merujuk ke state yang berbeda, akan terlihat bahwa kelas skrip yang sama berperilaku berbeda. Ini adalah inti dari templat "Status".
Dalam proyek kami, kelas 
Karakter yang disebutkan di atas akan berperilaku berbeda tergantung pada keadaan yang berbeda. Tapi kita butuh dia untuk berperilaku sendiri!
Dalam kasus umum, ada tiga poin utama untuk setiap kelas negara yang memungkinkan perilaku negara secara keseluruhan:
- Entri : ini adalah saat ketika entitas memasuki negara dan melakukan tindakan yang perlu dilakukan hanya sekali ketika memasuki negara.
- Keluar : mirip dengan input - semua operasi reset dilakukan di sini, yang harus dilakukan hanya sebelum keadaan berubah.
- Perbarui Loop : Berikut adalah logika pembaruan dasar yang berjalan di setiap frame. Ini dapat dibagi menjadi beberapa bagian, misalnya, siklus untuk memperbarui fisika dan siklus untuk memproses input pemain.
Mendefinisikan state and state machine
Pergi ke 
RW / Script dan buka 
StateMachine.cs .
Mesin Negara , seperti yang Anda duga, memberikan abstraksi untuk mesin negara. Perhatikan bahwa 
CurrentState terletak dengan benar di dalam kelas ini. Ini akan menyimpan tautan ke status mesin aktif saat ini.
Sekarang, untuk mendefinisikan konsep 
negara , mari kita pergi ke 
RW / Script dan buka script 
State.cs di IDE.
State adalah kelas abstrak yang akan kita gunakan sebagai 
model dari mana semua 
kelas status proyek diturunkan. Sebagian kode dalam materi proyek sudah siap.
DisplayOnUI hanya menampilkan nama status saat ini di UI di layar. Anda tidak perlu mengetahui perangkat internalnya, cukup pahami bahwa ia menerima enumerator dari jenis 
UIManager.Alignment sebagai parameter input, yang bisa 
Left atau 
Right . Tampilan nama status di kiri bawah atau kanan layar tergantung padanya.
Selain itu, ada dua variabel yang dilindungi, 
character dan 
stateMachine . Variabel 
character merujuk ke turunan dari kelas 
Karakter , dan 
stateMachine merujuk ke turunan 
dari mesin keadaan yang terkait dengan keadaan.
Saat membuat instance keadaan, konstruktor mengikat 
character dan 
stateMachine .
Masing-masing dari banyak contoh 
Character dalam sebuah adegan dapat memiliki set negara dan mesin negara sendiri.
Sekarang tambahkan metode berikut ke 
State.cs dan simpan file:
 public virtual void Enter() { DisplayOnUI(UIManager.Alignment.Left); } public virtual void HandleInput() { } public virtual void LogicUpdate() { } public virtual void PhysicsUpdate() { } public virtual void Exit() { } 
Metode virtual ini menentukan poin status kunci yang dijelaskan di atas. Ketika 
mesin status melakukan transisi antar status, kami memanggil 
Exit untuk status sebelumnya dan 
Enter status aktif baru.
HandleInput , 
LogicUpdate dan 
PhysicsUpdate bersama-sama menentukan 
loop pembaruan . 
HandleInput menangani input pemain. 
LogicUpdate memproses logika dasar, sementara 
PhyiscsUpdate memproses perhitungan logika dan fisika.
Sekarang buka 
StateMachine.cs lagi, tambahkan metode berikut dan simpan file:
 public void Initialize(State startingState) { CurrentState = startingState; startingState.Enter(); } public void ChangeState(State newState) { CurrentState.Exit(); CurrentState = newState; newState.Enter(); } 
Initialize mengkonfigurasi mesin keadaan dengan mengatur 
CurrentState untuk 
CurrentState dan memanggil 
Enter untuk itu. Ini menginisialisasi mesin keadaan, untuk pertama kalinya mengatur keadaan aktif.
ChangeState menangani transisi 
keadaan . Itu panggilan 
Exit untuk 
CurrentState lama sebelum mengganti referensi dengan 
newState . Pada akhirnya, ia memanggil 
Enter untuk 
newState .
Jadi, kami mengatur 
negara dan 
mesin negara .
Menciptakan kondisi gerak
Lihatlah diagram keadaan berikut, yang menunjukkan berbagai kondisi 
pergerakan esensi dalam gim pemain. Di bagian ini, kami menerapkan template "Status" untuk 
gerakan yang ditunjukkan pada gambar 
FSM :
Perhatikan status gerakan, yaitu 
Standing , 
Ducking dan 
Jumping , serta bagaimana data yang masuk menyebabkan transisi antar negara. Ini adalah FSM hierarkis di mana 
Grounded adalah sub-negara bagian untuk sub-state 
Ducking dan 
Standing .
Kembali ke Unity dan pergi ke 
RW / Scripts / States . Di sana Anda akan menemukan beberapa file C # dengan nama yang berakhir dengan 
Negara .
Masing-masing file ini mendefinisikan satu kelas, yang masing-masing diwarisi dari 
State . Oleh karena itu, kelas-kelas ini mendefinisikan status yang akan kita gunakan dalam proyek.
Sekarang buka 
Character.cs dari folder 
RW / Scripts .
Gulir di atas file 
#region Variables dan tambahkan kode berikut:
 public StateMachine movementSM; public StandingState standing; public DuckingState ducking; public JumpingState jumping; 
movementSM ini mengacu pada mesin keadaan yang memproses logika gerak untuk instance 
Character . Kami juga menambahkan tautan ke tiga negara bagian yang kami terapkan untuk setiap jenis gerakan.
Pergi ke 
#region MonoBehaviour Callbacks dalam file yang sama. Tambahkan metode 
MonoBehaviour berikut dan kemudian simpan
 private void Start() { movementSM = new StateMachine(); standing = new StandingState(this, movementSM); ducking = new DuckingState(this, movementSM); jumping = new JumpingState(this, movementSM); movementSM.Initialize(standing); } private void Update() { movementSM.CurrentState.HandleInput(); movementSM.CurrentState.LogicUpdate(); } private void FixedUpdate() { movementSM.CurrentState.PhysicsUpdate(); } 
- Dalam Startkode menciptakan turunan dari Mesin Negara dan menugaskannya untukmovementSM, dan juga instantiates berbagai gerak negara. Saat membuat setiap status gerakan, kami meneruskan referensi ke instanceCharactermenggunakanthis, serta instancemovementSM. Pada akhirnya, kami memanggilInitializeuntukmovementSMdan lulusStandingsebagai keadaan awal.
- Dalam metode Update, kami memanggilHandleInputdanLogicUpdateuntukCurrentStatedari mesinmovementSM. Demikian pula, diFixedUpdatekami memanggilPhysicsUpdateuntukCurrentStatedari mesinmovementSM. Intinya, ini mendelegasikan tugas ke keadaan aktif; ini adalah arti dari templat โStatusโ.
Sekarang kita perlu mengatur perilaku di dalam masing-masing kondisi gerak. Bersiaplah, akan ada banyak kode!
Perusahaan yang Berdiri
Kembali ke 
RW / Script / Negara di jendela Proyek.
Buka 
Grounded.cs dan perhatikan bahwa kelas ini memiliki konstruktor yang cocok dengan konstruktor 
State . Ini logis karena kelas ini mewarisi darinya. Anda akan melihat hal yang sama di semua kelas 
negara bagian lainnya.
Tambahkan kode berikut:
 public override void Enter() { base.Enter(); horizontalInput = verticalInput = 0.0f; } public override void Exit() { base.Exit(); character.ResetMoveParams(); } public override void HandleInput() { base.HandleInput(); verticalInput = Input.GetAxis("Vertical"); horizontalInput = Input.GetAxis("Horizontal"); } public override void PhysicsUpdate() { base.PhysicsUpdate(); character.Move(verticalInput * speed, horizontalInput * rotationSpeed); } 
Inilah yang terjadi di sini:
- Kami mendefinisikan kembali salah satu metode virtual yang didefinisikan di kelas induk. Untuk mempertahankan semua fungsi yang mungkin ada di induk, kami memanggil metode basedengan nama yang sama dari setiap metode yang diganti. Ini adalah templat penting yang akan terus kami gunakan.
- Baris berikutnya, EntersethorizontalInputdanverticalInputnilai standarnya.
- Di dalam Exitkami, seperti yang disebutkan di atas, memanggil metodeResetMoveParams- Dalam metode HandleInput, variabelhorizontalInputdanverticalInputHandleInputnilaiHandleInputnilai sumbu input horisontal dan vertikal. Berkat ini, pemain dapat mengontrol karakter menggunakan tombol W , A , S dan D.
- Di PhysicsUpdatekami membuat panggilanMove, melewati variabelPhysicsUpdatehorizontalInputdanverticalInputdikalikan dengan kecepatan yang sesuai. Dalamspeedvariabelspeedkecepatan gerakan disimpan, dan dalamrotationSpeed, kecepatan sudut.
Sekarang buka 
Standing.cs dan perhatikan fakta bahwa ia mewarisi dari 
Grounded . Itu terjadi karena, seperti yang kami katakan di atas, 
Berdiri adalah substrat untuk 
Grounded . Ada berbagai cara untuk menerapkan hubungan ini, tetapi dalam tutorial ini kami menggunakan warisan.
Tambahkan metode 
override berikut dan simpan skrip:
 public override void Enter() { base.Enter(); speed = character.MovementSpeed; rotationSpeed = character.RotationSpeed; crouch = false; jump = false; } public override void HandleInput() { base.HandleInput(); crouch = Input.GetButtonDown("Fire3"); jump = Input.GetButtonDown("Jump"); } public override void LogicUpdate() { base.LogicUpdate(); if (crouch) { stateMachine.ChangeState(character.ducking); } else if (jump) { stateMachine.ChangeState(character.jumping); } } 
- Di Enterkami mengonfigurasi variabel yang diwarisi dariGrounded. MenerapkanMovementSpeeddanRotationSpeedkarakter kespeeddanrotationSpeed. Kemudian mereka berhubungan, masing-masing, dengan kecepatan gerakan normal dan kecepatan sudut yang dimaksudkan untuk esensi karakter.
 
 Selain itu, variabel untuk menyimpan inputcrouchdanjumpdiatur ulang ke false.
- Di dalam HandleInput, variabelcrouchdanjumpmenyimpan input pemain untuk squat dan lompatan. Jika dalam adegan Utama pemain menekan tombol Shift, squat diatur ke true. Demikian pula, pemain dapat menggunakan tombol Space untukjump.
- Di LogicUpdatekami memeriksacrouchdanjumpvariabel dari tipebool. Jikacrouchbenar, makamovementSM.CurrentStateberubah kecharacter.ducking. Jikajumpbenar, maka status berubah menjadicharacter.jumping.
Simpan dan rakit proyek, lalu klik 
Play . Anda dapat bergerak di sekitar layar menggunakan tombol 
W , 
A , 
S dan 
D. Jika Anda mencoba menekan 
Shift atau 
Spasi , perilaku tak terduga akan terjadi, karena status yang sesuai belum diterapkan.
Cobalah bergerak di bawah objek tabel. Anda akan melihat bahwa karena ketinggian karakter collider ini tidak mungkin. Agar karakter melakukan ini, tambahkan perilaku squat.
Kami memanjat di bawah meja
Buka skrip 
Ducking.cs . Perhatikan bahwa 
Ducking juga mewarisi dari kelas 
Grounded untuk alasan yang sama seperti 
Standing . Tambahkan metode 
override berikut dan simpan skrip:
 public override void Enter() { base.Enter(); character.SetAnimationBool(character.crouchParam, true); speed = character.CrouchSpeed; rotationSpeed = character.CrouchRotationSpeed; character.ColliderSize = character.CrouchColliderHeight; belowCeiling = false; } public override void Exit() { base.Exit(); character.SetAnimationBool(character.crouchParam, false); character.ColliderSize = character.NormalColliderHeight; } public override void HandleInput() { base.HandleInput(); crouchHeld = Input.GetButton("Fire3"); } public override void LogicUpdate() { base.LogicUpdate(); if (!(crouchHeld || belowCeiling)) { stateMachine.ChangeState(character.standing); } } public override void PhysicsUpdate() { base.PhysicsUpdate(); belowCeiling = character.CheckCollisionOverlap(character.transform.position + Vector3.up * character.NormalColliderHeight); } 
- Di dalam Enterparameter yang menyebabkan matikan animasi jongkok diatur ke jongkok, yang memungkinkan animasi jongkok. Properticharacter.CrouchSpeeddancharacter.CrouchRotationSpeeddiberi nilaispeeddanrotation, yang mengembalikan gerakan karakter dan kecepatan sudut saat bergerak dalam squat .
 
 character.CrouchColliderHeightberikutnya.CrouchColliderHeight menetapkan ukuran collider karakter, yang mengembalikan tinggi collider yang diinginkan saat jongkok. Pada akhirnya,belowCeilingdireset ke false.
- Di dalam Exitparameter animasi jongkok diatur ke false. Ini menonaktifkan animasi squat. Kemudian ketinggian collider normal diatur, dikembalikan olehcharacter.NormalColliderHeight.character.NormalColliderHeight.
- Di dalam HandleInputvariabel menetapkan nilai input pemain. Di adegan Utama , memegang Shift setcrouchHeldmenjadi true.
- Di dalam PhysicsUpdatevariabelbelowCeilingdiberi nilai dengan melewati titik dalam formatVector3dengan kepala objek game karakter ke metodeCheckCollisionOverlap. Jika ada tabrakan di dekat titik ini, maka ini berarti bahwa karakter berada di bawah semacam langit-langit.
- Secara internal, LogicUpdatememeriksa apakahcrouchHeldataubelowCeilingbenar. Jika tidak ada yang benar, makamovementSM.CurrentStateberubah menjadicharacter.standing.
Bangun proyek dan klik 
Play . Sekarang Anda dapat bergerak di sekitar tempat kejadian. Jika Anda menekan 
Shift , karakter akan duduk dan Anda dapat bergerak dalam squat.
Anda juga bisa naik di bawah platform. Jika Anda melepaskan 
Shift saat berada di bawah platform, karakter akan tetap berada dalam squat hingga ia meninggalkan tempat perlindungannya.
Melambunglah!
Buka 
Jumping.cs . Anda akan melihat metode yang disebut 
Jump . Jangan khawatir tentang cara kerjanya; itu cukup untuk memahami bahwa itu digunakan sehingga karakter dapat melompat dengan memperhitungkan fisika dan animasi.
Sekarang tambahkan metode 
override biasa dan simpan skrip
 public override void Enter() { base.Enter(); SoundManager.Instance.PlaySound(SoundManager.Instance.jumpSounds); grounded = false; Jump(); } public override void LogicUpdate() { base.LogicUpdate(); if (grounded) { character.TriggerAnimation(landParam); SoundManager.Instance.PlaySound(SoundManager.Instance.landing); stateMachine.ChangeState(character.standing); } } public override void PhysicsUpdate() { base.PhysicsUpdate(); grounded = character.CheckCollisionOverlap(character.transform.position); } 
- Di dalam EnterSoundManagersingleton memainkan suara lompatan. Kemudiangroundedreset ke nilai standarnya. Pada akhirnya,Jumpdipanggil.
- Di dalam PhysicsUpdatetitikPhysicsUpdatesebelah kaki karakter dikirim keCheckCollisionOverlap, yang berarti bahwa ketika karakter berada di tanah,groundedakan disetel ke true.
- Di LogicUpdate, jikagroundedbenar, kami memanggilTriggerAnimationuntuk mengaktifkan animasi touchdown, suara touchdown dimainkan, danmovementSM.CurrentStateTriggerAnimationberubah menjadicharacter.standing.
Jadi, dalam hal ini kami telah menyelesaikan implementasi penuh perpindahan FSM menggunakan 
templat โNegaraโ . Bangun proyek dan jalankan. Tekan 
Spasi untuk membuat karakter melompat.
Ke mana harus pergi selanjutnya?
Bahan - 
bahan proyek memiliki rancangan proyek dan proyek selesai.
Terlepas dari kegunaannya, mesin negara memiliki keterbatasan. Concurrent State Machines dan Pushdown Automaton machines dapat menangani beberapa batasan ini. Anda dapat membacanya di buku oleh Robert Nystrom 
Game Programming Patterns .
Selain itu, topik tersebut dapat dieksplorasi lebih dalam dengan memeriksa 
pohon perilaku yang digunakan untuk membuat entitas dalam game yang lebih kompleks.