Selasa, 23 Desember 2014

Greenfoot: Mengurangi Nyawa yang Berbentuk Koleksi Actor

Daftar Kelas pada Greenfoot
Tidak perlu berlama-lama, pos ini bertujuan:
  • Mengenalkan kepada Anda penggunaan kerangka koleksi (collection framework) di Java.
  • Mendemonstrasikan cara membuat dan menempatkan objek (Actor) di Greenfoot secara dinamis (melalui kode program).
  • Mendemonstrasikan mekanisme collision detection di Greenfoot.
Sebagai pengantar, pos ini menjelaskan tentang permainan (selanjutnya disebut game) sederhana yang saya buat di Greenfoot. Game ini terdiri atas 1 skenario (kelas Skenario) dan 3 aktor (AktorUtama [Kura-Kura], Nyawa [Pizza], dan Musuh [Mobil]). Untuk lebih jelasnya, lihat gambar Daftar Kelas pada Greenfoot. Kelas Nyawa tersebut nantinya akan digunakan sebagai objek nyawa Kura-Kura.


Listing 1: Kelas Nyawa

import greenfoot.*;

/**
 * Abstraksi nyawa
 * 
 * @author Nanang F. Rozi <nfrozy gmail.com>
 * @version 1.0.0
 */
public class Nyawa extends Actor
{
    private int lokasiX;
    private int lokasiY;
    
    public void act() 
    {
        // Add your action code here.
    }
    
    public void setLokasiX(int lokasiX) {
        this.lokasiX = lokasiX;
    }
    
    public int getLokasiX() {
        return lokasiX;
    }
    
    public void setLokasiY(int lokasiY) {
        this.lokasiY = lokasiY;
    }
    
    public int getLokasiY() {
        return lokasiY;
    }
}

Pada kelas Nyawa, saya menambahkan atribut lokasiX dan lokasiY yang digunakan untuk menyimpan posisi aktor Nyawa di sumbu x dan y tertentu.

Listing 2: Kelas Musuh

import greenfoot.*;

/**
 * Musuhnya (Mobil)
 * 
 * @author Nanang F. Rozi <nfrozy@gmail.com>
 * @version 1.0.0)
 */
public class Musuh extends Actor
{
    public void act() 
    {
        // Add your action code here.
    }    
}

Listing 3: Kelas Skenario

import greenfoot.*;
import java.util.ArrayList;  // import koleksi

/**
 * Skenario Utama
 * 
 * @author Nanang F. Rozi <nfrozy@gmail.com>
 * @version 1.0.0
 */
public class Skenario extends World
{   
    // Maksimum Nyawa yg akan ditampilkan
    private final int NYAWA_MAKS = 3;
    // Model bar yg berisi daftar nyawa (pizza)
    private static ArrayList nyawaBar = new ArrayList();
    
    /**
     * Constructor for objects of class Scene.
     * 
     */
    public Skenario()
    {    
        // Create new 600x400 cells with a cell size of 1x1 px
        super(600, 400, 1); 

        prepare();
    }

    /**
     * Prepare the world for the start of the program.
     */
    private void prepare()
    {
        // Menyiapkan nyawaBar
        prepareNyawaBar(40, 30);
        
        // Membuat objek AktorUtama
        AktorUtama aktorutama = new AktorUtama();
        addObject(aktorutama, 108, 303);
        
        // Membuat objek Musuh
        Musuh musuh = new Musuh();
        addObject(musuh, 425, 339);
        Musuh musuh2 = new Musuh();
        addObject(musuh2, 532, 191);
        Musuh musuh3 = new Musuh();
        addObject(musuh3, 365, 128);
        Musuh musuh4 = new Musuh();
        addObject(musuh4, 273, 295);
    }
    
    /**
     * Menyiapkan nyawa (bar) yg jg akan ditambahkan ke skenario
     */
    private void prepareNyawaBar(int offsetX, int offsetY) {
        int spasiAntarNyawa = 10;  // spasi antargambar pizza
        
        for (int i = 0; i < NYAWA_MAKS; i++) {
            Nyawa nyawa = new Nyawa();
            nyawa.setLokasiX(
                offsetX+((nyawa.getImage().getWidth()+spasiAntarNyawa)*i)
            );
            nyawa.setLokasiY(offsetY);
            addObject(nyawa, nyawa.getLokasiX(), nyawa.getLokasiY());
            
            nyawaBar.add(nyawa);
        }
    }
    
    /**
     * Mendapatkan objek nyawaBar
     */
    public static ArrayList getNyawaBar() {
        return nyawaBar;
    }
}

World (Skenario) yang digunakan berukuran 600×400. Sintaks import java.util.ArrayList; digunakan untuk mengimpor kelas ArrayList yang berada di paket java.util. Kelas tersebut merupakan salah satu kelas yang termasuk dalam collection framework yang disediakan oleh Java. Kita bisa menggunakan kelas tersebut untuk menyimpan koleksi (array/himpunan) objek. Dalam hal ini, objek nyawaBar yang bertipe ArrayList digunakan untuk menyimpan objek-objek Nyawa yang nantinya akan ditampilkan pada Skenario.

Method prepare() berisi baris-baris program untuk menambahkan/membuat objek dari aktor-aktor yang akan digunakan dalam game. Dalam Skenaro, akan dibuat 1 objek AktorUtama dan 4 objek Musuh yang diletakkan pada posisi x dan y tertentu.

Method prepareNyawaBar() berisi kode program untuk membuat dan menempatkan objek-objek Nyawa pada koleksi nyawaBar sekaligus menampatkannya pada Skenario. Sintaks nyawa.setLokasiX(offsetX + ((nyawa.getImage().getWidth() + spasiAntarNyawa) * i)); digunakan untuk menentukan posisi x dari objek Nyawa yang dibuat. Begitu pula pada saat pemanggilan method setLokasiY() melalui objek nyawa, digunakan untuk menentukan posisi y dari objek Nyawa. Sintaks addObject(nyawa, nyawa.getLokasiX(), nyawa.getLokasiY()); digunakan untuk memvisualisasikan objek nyawa ke Skenario. Sedangkan sintaks nyawaBar.add(nyawa); digunakan untuk memasukkan objek ke koleksi nyawaBar yang berbentuk ArrayList. Mengapa harus ditambahkan ke koleksi ArrayList juga, bukankah cukup divisualisasikan di Skenario? Tujuannya adalah agar nanti kita dapat mendapatkan referensi (alamat memori) setiap objek nyawa yang sudah dibuat untuk keperluan-keperluan lebih lanjut.

Method getNyawaBar() digunakan untuk mendapatkan koleksi ArrayList nyawaBar. Method ini dideklarasikan static karena objek nyawaBar juga merupakan atribut yang bertipe static. Mengapa objek tersebut didefinisikan static? Objek didefinisikan static karena kita akan sedikit memanfaatkan konsep pola desain (design pattern) Singleton. Apa lagi itu Singleton? Gampangannya, dengan menerapkan konsep Singleton, kita dapat membuat sebuah atribut yang diinisialisasi 1× dan bisa dipakai oleh objek lain tanpa harus membuat objek dari kelas yang mendefinisikan atribut tersebut. Masih bingung? Dengan kata lain, kita membuat sebuah class variable. Apa lagi itu? Hahaha... silakan cek materi Pemrograman Berorientasi Objek saja.

Listing 4: Kelas AktorUtama

import greenfoot.*;

/**
 * Aktor Utama (Kura-Kura)
 * 
 * @author Nanang F. Rozi <nfrozy@gmail.com>
 * @version 1.0.0
 */
public class AktorUtama extends Actor
{
    public void act() 
    {
        jalan();
        kurangiNyawaKetikaMenabrakMusuh();
    }
    
    /**
     * Menjalankan si kura-kura
     */
    private void jalan() {
        if (Greenfoot.isKeyDown("right")) {
            setLocation(getX()+1, getY());
        }
    }
    
    /**
     * Action ketika kura-kura menabrak mobil
     * -> Mengurangi nyawa si kura-kura satu persatu...
     * -> Action masih sederhana, silakan dimodifikasi sesuai kebutuhan :)
     */
    private void kurangiNyawaKetikaMenabrakMusuh() {
        Actor musuh = getOneIntersectingObject(Musuh.class);
        if (musuh != null) {
            int indeksTerakhir  = Skenario.getNyawaBar().size()-1;
            Nyawa nyawaTerakhir =
                Skenario.getNyawaBar().get(indeksTerakhir);
            
            getWorld().removeObject(musuh);
            
            getWorld().removeObject(nyawaTerakhir);
            Skenario.getNyawaBar().remove(indeksTerakhir);
        }
    }
}

Kelas AktorUtama merepresentasikan objek inti yang akan digunakan dalam game. Pada kelas AktorUtama, terdapat method jalan(). Method ini digunakan untuk mengontrol jalannya kura-kura sebagai aktor utama. Kebetulan, event handling yang saya terapkan hanya perpindahan posisi kura-kura ke kanan ketika tombol kanan "right" ditekan. Untuk kepentingan itu, saya memanfaatkan method setLocation() sebagai alternatif dari method move().

Method kurangiNyawaKetikaMenabrakMusuh() didefinisikan untuk kontrol event kura-kura ketika menyentuh/menabrak objek Musuh. Untuk mendeteksi apakah kura-kura menyentuh objek Musuh atau tidak, kita dapat melakukannya dengan mendapatkan objek yang bersinggungan (intersect) dengan kura-kura melalui method getOneIntersectingObject() dan memasukkan Musuh.class sebagai parameternya untuk menunjukkan bahwa kita akan mengecek apakah kura-kura sedang menabrak objek Musuh atau tidak. Objek yang didapatkan dari method getOneIntersectingObject() tersebut disimpan dalam sebuah variabel bernama musuh yang memiliki tipe Actor. Kenapa ditaruh di variabel bertipe Actor? Karena memang return type dari method tersebut ya Actor, kalau Anda taruh di variabel bertipe data lain ya repot, error compile nanti :D Objek kura-kura diidentifikasi menyentuh objek musuh ketika isi variabel musuh tidak null. Jika isinya null, berarti kura-kura sedang tidak menyentuh objek Musuh.

Sintaks Skenario.getNyawaBar().size()-1; digunakan untuk mendapatkan indeks terakhir koleksi (seperti dalam array, jika objek ada 3, maka indeksnya terakhirnya adalah 2) yang selanjutnya disimpan di variabel indeksTerakhir bertipe int. Variabel indeksTerakhir tersebut akan digunakan untuk mendapatkan objek nyawa yang ditambahkan terakhir kali ke koleksi melalui sintaks Skenario.getNyawaBar().get(indeksTerakhir). Nah, itu tadi yang kita katakan, static method, method-nya dapat kita akses tanpa membuat objek dari kelas Skenario. Method dapat kita akses dengan cara menuliskan nama kelas Skenario diikuti titik dan nama method getNyawaBar(). Ketika referensi ke objek nyawa sudah didapatkan (di variabel nyawaTerakhir), maka kita akan dengan mudah menghapus objek nyawa tersebut dari Skenario dengan memanggil method removeObject(nyawaTerakhir) yang berakibat nyawa akan menghilang satu dari Skenario. Ingat, jika kode program ada di kelas turunan Actor, maka untuk memanggil method removeObject() harus melalui pemanggilan method getWorld() terlebih dahulu.

Sintaks removeObject(musuh) digunakan untuk menghilangkan musuh dari Skenario ketika telah menyentuh kura-kura. Sehingga, sekali kura-kura menyentuh musuh, akan ada 2 objek yang hilang dari skenario, yaitu nyawa dan musuh itu sendiri.

Setelah itu, nyawaTerakhir tadi harus juga dihapus dari koleksi ArrayList dengan memanggil method remove() dengan sebelumnya mencari instance objek nyawaBar melalui method Skenario.getNyawaBar(). Mengapa harus dihapus juga dari ArrayList? Ini dibutuhkan kalau memang kita berencana untuk menghapus referensi ke objek tersebut dari ArrayList agar nanti si JVM dapat mengoptimalkan penggunaan memori melalui garbage collector-nya.

Demikian, jikalau pembaca yang budiman (cie... dikatain pembaca yang budiman) mempunyai unek-unek atau pertanyaan, mari kita diskusikan di boks komentar di bawah :) Sebagai tambahan, saya sertakan pula video demonstrasi hasil running dari game di bawah ini (tunjuk bawah, bawah ini, iya, bawah ini... :p).