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.