Membuat Game Tizen Mobile Menggunakan Framework Phaser

Toni Haryanto 18 Agustus 2016

Membuat Game Tizen Mobile Menggunakan Framework Phaser

Sistem Operasi Tizen yang mendukung penuh aplikasi berbasis web membuat proses pembuatan aplikasi mobile di Tizen menjadi lebih mudah, termasuk membuat game mobile. Ada banyak pilihan framework dan game engine yang bisa kita pelajari untuk membuat game mobile berbasis HTML5. Ada yang berbasis GUI seperti Construct 2 dan GameMaker, ada pula yang scripting, seperti Phaser, Pixi.js, ImpactJS, EaselJS, dan lain sebagainya. Pada tutorial ini kita akan menggunakan framework Phaser untuk membuat game puzzle Simon Says.

Skenario gamenya adalah Simon (nama aplikasinya) akan memilih angka dan kita harus mengikuti memilih angka yang dia pilih. Simon kemudian akan memilih angka lama dan menambah angka baru lalu kita harus memilih angka yang sama dengan urutan yang sama pula. Setiap kali kita berhasil memilih angka dengan urutan yang sama, Simon akan menambah angka sehingga urutannya menjadi semakin panjang. Pemain harus mengingat dengan baik urutan angka yang sudah dipilih Simon. Pemain dinyatakan kalah apabila salah dalam memilih urutan angka. Berikut adalah hasil akhir dari game yang akan kita buat (klik area game untuk resume):

Game ini saya adaptasi dari sample game Phaser yang dapat Kamu akses juga di tautan http://phaser.io/examples/v2/games/simon. Pada tutorial ini saya menyesuaikan kode program dan asset gambar agar mudah dijelaskan dalam tutorial.

Persiapan

Buat project Tizen baru di Tizen IDE dengan mengklik menu File > New > Tizen Web Project. Klik tab Sample dan pilih template Basic. Beri nama project lalu klik tombol Finish.

Unduh library Phaser dari tautan yang ada di halaman http://phaser.io/download/stable. Pilih yang min.js untuk production agar ukurannya lebih kecil, disamping karena kita tidak akan memodifikasi ataupun membuka file tersebut. Setelah  diunduh, simpan di dalam folder js/ pada project. Kemudian load di dalam file index.html di dalam tag <head>, sehingga kode html kita menjadi seperti berikut:

<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8" />
    <title>Simon</title>
    <script type="text/javascript" src="js/phaser.min.js"></script>
    <script type="text/javascript" src="js/main.js"></script>
    <style type="text/css">
        body {margin: 0;}
    </style>
</head>

Kemudian dalam tag <body>, tambahkan script berikut:

<script type="text/javascript">
var game = new Phaser.Game(window.innerWidth, window.innerHeight, Phaser.AUTO, '', 
    { preload: preload, create: create, update: update, render: render });
function preload()
{

}

function create()
{

}

function update()
{

}

function render()
{

}

Ini adalah struktur dasar program game di Phaser. Baris pertama adalah kode yang membangkitkan phaser dengan membuat instance dari objek Phaser.Game dan menyimpannya di dalam variabel ‘game’. Variabel untuk Phaser.Game ini tidak mesti bernama ‘game’. Kamu bisa menggunakan nama variabel lain. Tapi penggunaan nama variabel ‘game’ ini akan sering Kamu temui bila membuka contoh-contoh game yang ada di website Phaser.

Dua parameter pertama adalah lebar dan tinggi dari elemen canvas yang akan dibuat oleh Phaser. Pada contoh di atas, kita menggunakan ukuran lebar dan tinggi dari jendela browser, dengan menggunakan kode window.innerWidth dan window.innerHeight. Sehingga ukuran canvas game akan selalu menggikuti ukuran jendela browser. Perhatikan bahwa pada device Tizen, kode window.innerWidth dan window.innerHeight tidak mengembalikan nilai yang tepat, oleh karenanya gunakan screen.width dan screen.height.

Parameter ketiga adalah rendering context, dapat diisi dengan konstanta Phaser.CANVAS, Phaser.WEBGL, atau Phaser.AUTO. Kita dianjurkan menggunakan Phaser.AUTO, karena ini akan membuat Phaser menggunakan WebGL ketika browser mendukung WebGL, dan bila tidak maka Phaser akan menggunakan Canvas.

Parameter keempat adalah string nama id untuk elemen canvas yang nantinya akan dibuat oleh Phaser. Parameter terakhir adalah objek berisi 4 buah referensi dari fungsi utama Phaser, yakni preload(), create(), update() dan render().

Load Asset

Kita punya satu buah file gambar angka-angka dari 0-9 seperti panel tombol telepon seperti ini: buttons

Kita akan memasukkan asset game kita ke dalam program. Simpan gambar di atas ke dalam folder images/ pada project, lalu coba masukkan kode program berikut ke dalam fungsi preload():

function preload() {
    game.load.image('number', "./images/buttons.png");
}

Method game.load.image() pada kode di atas berfungsi untuk memuat asset gambar kita ke dalam program dan memberinya label ‘number’. Kemudian pada fungsi create(), tambahkan kode berikut:

function create()
{
    var number = game.add.sprite(0,0,'number');
}

Method game.add.sprite() pada kode di atas berfungsi untuk membuat objek sprite dari asset berlabel ‘number’ yang sudah kita muat sebelumnya, dan menyimpannya di dalam variabel number. Parameter pertama dan kedua adalah posisi x dan y dimana kita akan menampilkan gambar tersebut. Bila kamu menjalankan aplikasi di web simulator, maka akan tampil seperti berikut:

1

Menggunakan Spritesheet

Baiklah, kode di atas sebenarnya hanya untuk memberikan gambaran bagaimana Phaser menampilkan asset gambar di dalam aplikasi. Pada scenario game kita, kita ingin agar setiap tombol angka berdiri sendiri dan masing-masing dapat ditekan tanpa mempengaruhi tombol yang lain. Untuk itu kita harus memecah setiap angka agar menjadi objek tersendiri. Method game.load.image() hanya memuat satu buah gambar sebagai gambar utuh. Sedangkan untuk membagi gambar dengan ukuran tertentu kita menggunakan method game.load.spritesheet(). Ganti method yang ada ada di dalam fungsi preload() menjadi seperti berikut:
function preload() {
    game.load.spritesheet('number', "./images/buttons.png", 60, 60);
}
Jalankan lagi aplikasi. Apa yang terjadi? Yups, aplikasi menampilkan hanya angka 1 saja. Method game.load.spritesheet() menerima dua parameter pertama seperti method game.load.image() yang sebelumnya kita gunakan. Selain itu, ada 2 parameter lagi yakni ukuran lebar dan tinggi dari setiap angka pada gambar, yakni 60 x 60px. Method spritesheet() akan otomatis memotong-motong gambar seperti kita memotong kue, masing-masing dengan ukuran 60x60, ke samping dan ke bawah, kemudian setiap potongan diberi indeks dari 0 – n sebanyak potongan yang ada. Gambar angka kita berisi 3 kolom dan 4 baris angka. Kita akan menyimpan setiap angka ke dalam objek tersendiri.

Buat variabel global di luar fungsi create():

var column;
var numpad;

Hapus kode method yang ada di dalam fungsi create() sebelumnya, lalu ganti dengan kode berikut:

    // bagi lebar canvas menjadi 4 ukuran kolom
    column = game.world.width/4;

    // buat grup objek untuk angka
    numpad = game.add.group();

    // siapkan variabel untuk menyimpan objek angka
    var number;

    // siapkan variabel untuk mengacu indeks angka
    var k = 0;

    // iterasi baris
    for (var i = 1; i <= 4; i++)
    {
        // iterasi kolom
        for (var j = 1; j <= 3; j++)
        {
            // buat objek angka berdasarkan indeksnya
            number = numpad.create(column * j, column * i, 'number', k);

            // set anchor point ke tengah gambar
            number.anchor.setTo(.5, .5);

            // naikkan indeks
            k++;
        }
    }

Jalankan aplikasi, dan sekarang angka-angka akan berada di posisi yang lebih enak dilihat.

2

Pada kode di atas, kita mengambil ukuran dari lebar jendela dibagi 4 untuk menjadi patokan penempatan kolom dan baris dari angka-angka. Kemudian kita melakukan pengulangan sebanyak baris dan kolom untuk angka-angka kita, yakni 4 baris dan 3 kolom. Kita membuat objek angkake dalam variabel number dengan menggunakan method numpad.create(), dengan parameter pertama dan kedua adalah posisi penempatan gambar. Nilai lebar tiap kolom dan baris itulah yang menjadi koordinat posisi gambar angka-angka tersebut. Parameter ketiga adalah label untuk asset gambar yang akan digunakan, dan parameter keempat adalah indeks dari spritesheet gambar yang sudah kita potong-potong di awal. Kita juga memanggil method number.anchor.setTo(.5, .5); untuk memposisikan titik acuan perhitungan posisi koordinat di tengah gambar. Secara default titik acuan koordinat objek ada di 0,0 atau di kiri atas objek. Makanya kita pindahkan ke posisi 0.5,0.5 agar titiknya ada di tengah, sehingga bambar berada tepat di tengah garis pembagi. Untuk lebih jelasnya lihat ilustrasi berikut:

4

Menambahkan Input Handler

Sampai sini game kita belum bisa diberi interaksi apapun. Kita akan mengaktifkan objek number agar dapat menerima input klik/tekan. Secara default semua objek tidak dapat menerima input apapun hingga ia diaktifkan. Untuk mengaktifkannya kita set properti inputEnabled menjadi true dan menjalankan input handler untuk objek dengan menggunakan method input.start(). Lebih jelasnya, tambahkan dua baris berikut sebelum kode k++; di dalam perulangan:
number.inputEnabled = true;
number.input.start(0, true);
Kita akan membuat efek tekan, dengan mengeset alpha pada semua angka menjadi 0.25 dan mengeset alpha menjadi 1 pada angka yang ditekan. Untuk itu tambahkan kode berikut sebelum kode k++; pada perulangan:
numpad.getAt(k).alpha = .25;
number.events.onInputDown.add(select);
number.events.onInputUp.add(release);
Kita menambahkan event listener ketika angka diklik/tekan dengan memanggil fungsi callback ‘select’ dan ketika angka dilepas dengan memanggil fungsi callback ‘release’. Untuk itu buat fungsi select() dan release() seperti kode berikut di bawah fungsi create():
function select(number, pointer)
{
    number.alpha = 1;
}

function release(number, pointer) {     number.alpha = .25; }

Jalankan aplikasi, dan Kamu akan melihat angkanya seolah-olah menyala ketika ditekan, dan kembali redup ketika dilepas.

Membuat Countdown Aba-aba

Kita tidak ingin begitu pemain menjalankan game, game langsung memilih angka begitu saja. Kita ingin agar pemain mempersiapkan diri terlebih dahulu supaya permainan tidak terlewat. Kita akan membuat hitung mundur supaya pemain bersiap-siap menjelang permainan.

Deklarasikan variabel commandText sebelum fungsi create(). Kemudian tambahkan kode berikut di bagian akhir fungsi create():

commandText = game.add.text(game.world.width/2, game.world.height - 120, "Get ready in 5", 
              {fontSize: '30px', fill: '#fff'});
commandText.anchor.setTo(.5, .5);
getReady(5);

Kemudian buat fungsi getReady() di bawah fungsi create():

function getReady(count)
{
    commandText.text = "Get ready in " + count;
    intro = true;
    if(count > 0)
        setTimeout(function(){getReady(--count);}, 1000);
}

Kita membuat objek teks countdown berwarna putih di bagian bawah layar. Kemudian mengupdate teksnya dengan hitungan mundur menggunakan fungsi getReady() yang kita buat. Fungsi ini rekursif, artinya dia memanggil dirinya sendiri selama variabel count di dalam parameter lebih dari 0.

Mengatur Urutan Angka

Sekarang kita akan mempersiapkan angka yang akan dipilih oleh Simon supaya diikuti oleh pemain. Yup, kita akan mempersiapkan urutannya sejak awal. Untuk itu tulis kode berikut di bagian akhir fungsi create():
setUpSequence();
setTimeout(function(){simonSequence(); intro = false;}, 6000);
Kemudian buat dua fungsi setUpSequence() dan simonSequence() seperti yang dipanggil pada kode di atas:
function setUpSequence()
{
    for (var i = 0; i < totalSequence; i++)
    {
        sequence = game.rnd.integerInRange(0,11);
        sequenceList.push(sequence);
    }
}

function simonSequence() {     simonSays = true;     litSquare = sequenceList[currentCount];     numpad.getAt(litSquare).alpha = 1;     timeCheck = game.time.now;     currentCount++; }

Kemudian tambahkan juga kode ini di dalam fungsi update():

function update()
{
    if (simonSays)
    {
        if (game.time.now - timeCheck >700-N*40)
        {
            numpad.getAt(litSquare).alpha = .25;
            game.paused = true;

            setTimeout(function()
            {
                if ( currentCount< N)
                {
                    game.paused = false;
                    simonSequence();
                }
                else
                {
                    simonSays = false;
                    game.paused = false;
                }
            }, 400 - N * 20);
        }
    }
}

Ada beberapa variabel global yang harus kita deklarasikan sehubungan dengan dipakainya di dalam kedua fungsi ini, yakni:

var totalSequence = 16;
var sequenceList = [];
var simonSays = false;
var litSquare;
var currentCount = 0;
var timeCheck;
var N = 1;

Pertama-tama pada fungsi setUpSequence() kita siapkan urutan angka acaknya sebanyak 16 digit dan simpan di dalam variabel array sequenceList. Kemudian kita panggil fungsi simonSequence() untuk memulai menampilkan angka pilihan tersebut. Bila Kamu jalankan aplikasinya sekarang, maka game akan menghitung mundur untuk persiapan, lalu menyalakan satu angka acak pertama, lalu mati. Itu terjadi karena memang kita buat supaya pertama kali dia mulai menampilkan 1 angka saja.

Setelah ini kita ingin agar pemain menekan angka yang dipilih Simon. Bila pilihannya benar, maka Simon akan menambah satu angka lagi dari yang sudah dipilih pertama tadi, begitu seterusnya. Untuk itu kita harus membuat fungsi untuk menerima angka pilihan pemain.

function playerSequence(selected)
{
    correctSquare = sequenceList[userCount];
    userCount++;
    sequence = numpad.getIndex(selected);

    if (sequence == correctSquare)
    {
        if (userCount == N)
        {
            if (N == totalSequence)
            {
                winner = true;
                setTimeout(function(){restart();}, 3000);
            }
            else
            {
                userCount = 0;
                currentCount = 0;
                N++;
                simonSays = true;
            }
        }
    }
    else
    {
        loser = true;
        setTimeout(function(){restart();}, 3000);
    }
}

Kemudian panggil fungsi ini setiap kali angka ditekan, yakni pada fungsi release():

function release(number, pointer)
{
    number.alpha = .25;
    playerSequence(number);
}

Fungsi playerSequence() akan mencatat setiap angka yang ditekan, dan dicek kesamaan urutannya dengan yang diminta oleh Simon. Bila satu angka saja salah, maka catat bahwa pemain sudah kalah. Pada fungsi ini diperlukan beberapa variabel global:

var userCount = 0;
var winner;
var loser;

Sampai sini, bila Kamu menjalankan gamenya, maka kamu sudah bisa memainkan game sesuai dengan skenario!

Menampilkan Pesan Aksi

Kita ingin agar game kita dapat dipahami oleh pemain tanpa harus dijelaskan terlebih dahulu. Oleh karena itu panduan interaksi dan pesan aksi pada game sangat diperlukan. Tambahkan kode berikut ke dalam fungsi render():
function render()
{
    if (!intro)
    {
        if (simonSays)
        {
            commandText.text = 'Simon says';
            commandText.fill = 'orange';
        }
        else
        {
            commandText.text = 'Your turn';
            commandText.fill = 'green';
        }
    }

    if (winner)     {         commandText.text = 'You win!';         commandText.fill = 'green';     }     else if (loser)     {         commandText.text = 'You lose!';         commandText.fill = 'red';     } }

Fungsi render ini akan menampilkan caption “Simon says” ketika Simon memilih angka, menampilkan caption “Your turn” untuk memberitahu bahwa saatnya bagi pemain untuk memilih angka, dan caption “You lose” bila pemain salah memilih urutan angka. Oh ya, game juga akan menampilkan caption “You win!” bila pemain bisa menyelesaikan ke-16 urutan angka tersebut. Apakah Kamu bisa?? :D

5

Penutup

Tentu saja tutorial ini hanya menjelaskan inti dari alur logika pemrogramannya. Kamu masih harus menambahkan fungsi untuk menahan pemain agar tidak dapat menekan tombol ketika waktu persiapan dan ketika Simon sedang memilih angka. Kamu juga masih harus membuat dan memanggil fungsi restart() ketika user sudah menang atau kalah dan ingin main lagi. Kamu juga dapat memperindah tampilan gamenya dengan warna-warna yang enak dilihat supaya pemain lebih betah bermain game ini.

Bila Kamu kesulitan mempelajari tutorial ini dari awal, Kamu bisa langsung merujuk pada kode jadinya yang bisa diunduh di tautan berikut ini. Selamat bereksplorasi! :D