CLICK HERE TO READ THE ENGLISH VERSION
Membuat Game Sederhana dengan Blazor Web Assembly
Mengembangkan game “Monkey Counting” dengan Blazor Web Assembly .NET 8
Saya membuat game sederhana “Monkey Counting” untuk anak saya yang berusia dua tahun:
Website tersebut dikembangan dengan Blazor Web Assembly dan di-host Azure Static Apps. Ini adalah bagian dari perjalanan saya belajar Blazor dengan cara praktek langsung.
Artikel kali ini akan menjelaskan bagaimana cara membuat game “Monkey Counting” dari dasar.
Mari kita mulai.
Membuat Project Blazor Web Assembly Baru
Buka Visual Studio 2022, kemudian pilih project template yang bernama Blazor Web Assembly Standalone App.
Atur nama project menjadi BlazorCountingGame atau dengan nama apapun.
Atur framework menjadi .NET 8.0, authentication type menjadi None, dan pilih Configure for HTTPS.
Click tombol [Create] untuk membuat project baru. Struktur project yang terbentuk akan terlihat seperti ini:
Menambahkan Bootstrap CSS dari CDN
Selanjutkan kita akan menggunakan bootstrap CSS untuk styling. Caranya buka file index.html yang berada di dalam folder wwwroot, kemudian tambahkan link bootstrap CSS di bawah ini ke dalam tag head:
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css"
integrity="sha384-T3c6CoIi6uLrA9TneNEoa7RxnatzjcDSCmG1MXxSR1GAsXEV/Dwwykc2MPK8M2HN" crossorigin="anonymous">
Saya juga mengubah isi file index.html agar lebih sederhana. Isi lengkap dari file tersebut adalah seperti berikut ini:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Monkey Count - juldhais.net</title>
<base href="/" />
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css"
integrity="sha384-T3c6CoIi6uLrA9TneNEoa7RxnatzjcDSCmG1MXxSR1GAsXEV/Dwwykc2MPK8M2HN" crossorigin="anonymous">
<link rel="stylesheet" href="css/app.css" />
</head>
<body>
<div id="app">
Loading...
</div>
<script src="_framework/blazor.webassembly.js"></script>
</body>
</html>
Random Number untuk Jumlah Monyet
Buka file Home.razor yang terdapat pada folder Pages. Di sinilah kita akan menulis code untuk game yang akan kita buat:
@page "/"
<PageTitle>Monkey Count</PageTitle>
<div class="mx-4 my-4">
<div class="display-3 text-center">How many monkeys are there?</div>
<div>
<div class="row row-cols-3">
@for (int i = 0; i < count; i++)
{
<div class="col">🙉</div>
}
</div>
</div>
</div>
@code {
int count;
protected override void OnInitialized()
{
NextQuestion();
}
void NextQuestion()
{
// generate random number of monkeys
count = Random.Shared.Next(1, 10);
}
}
@page Directive
@page "/"
Directive ini membuat component menjadi halaman yang dapat diakses melalui URL root (“/”). Ini adalah routing endpoint dari aplikasi.
PageTitle Component
<PageTitle>Monkey Count</PageTitle>
Mengatur judul dari halaman web yang akan ditampilkan pada browser tab.
HTML Content
Element <div class="mx-4 my-4">
berfungsi sebagai main container dengan margin yang diatur dengan Bootstrap class (mx-4
untuk horizontal margin danmy-4
untuk vertical margin).
Struktur div
digunakan untuk membuat grid layout (menggunakan sistem grid Bootstrap dengan class row row-cols-3
) yang akan menampilkan emoji monyet. Jumlah emoji yang ditampilkan dihasilkan secara dinamis berdasarkan nilai dari variable count
.
@code Block
@code
block berisi code C# code yang menangani interactivy pada halaman:
count
adalah variable yang menyimpan nilai banyaknya emoji monyet yang akan ditampilkan.OnInitialized
adalah lifecycle method yang di dalamnya dipanggil methodNextQuestion
ketika component pertama kali diinisialisasi.NextQuestion
adalah method yang menghasilkan random number antara 1 sampai 9 dan menyimpannya ke dalam variablecount
.
Membuat dan Mengacak Pilihan Jawaban
Pemain harus memilih jumlah monyet yang benar dari tiga pilihan jawaban.
Kita perlu mengubah section code menjadi sebagai berikut:
@code {
int count;
List<int> choices = [];
protected override void OnInitialized()
{
NextQuestion();
}
void NextQuestion()
{
// generate random number
count = Random.Shared.Next(1, 10);
choices.Clear();
// add count to choices
choices.Add(count);
// add 2 more unique random number to choices
while (choices.Count < 3)
{
int choice = Random.Shared.Next(1, 9);
if (!choices.Contains(choice))
{
choices.Add(choice);
}
}
// shuffle the choices using Fisher-Yates algorithm
var n = choices.Count;
while (n > 1)
{
n--;
int k = Random.Shared.Next(n + 1);
int value = choices[k];
choices[k] = choices[n];
choices[n] = value;
}
}
}
Variable choices
adalah list of integer yang digunakan untuk menyimpan pilihan jawaban.
Perubahan pada method NextQuestion
adalah sebagai berikut:
- Fungsi
choices.Clear()
menghapus semua pilihan dari pertanyaan sebelumnya. - Fungsi
choices.Add(count)
memasukkan nilai variablecount
(jawaban yang benar) ke dalam listchoices
. - Looping
while
digunakan untuk memasukkan nilai random 1 sampai 9 ke dalam listchoices
hingga terdapat tiga jawaban. Pilihan dipastikan harus unique. - Terakhir, kita mengimplementasikan algoritma Fisher-Yates untuk mengacak isi dari list
choices
.
Berikutnya kita akan menampilan isi dari list choices
:
<div class="mx-4 my-4">
...
<div class="row">
@foreach (var choice in choices)
{
<div class="col-4 text-center border py-3">
@choice
</div>
}
</div>
</div>
Event Handler Ketika Jawaban Dipilih
Kita perlu membuat method CheckAnswer
untuk meng-handle event ketika pemain memilih jawaban. Jika jawaban yang dipilih benar, maka lanjut ke pertanyaan berikutnya.
@code {
...
void CheckAnswer(int choice)
{
if (choice == count)
{
NextQuestion();
}
}
}
Kita juga harus mengatur event callback @onclick
untuk memanggil method CheckAnswer
ketika pilihan di-click:
<div class="row">
@foreach (var choice in choices)
{
<div class="col-4 text-center border py-3" @onclick="(() => CheckAnswer(choice))">
@choice
</div>
}
</div>
Menambahkan Custom CSS dan Animation
Kita akan menambahkan custom CSS untuk membuat si monyet terlihat lebih besar, dan juga memainkan animasi ketika jawaban yang benar atau salah dipilih.
Custom CSS
Buka file app.css yang terdapat pada folder wwwroot/css, kemudian ganti isinya seperti berikut ini:
.monkey {
font-size: 60px;
text-align: center;
}
.choice {
font-size: 60px;
cursor: pointer;
}
@keyframes shake {
0%, 100% {
transform: translateX(0);
}
10%, 30%, 50%, 70%, 90% {
transform: translateX(-10px);
}
20%, 40%, 60%, 80% {
transform: translateX(10px);
}
}
.shake {
animation: shake 0.82s cubic-bezier(.36, .07, .19, .97) both;
color: darkred;
}
@keyframes jump {
0%, 100% {
transform: translateY(0);
}
10%, 30%, 50%, 70%, 90% {
transform: translateY(-10px);
}
20%, 40%, 60%, 80% {
transform: translateY(0);
}
}
.jump {
animation: jump 1s ease;
color: green;
}
Sekarang kita kembali ke file Home.razor, kemudian tambahkan css class .monkey
dan .choice
ke element yang sesuai:
<div style="height:330px">
<div class="row row-cols-3">
@for (int i = 0; i < count; i++)
{
<div class="col monkey">🙉</div>
}
</div>
</div>
<div class="row">
@foreach (var choice in choices)
{
<div class="col-4 text-center border py-3 choice" @onclick="(() => CheckAnswer(choice))">
@choice
</div>
}
</div>
Animation
Animasi “jump” akan dimainkan ketika pemain memilih jawaban yang benar. Jika jawabannya salah, maka animasi “shake” yang akan dimainkan.
Kita harus secara dinamis menambahkan kelas .shake
atau .jump
tergantung pada jawaban yang dipilih pemain. Dalam JavaScript, kita dapat dengan mudah melakukan hal tersebut dengan menggunakan kombinasi dari getElementById
dan classList.add
.
Namun dalam Blazor, mengakses DOM element secara langsung bukanlah standard approach. Blazor menggunakan component based architecture, di mana kita mengontrol UI element mengunakan state dan properties.
Kita perlu menambahkan dua buah variable animationClass
dan selectedChoice
agar animasi dapat dijalankan dengan sesuai.
@code {
...
string animationClass = "";
int? selectedChoice = null;
...
}
Kita juga perlu menambahkan method TriggerAnimation
dan GetAnimationClass
:
@code {
...
async Task TriggerAnimation(int choice)
{
// prevent overlapping animation
if (selectedChoice != null) return;
selectedChoice = choice;
animationClass = selectedChoice == count ? "jump" : "shake";
await Task.Delay(1500);
animationClass = "";
selectedChoice = null;
}
string GetAnimationClass(int choice)
{
return selectedChoice == choice ? animationClass : "";
}
}
Kita harus mengubah method CheckAnswer
untuk memanggil method TriggerAnimation
method dan juga memanggil StateHasChanged
untuk memberi tahu component kalau terjadi perubahan pada state-nya:
async void CheckAnswer(int choice)
{
await TriggerAnimation(choice);
if (choice == count)
{
NextQuestion();
}
StateHasChanged();
}
Agar animasi hanya dimainkan pada jawaban yang dipilih, kita perlu menggunakan method GetAnimationClass(choice)
di dalam looping@foreach
yang me-render pilihan jawaban:
<div class="row">
@foreach (var choice in choices)
{
<div class="col-4 text-center border py-3 choice" @onclick="(() => CheckAnswer(choice))">
<div class="@GetAnimationClass(choice)">
@choice
</div>
</div>
}
</div>
Kita juga dapat menjalankan animasi pada gambar monyet dengan menambahkan @animationClass
ke monkey emoji:
<div style="height:330px">
<div class="row row-cols-3">
@for (int i = 0; i < count; i++)
{
<div class="col monkey @animationClass">🙉</div>
}
</div>
</div>
Berikut adalah source code lengkap dari artikel ini:
Terima kasih telah membaca. Semoga bermanfaat 👍