Selamat siang, kawan-kawan tercinta!
Nama saya Alexander, saya adalah pengembang game HTML5.
Di salah satu perusahaan tempat saya mengirimkan resume saya, saya diminta menyelesaikan tugas tes. Saya setuju dan, setelah 1 hari, dikirim sebagai hasilnya game dikembangkan sesuai dengan TOR HTML5.

Karena saya berlatih pemrograman game, dan juga untuk penggunaan kode saya yang lebih efisien, saya memutuskan bahwa akan bermanfaat untuk menulis artikel pelatihan tentang proyek yang sudah selesai. Dan karena tes yang selesai menerima penilaian positif dan mengarah ke undangan untuk wawancara, mungkin keputusan saya memiliki hak untuk eksis dan, mungkin, akan membantu seseorang di masa depan.
Artikel ini akan memberikan gambaran tentang jumlah pekerjaan yang cukup untuk berhasil menyelesaikan tugas tes rata-rata untuk posisi HTML5 pengembang. Materi tersebut mungkin juga menarik bagi siapa saja yang ingin berkenalan dengan kerangka Phaser. Dan jika Anda sudah bekerja dengan Phaser dan menulis dalam JS - lihat bagaimana mengembangkan proyek di TypeScript.
Jadi, di bawah cat ada banyak kode TypeScript!
Pendahuluan
Kami memberikan pernyataan singkat tentang masalahnya.
- Kami akan mengembangkan game HTML5 sederhana - pencari ranjau klasik.
- Sebagai alat utama kita akan menggunakan phaser 3, naskah dan webpack.
- Game ini akan dirancang untuk desktop dan dijalankan di browser.
Kami menyediakan tautan ke proyek akhir.
Tautan ke demo dan sumber Dan ingat kembali mekanisme mesin pencari ranjau, jika tiba-tiba seseorang lupa aturan mainnya. Tetapi karena ini adalah kasus yang tidak mungkin, aturan ditempatkan di bawah spoiler :)
Aturan pencari kerjaLapangan bermain terdiri dari sel-sel yang diatur dalam sebuah tabel. Secara default, saat permainan dimulai, semua sel ditutup. Bom ditempatkan di beberapa sel.
Ketika mengklik kiri pada sel yang tertutup, itu terbuka. Jika ada bom di sel terbuka, maka pertandingan berakhir dengan kekalahan.
Jika tidak ada bom di dalam sel, maka angka ditampilkan di dalamnya, menunjukkan jumlah bom yang berada di sel tetangga relatif terhadap pembukaan saat ini. Jika tidak ada bom di dekatnya, maka sel itu terlihat kosong.
Mengklik kanan pada sel yang tertutup menetapkan bendera di atasnya. Tugas pemain adalah mengatur semua bendera yang tersedia untuknya sehingga mereka menandai semua sel yang ditambang. Setelah meletakkan semua bendera, pemain menekan tombol kiri mouse pada salah satu sel yang terbuka untuk memeriksa apakah dia menang.
Selanjutnya, kita langsung menuju manual itu sendiri. Semua materi dibagi menjadi langkah-langkah kecil, yang masing-masing menggambarkan implementasi tugas tertentu dalam waktu singkat. Jadi, melakukan gol kecil selangkah demi selangkah, pada akhirnya kami akan membuat game lengkap. Gunakan daftar isi jika Anda memutuskan untuk dengan cepat pergi ke langkah tertentu.
1. Persiapan
1.1 Template Proyek
Unduh
templat proyek phaser default . Ini adalah template yang direkomendasikan dari pembuat framework dan ia menawarkan kepada kita struktur direktori berikut:
Untuk proyek kami, kami tidak memerlukan file
index.js
saat ini, jadi hapus saja. Kemudian buat direktori
/src/scripts/
dan tempatkan file
index.ts
kosong di dalamnya. Kami akan menambahkan semua skrip kami ke folder ini.
Perlu juga diingat bahwa ketika membangun proyek untuk produksi, direktori
dist
akan dibuat di root, di mana rilis rilis akan ditempatkan.
1.2 Membangun Konfigurasi
Kami akan menggunakan webpack untuk perakitan. Karena template kami awalnya disiapkan untuk bekerja dengan JavaScript, dan kami menulis dalam TypeScript, kami perlu membuat perubahan kecil pada konfigurasi kolektor.
Dalam file
webpack/base.js
tambahkan kunci
entry
yang menunjukkan titik masuk ketika membangun proyek kami, serta konfigurasi
ts-loader
yang menjelaskan aturan untuk membangun skrip TS:
Kita juga perlu membuat file tsconfig.json di root proyek. Bagi saya itu memiliki konten berikut:
{ "compilerOptions": { "module": "commonjs", "lib": [ "dom", "es5", "es6", "es2015", "es2017", "es2015.promise" ], "target": "es5", "skipLibCheck": true }, "exclude": ["node_modules", "dist"] }
1.3 Memasang Modul
Instal semua dependensi dari package.json dan tambahkan modul skrip dan ts-loader ke dalamnya:
npm i npm i typescript --save-dev npm i ts-loader --save-dev
Sekarang proyek siap untuk memulai pengembangan. Kami memiliki 2 perintah yang kami miliki yang sudah ditentukan dalam properti
scripts
di file
package.json
.
- Bangun proyek untuk debugging dan buka di browser melalui server lokal
npm start
- Jalankan build untuk dijual dan letakkan build rilis di dist / folder
npm run build
1.4 Persiapan aset
Semua aset untuk game ini diunduh dengan jujur dari
OpenGameArt (versi 61x61) dan memiliki lisensi yang ramah yang disebut
Jangan ragu untuk digunakan , yang diceritakan oleh laman dengan paket tersebut dengan cermat). Omong-omong, kode yang disajikan dalam artikel memiliki lisensi yang sama! ;)
Saya menghapus gambar jam dari set yang diunduh, dan mengganti nama sisa file sehingga mendapatkan nama bingkai yang mudah digunakan. Daftar nama dan file yang sesuai ditampilkan pada layar di bawah ini.
Dari sprite yang dihasilkan, kita akan membuat atlas format
Phaser JSONArray
dalam program
TexturePacker (ada lebih dari cukup versi gratis, saya belum mendapatkan pekerjaan) dan meletakkan file
spritesheet.json
dan
spritesheet.json
di direktori
src/assets/
project.

2. Membuat adegan
2.1 Titik Masuk
Kami memulai pengembangan dengan membuat titik masuk yang dijelaskan dalam konfigurasi webpack.
Karena permainan yang kami miliki dirancang untuk desktop dan akan memenuhi seluruh layar, kami dengan berani menggunakan seluruh lebar dan tinggi browser untuk bidang
width
dan
height
.
Bidang
scene
saat ini adalah array kosong dan kami akan memperbaikinya!
2.2 Adegan Mulai
Buat kelas adegan pertama dalam file
src/scripts/scenes/StartScene.ts
:
export class StartScene extends Phaser.Scene { constructor() { super('Start'); } public preload(): void { } public create(): void { } }
Untuk warisan
Phaser.Scene
valid
Phaser.Scene
kami meneruskan nama adegan sebagai parameter ke konstruktor dari kelas induk.
Adegan ini akan menggabungkan fungsionalitas preloading sumber daya dan layar mulai, mengundang pengguna ke permainan.
Biasanya dalam proyek saya seorang pemain melewati dua adegan sebelum ia sampai ke yang mulai, dalam urutan ini:
Boot => Preload => Start
Tetapi dalam kasus ini, gim ini sangat sederhana, dan ada sangat sedikit aset sehingga tidak ada alasan untuk memasukkan preload ke adegan terpisah dan terlebih lagi untuk melakukan
Boot
loader terpisah awal.
Kami akan memuat semua aset dalam metode
preload
. Agar dapat bekerja dengan atlas yang dibuat di masa mendatang, kita perlu melakukan 2 langkah:
- mendapatkan kedua file
png
dan json
atlas menggunakan require
:
- memuatnya dalam metode
preload
adegan awal:
2.3 Teks-teks adegan awal
Ada 2 hal yang harus dilakukan di adegan awal:
- beri tahu pemain cara memulai permainan
- memulai permainan atas inisiatif pemain
Untuk memenuhi poin pertama, pertama-tama kita membuat dua enumerasi di awal file adegan untuk menjelaskan teks dan gaya mereka:
Dan kemudian buat kedua teks sebagai objek dalam metode
create
. Biarkan saya mengingatkan Anda bahwa metode
create
adegan di
Phaser
akan dipanggil hanya setelah memuat semua sumber daya dalam metode
preload
dan ini sangat cocok untuk kami.
Dalam proyek lain yang lebih besar, kita dapat mengambil teks dan gaya ke dalam file lokal json atau ke konfigurasi terpisah, tetapi mengingat bahwa kita sekarang hanya memiliki 2 baris, saya menganggap langkah ini berlebihan dan dalam hal ini saya sarankan tidak menyulitkan hidup kita, membatasi diri pada daftar di awal file adegan.
2.4 Transisi ke tingkat permainan
Hal terakhir yang akan kita lakukan dalam adegan ini sebelum melanjutkan adalah melacak acara klik mouse untuk meluncurkan pemain ke dalam permainan:
Adegan 2,5 tingkat
Menilai oleh parameter
"Game"
diteruskan ke metode
this.scene.start
Anda sudah menduga bahwa sudah waktunya untuk membuat adegan kedua, yang akan memproses logika permainan utama. Buat file
src/scripts/scenes/GameScene.ts
:
export class GameScene extends Phaser.Scene { constructor() { super('Game'); } public create(): void { } }
Dalam adegan ini, kita tidak perlu metode
preload
, karena kami telah memuat semua sumber daya yang diperlukan dalam adegan sebelumnya.
2.6 Mengatur adegan pada titik masuk
Sekarang setelah kedua adegan dibuat, tambahkan mereka ke titik masuk kami
src/scripts/index.ts
:
3. Objek permainan
Jadi, kelas
GameScene
akan mengimplementasikan logika tingkat permainan. Dan apa yang kita harapkan dari level game pencari ranjau? Secara visual, kami berharap melihat lapangan bermain dengan sel tertutup. Kita tahu bahwa lapangan adalah sebuah tabel, yang berarti memiliki sejumlah baris dan kolom tertentu, di mana beberapa bom ditempatkan dengan nyaman. Dengan demikian, kami memiliki informasi yang cukup untuk membuat entitas terpisah yang menjelaskan bidang permainan.
3.1 Papan permainan
Buat file
src/scripts/models/Board.ts
mana kita menempatkan kelas
Board
:
import { Field } from "./Field"; export class Board extends Phaser.Events.EventEmitter { private _scene: Phaser.Scene = null; private _rows: number = 0; private _cols: number = 0; private _bombs: number = 0; private _fields: Field[] = []; constructor(scene: Phaser.Scene, rows: number, cols: number, bombs: number) { super(); this._scene = scene; this._rows = rows; this._cols = cols; this._bombs = bombs; this._fields = []; } public get cols(): number { return this._cols; } public get rows(): number { return this._rows; } }
Mari kita buat kelas sebagai penerus Phaser.Events.EventEmitter untuk mengakses antarmuka untuk mendaftarkan dan memanggil acara, yang akan kita butuhkan di masa depan.
Array objek dari kelas
Field
akan disimpan di properti pribadi
_fields
. Kami akan mengimplementasikan model ini nanti.
Kami menyiapkan properti numerik pribadi
_rows
dan
_cols
untuk menunjukkan jumlah baris dan kolom bidang bermain. Buat getter publik untuk membaca
_rows
dan
_cols
.
Bidang
_bombs
memberi tahu kita jumlah bom yang perlu dihasilkan untuk level tersebut. Dan dalam parameter
_scene
kami meneruskan referensi ke objek adegan game
GameScene
, di mana kami akan membuat turunan dari kelas
Board
.
Perlu dicatat bahwa kami mentransfer objek adegan ke model hanya untuk transmisi lebih lanjut ke tampilan, di mana kami akan menggunakannya hanya untuk menampilkan tampilan. Faktanya adalah phaser secara langsung menggunakan objek adegan untuk membuat sprite dan karenanya mengharuskan kita untuk memberikan tautan ke adegan saat ini ketika membuat prefab sprite, yang akan kita kembangkan di masa depan. Dan bagi kami sendiri, kami akan menerima perjanjian bahwa kami mentransfer tautan ke tempat kejadian hanya untuk penggunaan lebih lanjut sebagai mesin pajangan dan setuju bahwa kami tidak akan secara langsung memanggil metode khusus tempat kejadian dalam model dan tampilan.
Setelah kami memutuskan antarmuka pembuatan papan, saya mengusulkan untuk menginisialisasi di adegan level, menyelesaikan kelas
GameScene
:
Kami mengambil parameter papan ke konstanta di awal file adegan dan meneruskannya ke konstruktor papan saat membuat turunan dari kelas ini.
3.2 Model Sel
Papan terdiri dari sel, yang ingin Anda tampilkan di layar. Setiap sel harus ditempatkan di posisi yang sesuai, ditentukan oleh baris dan kolom.
Sel juga dipilih sebagai entitas terpisah. Buat file
src/scripts/models/Field.ts
di mana kita akan menempatkan kelas yang menjelaskan sel:
import { Board } from "./Board"; export class Field extends Phaser.Events.EventEmitter { private _scene: Phaser.Scene = null; private _board: Board = null; private _row: number = 0; private _col: number = 0; constructor(scene: Phaser.Scene, board: Board, row: number, col: number) { super(); this._init(scene, board, row, col); } public get col(): number { return this._col; } public get row(): number { return this._row; } public get board(): Board { return this._board; } private _init(scene: Phaser.Scene, board: Board, row: number, col: number): void { this._scene = scene; this._board = board; this._row = row; this._col = col; } }
Setiap sel harus memiliki metrik baris dan kolom di mana ia berada. Kami mengatur parameter
_board
dan
_scene
untuk mengatur tautan ke objek papan dan tempat kejadian. Kami menerapkan getter untuk membaca bidang
_row
,
_col
dan
_board
.
3.3 Tampilan Sel
Sel abstrak dibuat dan sekarang kami ingin memvisualisasikannya. Untuk menampilkan sel di layar, Anda harus membuat tampilan. Buat file
src/scripts/views/FieldView.ts
dan letakkan kelas view di dalamnya:
import { Field } from "../models/Field"; export class FieldView extends Phaser.GameObjects.Sprite { private _model: Field = null; constructor(scene: Phaser.Scene, model: Field) { super(scene, 0, 0, 'spritesheet', 'closed'); this._model = model; this._init(); this._create(); } private _init(): void { } private _create(): void { } }
Harap dicatat bahwa kami menjadikan kelas ini keturunan
Phaser.GameObjects.Sprite
. Dalam istilah phaser, kelas ini telah menjadi prefab sprite. Artinya, saya mendapatkan fungsionalitas objek game dari sprite, yang akan kami kembangkan lebih lanjut dengan metode kami sendiri.
Mari kita lihat konstruktor dari kelas ini. Di sini, pertama-tama, kita harus memanggil konstruktor dari kelas induk dengan set parameter berikut:
- tautan ke objek adegan (seperti yang saya peringatkan pada bagian 3.1: phaser mengharuskan kita untuk menautkan ke adegan saat ini untuk membuat sprite)
- koordinat
x
dan y
atas kanvas - kunci string yang tersedia untuk atlas, yang kami muat dalam metode
preload
dari adegan awal - kunci bingkai kunci di atlas ini yang ingin Anda pilih untuk menampilkan sprite
Tetapkan referensi ke model (yaitu, instance dari kelas
Field
) di properti
_model
pribadi.
Kami juga dengan bijaksana memulai 2
_create
dan
_init
kosong saat ini, yang akan kami implementasikan nanti.
3.4 Membuat sprite di kelas tampilan
Jadi, pemandangan telah dibuat, tetapi dia masih tidak tahu cara menggambar sprite. Untuk menempatkan sprite dengan bingkai yang kita butuhkan di kanvas, Anda perlu memodifikasi metode
_create
pribadi kita sendiri:
3.5 Posisi Sprite
Saat ini, semua sprite yang dibuat akan ditempatkan di koordinat (0, 0) kanvas. Kita juga perlu menempatkan setiap sel di posisi yang sesuai di papan tulis. Yaitu, ke tempat yang sesuai dengan baris dan kolom sel ini. Untuk melakukan ini, kita perlu menulis kode untuk menghitung koordinat setiap instance dari kelas
FieldView
.
Tambahkan properti
_position
ke kelas, yang bertanggung jawab untuk koordinat akhir sel di lapangan bermain:
Karena kami ingin menyelaraskan papan, dan sesuai dengan sel di dalamnya, relatif terhadap bagian tengah layar, kami juga memerlukan properti
_offset
, yang menunjukkan offset sel khusus ini relatif ke tepi kiri dan atas layar. Tambahkan dengan pengambil pribadi:
Jadi, kami:
- Dapatkan total lebar layar di
this._scene.cameras.main.width
. - Kami mendapatkan total lebar papan dengan mengalikan jumlah sel dengan lebar satu sel:
this._board.cols * this.width
. - Mengambil lebar papan dari lebar layar, kami mendapat tempat di layar, tidak ditempati oleh papan.
- Membagi angka yang dihasilkan dengan 2, kami mendapat nilai indentasi di kiri dan kanan papan.
- Dengan menggeser setiap sel dengan nilai indentasi ini, kami menjamin keselarasan seluruh papan sepanjang sumbu
x
.
Kami melakukan tindakan yang sangat mirip untuk mendapatkan perpindahan vertikal.
Tetap menambahkan kode yang diperlukan dalam metode
_init
:
Properti
this.x
,
this.y
,
this.width
dan
this.height
sini adalah properti yang diwarisi dari kelas induk
Phaser.GameObjects.Sprite
. Mengubah properti
this.x
dan
this.y
mengarah ke posisi sprite yang benar pada kanvas.
3.6 Membuat instance dari FieldView
Buat tampilan di kelas
Field
:
3.7 bidang papan display.
Mari kita kembali ke kelas
Board
, yang pada dasarnya adalah kumpulan objek
Field
dan akan membuat sel.
Kami akan mengeluarkan kode pembuatan papan ke metode
_create
terpisah dan memanggil metode ini dari konstruktor. Mengetahui bahwa dalam metode
_create
kita tidak hanya akan membuat sel, kita akan mengeluarkan kode untuk membuat sel dalam metode
_createFields
terpisah.
Dalam metode ini kita akan membuat jumlah sel yang diinginkan dalam loop bersarang:
Sudah waktunya untuk pertama kalinya menjalankan perakitan untuk debugging dengan perintah
npm start
Pastikan bahwa di tengah layar kita diharapkan melihat 64 sel dalam 8 baris.
3.8 Membuat bom
Sebelumnya, saya melaporkan bahwa dalam metode
_create
dari kelas
Board
, kami tidak hanya akan membuat bidang. Apa lagi Juga akan ada pembuatan bom, dan pengaturan sel yang dibuat dengan jumlah bom tetangga. Mari kita mulai dengan bomnya sendiri.
Kita perlu menempatkan bom N di papan dalam sel acak. Kami menjelaskan proses pembuatan bom dengan algoritma perkiraan:
Pada setiap iterasi dari loop, kita akan mendapatkan sel acak dari properti
this._fields
hingga kita membuat bom sebanyak yang ditunjukkan dalam bidang
this._bombs
,. Jika sel yang diterima kosong, maka kami akan memasang bom di dalamnya dan memperbarui penghitung bom yang diperlukan untuk pembangkitan.
Untuk menghasilkan angka acak, kami menggunakan metode statis
Phaser.Math.Between
.
Jangan lupa untuk menulis panggilan ke
this._createBombs();
di file
Board.ts
this._createBombs();
di akhir metode
_create
Seperti yang sudah Anda perhatikan, agar kode ini berfungsi dengan benar, Anda perlu memperbaiki kelas
Field
dengan menambahkan pengambil
empty
dan metode
setBomb
ke
setBomb
.
Tambahkan bidang
_value
pribadi ke
_value
bidang, yang akan mengatur konten sel. Kami menerima perjanjian berikut.
Mengikuti aturan-aturan ini, kami akan mengembangkan metode di kelas
Field
yang bekerja dengan properti
_value
:
3.9 Mengatur nilai
Bom diatur dan sekarang kami memiliki semua data untuk mengatur nilai numerik di semua sel yang membutuhkannya.
Biarkan saya mengingatkan Anda bahwa menurut aturan pencari ranjau, sel harus memiliki nomor yang sesuai dengan jumlah bom yang terletak di sebelah sel ini. Berdasarkan aturan ini, kami menulis pseudocode yang sesuai.
Di kelas
Board
, buat metode baru dan terjemahkan pseudocode yang ditentukan ke dalam kode nyata:
Mari kita lihat antarmuka mana yang kita gunakan yang tidak diimplementasikan. Anda perlu menambahkan metode
getClosestFields
untuk mendapatkan sel tetangga.
Bagaimana cara mengidentifikasi sel tetangga?
Sebagai contoh, perhatikan setiap sel papan yang tidak ada di tepi, yaitu, tidak di baris yang ekstrim dan tidak di kolom yang ekstrim. Sel-sel tersebut memiliki jumlah tetangga maksimum: 1 di atas, 1 di bawah, 3 di sebelah kiri dan 3 di sebelah kanan (termasuk sel di diagonal).
Dengan demikian, dalam setiap sel tetangga, indikator
_row
dan
_col
tidak berbeda lebih dari 1. Ini berarti bahwa kita dapat menentukan terlebih dahulu perbedaan antara parameter
_row
dan
_col
dengan bidang saat ini. Tambahkan konstanta di awal file ke deskripsi kelas:
Dan sekarang kita bisa menambahkan metode yang hilang, di mana kita akan mengulang melalui array ini:
Jangan lupa untuk memeriksa variabel
field
pada setiap iterasi, karena tidak semua sel di papan memiliki 8 tetangga. Misalnya, sel kiri atas tidak akan memiliki tetangga di sebelah kirinya, dan seterusnya.
Tetap menerapkan metode
getField
dan menambahkan semua panggilan yang diperlukan ke metode
_create
di kelas
Board
4. Menangani input event
4.1 Pelacakan peristiwa klik mouse
Saat ini, papan benar-benar diinisialisasi, memiliki bom dan ada sel dengan angka, tetapi semuanya saat ini ditutup dan tidak ada cara untuk membukanya. Kami akan memperbaiki ini dan menerapkan pembukaan sel dengan mengklik tombol kiri mouse.Pertama, kita perlu melacak klik ini. Di kelas, FieldView
tambahkan _create
kode berikut ke bagian paling akhir metode :
Di phaser, Anda dapat berlangganan objek dari namespace untuk berbagai aktivitas Phaser.GameObjects
. Secara khusus, kami akan berlangganan acara klik ( pointerdown
) cetakan dari sprite itu sendiri, yaitu objek dari kelas yang FieldView
diwarisi Phaser.GameObjects.Sprite
.Tetapi sebelum kita melakukan ini, kita harus secara eksplisit menunjukkan bahwa sprite berpotensi interaktif, yaitu, Anda umumnya perlu mendengarkan input pengguna di dalamnya. Anda perlu melakukan ini dengan memanggil metode setInteractive
tanpa parameter pada sprite itu sendiri, yang kami lakukan pada contoh di atas.Sekarang sprite telah menjadi interaktif, kita akan kembali ke kelas Board
di tempat objek model baru dibuat Field
, yaitu metode _createFields
dan mendaftarkan panggilan balik untuk peristiwa input untuk tampilan:
Setelah kami menetapkan bahwa dengan mengklik sprite kami ingin menjalankan metode _onFieldClick
, kami perlu mengimplementasikannya. Tetapi kami akan menghapus logika pemrosesan klik dari kelas Board
. Ada pendapat bahwa lebih baik memproses model tergantung pada input dan karenanya mengubah datanya dalam pengontrol terpisah, kesamaan yang kami miliki adalah kelas adegan permainan GameScene
. Jadi, kita perlu meneruskan acara klik lebih lanjut, dari kelas Board
ke adegan itu sendiri. Jadi kita akan melakukan:
Di sini kita tidak hanya melempar acara klik seperti sebelumnya, tetapi juga menentukan klik mana yang sebenarnya. Ini akan berguna di masa depan, ketika di kelas adegan kami akan memproses setiap opsi secara berbeda. Tentu saja, akan mungkin untuk mengirim acara klik sebagaimana adanya, tetapi kami akan menyederhanakan kode adegan, meninggalkan beberapa logika mengenai acara itu sendiri di kelas Field
.Nah, sekarang mari kita kembali ke kelas adegan permainan GameScene
dan menambahkan _create
kode di akhir metode yang melacak peristiwa klik pada sel:
4.2. Pemrosesan klik kiri
Kami terus menerapkan pemrosesan peristiwa klik mouse. Dan mulailah dengan membuka sel. Sel harus dibuka dengan menekan tombol kiri. Dan sebelum kita memulai pemrograman, mari kita menyuarakan kondisi yang harus dipenuhi:- ketika mengklik sel yang tertutup, itu harus dibuka
- jika ada tambang di sel terbuka - permainan hilang
- jika tidak ada ranjau atau nilai di sel terbuka, maka min tidak di sel tetangga, dalam hal ini Anda perlu membuka semua sel tetangga dan terus melakukannya sampai nilai muncul di sel terbuka
- ketika Anda mengklik pada sel terbuka, Anda harus memeriksa apakah semua bendera diatur dengan benar dan jika demikian, maka akhiri permainan dengan kemenangan
Dan sekarang, untuk menyederhanakan pemahaman fungsionalitas yang diperlukan, kami menerjemahkan logika di atas ke dalam pseudo-code:
Sekarang kami memiliki pemahaman tentang apa yang perlu diprogram. Kami menerapkan metode ini _onFieldClickLeft
:
Dan kemudian, seperti biasa, kita akan menyelesaikan kelas-kelas Field
dan Board
mengimplementasikan metode-metode di dalamnya yang kita panggil di handler.Kami menunjukkan 3 kemungkinan status sel dalam enumerasi States
, menambahkan bidang, _state
dan menerapkan pengambil untuk setiap kemungkinan kondisi:
Sekarang kita memiliki status yang menunjukkan apakah sel ditutup atau tidak, kita dapat menambahkan metode open
yang akan mengubah status:
Setiap perubahan dalam keadaan model harus memicu peristiwa yang melaporkan hal ini. Oleh karena itu, kami memperkenalkan metode pribadi tambahan _setState
di mana seluruh logika perubahan negara akan diimplementasikan. Metode ini akan dipanggil dalam semua metode publik model, yang harus mengubah kondisinya.Tambahkan bendera Boolean _exploded
untuk secara eksplisit menunjukkan objek bidang yang diledakkan:
Sekarang buka kelas Board
dan terapkan metode di dalamnya openClosestFields
. Metode ini bersifat rekursif dan tugasnya adalah untuk membuka semua bidang tetangga yang kosong relatif terhadap sel yang diterima dalam parameter.Algoritma akan menjadi sebagai berikut: :
Dan kali ini kita sudah memiliki semua antarmuka yang diperlukan untuk implementasi penuh dari metode ini:
Tambahkan rajin completed
ke kelas Board
untuk menunjukkan penempatan bendera yang benar di papan tulis. Bagaimana kita dapat menentukan apakah suatu papan telah berhasil dibersihkan? Jumlah bidang yang ditandai dengan benar harus sama dengan jumlah total bom di papan.
Metode ini memfilter array _fields
dengan pengambil completed
, yang harus menunjukkan validitas tanda bidang. Jika panjang array yang difilter (di mana hanya bidang yang ditandai dengan benar jatuh, yang pengambil completed
sudah bertanggung jawab untuk kelas Field
) sama dengan nilai bidang _bombs
(yaitu, jumlah bom di papan), maka kami kembali true
, dengan kata lain, kami menganggap permainan dimenangkan.Kami juga tidak keberatan dengan kesempatan untuk membuka seluruh papan dengan satu panggilan, apa yang harus kami lakukan di akhir level. Kami juga akan menambahkan fitur ini ke kelas Board
:
Masih menambahkan getter completed
ke kelas itu sendiri Field
. Dalam kasus apa bidang akan dianggap berhasil dibersihkan? Jika ditambang dan ditandai. Kedua getter yang diperlukan sudah ada di sana dan kita dapat menambahkan metode ini:
Untuk menyelesaikan pemrosesan klik kiri mouse, kami akan membuat metode _onGameOver
di mana kami menonaktifkan pelacakan peristiwa papan dan menunjukkan kepada pemain seluruh papan. Nanti kita juga akan menambahkan kode rendering laporan penyelesaian status berdasarkan parameter status
.
4.3 Tampilan Bidang
Sebelum Anda mulai memproses klik kanan, kami akan belajar cara menggambar ulang sel yang baru dibuka.Sebelumnya di kelas, Field
kami mengembangkan sebuah metode _setState
yang menembakkan suatu peristiwa change
ketika keadaan model berubah. Kami akan menggunakan ini dan di kelas kami akan FieldView
melacak acara ini:
Kami secara khusus membuat metode perantara sebagai _onStateChange
panggilan balik acara perubahan model. Di masa depan, kita perlu memeriksa bagaimana model itu diubah untuk memahami apakah itu perlu dilakukan _render
.Untuk menunjukkan sprite sel saat ini dalam keadaan baru, Anda perlu mengubah bingkainya. Karena kami memuat atlas sebagai aset, kami dapat memanggil metode setFrame
untuk mengubah kerangka saat ini ke yang baru.Untuk mendapatkan frame dalam satu baris, kami dengan cerdik menggunakan pengambil _frameName
, yang sekarang perlu diimplementasikan. Pertama, kami menjelaskan semua nilai yang mungkin diambil oleh bingkai sel.Kami mendapat deskripsi semua negara bagian dan sudah memiliki semua metode model ini, terima kasih yang dapat diperoleh negara bagian ini. Mari kita dapatkan konfigurasi kecil di awal file:
Kunci dalam objek ini akan menjadi nilai-nilai frame, dan nilai-nilai kunci ini adalah panggilan balik yang mengembalikan hasil Boolean. Berdasarkan konfigurasi ini, kita dapat mengembangkan metode untuk mendapatkan bingkai yang diinginkan (yaitu, kunci dari konfigurasi):
Dengan demikian, dengan penghitungan sederhana dalam satu lingkaran, kita pergi melalui semua kunci dari objek konfigurasi dan memanggil setiap panggilan balik secara bergantian. Fungsi yang mengembalikan kita terlebih dahulu akan true
menunjukkan bahwa kunci key
pada iterasi saat ini adalah bingkai yang benar untuk keadaan model saat ini.Jika tidak ada kunci yang cocok, maka untuk keadaan default kami akan mempertimbangkan bidang terbuka dengan nilai _value
, karena States
kami tidak menetapkan negara ini di konfigurasi .Sekarang kita dapat sepenuhnya menguji klik kiri pada bidang papan dan memeriksa bagaimana sel-sel terbuka dan apa yang ditampilkan setelah membukanya.4.4 Pemrosesan klik kanan
Seperti dalam kasus membuat penangan klik kiri, pertama-tama kita mendefinisikan dengan jelas fungsionalitas yang diharapkan. Dengan mengklik kanan, kita harus menandai sel yang dipilih dengan bendera. Tetapi ada beberapa kondisi tertentu.- Hanya bidang tertutup yang saat ini tidak ditandai yang dapat ditandai
- Jika bidang dicentang, maka klik kanan lagi harus menghapus bendera dari bidang
- Saat mengatur / menghapus bendera, perlu memperbarui jumlah bendera yang tersedia di tingkat dan menampilkan teks dengan nomor saat ini
Menerjemahkan kondisi ini ke dalam pseudo-code, kami mendapatkan baris komentar berikut:
Sekarang kita dapat menerjemahkan algoritme ini menjadi panggilan ke metode yang kita butuhkan, bahkan jika belum dikembangkan:
Di sini kami juga memulai bidang baru _flags
, yang pada awal tingkat permainan sama dengan jumlah bom di papan, karena di awal permainan tidak ada satu bendera pun yang telah ditetapkan. Bidang ini dipaksa untuk diperbarui dengan setiap klik kanan, karena dalam kasus ini bendera ditambahkan atau dihapus dari papan. Tambahkan Board
getter ke kelas countMarked
:
Mengatur dan menghapus bendera adalah perubahan dalam keadaan model Field
, jadi kami menerapkan metode ini di kelas yang sesuai dengan metode yang sama open
:
Biarkan saya mengingatkan Anda bahwa itu _setState
akan memicu peristiwa change
yang dilacak dalam tampilan dan, dengan demikian, sprite akan digambar ulang secara otomatis saat ini ketika model berubah.Saat menguji fungsionalitas yang dikembangkan, Anda pasti akan menemukan bahwa setiap kali Anda mengklik tombol mouse kanan, menu konteks terbuka. Tambahkan kode yang menonaktifkan perilaku ini ke konstruktor adegan permainan:
4.5 Objek GameSceneView
Untuk menampilkan UI pada adegan game, kami akan membuat kelas GameSceneView
dan menempatkannya di src/scripts/views/GameSceneView.ts
.Dalam hal ini, kami akan bertindak dengan cara yang berbeda dari penciptaan FieldView
dan tidak akan membuat kelas ini sebagai cetakan dan pewaris GameObjects
.Dalam hal ini, kita perlu menampilkan elemen-elemen berikut dari tampilan adegan:- teks dalam jumlah bendera
- tombol keluar
- Pesan status penyelesaian permainan (menang / kalah)
Mari kita buat setiap elemen UI bidang terpisah di kelas GameSceneView
.Kami akan menyiapkan rintisan. enum Styles { Color = '#008080', Font = 'Arial' } enum Texts { Flags = 'FLAGS: ', Exit = 'EXIT', Success = 'YOU WIN!', Failure = 'YOU LOOSE' }; export class GameSceneView { private _scene: Phaser.Scene = null; private _style: {font: string, fill: string}; constructor(scene: Phaser.Scene) { this._scene = scene; this._style = {font: `28px ${Styles.Font}`, fill: Styles.Color}; this._create(); } private _create(): void { } public render() { } }
Tambahkan teks dengan jumlah bendera.
Kode ini akan menempatkan teks yang kita butuhkan dalam posisi menjorok 50px dari sisi atas dan kiri dan mengaturnya ke gaya yang ditentukan. Selain itu, metode ini setOrigin
mengatur titik pivot teks ke koordinat (0, 1). Ini berarti bahwa teks akan sejajar dengan batas kirinya.Tambahkan pesan status.
Kami menempatkan teks status di tengah layar dan menyelaraskannya dengan tengah garis dengan memanggil setOrigin
dengan parameter 0,5 untuk koordinat x. Selain itu, secara default, teks ini perlu disembunyikan, karena kami hanya akan menampilkannya setelah permainan selesai.Buat tombol keluar, yang intinya juga merupakan objek teks.
Kami menempatkan tombol di sudut kanan atas layar dan menggunakannya lagi setOrigin
untuk menyelaraskan teks kali ini dengan tepi kanannya. Kami membuat tombol interaktif dan menambahkan panggilan balik ke acara klik, yang mengirim pemain ke adegan awal. Dengan demikian, kami memberikan pemain kesempatan untuk keluar dari level kapan saja.Tetap mengembangkan metode render
untuk memperbarui semua elemen UI dengan benar dan menambahkan panggilan ke semua metode yang dibuat di _create
.
Bergantung pada properti yang diteruskan dalam parameter, kami memperbarui UI, menampilkan perubahan yang diperlukan.Buat representasi dalam adegan permainan di kelas GameScene dan tulis panggilan ke metode _render di mana pun dibutuhkan dengan makna:
5. Animasi
Penggemar macam apa yang menciptakan game, bahkan sesederhana milik kita, jika tidak ada animasi di dalamnya ?! Selain itu, sejak kami mulai mempelajari phaser, mari berkenalan dengan fitur paling dasar dari animasi dan pertimbangkan fungsi si kembar. Kembar diimplementasikan dalam kerangka itu sendiri dan tidak ada perpustakaan pihak ketiga yang diperlukan.Tambahkan 2 animasi ke permainan: mengisi papan dengan sel di awal dan membalik sel di pembukaan. Mari kita mulai dengan yang pertama.5.1 Animasi isi papan
Kami memastikan bahwa semua sel papan terbang ke tempatnya dari tepi kiri atas layar. Saat memulai level permainan, kita perlu menggeser semua sel ke sudut kiri atas layar dan untuk setiap sel untuk memulai animasi gerakan ke koordinat yang sesuai.Di kelas, FiledView
tambahkan _create
panggilan ke akhir metode _animateShow
:
B kita menerapkan metode baru yang kita butuhkan. Di dalamnya, seperti yang kami sepakati di atas, perlu melakukan 2 hal:- gerakkan sel di belakang sudut kiri atas sehingga tidak terlihat di layar
- mulai gerakan kembar ke koordinat yang diinginkan dengan penundaan yang benar
Karena sudut kiri atas kanvas memiliki koordinat (0, 0), maka jika kita mengatur sel ke koordinat sama dengan nilai negatifnya lebar dan tinggi, ini akan menempatkan sel di belakang sudut kiri atas dan bersembunyi dari layar. Jadi, kami menyelesaikan tugas pertama kami.Anda akan mencapai tujuan kedua dengan memanggil metode _moveTo
.
Untuk membuat animasi, kami menggunakan properti scene tweens
. Dalam metodenya, add
kami melewatkan objek konfigurasi dengan pengaturan:- Properti di
targets
sini harus berisi sebagai nilai objek-objek game yang ingin Anda terapkan efek animasi. Dalam kasus kami, ini adalah tautan this
ke objek saat ini, karena ini adalah cetakan dari sprite. - Parameter kedua dan ketiga kami melewati koordinat tujuan.
- Properti
duration
ini bertanggung jawab atas durasi animasi, dalam kasus kami - 600ms. - Parameter
ease
dan easeParams
atur fungsi pelonggaran. - Di bidang penundaan, kami mengganti nilai dari argumen kedua, yang dihasilkan untuk setiap sel individu, dengan mempertimbangkan posisinya di papan tulis. Ini dilakukan agar sel-sel tidak terbang bersamaan. Sebaliknya, setiap sel akan muncul dengan sedikit penundaan relatif terhadap yang sebelumnya.
- Akhirnya,
onComplete
kami melakukan panggilan balik di properti , yang akan dipanggil pada akhir tindakan tween.
Adalah masuk akal untuk membungkus kembaran itu dalam sebuah janji sehingga di masa depan itu akan dapat dengan indah mem-dock animasi yang berbeda, jadi kami akan menempatkan pemanggilan fungsi dalam panggilan balik yang resolve
menunjukkan keberhasilan pelaksanaan animasi.5.2 Animasi Flip Cell
Akan lebih bagus jika, ketika sel dibuka, efek pembalikannya direproduksi. Bagaimana kita bisa mencapai ini?Membuka sel saat ini dilakukan dengan mengubah bingkai ketika metode dipanggil _render
dalam tampilan. Jika kita memeriksa keadaan model dalam metode ini, kita akan melihat apakah sel itu terbuka. Jika sel terbuka, mulai animasi bukannya langsung menampilkan bingkai pembalikan baru.
Untuk mendapatkan efek yang diinginkan, kami akan menggunakan transformasi sprite melalui properti scale
. Jika kita skala sprite sepanjang sumbu x
ke nol dari waktu ke waktu , itu akhirnya akan menyusut, menghubungkan sisi kiri dan kanan. Dan sebaliknya, jika Anda menskalakan sprite sepanjang sumbu x
dari nol hingga lebar penuh, kami merentangkannya ke ukuran penuhnya. Kami menerapkan logika ini dalam metode ini _animateFlip
.
Dengan analogi dengan metode ini, kami _moveTo
menerapkan _scaleTo
:
Dalam metode ini, sebagai parameter, kita mengambil nilai skala, yang akan kita gunakan untuk mengubah ukuran sprite di kedua arah dan meneruskannya sebagai parameter kedua ke objek konfigurasi animasi. Semua parameter konfigurasi lain sudah akrab bagi kita dari animasi sebelumnya.Sekarang kita akan memulai proyek untuk pengujian dan setelah debugging kita akan mempertimbangkan permainan kita selesai, dan tugas pengujian selesai! :)
Saya dengan tulus berterima kasih kepada semua orang karena telah mencapai momen ini dengan saya!Kesimpulan
Kolega, saya akan sangat senang jika materi yang disajikan dalam artikel ini bermanfaat bagi Anda dan Anda dapat menggunakan pendekatan ini atau yang dijelaskan dalam proyek Anda sendiri. Anda selalu dapat menoleh kepada saya dengan pertanyaan, baik pada artikel ini, dan pada pemrograman phaser atau bekerja di gamedev secara umum. Saya menyambut komunikasi dan akan senang untuk berkenalan dan bertukar pengalaman baru!Dan saya punya pertanyaan untuk Anda sekarang. Karena saya membuat video tutorial tentang pengembangan game, saya secara alami mengumpulkan selusin game kecil ini. Setiap game membuka framework dengan caranya sendiri. Misalnya, dalam game ini kami menyentuh topik tentang si kembar, tetapi ada banyak fitur lain, seperti fisika, tilemap, tulang belakang, dll.Dalam hal ini, pertanyaannya adalah, apakah Anda menyukai artikel ini dan, jika demikian, apakah Anda tertarik untuk terus membaca artikel seperti ini, tetapi tentang game klasik kecil lainnya? Jika jawabannya ya, saya dengan senang hati akan menerjemahkan materi tutorial video saya ke dalam format teks dan terus menerbitkan manual baru dari waktu ke waktu, tetapi untuk game lain. Saya membawa survei yang sesuai.Terima kasih atas perhatian Anda! Saya akan dengan senang hati memberikan tanggapan dan melihat Anda segera!