Pernahkah Anda bertanya-tanya bagaimana dalam game seperti
Super Meat Boy fungsi replay diimplementasikan? Salah satu cara untuk mengimplementasikannya adalah dengan melakukan input dengan cara yang sama seperti yang dilakukan pemain, yang, pada gilirannya, berarti bahwa input tersebut perlu disimpan. Anda dapat menggunakan
pola Command untuk ini dan banyak lagi.
Templat Perintah juga berguna untuk membuat fungsi Batalkan dan Ulangi dalam permainan strategi.
Dalam tutorial ini, kami mengimplementasikan Templat perintah di C # dan menggunakannya untuk memandu karakter bot melalui labirin tiga dimensi. Dari tutorial Anda akan belajar:
- Dasar-dasar pola Perintah.
- Bagaimana menerapkan pola Perintah
- Cara membuat antrian perintah input dan menunda eksekusi.
Catatan : diasumsikan bahwa Anda sudah terbiasa dengan Unity dan memiliki pengetahuan rata-rata tentang C #. Dalam tutorial ini kita akan bekerja dengan Unity 2019.1 dan C # 7 .
Mulai bekerja
Untuk memulai, unduh
materi proyek . Buka zip file dan buka proyek
Starter di Unity.
Pergi ke
RW / Adegan dan buka adegan
Utama . Adegan ini terdiri dari bot dan labirin, serta UI terminal yang menampilkan instruksi. Desain level dibuat dalam bentuk kisi, yang berguna ketika kita memindahkan bot secara visual melalui labirin.
Jika Anda mengklik
Play , kita akan melihat bahwa instruksi tidak berfungsi. Ini normal karena kami akan menambahkan fungsionalitas ini ke tutorial.
Bagian paling menarik dari adegan ini adalah GameObject
Bot . Pilih di jendela Hierarki dengan mengkliknya.
Di Inspektur, Anda dapat melihat bahwa ia memiliki komponen
Bot . Kami akan menggunakan komponen ini dengan mengeluarkan perintah input.
Kami memahami logika bot
Pergi ke
RW / Skrip dan buka skrip
Bot di editor kode. Anda tidak perlu tahu apa yang terjadi dalam skrip
Bot . Tapi lihat dua metode:
Move
dan
Shoot
. Sekali lagi, Anda tidak perlu mencari tahu apa yang terjadi di dalam metode ini, tetapi Anda perlu memahami cara menggunakannya.
Perhatikan bahwa metode
Move
menerima parameter input
CardinalDirection
.
CardinalDirection
adalah enumerasi. Elemen enumerasi dari tipe
CardinalDirection
bisa
Up
,
Down
,
Right
atau
Left
. Tergantung pada
CardinalDirection
dipilih
CardinalDirection
bot bergerak tepat satu kotak di sepanjang kisi dalam arah yang sesuai.
Metode
Shoot
memaksa bot untuk menembakkan peluru yang menghancurkan
dinding kuning , tetapi tidak berguna terhadap dinding lain.
Akhirnya, lihat metode
ResetToLastCheckpoint
; untuk memahami apa yang dia lakukan, lihat labirin. Ada titik-titik di labirin yang disebut
pos pemeriksaan . Untuk melewati labirin, bot harus sampai ke titik kontrol
hijau .
Ketika bot menginjak titik kontrol baru, maka itu menjadi yang
terakhir baginya.
ResetToLastCheckpoint
me-reset posisi bot, memindahkannya ke titik kontrol terakhir.
Meskipun kami tidak dapat menggunakan metode ini, kami akan segera memperbaikinya. Untuk memulai, Anda perlu belajar tentang pola desain
Perintah .
Apa itu Pola Desain Perintah?
Pola Perintah adalah salah satu dari 23 pola desain yang dijelaskan dalam
Pola Desain: Elemen-elemen buku
Perangkat Lunak Berorientasi Objek yang Dapat Digunakan Kembali yang ditulis oleh Geng Empat oleh Erich Gamma, Richard Helm, Ralph Johnson dan John Vlissides (GoF, Geng Empat).
Para penulis melaporkan bahwa "pola Perintah merangkum permintaan sebagai objek, sehingga memungkinkan kami untuk membuat parameter objek lain dengan berbagai permintaan, antrian atau permintaan log, dan mendukung operasi yang dapat dibalik."
Wow! Bagaimana itu?
Saya mengerti definisi ini tidak terlalu sederhana, jadi mari kita menganalisisnya.
Enkapsulasi berarti pemanggilan metode dapat dienkapsulasi sebagai objek.
Metode enkapsulasi dapat mempengaruhi banyak objek tergantung pada parameter input. Ini disebut
parameterisasi objek lain.
"Perintah" yang dihasilkan dapat disimpan bersama dengan tim lain sampai mereka dieksekusi. Ini adalah
antrian permintaan.
Antrian TimAkhirnya,
reversibilitas berarti operasi dapat dikembalikan menggunakan fungsi Undo.
OK, tapi bagaimana ini tercermin dalam kode?
Kelas
Command akan memiliki metode
Execute , yang menerima sebagai input parameter objek (dengan mana perintah dieksekusi) yang disebut
Receiver . Faktanya, metode Execute
dienkapsulasi oleh kelas Command.
Banyak instance dari kelas Command dapat dilewatkan sebagai objek biasa, yaitu, mereka dapat disimpan dalam struktur data seperti antrian, tumpukan, dll.
Untuk menjalankan perintah, Anda harus memanggil metode Execute-nya. Kelas yang memulai eksekusi disebut
Invoker .
Proyek saat ini berisi kelas kosong yang disebut
BotCommand
. Di bagian selanjutnya, kami akan mengimplementasikan implementasi di atas untuk memungkinkan bot melakukan tindakan menggunakan templat Perintah.
Pindahkan bot
Implementasi pola perintah
Di bagian ini, kami menerapkan pola Perintah. Ada banyak cara untuk mengimplementasikannya. Dalam tutorial ini kita akan membahas salah satunya.
Untuk memulai, buka
RW / Skrip dan dan buka skrip
BotCommand di editor. Kelas
BotCommand
masih kosong, tetapi tidak lama.
Masukkan kode berikut ke dalam kelas:
Apa yang sedang terjadi di sini?
- Variabel
commandName
digunakan hanya untuk menyimpan nama perintah yang dapat dibaca manusia. Tidak perlu menggunakannya dalam template, tetapi kita akan membutuhkannya nanti dalam tutorial. - Konstruktor
BotCommand
menerima fungsi dan string. Ini akan membantu kita mengatur metode Execute
objek Command dan name
. - Delegasi
ExecuteCallback
mendefinisikan jenis metode yang dienkapsulasi. Metode enkapsulasi akan mengembalikan batal dan menerima sebagai parameter input objek bertipe Bot
(komponen Bot ). - Properti
Execute
akan merujuk ke metode enkapsulasi. Kami akan menggunakannya untuk memanggil metode enkapsulasi. - Metode
ToString
diganti untuk mengembalikan string commandName
. Ini nyaman, misalnya, untuk digunakan di UI.
Simpan perubahan dan hanya itu! Kami telah berhasil menerapkan pola Perintah.
Masih menggunakannya.
Membangun tim
Buka
BotInputHandler di folder
RW / Scripts .
Di sini kita akan membuat lima contoh
BotCommand
. Contoh-contoh ini akan merangkum metode untuk memindahkan GameObject Bot ke atas, bawah, kiri, dan kanan, serta untuk pemotretan.
Untuk mengimplementasikan ini, masukkan berikut ini ke dalam kelas ini:
Dalam setiap contoh ini,
metode anonim diteruskan ke konstruktor. Metode anonim ini akan diringkas di dalam objek perintah yang sesuai. Seperti yang Anda lihat, tanda tangan dari masing-masing metode anonim memenuhi persyaratan yang ditentukan oleh delegasi
ExecuteCallback
.
Selain itu, parameter kedua ke konstruktor adalah string yang menunjukkan nama perintah. Nama ini akan dikembalikan oleh metode
ToString
dari instance perintah. Nanti kita akan menerapkannya untuk UI.
Dalam empat contoh pertama, metode anonim memanggil metode
Move
pada objek
bot
. Namun, parameter inputnya berbeda.
Perintah
MoveUp
,
MoveDown
,
MoveLeft
dan
MoveRight
melewati parameter
Move
CardinalDirection.Up
,
CardinalDirection.Down
,
CardinalDirection.Left
dan
CardinalDirection.Right
. Seperti yang disebutkan di bagian
What is Command Design Pattern , mereka menunjukkan arah yang berbeda untuk Bot GameObject untuk dipindahkan.
Dalam contoh kelima, metode anonim memanggil metode
Shoot
untuk objek
bot
. Berkat ini, bot akan menembakkan shell selama eksekusi perintah.
Sekarang kita telah membuat perintah, kita perlu mengaksesnya entah bagaimana ketika pengguna membuat input.
Untuk melakukan ini,
BotInputHandler
kode berikut dalam
BotInputHandler
, segera setelah perintah instances:
public static BotCommand HandleInput() { if (Input.GetKeyDown(KeyCode.W)) { return MoveUp; } else if (Input.GetKeyDown(KeyCode.S)) { return MoveDown; } else if (Input.GetKeyDown(KeyCode.D)) { return MoveRight; } else if (Input.GetKeyDown(KeyCode.A)) { return MoveLeft; } else if (Input.GetKeyDown(KeyCode.F)) { return Shoot; } return null; }
Metode
HandleInput
mengembalikan satu instance dari perintah tergantung pada tombol yang ditekan oleh pengguna. Simpan perubahan Anda sebelum melanjutkan.
Menerapkan perintah
Luar biasa, sekarang saatnya menggunakan tim yang kami buat. Buka
RW / Script lagi dan buka skrip
SceneManager di editor. Di kelas ini, Anda akan melihat tautan ke variabel
uiManager
dari tipe
UIManager
.
Kelas
UIManager
menyediakan metode pembantu yang berguna untuk
UI terminal yang kami gunakan dalam adegan ini. Jika metode dari
UIManager
digunakan, maka tutorial akan menjelaskan apa yang dilakukannya, tetapi secara umum untuk tujuan kita, tidak perlu mengetahui struktur internalnya.
Selain itu, variabel
bot
mengacu pada komponen bot yang terpasang pada GameObject
Bot .
Sekarang tambahkan kode berikut ke kelas
SceneManager
, ganti dengan komentar
//1
:
Wow, berapa banyak kodenya! Tapi jangan khawatir; kami akhirnya siap untuk peluncuran nyata pertama proyek di jendela Game.
Saya akan menjelaskan kodenya nanti. Ingatlah untuk menyimpan perubahan.
Menjalankan game untuk menguji templat Perintah
Jadi sekarang saatnya untuk membangun; Klik
Mainkan di editor Persatuan.
Anda harus bisa memasukkan perintah pindah menggunakan
tombol WASD . Untuk memasukkan perintah pemotretan, tekan tombol
F. Untuk menjalankan perintah, tekan
Enter .
Catatan : sampai proses eksekusi selesai, memasukkan perintah baru tidak mungkin.
Perhatikan bahwa garis ditambahkan ke UI terminal. Tim di UI ditandai dengan namanya. Ini dimungkinkan berkat variabel
commandName
.
Juga, perhatikan bagaimana UI bergulir sebelum eksekusi dan bagaimana garis dihapus selama eksekusi.
Kami mempelajari tim lebih dekat
Saatnya mempelajari kode yang kami tambahkan di bagian "Menerapkan Perintah":
- Daftar
botCommands
menyimpan tautan ke instance dari BotCommand
. Ingat bahwa untuk menghemat memori, kita hanya dapat membuat lima instance perintah, tetapi mungkin ada beberapa referensi untuk satu perintah. Selain itu, variabel executeCoroutine
merujuk ke ExecuteCommandsRoutine
, yang mengontrol eksekusi perintah. Update
memeriksa apakah pengguna telah menekan tombol Enter; jika demikian, ia memanggil ExecuteCommands
, atau ExecuteCommands
CheckForBotCommands
.CheckForBotCommands
menggunakan metode HandleInput
statis dari BotInputHandler
untuk memeriksa apakah pengguna telah menyelesaikan input, dan jika demikian, perintah dikembalikan . Perintah yang dikembalikan dilewatkan ke AddToCommands
. Namun, jika perintah dijalankan, mis. jika executeRoutine
bukan nol, maka itu akan kembali tanpa melewati apa pun ke AddToCommands
. Artinya, pengguna harus menunggu sampai selesai.AddToCommands
menambahkan tautan baru ke instance perintah yang botCommands
di botCommands
.- Metode
InsertNewText
dari kelas InsertNewText
menambahkan baris teks baru ke UI terminal. String teks adalah string yang diteruskan sebagai parameter input. Dalam hal ini, kami memberikan commandName ke commandName
. - Metode
ExecuteCommandsRoutine
meluncurkan ExecuteCommandsRoutine
. ResetScrollToTop
dari UIManager
menggulirkan terminal UI ke atas. Ini dilakukan sesaat sebelum dimulainya eksekusi.ExecuteCommandsRoutine
berisi perulangan for
yang mengulangi perintah di dalam daftar botCommands
dan mengeksekusi mereka satu per satu, meneruskan objek bot
ke metode yang dikembalikan oleh properti Execute
. Setelah setiap eksekusi, jeda ditambahkan dalam CommandPauseTime
detik.- Metode
RemoveFirstTextLine
dari UIManager
menghapus baris teks pertama di UI terminal, jika ada. Yaitu, ketika sebuah perintah dieksekusi, namanya dihapus dari UI. - Setelah semua perintah
botCommands
dihapus dan bot diatur ulang ke breakpoint terakhir menggunakan ResetToLastCheckpoint
. Pada akhirnya, executeRoutine
null
dan pengguna dapat terus memasukkan perintah.
Menerapkan Fitur Undo dan Redo
Jalankan lagi pemandangan itu dan cobalah untuk sampai ke titik kontrol hijau.
Anda akan melihat bahwa sementara kami tidak dapat membatalkan perintah yang dimasukkan. Ini berarti bahwa jika Anda membuat kesalahan, Anda tidak dapat kembali sampai Anda menyelesaikan semua perintah yang Anda masukkan. Anda dapat memperbaikinya dengan menambahkan fitur
Undo dan
Redo .
Kembali ke
SceneManager.cs dan tambahkan deklarasi variabel berikut segera setelah deklarasi
Daftar untuk
botCommands
:
private Stack<BotCommand> undoStack = new Stack<BotCommand>();
Variabel
undoStack
adalah
tumpukan (dari keluarga Koleksi) yang akan menyimpan semua referensi ke perintah yang dapat dibatalkan.
Sekarang kita menambahkan dua metode
UndoCommandEntry
dan
RedoCommandEntry
yang akan mengeksekusi Undo dan Redo. Di kelas
SceneManager
,
SceneManager
kode berikut setelah
ExecuteCommandsRoutine
:
private void UndoCommandEntry() {
Mari kita menganalisis kodenya:
- Jika perintah dieksekusi atau daftar
botCommands
kosong, metode UndoCommandEntry
apa UndoCommandEntry
apa. Kalau tidak, itu menulis tautan ke perintah terakhir yang dimasukkan pada tumpukan undoStack
. Ini juga menghapus tautan ke perintah dari daftar botCommands
. - Metode
RemoveLastTextLine
dari UIManager
menghapus baris teks terakhir dari UI terminal sehingga UI cocok dengan konten botCommands
. - Jika tumpukan
undoStack
kosong, maka RedoCommandEntry
tidak melakukan apa pun. Jika tidak, ia mengekstrak perintah terakhir dari atas undoStack
dan menambahkannya kembali ke daftar AddToCommands
menggunakan AddToCommands
.
Sekarang kita akan menambahkan input keyboard untuk menggunakan fungsi-fungsi ini. Di dalam kelas
SceneManager
ganti tubuh metode
Update
dengan kode berikut:
if (Input.GetKeyDown(KeyCode.Return)) { ExecuteCommands(); } else if (Input.GetKeyDown(KeyCode.U))
- Ketika Anda menekan tombol U , metode
UndoCommandEntry
. - Ketika Anda menekan tombol R , metode
RedoCommandEntry
.
Penanganan Case Tepi
Bagus, kita hampir selesai! Tetapi pertama-tama, kita perlu melakukan hal berikut:
- Saat memasukkan perintah baru, tumpukan
undoStack
harus dihapus. - Sebelum menjalankan perintah, tumpukan
undoStack
harus dibersihkan.
Untuk mengimplementasikan ini, pertama-tama kita perlu menambahkan metode baru ke
SceneManager
. Masukkan metode berikut setelah
CheckForBotCommands
:
private void AddNewCommand(BotCommand botCommand) { undoStack.Clear(); AddToCommands(botCommand); }
Metode ini menghapus
undoStack
dan kemudian memanggil metode
AddToCommands
.
Sekarang ganti panggilan ke
AddToCommands
di dalam
AddToCommands
dengan kode berikut:
AddNewCommand(botCommand);
Kemudian masukkan baris berikut setelah
if
di dalam metode
ExecuteCommands
untuk menghapus sebelum menjalankan perintah
undoStack
:
undoStack.Clear();
Dan akhirnya kita selesai!
Simpan pekerjaan Anda. Bangun proyek dan klik di editor
Play . Masukkan perintah seperti sebelumnya. Tekan
U untuk membatalkan perintah. Tekan
R untuk mengulangi perintah yang dibatalkan.
Cobalah untuk sampai ke pos pemeriksaan hijau.
Ke mana harus pergi selanjutnya?
Untuk mempelajari lebih lanjut tentang pola desain yang digunakan dalam pemrograman game, saya sarankan Anda mempelajari
Pola Pemrograman Game Robert Nystrom.
Untuk mempelajari lebih lanjut tentang teknik C # lanjutan, ambil kursus
C # Collections, Lambdas, dan LINQ .
Tugas
Sebagai tugas, cobalah untuk sampai ke titik kontrol hijau di ujung labirin. Saya menyembunyikan salah satu solusi di bawah spoiler.
Solusi- pindah à 2
- moveRight à 3
- pindah à 2
- moveLeft
- tembak
- moveLeft à 2
- pindah à 2
- moveLeft à 2
- moveDown à 5
- moveLeft
- tembak
- moveLeft
- pindah à 3
- menembak à 2
- pindah à 5
- moveRight à 3