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 speed tipe 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 param animasi param dari tipe Bool ke nilai.CheckCollisionOverlap : menerima bertipe Vector3 dan mengembalikan bool yang menentukan apakah ada colliders dalam radius yang ditentukan dari .TriggerAnimation : TriggerAnimation parameter animasi parameter input.ApplyImpulse : ApplyImpulse pulsa ke Karakter sama dengan force parameter 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
Start kode menciptakan turunan dari Mesin Negara dan menugaskannya untuk movementSM , dan juga instantiates berbagai gerak negara. Saat membuat setiap status gerakan, kami meneruskan referensi ke instance Character menggunakan this , serta instance movementSM . Pada akhirnya, kami memanggil Initialize untuk movementSM dan lulus Standing sebagai keadaan awal. - Dalam metode
Update , kami memanggil HandleInput dan LogicUpdate untuk CurrentState dari mesin movementSM . Demikian pula, di FixedUpdate kami memanggil PhysicsUpdate untuk CurrentState dari mesin movementSM . 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
base dengan nama yang sama dari setiap metode yang diganti. Ini adalah templat penting yang akan terus kami gunakan. - Baris berikutnya,
Enter set horizontalInput dan verticalInput nilai standarnya. - Di dalam
Exit kami, seperti yang disebutkan di atas, memanggil metode ResetMoveParams untuk mengatur ulang ketika mengubah ke keadaan lain. - Dalam metode
HandleInput , variabel horizontalInput dan verticalInput HandleInput nilai HandleInput nilai sumbu input horisontal dan vertikal. Berkat ini, pemain dapat mengontrol karakter menggunakan tombol W , A , S dan D. - Di
PhysicsUpdate kami membuat panggilan Move , melewati variabel PhysicsUpdate horizontalInput dan verticalInput dikalikan dengan kecepatan yang sesuai. Dalam speed variabel speed kecepatan gerakan disimpan, dan dalam rotationSpeed , 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
Enter kami mengonfigurasi variabel yang diwarisi dari Grounded . Menerapkan MovementSpeed dan RotationSpeed karakter ke speed dan rotationSpeed . Kemudian mereka berhubungan, masing-masing, dengan kecepatan gerakan normal dan kecepatan sudut yang dimaksudkan untuk esensi karakter.
Selain itu, variabel untuk menyimpan input crouch dan jump diatur ulang ke false. - Di dalam
HandleInput , variabel crouch dan jump menyimpan 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 untuk jump . - Di
LogicUpdate kami memeriksa crouch dan jump variabel dari tipe bool . Jika crouch benar, maka movementSM.CurrentState berubah ke character.ducking . Jika jump benar, maka status berubah menjadi character.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
Enter parameter yang menyebabkan matikan animasi jongkok diatur ke jongkok, yang memungkinkan animasi jongkok. Properti character.CrouchSpeed dan character.CrouchRotationSpeed diberi nilai speed dan rotation , yang mengembalikan gerakan karakter dan kecepatan sudut saat bergerak dalam squat .
character.CrouchColliderHeight berikutnya.CrouchColliderHeight menetapkan ukuran collider karakter, yang mengembalikan tinggi collider yang diinginkan saat jongkok. Pada akhirnya, belowCeiling direset ke false. - Di dalam
Exit parameter animasi jongkok diatur ke false. Ini menonaktifkan animasi squat. Kemudian ketinggian collider normal diatur, dikembalikan oleh character.NormalColliderHeight . character.NormalColliderHeight . - Di dalam
HandleInput variabel menetapkan nilai input pemain. Di adegan Utama , memegang Shift set crouchHeld menjadi true. - Di dalam
PhysicsUpdate variabel belowCeiling diberi nilai dengan melewati titik dalam format Vector3 dengan kepala objek game karakter ke metode CheckCollisionOverlap . Jika ada tabrakan di dekat titik ini, maka ini berarti bahwa karakter berada di bawah semacam langit-langit. - Secara internal,
LogicUpdate memeriksa apakah crouchHeld atau belowCeiling benar. Jika tidak ada yang benar, maka movementSM.CurrentState berubah menjadi character.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
Enter SoundManager singleton memainkan suara lompatan. Kemudian grounded reset ke nilai standarnya. Pada akhirnya, Jump dipanggil. - Di dalam
PhysicsUpdate titik PhysicsUpdate sebelah kaki karakter dikirim ke CheckCollisionOverlap , yang berarti bahwa ketika karakter berada di tanah, grounded akan disetel ke true. - Di
LogicUpdate , jika grounded benar, kami memanggil TriggerAnimation untuk mengaktifkan animasi touchdown, suara touchdown dimainkan, dan movementSM.CurrentState TriggerAnimation berubah menjadi character.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.