Monday, January 19, 2009

JAVA 2D Graphics dan Animation (Part 3) - Games Engineering

Getting Rid of Flicker and Tearing
Sekarang kita lanjutkan lagi pembahasan games engineering, pada tulisan sebelumnya animasi yang kita jalankan AnimationTest.java, berjalan dengan baik tapi kok flicker yah animasinya ngga smooth.
Ini terjadi karena kita langsung drawing ke screen secara langsung dan terus menerus, jika kita analogikan seperti ketika kita menggambar pada sebuah kanvas, kemudian dihapus dilanjutkan dengan drawing dengan gambar selanjutnya.

Lalu bagaimana solusinya? jawabannya adalah dengan double buffering.

Double Buffering
Jadi begini dengan menggunakan double buffering kita membuat buffer terlebih dahulu kemudian baru ditampilkan ke screen disamping kita menggunakan direct drawing, maksudnya begini kita drawing pada buffer terlebih dahulu baru kemudian kita tampilkan duplikat dari buffer tadi ke screen, sehingga screen hanya akan terupdate sekali. berikut adalah gambar ilustrasinya :

Back buffer adalah sebuah class image juga. Kita bisa menggunakan method createImage(int w, int h) pada class Component untuk membuat back buffer. Sebagai contoh, jika kita ingin membuat double bufferring pada applet yang tidak menggunakan active rendering, kita lakukan override method update() untuk menggunakan double buffer dan memanggil methos paint() dengan konteks berupa double buffer:

private Image doubleBuffer;
...
public void update(Graphics g) {
Dimension size = getSize();
if (doubleBuffer == null ||
doubleBuffer.getWidth(this) != size.width ||
doubleBuffer.getHeight(this) != size.height)
{
doubleBuffer = createImage(size.width, size.height);
}
if (doubleBuffer != null) {
// paint to double buffer
Graphics g2 = doubleBuffer.getGraphics();
paint(g2);
g2.dispose();
// copy double buffer to screen
g.drawImage(doubleBuffer, 0, 0, null);
}
else {
// couldn't create double buffer, just paint to screen
paint(g);
}
}
public void paint(Graphics g) {
// do drawing here
...
}

Page Flipping
Ketika melakukan double buffer akan memakan beberapa saat untuk mengambil gambar dari buffer kemudian ditampilkan ke screen. Resolusi 800x600 dengan kedalaman bit 16 akan memanfaatkan 800x600 bytes, atau 938KB. Nilai tersebut mendekati 1Mb memory yang kemudian diacak untuk 30 frames per second (fps). Akan tetapi melkukan copy terhadap memory tersebut adalah sesuatu yang cukup cepat untuk kebanyakan games, bagaimana jika kita tidak membuat buffer sepenuhnya dan dapatkah secara instans membuat back buffer sebagai display buffer?
Itu dapat dilakukan, teknik seperti ini disebut sebagai page flipping. Dengan page flipping, kita menggunakan 2 buffer, satu untuk back buffer dan satunya lagi untuk display buffer, berikut adalah ilustrasinya:

Display pointer akan menunjuk ke buffer yang sedang ditampilkan. Display pointer ini dapat dirubah pada kebanyakan sistem modern. Ketika kita selesai drawing pada back buffer, display pointer dapat dipindahkan dari display buffer saat ini ke back buffer yang lain, sebagai mana ditunjukkan pada gambar dibawah ini. Ketika pointer berubah, kemudian display buffer akan berubah menjadi back buffer, begitu juga sebaliknya.

Tentu saja, merubah pointer akan sangat lebih cepat dari pada melakukan duplikasi pada sebuah block memory, jadi dengan page flipping akan lebih cepat daripada double buffering.


Monitor Refresh and Tearing

Perlu diingat bahwa monitor mempunyai refresh rate. Refresh rate bisanya antara 75Hz, yang berarti monitor melakukan refreshing sebanyak 75 kali dalam satu detik. Tetapi apa yang terjadi ketika page flipping/double buffer terjadi ditengah2 dari refresh rate? ya, bisa kita tebak sebagian dari buffer yang lama akan ditampilkan bersamaan dengan sebagian dari buffer yang baru. Effek ini sama dengan flickering, sering disebut dengan tearing seperti yang ditunjukkan pada gambar dibaewah ini.

Untuk mengatasi masalah ini kita bisa cukup melakukan page flip tepat sebelum monitor melakukan refresh, kedengarannya sulit untuk dilakukan tetapi jangan khawatir java runtime telah melakukannya untuk kita, dengan menggunakan class BufferStrategy.

BufferStrategy Class
Double buffering, page flipping, dan menunggu refresh monitor semuanya telah ditangani oleh class BufferStrategy. BufferStrategy akan memilih metode buffering yang cocok sesuai dengan kemampuan sistem. Pertama, akan mencoba untuk menerapkan page flipping, jika tidak memungkinkan, akan mencoba dengan double buffering. Juga, menunggu sampai monitor refresh selesai sebelum melakukan page flip. Singkat kata, BufferStrategy akan melakukan hal2 tersebut untuk kita, tanpa kita harus susah payah untuk melakukannya.
Sebagai catatan FPS(Frames Per Second) pada games ditentukan oleh refresh rate yang digunakan, jika monitor menggunakan 75Hz, maka games display maximum hanya mampu menampilkan 75 FPS. Ini berarti kita tidak bisa menggunakan FPS sebagai "BENCHMARK" seberapa cepat sistem berjalan.
Tentu saja, bukanlah suatu masalah ketika suatu games berjalan pada 200 FPS kita akan melihatnya sesuai dengan kemapuan monitor. Tidak masalah secepat apa games tersebut berjalan, kita akan tetap melihatnya dalam 75 FPS pada monitor dengan refresh rate 75Hz.
Canvas dan Window objek keduanya dapat diterapkan BufferStrategy. Gunakan method createBufferStrategy() untuk membuat BufferStrategy sesuai dengan jumlah buffer yang kita inginkan. Kita akan membutuhkan kurang lebih 2 buffer untuk double buffering dan page flipping. Sebaga contoh:
frame.createBufferStrategy(2);

Setelah membuat BufferStrategy, kita bisa panggil method getBufferStrategy() untuk mereferensi ke buffer yang telah dibuat dan gunakan method getDrawGraphics() untuk mendapatkan graphic konteks untuk drawing buffer. Setelah selesai drawing, kita bisa panggil method show() untuk menampilkan graphic di buffer, berikut contohnya:
BufferStrategy strategy = frame.getBufferStrategy();
Graphics g = strategy.getDrawGraphics();
draw(g);
g.dispose();
strategy.show();

Membuat Screen Manager
Oke sekarang kita akan update SimpleScreenManager pada tulisan sebelumnya dengan feature yang baru. Berikut ini yang akan ditambahkan:
  • Double buffering dan page flipping dengan membuat BufferStrategy

  • getGraphics(), yang akan mendapatkan graphic konteks untuk ditampilkan

  • update(), yang akan melakukan update ke display

  • getCompatibleDisplayModes(), yang akan mendapatkan list dari display mode

  • getCurrentDispalayMode(), yang akan mendapatkan display mode saat ini

  • findFirstCompatibleMode(), yang akan mendapatkan compatible mode yang pertama yang berada di list mode


Sekarang kita akan melakukan active rendering, tidak membutuhkan JFrame untuk full-screen window untuk menerima paint event dari sistem operasi, jadi kita bisa matikan saja dengan:
frame.ignoreRepaint(true);

Ini tidak akan mematikan normal repaint event. Memangil repaint() pada JFrame masih akan tetap bisa. Oke sekarang kita update SimpleScreenManager menjadi sebagai berikut:
package brain.left.games;

import java.awt.*;
import java.awt.image.BufferStrategy;
import java.awt.image.BufferedImage;

import javax.swing.JFrame;

/**
* The ScreenManager class manages initializing and displaying full screen
* graphics modes.
*/
public class ScreenManager {
private GraphicsDevice device;

/**
* Creates a new ScreenManager object.
*/
public ScreenManager() {
GraphicsEnvironment environment = GraphicsEnvironment
.getLocalGraphicsEnvironment();
device = environment.getDefaultScreenDevice();
}

/**
* Returns a list of compatible display modes for the default device on the
* system.
*/
public DisplayMode[] getCompatibleDisplayModes() {
return device.getDisplayModes();
}

/**
* Returns the first compatible mode in a list of modes. Returns null if no
* modes are compatible.
*/
public DisplayMode findFirstCompatibleMode(DisplayMode modes[]) {
DisplayMode goodModes[] = device.getDisplayModes();
for (int i = 0; i < modes.length; i++) {
for (int j = 0; j < goodModes.length; j++) {
if (displayModesMatch(modes[i], goodModes[j])) {
return modes[i];
}
}
}
return null;
}

/**
* Returns the current display mode.
*/
public DisplayMode getCurrentDisplayMode() {
return device.getDisplayMode();
}

/**
* Determines if two display modes "match". Two display modes match if they
* have the same resolution, bit depth, and refresh rate. The bit depth is
* ignored if one of the modes has a bit depth of
* DisplayMode.BIT_DEPTH_MULTI. Likewise, the refresh rate is ignored if one
* of the modes has a refresh rate of DisplayMode.REFRESH_RATE_UNKNOWN.
*/
public boolean displayModesMatch(DisplayMode mode1, DisplayMode mode2) {
if (mode1.getWidth() != mode2.getWidth()
|| mode1.getHeight() != mode2.getHeight()) {
return false;
}
if (mode1.getBitDepth() != DisplayMode.BIT_DEPTH_MULTI
&& mode2.getBitDepth() != DisplayMode.BIT_DEPTH_MULTI
&& mode1.getBitDepth() != mode2.getBitDepth()) {
return false;
}
if (mode1.getRefreshRate() != DisplayMode.REFRESH_RATE_UNKNOWN
&& mode2.getRefreshRate() != DisplayMode.REFRESH_RATE_UNKNOWN
&& mode1.getRefreshRate() != mode2.getRefreshRate()) {
return false;
}
return true;
}

/**
* Enters full screen mode and changes the display mode. If the specified
* display mode is null or not compatible with this device, or if the
* display mode cannot be changed on this system, the current display mode
* is used.
*


* The display uses a BufferStrategy with 2 buffers.
*/
public void setFullScreen(DisplayMode displayMode) {
JFrame frame = new JFrame();
frame.setUndecorated(true);
frame.setIgnoreRepaint(true);
frame.setResizable(false);
device.setFullScreenWindow(frame);
if (displayMode != null && device.isDisplayChangeSupported()) {
try {
device.setDisplayMode(displayMode);
} catch (IllegalArgumentException ex) {
}
}
frame.createBufferStrategy(2);
}

/**
* Gets the graphics context for the display. The ScreenManager uses double
* buffering, so applications must call update() to show any graphics drawn.
*


* The application must dispose of the graphics object.
*/
public Graphics2D getGraphics() {
Window window = device.getFullScreenWindow();
if (window != null) {
BufferStrategy strategy = window.getBufferStrategy();
return (Graphics2D) strategy.getDrawGraphics();
} else {
return null;
}
}

/**
* Updates the display.
*/
public void update() {
Window window = device.getFullScreenWindow();
if (window != null) {
BufferStrategy strategy = window.getBufferStrategy();
if (!strategy.contentsLost()) {
strategy.show();
}
}
// Sync the display on some systems.
// (on Linux, this fixes event queue problems)
Toolkit.getDefaultToolkit().sync();
}

/**
* Returns the window currently used in full screen mode. Returns null if
* the device is not in full screen mode.
*/
public Window getFullScreenWindow() {
return device.getFullScreenWindow();
}

/**
* Returns the width of the window currently used in full screen mode.
* Returns 0 if the device is not in full screen mode.
*/
public int getWidth() {
Window window = device.getFullScreenWindow();
if (window != null) {
return window.getWidth();
} else {
return 0;
}
}

/**
* Returns the height of the window currently used in full screen mode.
* Returns 0 if the device is not in full screen mode.
*/
public int getHeight() {
Window window = device.getFullScreenWindow();
if (window != null) {
return window.getHeight();
} else {
return 0;
}
}

/**
* Restores the screen's display mode.
*/
public void restoreScreen() {
Window window = device.getFullScreenWindow();
if (window != null) {
window.dispose();
}
device.setFullScreenWindow(null);
}

/**
* Creates an image compatible with the current display.
*/
public BufferedImage createCompatibleImage(int w, int h, int transparency) {
Window window = device.getFullScreenWindow();
if (window != null) {
GraphicsConfiguration gc = window.getGraphicsConfiguration();
return gc.createCompatibleImage(w, h, transparency);
}
return null;
}
}


Pada ScreenManager, kita berikan catatan tersendiri untuk line berikut pada methode update():
Toolkit.getDefaultToolkit().sync();

Baris diatas akan memastikan bahwa display telah disinkronisasi dengan sistem window. Pada kebanyakan sistem, methos ini tidak berpengaruh, tetapi pada linux, memanggil method ini akan memperbaiki AWT event queue. Tanpa memanggil method ini, pada beberapa sistem linux akan menyebabkan terjadinya delay pada mouse dan keyboard event.

Dua method baru yang perlu digaris bawahi pada class ScreenManager adalah displayModesMatch() dam createCompatibleImage().
Method displayModesMatch() akan mengecek antara dua DisplayMode objek sama, yaitu dari sisi resolusi, kedalaman bit, dan refresh rate. Kedalaman bit dan refresh rate akan diabaikan jika jika ada salah satu saja yang tidak sama.
Method createCompatibleImage() akan membuat image yang sesuai dengan display, image akan mempunyai kedalaman bit yang sama dan model warna yang sama dengan display. Kita memanfaatkan BufferedImage karena method ini berguna untuk membuat image transparent atau translucent karena method createImage() hanya akan membuat opaque image.

Sekarang kita akan mengupdate AnimationTest1 pada tulisan sebelumnya menjadi AnimationTest2 dengan menggunakan ScreenManager yang telah kita tingkatkan kemampunannya, listing kodenya adalah sebagai berikut: HOOOREEEE ga ada flickering lagi!!
package brain.left.games;

import java.awt.*;
import javax.swing.ImageIcon;

public class AnimationTest2 {
public static void main(String args[]) {
AnimationTest2 test = new AnimationTest2();
test.run();
}

private static final DisplayMode POSSIBLE_MODES[] = {
new DisplayMode(800, 600, 32, 0), new DisplayMode(800, 600, 24, 0),
new DisplayMode(800, 600, 16, 0), new DisplayMode(640, 480, 32, 0),
new DisplayMode(640, 480, 24, 0), new DisplayMode(640, 480, 16, 0) };
private static final long DEMO_TIME = 10000;
private ScreenManager screen;
private Image bgImage;
private Animation anim;

public void loadImages() {
// load images
bgImage = loadImage("images/tile-games/background.jpg");
Image player1 = loadImage("images/tile-games/hero1.png");
Image player2 = loadImage("images/tile-games/hero2.png");
Image player3 = loadImage("images/tile-games/hero3.png");
// create animation
anim = new Animation();
anim.addFrame(player1, 250);
anim.addFrame(player2, 150);
anim.addFrame(player1, 150);
anim.addFrame(player2, 150);
anim.addFrame(player3, 200);
anim.addFrame(player2, 150);
}

private Image loadImage(String fileName) {
return new ImageIcon(fileName).getImage();
}

public void run() {
screen = new ScreenManager();
try {
DisplayMode displayMode = screen
.findFirstCompatibleMode(POSSIBLE_MODES);
screen.setFullScreen(displayMode);
loadImages();
animationLoop();
} finally {
screen.restoreScreen();
}
}

public void animationLoop() {
long startTime = System.currentTimeMillis();
long currTime = startTime;
while (currTime - startTime < DEMO_TIME) {
long elapsedTime = System.currentTimeMillis() - currTime;
currTime += elapsedTime;
// update animation
anim.update(elapsedTime);
// draw and update screen
Graphics2D g = screen.getGraphics();
draw(g);
g.dispose();
screen.update();
// take a nap
try {
Thread.sleep(20);
} catch (InterruptedException ex) {
}
}
}

public void draw(Graphics g) {
// draw background
g.drawImage(bgImage, 0, 0, null);
// draw image
g.drawImage(anim.getImage(), 0, 0, null);
}
}

Tidak banyak yang berubah dari kode sebelumnya. Satu hal yang berubah adalah cara AnimationTest2 dalam memilih display mode. AnimationTest2 menggunakan ScreenManager untuk mendapatkan list display mode yang compatible, dan ScreenManager memilih list pertama.
ScreenManager membuat JFrame sendiri, jadi AnimationTeast2 tidak ada hubungannya dengan pembuatan JFrame untuk full-screen mode.

Sprites
Animation sekarang berjalan dengan smooth, tetapi tidak menarik jika kita membuat animasi hanya ditempat. Sekarang kita akan membuat sprite.
Sprite adalah graphic yang bergerak secara independen pada screen. Pada kasus ini, sprite akan dianimasikan dan bergerak pada suatu waktu.
Berdasarkan sebuah animasi, sprite akan disusun dari dua hal: posisi dan velocity. Jika kita mau mengingat kembali tentang velocity sewaktu kita disekolah, velocity adalah kecepatan(misal 22mph) dan arah(misal utara). Pada kasus ini, kita akan membagi percepatan kedalam dua bagian yaitu komponen horizontal dan vertical. Kita tidak menggunakan miles per jam atau meters per detik, tetapi kita akan menggunakan pixel per milidetik.
Kita mungkin bertanya-tanya, "Mengapa kita menggunakan velocity? Mengapa tidak hanya update posisi sprite pada beberapa frame?" Oke, jika ada pertanyaan seperti itu, sprite akan bergerak pada kecepatan yang berbeda tergantung dari kecepatan mesin. Lebih besar frame rate berarti kecepatan sprite akan lebih cepat juga. Sprite akan bergerak berubah secara real time karena sprite bergerak dengan pace yang konsisten, apakah waktu antara frames pendek atau panjang.
Sprite class pada listing program dibawah ini akan dianimasikan posisi dan velocity-nya.
Kita dapat mendefinisikan posisi sprite dalam integer, tetapi bagaimana jika sprite bergerak lambat? Sebagai contoh, bayangkan sebuah sprite bergerak pada pixel kesepuluh setiap kali diupdate. Ini berarti bahwa akan ada 9 gerakan tak terlihat dari 10 gerakan saat diupdate. Jika posisi sprite dalam integer, sprite tidak akan pernah bergerak karena hasilnya tidak akan dibulatkan setiap waktu.
Jika posisi sprite dalam floating point, posisi sprite dapat mengincrement pergerakan yang tidak terlihat, dan sprite akan bergerak 1 pixel pada setiap memanggil update() kesepuluh. Berikut adalah listing program Sprite.java
package brain.left.games;

import java.awt.Image;

public class Sprite {
private Animation anim;
// position (pixels)
private float x;
private float y;
// velocity (pixels per millisecond)
private float dx;
private float dy;

/**
* Creates a new Sprite object with the specified Animation.
*/
public Sprite(Animation anim) {
this.anim = anim;
}

/**
* Updates this Sprite's Animation and its position based on the velocity.
*/
public void update(long elapsedTime) {
x += dx * elapsedTime;
y += dy * elapsedTime;
anim.update(elapsedTime);
}

/**
* Gets this Sprite's current x position.
*/
public float getX() {
return x;
}

/**
* Gets this Sprite's current y position.
*/
public float getY() {
return y;
}

/**
* Sets this Sprite's current x position.
*/
public void setX(float x) {
this.x = x;
}

/**
* Sets this Sprite's current y position.
*/
public void setY(float y) {
this.y = y;
}

/**
* Gets this Sprite's width, based on the size of the current image.
*/
public int getWidth() {
return anim.getImage().getWidth(null);
}

/**
* Gets this Sprite's height, based on the size of the current image.
*/
public int getHeight() {
return anim.getImage().getHeight(null);
}

/**
* Gets the horizontal velocity of this Sprite in pixels per millisecond.
*/
public float getVelocityX() {
return dx;
}

/**
* Gets the vertical velocity of this Sprite in pixels per millisecond.
*/
public float getVelocityY() {
return dy;
}

/**
* Sets the horizontal velocity of this Sprite in pixels per millisecond.
*/
public void setVelocityX(float dx) {
this.dx = dx;
}

/**
* Sets the vertical velocity of this Sprite in pixels per millisecond.
*/
public void setVelocityY(float dy) {
this.dy = dy;
}

/**
* Gets this Sprite's current image.
*/
public Image getImage() {
return anim.getImage();
}
}

Class sprite sangatlah simpel. Kebanyakan terdiri dari get dan set method. Semuanya akan diakhiri dengan method update(), yang akan mengupdate posisi dari sprite berdasarkan velocity dan banyaknya waktu yang tersedia.
Sekarang kita bersenang-senang dengan menggunakan class Sprite yang telah kita buat sebelumnya, kita akan membuat karakter memantul-mantul pada screen, berikut adalah listing program SpriteTest1 :
package brain.left.games;

import java.awt.*;
import javax.swing.ImageIcon;

public class SpriteTest1 {
public static void main(String args[]) {
SpriteTest1 test = new SpriteTest1();
test.run();
}

private static final DisplayMode POSSIBLE_MODES[] = {
new DisplayMode(800, 600, 32, 0), new DisplayMode(800, 600, 24, 0),
new DisplayMode(800, 600, 16, 0), new DisplayMode(640, 480, 32, 0),
new DisplayMode(640, 480, 24, 0), new DisplayMode(640, 480, 16, 0) };
private static final long DEMO_TIME = 10000;
private ScreenManager screen;
private Image bgImage;
private Sprite sprite;

public void loadImages() {
// load images
bgImage = loadImage("images/tile-games/background.jpg");
Image player1 = loadImage("images/tile-games/hero1.png");
Image player2 = loadImage("images/tile-games/hero2.png");
Image player3 = loadImage("images/tile-games/hero3.png");
// create sprite
Animation anim = new Animation();
anim.addFrame(player1, 250);
anim.addFrame(player2, 150);
anim.addFrame(player1, 150);
anim.addFrame(player2, 150);
anim.addFrame(player3, 200);
anim.addFrame(player2, 150);
sprite = new Sprite(anim);
// start the sprite off moving down and to the right
sprite.setVelocityX(0.2f);
sprite.setVelocityY(0.2f);
}

private Image loadImage(String fileName) {
return new ImageIcon(fileName).getImage();
}

public void run() {
screen = new ScreenManager();
try {
DisplayMode displayMode = screen
.findFirstCompatibleMode(POSSIBLE_MODES);
screen.setFullScreen(displayMode);
loadImages();
animationLoop();
} finally {
screen.restoreScreen();
}
}

public void animationLoop() {
long startTime = System.currentTimeMillis();
long currTime = startTime;
while (currTime - startTime < DEMO_TIME) {
long elapsedTime = System.currentTimeMillis() - currTime;
currTime += elapsedTime;
// update the sprites
update(elapsedTime);
// draw and update the screen
Graphics2D g = screen.getGraphics();
draw(g);
g.dispose();
screen.update();
// take a nap
try {
Thread.sleep(20);
} catch (InterruptedException ex) {
}
}
}

public void update(long elapsedTime) {
// check sprite bounds
if (sprite.getX() < 0) {
sprite.setVelocityX(Math.abs(sprite.getVelocityX()));
} else if (sprite.getX() + sprite.getWidth() >= screen.getWidth()) {
sprite.setVelocityX(-Math.abs(sprite.getVelocityX()));
}
if (sprite.getY() < 0) {
sprite.setVelocityY(Math.abs(sprite.getVelocityY()));
} else if (sprite.getY() + sprite.getHeight() >= screen.getHeight()) {
sprite.setVelocityY(-Math.abs(sprite.getVelocityY()));
}
// update sprite
sprite.update(elapsedTime);
}

public void draw(Graphics g) {
// draw background
g.drawImage(bgImage, 0, 0, null);
// draw sprite
g.drawImage(sprite.getImage(), Math.round(sprite.getX()), Math
.round(sprite.getY()), null);
}
}

Karena Sprite objek mengontrol gerakannya sendiri, pada kelas SpriteTest1 tidak akan berubah banyak dibandingkan dengan AnimationTest2. Hal yang beru adalah method update(), yang menyebabkan sprite memantul ketika membentur batas screen. Jika sprite membentur batas kiri atau kanan, horizontal velocity akan dibalik. Jika sprite membentur batas atas atau bawah vertical velocity akan dibalik.

Tuesday, January 13, 2009

Swing Layout in Action



Rehat dulu ah dari bahas games engineering, capek... refreshing dulu..., kemaren lagi iseng2 main2 layout swing sambil nginget2 sama mau optimasi dengan layout2 yang ada di java, enaknya sih emang klo kita pake visual swing desainer kayak punyanya netbeans tiggal drag n drop (what you see is what you get) tapi ga papalah buat iseng2 main2 layout biar ngerti konsep layout. sebenarnya java awt punya beberapa layout sebagai berikut:
  • CardLayout ---> Merupakan model seperti stack kartu yang dapat ditampilkan secara berurutan biasanya diaplikasikan pada waktu kita ingin membuat model interface seperti wizard2 atau model interface seperti kalo kita menginstall applikasi dengan next/previous.
  • BorderLayout ---> Akan membagi area menjadi 5 wilayah yaitu North, West, East, South, dan Center.
  • FlowLayout ---> FlowLayout merupakan default layout yang digunakan oleh swing, layout ini akan menampilkan komponen secara berurutan dari kiri ke kanan.
  • GridLayout ---> GridLayout merupakan layout yang akan membagi sebuah frama/panel kedalam baris dan kolom dengan ukuran yang sama. Misalnya ketika kita ingin membuat tombol2 kalkulator dengan ukuran tombol yang sama.
  • GridBagLayout ---> Layout yang sangat fleksibel yang akan membagi area menjadi grid2 sehingga komponen dapat ditempatkan secara fleksibel, dalam tulisan ini kita akan memanfaatkan layout ini.
  • GroupLayout ---> Layout yang fleksibel yang dipakai oleh netbeans dan dikembangkan oleh third party yaitu apache.

Oke dalam tulisan ini akan dibatasi untuk membahas GridbagLayout, karena layout ini cukup menarik untuk dibahas sedangkan untuk layout2 yang lain bisa tutorialnya di situs resminya java atau resource2 yang lain.

Untuk membuat layout ini cukup simple kita bisa mengkonstruksi dengan :
GridBagLayout gbl = new GridBagLayout();
kemudian dari situ kita bisa melakukan set layout pada JFrame/JPanel yang akan kita gunakan cukup menambahkan object gbl pada parameter, misalnya:
JFrame frame = new JFrame(); // jika layout diimplementasikan pada JFrame
frame.serLayout(gbl);
JPanel panel = new JPanel(); // jika layout diimplementasikan pada JPanel
panel.setLayout(gbl);
Untuk mengatur tataletak komponen tadi pada frame/panel kita memanfaatkan GridBagConstraints, class ini mempunyai properti2 yang penting untuk mengatur tataletak sebagai berikut :
*Note: properti dibawah ini diassignkan dengan nilai defaultnya
  • fill = GridBagConstrains.NONE; ----> properti ini digunakan untuk setting arah resize dari komponen, Nilainya bisa GridBagConstrains.NONE, GridBagConstrains.HORIZONTAL, dan GridBagConstrains.VERTICAL
  • gridx = GridBagConstrains.CENTER; ----> untuk meletakkan komponen pada grid tertentu terhadap posisi x
  • gridy = GridBagConstrains.RELATIVE; ----> untuk meletakkan komponen pada grid tertentu pada posisi y
  • gridheight = 1; ----> untuk menentukan span yang digunakan oleh komponen, misalkan kita isi dengan nilai 2 berarti komponen akan menempati area sebesar 2 grid searah sumbu y
  • gridwidth = 1; ----> sama seperti gridheight hanya saja span gridnya searah sumbu x
  • insets = new Insets(0, 0, 0, 0); ----> digunakan untuk menentukan jarak antar komponen Insets(top, left, bottom, right)
  • ipadx = 0; ----> digunakan untuk menentukan padding dari dalam komponen searah sumbu x
  • ipady = 0; ----> digunakan untuk menentukan padding dari dalam komponen searah sumbu y
  • weightx = 0; ----> digunakan untuk menentukan ukuran maksimum resize dari komponen searah sumbu x
  • weighty = 0; ----> digunakan untuk menentukan ukuran maksimum resize dari komponen searah sumbu x
  • anchor = GridBagConstrains.CENTER; digunakan untuk menentukan perataan letak komponen, bisa CENTER, EAST, WEST, SOUTH, NORTH ...
Untuk memyederhanakan dalam pembuatan GridBagConstrains yang digunakan, saya membuat class GBCHelper.java yang merupakan subclass dari GridBagCondtrains sehingga kita cukup menggunaka sekali konstruksi object GridBagConstrains untuk semua komponen, listing programnya sebagai berikut:
package brain.left.swingtest;

import java.awt.GridBagConstraints;
import java.awt.Insets;

/**
* This class for simplifies using GridbagConstrains copyleft by amru rosyada,
* you can redistribute with any change to this code license GPLV3
*
* @version = 1.00 12 Jan 2009
* @author amru rosyada, amrurosyada.blogspot.com, taka86[AT]gmail.com
*/

public class GBCHelper extends GridBagConstraints {
private static final long serialVersionUID = 582260222458228387L;

public GBCHelper() {

}

/**
* Set GridbagConstrains to default value
*/
public GBCHelper clear() {
this.fill = GBCHelper.NONE;
this.gridx = GBCHelper.CENTER;
this.gridy = GBCHelper.RELATIVE;
this.gridheight = 1;
this.gridwidth = 1;
this.insets = new Insets(0, 0, 0, 0);
this.ipadx = 0;
this.ipady = 0;
this.weightx = 0;
this.weighty = 0;
this.anchor = GBCHelper.CENTER;
return this;
}

/**
* set gridy gridx position
*
* @param gridx
* set gridx position
* @param gridy
* set gridy position
*/
public GBCHelper setGrid(int gridx, int gridy) {
this.gridx = gridx;
this.gridy = gridy;
return this;
}

/**
* Sets the fill direction.
*
* @param fill
* the fill direction
* @return this object for further modification
*/
public GBCHelper setFill(int fill) {
this.fill = fill;
return this;
}

/**
* set value for gridx, gridy, gridwidth, gridheight
*
* @param gridx
* the gridx position
* @param gridy
* the gridy position
* @param gridwidth
* the cell span in x-direction
* @param gridheight
* the cell span in y-direction
*/
public GBCHelper setGrid(int gridx, int gridy, int gridwidth, int gridheight) {
this.gridx = gridx;
this.gridy = gridy;
this.gridwidth = gridwidth;
this.gridheight = gridheight;
return this;
}

/**
* Sets the insets of this cell.
*
* @param distance
* the spacing to use in all directions
* @return this object for further modification
*/
public GBCHelper setInsets(int distance) {
this.insets = new Insets(distance, distance, distance, distance);
return this;
}

/**
* Sets the insets of this cell.
*
* @param top
* the spacing to use on top
* @param left
* the spacing to use to the left
* @param bottom
* the spacing to use on the bottom
* @param right
* the spacing to use to the right
* @return this object for further modification
*/
public GBCHelper setInsets(int top, int left, int bottom, int right) {
this.insets = new Insets(top, left, bottom, right);
return this;
}

/**
* Sets the internal padding
*
* @param ipadx
* the internal padding in x-direction
* @param ipady
* the internal padding in y-direction
* @return this object for further modification
*/
public GBCHelper setIpad(int ipadx, int ipady) {
this.ipadx = ipadx;
this.ipady = ipady;
return this;
}

/**
* Sets the anchor.
*
* @param anchor
* the anchor value
* @return this object for further modification
*/
public GBCHelper setAnchor(int anchor) {
this.anchor = anchor;
return this;
}

/**
* Sets the cell weights.
*
* @param weightx
* the cell weight in x-direction
* @param weighty
* the cell weight in y-direction
* @return this object for further modification
*/
public GBCHelper setWeight(double weightx, double weighty) {
this.weightx = weightx;
this.weighty = weighty;
return this;
}
}

Pada listing GBCHelper diatas telah terdokumentasikan setiap methodnya, perlu dicatat untuk setiap kali kita akan menggunakan kembali object ini harus memanggil terlebih dahulu method clear() sebelum method yang lain, sekarang kita akan buat class GridBagTester.java untuk melakukan tes menggunakan class GBCHelper. Listing programnya sebagai berikut :
package brain.left.swingtest;

import java.awt.EventQueue;
import java.awt.GridBagLayout;

import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.JTextField;
import javax.swing.border.Border;
import javax.swing.border.SoftBevelBorder;

public class GridbagTester extends JFrame {
/**
*
*/
private static final long serialVersionUID = -2094745569627207471L;
private JPanel panel = new JPanel();
private JLabel label1 = new JLabel("Label 1");
private JTextField jtext1 = new JTextField(20);
private JLabel label2 = new JLabel("Label 2");
private JTextField jtext2 = new JTextField(20);
private JButton button1 = new JButton("Tombol 1");
private GBCHelper GBCH = new GBCHelper();
private JTextArea jta = new JTextArea();
JScrollPane pane = new JScrollPane(jta);

public GridbagTester() {
jta.setRows(5);
Border br = BorderFactory.createBevelBorder(SoftBevelBorder.LOWERED);
panel.setBorder(br);
panel.setLayout(new GridBagLayout());
//menambahkan komponen pada layout
//jangan lupa untuk memanggil method clear sebelum method2 yang lain
panel.add(label1, GBCH.clear().setGrid(0, 0).setAnchor(GBCHelper.EAST));
panel.add(jtext1, GBCH.clear().setGrid(1, 0).setFill(
GBCHelper.HORIZONTAL).setInsets(1, 5, 2, 1));
panel.add(label2, GBCH.clear().setGrid(0, 1).setAnchor(GBCHelper.EAST));
panel.add(jtext2, GBCH.clear().setGrid(1, 1).setFill(
GBCHelper.HORIZONTAL).setInsets(1, 5, 2, 1));
panel.add(pane, GBCH.clear().setGrid(0, 3, 4, 1).setAnchor(
GBCHelper.WEST).setFill(GBCHelper.BOTH));
panel.add(button1, GBCH.clear().setGrid(1, 4).setAnchor(GBCHelper.EAST)
.setInsets(10, 1, 5, 2));
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
add(panel);
pack();
setVisible(true);
}

public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {

@Override
public void run() {
new GridbagTester();
}

});
}
}


screenshoot hasil running programmnya sebagai berikut :



oke done!

Sunday, January 11, 2009

JAVA 2D Graphics dan Animation (Part 2) - Games Engineering

Images
Menggambar text pada screen pada tulisan sebelumnya adalah sesuatu yang menyenangkan, tetapi akan sangat menyenangkan ketika kita bisa menambahkan image pada game yang kita develop, bener ga? bener!. Sebelum memulai menggambarkan image pada screen ada baiknya jika kita mempelajari beberapa hal fundamental yang berkaitan dengan image, seperti tipe transparansi dan format file.

Transparensi
Oke sekarang bayangkan kita punya gambar seperti di bawah ini :

gambar image di atas mempunyi backgroun berwarna putih, tetapi perlu diingat bahwa background adalah bagian dari image juga, dan apakah background tersebut akan ditampilkan juga? tergantung dari transparansi image. Kita bisa menggunakan tiga tipe image tranparansi: opaque, transparent, dan translucent.
  • Opaque: Setiap pixel pada image akan ditampilkan.

  • Transparent: Setiap image juga akan ditampilakan, tetapi background putih akan dibuat transparan.

  • Translucent: Pixel dapat ditransparansi secara terpisah, seperti ketika ingin menampilkan effect ghost/hantu.


File Format
Ada dua tipe format dasar image yaitu raster dan vector. Raster image format akan ditampilkan sesuai dengan ukuran kedalaman pixel, jika diperbesar image akan pecah. Vector image akan ditampilakan secara geometri dan dapat diresize tanpa mengurangi kualitas image.
Java API tidak mempunyai built in vector format, maka kita akan memfokuskan pada raster image, jika ingin mengetahui manipulasi tentang vector image bisa memanfaatkan SVG punya apache yang disebut batik http://xml.apache.org/batik/.

Java runtime mempuyai tiga format file raster yang berbeda yaitu GIF, PNG, dan JPEG:
  • GIF, GIF image bisa opaque atau transparent, dan dapat mempunyai warna 8 bit atau bahkan kurang. Saat kita sudah mengenal format PNG dan mempunyai kemampuan seperti GIF, sehingga disarankan menggunakan PNG.

  • PNG, PNG image mempunyai semua tipe transparansi: opaque, transparent, atau translucent. PNG image bisa mempunyai kedalaman warna sampai 24 bit.

  • JPEG, JPEG image hanya bersifat 24 bit opaque. JPEG mempunyai kompresi yang tinggi untuk fotografi, tetapi bersifat lossy commpression, jadi image tidak akan sama persis dengan replika.


Membaca Image
Jadi bagaimana kita mentranslate GIF, PNG, atau JPEG file kedalam sesuatu yang dapat kita tampilkan? Dengan memanfaatkan API java Toolkit kita bisa memanggil method getImage(), yang akan memparsing file image dan akan mengembalikan image object. Contohnya sebagai berikut:
Toolkit toolkit = Toolkit.getDefaultToolkit();
Image image = toolkit.getImage(fileName);

Dengan kode diata sebernarnya image belum benar2 diload karena image akan diload pada thread yang lain, jika kita langsung menampilakan image tersebut dan image belum selesai diload maka hanya sebagian image saja yang akan ditampilkan atau malah tidak sama sekali.
Untuk mengatasi hal tersebut kita bisa memanfaatkan MediaTracker object untuk melihat apakah image sudah selesai diload, tetapi ada cara yang lebih mudah yaitu memanfaatkan ImageIcon class yang akan secara otomatis menggunkan MediaTracker dan menunggu sampai proses loading selesai. ImageIcon class di package javax.swing akan melakukan load pada image menggunakan Toolkit kemudian akan menunggu sampai proses loading selesai. Sebagai contoh sebagai berikut:
ImageIcon icon = new ImageIcon(fileName);
Image image = icon.getImage();

Oke, sekarang kita bisa mencona untuk menampilkan image dengan full-screen dengan memanfaatkan SimpleClassManager yang telah kita bahas pada key topic Full-Screen Graphics. Sekarang kita akan buat ImageTest.java dengan listing sebagai berikut:
package brain.left.games;

import java.awt.*;
import javax.swing.ImageIcon;
import javax.swing.JFrame;

public class ImageTest extends JFrame {
/**
*
*/
private static final long serialVersionUID = -4134922005163510957L;

public static void main(String[] args) {
DisplayMode displayMode;
if (args.length == 3) {
displayMode = new DisplayMode(Integer.parseInt(args[0]), Integer
.parseInt(args[1]), Integer.parseInt(args[2]),
DisplayMode.REFRESH_RATE_UNKNOWN);
} else {
displayMode = new DisplayMode(800, 600, 16,
DisplayMode.REFRESH_RATE_UNKNOWN);
}
ImageTest test = new ImageTest();
test.run(displayMode);
}

private static final int FONT_SIZE = 24;
private static final long DEMO_TIME = 10000;
private SimpleScreenManager screen;
private Image bgImage;
private Image opaqueImage;
private Image transparentImage;
private Image translucentImage;
private Image antiAliasedImage;
private boolean imagesLoaded;

public void run(DisplayMode displayMode) {
setBackground(Color.blue);
setForeground(Color.white);
setFont(new Font("Dialog", Font.PLAIN, FONT_SIZE));
imagesLoaded = false;
screen = new SimpleScreenManager();
try {
screen.setFullScreen(displayMode, this);
loadImages();
try {
Thread.sleep(DEMO_TIME);
} catch (InterruptedException ex) {
}
} finally {
screen.restoreScreen();
}
}

public void loadImages() {
bgImage = loadImage("images/tile-games/background.jpg");
opaqueImage = loadImage("images/tile-games/opaque.png");
transparentImage = loadImage("images/tile-games/transparent.png");
translucentImage = loadImage("images/tile-games/translucent.png");
antiAliasedImage = loadImage("images/tile-games/antialiased.png");
imagesLoaded = true;
// signal to AWT to repaint this window
repaint();
}

private Image loadImage(String fileName) {
return new ImageIcon(fileName).getImage();
}

public void paint(Graphics g) {
// set text anti-aliasing
if (g instanceof Graphics2D) {
Graphics2D g2 = (Graphics2D) g;
g2.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
}
// draw images
if (imagesLoaded) {
g.drawImage(bgImage, 0, 0, null);
drawImage(g, opaqueImage, 0, 0, "Opaque");
drawImage(g, transparentImage, 320, 0, "Transparent");
drawImage(g, translucentImage, 0, 300, "Translucent");
drawImage(g, antiAliasedImage, 320, 300,
"Translucent (Anti-Aliased)");
} else {
g.drawString("Loading Images...", 5, FONT_SIZE);
}
}

public void drawImage(Graphics g, Image image, int x, int y, String caption) {
g.drawImage(image, x, y, null);
g.drawString(caption, x + 5, y + FONT_SIZE + image.getHeight(null));
}
}

Benchmarking pada Image-Drawing
Oke kita akan lakukan benchmarking pada kecepatan image drawing, kita akan memodifikasi pada listing program sebelumnya menjadi ImageSpeedTest.java, idenya adalah sebagai berikut kita akan melakukan drawing secara berulang dengan periode waktu sebesar 1500 milidetik dan kemudian menghitung berapa kali image akan ditampilkan setiap second.

PERHTIAN!
Oke sebagai peringatan saja jangan lakukan hal semacam ini pada pembuatan game yang sesungguhnya, karena ini tidak akan bekerja. AWT event dispacth thread memanggil method paint(), tetapi disamping itu AWT event juga menangani keyboard, mouse dan masih banyak lagi event yang ditangani. Kita akan membahas hal ini pada pembuatan animasi.

listing ImageSpeesTest.java :
package brain.left.games;

import java.awt.*;
import javax.swing.ImageIcon;
import javax.swing.JFrame;

public class ImageSpeedTest extends JFrame {
private static final long serialVersionUID = -223039028953577654L;

public static void main(String args[]) {
DisplayMode displayMode;
if (args.length == 3) {
displayMode = new DisplayMode(Integer.parseInt(args[0]), Integer
.parseInt(args[1]), Integer.parseInt(args[2]),
DisplayMode.REFRESH_RATE_UNKNOWN);
} else {
displayMode = new DisplayMode(800, 600, 16,
DisplayMode.REFRESH_RATE_UNKNOWN);
}
ImageSpeedTest test = new ImageSpeedTest();
test.run(displayMode);
}

private static final int FONT_SIZE = 24;
private static final long TIME_PER_IMAGE = 1500;
private SimpleScreenManager screen;
private Image bgImage;
private Image opaqueImage;
private Image transparentImage;
private Image translucentImage;
private Image antiAliasedImage;
private boolean imagesLoaded;

public void run(DisplayMode displayMode) {
setBackground(Color.blue);
setForeground(Color.white);
setFont(new Font("Dialog", Font.PLAIN, FONT_SIZE));
imagesLoaded = false;
screen = new SimpleScreenManager();
try {
screen.setFullScreen(displayMode, this);
synchronized (this) {
loadImages();
// wait for test to complete
try {
wait();
} catch (InterruptedException ex) {
}
}
} finally {
screen.restoreScreen();
}
}

public void loadImages() {
bgImage = loadImage("images/tile-games/background.jpg");
opaqueImage = loadImage("images/tile-games/opaque.png");
transparentImage = loadImage("images/tile-games/transparent.png");
translucentImage = loadImage("images/tile-games/translucent.png");
antiAliasedImage = loadImage("images/tile-games/antialiased.png");
imagesLoaded = true;
// signal to AWT to repaint this window
repaint();
}

private final Image loadImage(String fileName) {
return new ImageIcon(fileName).getImage();
}

public void paint(Graphics g) {
// set text anti-aliasing
if (g instanceof Graphics2D) {
Graphics2D g2 = (Graphics2D) g;
g2.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
}
// draw images
if (imagesLoaded) {
drawImage(g, opaqueImage, "Opaque");
drawImage(g, transparentImage, "Transparent");
drawImage(g, translucentImage, "Translucent");
drawImage(g, antiAliasedImage, "Translucent (Anti-Aliased)");
// notify that the test is complete
synchronized (this) {
notify();
}
} else {
g.drawString("Loading Images...", 5, FONT_SIZE);
}
}

public void drawImage(Graphics g, Image image, String name) {
int width = screen.getFullScreenWindow().getWidth()
- image.getWidth(null);
int height = screen.getFullScreenWindow().getHeight()
- image.getHeight(null);
int numImages = 0;
g.drawImage(bgImage, 0, 0, null);
long startTime = System.currentTimeMillis();
while (System.currentTimeMillis() - startTime < TIME_PER_IMAGE) {
int x = Math.round((float) Math.random() * width);
int y = Math.round((float) Math.random() * height);
g.drawImage(image, x, y, null);
numImages++;
}
long time = System.currentTimeMillis() - startTime;
float speed = numImages * 1000f / time;
System.out.println(name + ": " + speed + " images/sec");
}
}

Program ini tidaklah menjadi patokan, karena semuanya tergantung dari jenis image yang digunakan serta spesifikasi komputer yang digunakan. Pada saat pembuatan program ini saya menggunakan komputer dengan spesifikasi processor Intel Centrino 1.7G, VGA Intel 64M, Resolusi layar 800x600 dan kedalaman bit 16, hasilnya adalah sebagai berikut:
Opaque: 10985.074 images/sec
Transparent: 239.68042 images/sec
Translucent: 260.98535 images/sec
Translucent (Anti-Aliased): 264.31424 images/sec

Dapat disimpulkan dari hasil benchmarking bahwa image dengan opaque paling cepat..!!, siip saatnya sekarang melangkah lebih lanjut kita akan membahas tentang animasi.

Animation
Animasi yang pertama adalah cartoon-style animation. Animasi jenis ini akan menampilkan urutan gambar secara bergantian. Sebagai contoh kita akan buat animasi dengan urutan sebagai berikut :

Urutan tersebut kita analogikan sebagai urutan frame, setiap frames akan ditampilkan dengan waktu tertentu, tetapi frame akan ditampilakan dengan waktu yang berbeda-beda. Sebagai contoh, misal frame pertama ditampilkan selama 200milisecond, frame kedua akan ditampilkan 75milisecond, dan seterusnya.
Kita akan menampilkan dengan urutan sebagai berikut :

Oke sekarang kita implementasikan urutan animasi tersebut pada programming. Sekarang kita akan membuat class Animation yang mempunyai tiga method penting: addFrame(), update(), dan getImage(). Method addFrame digunakan untuk menambahkan image kedalam animasi dengan waktu dalam milisecond. Method update() akan memberitahukan bahwa bahwa waktu penampilan telah selesai. Yang terakhir getImage() digunakan untuk mendapatkan image yang seharusnya akan ditampilakan setelah suatu waktu terlewati.
Listing Animation.java adalah sebagai berikut:
package brain.left.games;

import java.awt.Image;
import java.util.ArrayList;

/**
* Animation class digunakan untuk menangani urutan image yang akan
* ditampilakan pada frame untuk waktu tertentu.
*/
public class Animation {
private ArrayList frames;
private int currFrameIndex;
private long animTime;
private long totalDuration;

/**
* Konstruktor untuk Animasi.
*/
public Animation() {
frames = new ArrayList();
totalDuration = 0;
start();
}

/**
* Menambahkan image ke frame dengan waktu tertentu
*/
public synchronized void addFrame(Image image, long duration) {
totalDuration += duration;
frames.add(new AnimFrame(image, totalDuration));
}

/**
* Memulai animasi mulai dari awal frame.
*/
public synchronized void start() {
animTime = 0;
currFrameIndex = 0;
}

/**
* Melakukan update pada image pada animasi.
*/
public synchronized void update(long elapsedTime) {
if (frames.size() > 1) {
animTime += elapsedTime;
if (animTime >= totalDuration) {
animTime = animTime % totalDuration;
currFrameIndex = 0;
}
while (animTime > getFrame(currFrameIndex).endTime) {
currFrameIndex++;
}
}
}

/**
* Mendapatkan image yang sedang ditampilkan pada animasi yang sedang berlangsung,
* akan mengembalikan nilai null jika image tidak ada
*/
public synchronized Image getImage() {
if (frames.size() == 0) {
return null;
} else {
return getFrame(currFrameIndex).image;
}
}

private AnimFrame getFrame(int i) {
return frames.get(i);
}

private class AnimFrame {
Image image;
long endTime;

public AnimFrame(Image image, long endTime) {
this.image = image;
this.endTime = endTime;
}
}
}

Active Rendering
Untuk mengimplementasikan animation, kita harus secara terus-menerus melakukan update secara eficient. Sebelum melakukan rendering dengan memanggil paint(), kita harus memanggil method repaint() terlebih dahulu untuk memberikan signal AWT event dispatch thread untuk me-repaint screen, tetapi ini akan menyebabkan delay karena AWT thread mungkin sedang melakukan hal lain.
Cara lain untuk melakukan active rendering, active rendering akan melakukan drawing secara langsung pada screen di main thread. Dengan cara ini kita dapat mengkontrol yang sedang digambarkan pada screen saat ini, dan lebih mempersingkat kode.
Untuk menggunakan teknik active rendering, kita gunakan method getGraphic() dari class Componen untuk mendapatkan graphic contex.
Graphics g = screen.getFullScreenWindow().getGraphics();
draw(g);
g.dispose();

Simplekan ???, jangan lupa untuk memnggil dispose() setelah selesai melakukan draw(g), karena dengan memanggil dispose() akan melepaskan resourse yang sudah tidak dipakai.

Animasi Loop
Sekarang kita akan gunakan active rendering untuk melakukan draw pada sebuah loop. Animasi loop mempunyai step sebagai berikut:
  • Melakukan update pada animasi

  • Melakukan draw pada screen

  • Pilihan optional bisa menggunakan sleep untuk periode tertentu

  • Mulai dari step pertama kembali


jika dikodingkan maka loop akan menjadi sebagai berikut:
while (true) {
// Melakukan update pada animasi
updateAnimations();
// Melakukan draw pada screen
Graphics g = screen.getFullScreenWindow().getGraphics();
draw(g);
g.dispose();
// Optionl ---> melakukan sleep pada periode tertentu
try {
Thread.sleep(20);
}
catch (InterruptedException ex) { }
}

Pada kenyataannya loop pada animasi tidak akan berkjalan secara terus menerus, pada contoh yang akan kita buat kali ini, animasi akan berhenti setelah beberapa detik.
Listing program AnimationTest sebagai berikut:
package brain.left.games;

import java.awt.*;
import javax.swing.ImageIcon;
import javax.swing.JFrame;

public class AnimationTest1 {
public static void main(String args[]) {
DisplayMode displayMode;
if (args.length == 3) {
displayMode = new DisplayMode(Integer.parseInt(args[0]), Integer
.parseInt(args[1]), Integer.parseInt(args[2]),
DisplayMode.REFRESH_RATE_UNKNOWN);
} else {
displayMode = new DisplayMode(800, 600, 16,
DisplayMode.REFRESH_RATE_UNKNOWN);
}
AnimationTest1 test = new AnimationTest1();
test.run(displayMode);
}

private static final long DEMO_TIME = 5000;
private SimpleScreenManager screen;
private Image bgImage;
private Animation anim;

public void loadImages() {
// load images
bgImage = loadImage("images/tile-games/background.jpg");
Image player1 = loadImage("images/tile-games/hero1.png");
Image player2 = loadImage("images/tile-games/hero2.png");
Image player3 = loadImage("images/tile-games/hero3.png");
// create animation
anim = new Animation();
anim.addFrame(player1, 250);
anim.addFrame(player2, 150);
anim.addFrame(player1, 150);
anim.addFrame(player2, 150);
anim.addFrame(player3, 200);
anim.addFrame(player2, 150);
}

private Image loadImage(String fileName) {
return new ImageIcon(fileName).getImage();
}

public void run(DisplayMode displayMode) {
screen = new SimpleScreenManager();
try {
screen.setFullScreen(displayMode, new JFrame());
loadImages();
animationLoop();
} finally {
screen.restoreScreen();
}
}

public void animationLoop() {
long startTime = System.currentTimeMillis();
long currTime = startTime;
while (currTime - startTime < DEMO_TIME) {
long elapsedTime = System.currentTimeMillis() - currTime;
currTime += elapsedTime;
// update animation
anim.update(elapsedTime);
// draw to screen
Graphics g = screen.getFullScreenWindow().getGraphics();
draw(g);
g.dispose();
// take a nap
try {
Thread.sleep(20);
} catch (InterruptedException ex) {
}
}
}

public void draw(Graphics g) {
// draw background
g.drawImage(bgImage, 0, 0, null);
// draw image
g.drawImage(anim.getImage(), 0, 0, null);
}
}

Pada contoh program diatas animasi akan berjalan tetapi akan kelihatan ficker pada animasi, untuk mengatasi masalah ini maka akan dibahas pada Get Rid of Flicker and Tearing.

Friday, January 9, 2009

JAVA 2D Graphics dan Animation (Part 1) - Games Engineering

Key topics pada pembahasan:
  • Full-screen Graphics
  • Images
  • Getting Rid of Flicker and Tearing
  • Simple Effects
  • Summary
Full-screen Graphics
Pembuatan games akan lebih interaktif dan menarik ketika kita menambahkan adanya user interface(graphic) dan animation. Tetapi dalam pembuatannya kita perlu membuat spesifikasi2 seperti berapa resolusi yang digunakan dan juga jangan sampai mengabaikan masalah refresh rate pada monitor.
Oke sekarang kita akan langsung melihat bagaimana hardware displai bekerja sebelum menginjak ke masalah programming. Secara garis besar ada dua bagian pada display hardware : video card dan monitor. Video card merepresentasikan apa yang ada di memorinya dan mempunyai fungsi2 untuk melakukan modifikasi pada apa yang ditampilkan. Memori card juga bekerja dibackground untuk mempush memorinya ke monitor. Sedangkan monitor akan menampilkan informasi yang diberikan oleh video card.

Screen Layout
Screen pada monitor sebenarnya akan dibagi dalan pixel warna kecil2 yang mempunyai ukuran sama. Perkalian dari pixel horizontal dan vertikal akan membuat sebuah ukuran screen yang kita sebut sebagai resolusi.
Screen akan dimulai dari pojok kiri atas, yang diperlihatkan pada gambar 1 dibawah ini. Pixel akan dimasukkan mulai dari pojok kiri atas, kemudian ke kanan dan seterusnya samapi ke pojok paling kiri bawah. Area pada screen bisa kita akses perdasarkan posisi koordinat (x, y), x merepresentasikan horizontal pixel dan y merepresentasikan vertical pixel.

gambar 1 ilustrasi pixel dalam screen 800x600.

Resolusi yang kita manipulasi sangat bergantung pada kemampuan video card dan monitor. Umumnya resolusi yang sering di pakai adalah 640x480, 800x600, 1024x768, 1280x1024.
Secara umum monitor dan televisi mempunyai size ratio 4:3. Yang berarti tinggi dari monitor adalah 3/4 dari ukuran lebarnya. Beberapa monitor yang baru dibuat dalam wides creen dengan display size retio 3:2 atau 16:10 dan 16:9(untuk wide screen movie).

Warna Pixel dan Kedalaman Bit
Warna primer ada tiga merah, kuning, dan biru. Mungkin kita masih ingat dengan percampuran warna, "kuning + biru = hijau." Idenya adalah kita bisa memanipulasi warna-warna tersebut menjadi warna yang lain yang kita inginkan. Model semacam ini sering disebut sebagai subtractive color model, dan percampuran semua warna akan mejadi putih.
Ini sama sebagaimana monitor komputer dan televisi bekerja. Monitor mengkombinasikan merah, biru dan hijau untuk membuat banyak warna lain.
Warna2 pada monitor dapat ditampilkan tergantung pada kedalaman bit-nya. Secara umum kedalaman bit yang sering digunakan adalah 8,15, 16, 24, dan 32 bit.
  • Warna 8-bit mempunyai 2^8=256 warna. Hanya 256 warna yang bisa ditampilkan pada suatu waktu.
  • Warna 15-bit mempunyai 5 bit untuk merah, biru dan hijau, totalnya ada 2^15=32,768 warna.
  • Warna 16-bit mempunyai 5 bit untuk merah dan biru, dan 6 bit untuk hijau, totalnya ada 2^16=65,536 warna.
  • Waran 24-bit mempunyai 8 bit untuk merah, hijau dan biru, total ada 2^24=16,777,216 warna.
  • Warna 32-bit mempunyai warna sama dengan 24 bit, tetapi ditambah dengan extra 8 bit sebagai padding.
Video card saat ini sudah mendukung 8-, 16-, dan 32-bit. Karena disesuikan dengan mata manusia dapat melihat sampai 10 miliar warna, 24-bit ideal digunakan. 16-bit sedikit lebih cepat dari pada 24-bit karena data yang ditransfer juga lebih sedikit, tetapi kualitas warnanya tidak akurat.

Refresh Rate
Bagaimana monitor bisa menampilkan pixel seperti gambar beneran ?, sebenarnya setiap pixel akan menghilang setelah beberapa milisecond. Untuk tetap menjaga agar bisa tetap ditampilkan, maka monitor akan secara berkelanjutan melakukan refresh pada display biar tidak menghilang. Kecepatan melakukan refresh ini yang disebut sebagai refresh rate. Refresh rate mempunyai satuan Hertz (Hz), yang berarti cycles per second. Refresh rate antara 75Hz dan 85Hz sesuai untuk mata manusia.

Switching Display ke Mode Full-Screen
Sekarang kita sudah mengetahui tentang konsep resolusi, kedalaman warna, dan refresh rate, sekarang tinggal masuk koding. Di dalam java kita membutuhkan beberapa object untuk merubah ke mode full-screen, sebagai berikut:
  • Window object. Window object adalah abstraksi dari apa yang ditampilkan ke layar, anggaplah sebuah kanvas untuk menggambar. Kita akan memanfaatkan JFrame, yang merupakan subclass dari Windows class dan dapat juga digunakan untuk membuat applikasi windowed.
  • DisplayMode object. DisplayMode object digunakan untuk spesifikasi resolusi, kedalaman bit, dan refresh rate untuk switch display.
  • GraphicsDisplay object. GraphicsDislay object digunakan untuk merubah display mode dan melihat display properti. Bayangkan saja ini sebagai video card. GraphicsDisplay object diperoleh dari GraphicsEnvironment object.

Berikut adalah contoh untuk melakukan switch ke mode full-screen:
JFrame window = new JFrame();
DisplayMode displayMode = new DisplayMode(800, 600, 16, 75);

// mendapatkan GraphicsDevice
GraphicsEnvironment environment = GraphicsEnvironment.getLocalGraphicsEnvironment();
GraphicsDevice device = environment.getDefaultScreenDevice();
// menggunakan JFrame untuk full screen window
device.setFullScreenWindow(window);
// mengubah display mode
device.setDisplayMode(displayMode);
Setelah itu jika ingin mengembalikan pada resolusi semula, set full-screen window dengan nilai null:
device.setFullScreenWindow(null);
Kadang pada sistem tertentu tidak mengijinkan kita untuk mengubah mode display, dan saat kita melakukan pemanggilan pada setDisplayMode() akan menampilakan IllegalArgumentException.

Oke sekarang kita akan membuat class wrapper untuk memudahkan dalam pembuatan mode full-screen simpan dengan nama SimpleScreenManager.java, listing programmnya sebagai berikut:
import java.awt.*;
import javax.swing.JFrame;
/**
SimpleScreenManager class menangani inisialisasi dan
menampilkan mode full-screen.
*/
public class SimpleScreenManager {
private GraphicsDevice device;
/**
Membuat SimpleScreenManager object.
*/
public SimpleScreenManager() {
GraphicsEnvironment environment =
GraphicsEnvironment.getLocalGraphicsEnvironment();
device = environment.getDefaultScreenDevice();
}
/**
Memasukkan full screen mode and mengubah display mode.
*/
public void setFullScreen(DisplayMode displayMode,
JFrame window)
{
window.setUndecorated(true); // menghilangkan border
window.setResizable(false);
device.setFullScreenWindow(window);
if (displayMode != null &&
device.isDisplayChangeSupported())
{
try {
device.setDisplayMode(displayMode);
}
catch (IllegalArgumentException ex) {
// illegal mode untuk device
}
}
}
/**
Mengembalikan window yang sedang digunakan pada full screen mode.
*/
public Window getFullScreenWindow() {
return device.getFullScreenWindow();
}
/**
Mengembalikan display mode semula.
*/
public void restoreScreen() {
Window window = device.getFullScreenWindow();
if (window != null) {
window.dispose();
}
device.setFullScreenWindow(null);
}
}
Selanjutnya kita akan membuat kode untuk melakukan test menggunakan SimpleScreenManager, yang menerima parameter berupa ukuran resolusi yang digunakan dan kedalaman bit. Untuk menjalankannya dengan cara sebagai berikut :
java FullScreenTest 1024 768 32 ------> berarti merubah ke fullscreen mode menjadi 1024x768 dengan kedalaman bit 32-bit.

Perlu diingat jangan menggunakan resolusi yang tidak disupport oleh system, karen program tidak akan jalan. Berikut adalah kode program untuk melakukan test FullScreenTest.java :
import java.awt.*;
import javax.swing.JFrame;
public class FullScreenTest extends JFrame {
public static void main(String[] args) {
DisplayMode displayMode;
if (args.length == 3) {
displayMode = new DisplayMode(
Integer.parseInt(args[0]),
Integer.parseInt(args[1]),
Integer.parseInt(args[2]),
DisplayMode.REFRESH_RATE_UNKNOWN);
}
else {
displayMode = new DisplayMode(800, 600, 16,
DisplayMode.REFRESH_RATE_UNKNOWN);
}
FullScreenTest test = new FullScreenTest();
test.run(displayMode);
}
private static final long DEMO_TIME = 5000;
public void run(DisplayMode displayMode) {
setBackground(Color.blue);
setForeground(Color.white);
setFont(new Font("Dialog", Font.PLAIN, 24));
SimpleScreenManager screen = new SimpleScreenManager();
try {
screen.setFullScreen(displayMode, this);
try {
Thread.sleep(DEMO_TIME);
}
catch (InterruptedException ex) { }
}
finally {
screen.restoreScreen();
}
}
public void paint(Graphics g) {
g.drawString("Hello World!", 20, 50);
}
}
Berikut adalah hasil screenshot running programnya :


Anti Aliasing
Kita bisa memanfaatkan anti aliasing untuk membuat tulisan "Hello World!" menjadi lebih smooth, karena dengan anti aliasing kita bisa melakukan blending antara text dengan background. Ubah kode menjadi sebagai berikut:
public void paint(Graphics g) {
if (g instanceof Graphics2D) {
Graphics2D g2 = (Graphics2D)g;
g2.setRenderingHint(
RenderingHints.KEY_TEXT_ANTIALIASING,
RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
}
g.drawString("Hello World!", 20, 50);
}
Mode Mana yang Sebaiknya digunakan?
Banyak pilihan resolusi yang dapat digunakan, tetapi yang mana yang seharusnya digunakan untuk running game yang kita buat?
Oke, sebaiknya game yang kita develop berjalan minimal pada dua resolusi, sehingga player akan menentukan sendiri resolusi mana yang cocok digunakan.
Jika memungkinkan, inisialisasi game yang kita develop sesuai dengan resolusi monitor yang digunakan, sehingga game display akan ditampilkan dengan bagus. Penggunaan kedalaman warna 16-, 24-, atau 32-bit warna adalah ide yang bagus. 16-bit warna akan berjalan sedikit lebih cepat, tetapi gunakanlah resolusi yang lebih besar jika menginginkan representasi warna yang akurat. Gunakan refresh rate antara 75Hz dan 85Hz karena sesuai dengan mata manusia.

Reference :
  • http://java.sun.com
  • David Brackeen, Bret Barker, Laurence Vanhelsuwé, Developing Games in Java, 2003, New Riders Publishing

Wednesday, January 7, 2009

Java Thread - Games Engineering


original image link http://static.howstuffworks.com/gif/best-games-never-made-6.jpg

Games engineering sangatlah menarik untuk dibahas, karena dalam pembuatan games sendiri diperlukan gabungan dari berbagai disiplin ilmu mulai dari aspek sosial sampai aspek eksak. Multithreading sangat diperlukan dalam games engineering, karena dalam sebuah games diperlukan adanya pemrosesan yang berjalan bersama2 misalnya ketika kita membuat games realtime strategi seperti red alert, berapa banyak thread yang dibutuhkan ??, apalagi kalo membuat games seperti football manager (my favourite) dimana setiap club bahkan setiap pemain akan mempunyai perubahan perilaku, skill, usia, mood, technique dll dalam siklus tertentu. Dalam dunia nyata-pun tak dapat dipisahkan, simpelnya saja nih ... ketika kita browsing browser membuka banyak tab dan tiap2 tab mengkses halaman web yang berbeda itu juga merupakan proses multithreading, sedangkan disisi server seperti webserver akan membuat thread jika ada request yang masuk.
Dalam java untuk membuat thread ada 3 cara :

1. Menurunkan class Thread (Extends)
2. Mengimplementasikan interface Runnable
3. Menggunakan anonymous inner class

Download artikel dalam pdf

Sekarang kita akan bahas satu persatu bagaimana ketiga cara itu digunakan :
ex, dengan menurunkan class thread :

public class MyThread extends Thread {
public void run() { System.out.println("Ini threading lho !!!");
}
}
Thread mythread = new MyThread();
mythread.start();


ex, dengan mengimplementasikan interface Runnable:
dengan menggunakan cara ini mempunyai keuntungan class yang kita buat masih bisa menurunkan class yang lain, karena java tidak mengijinkan multiple inharitance
public class MyThread extends SomeOthersClass implements Runnable {
public MyThread() {

}
public void run() {
System.out.println("threading pake implementd Runnable");
}
}

Thread mythread = new Thread(new MyThread);
mythread.start();


ex, menggunakan anonymous class:
dengan ini kita tidak perlu untuk menurunkan class Thread dan mengimplementasikan interface Runnable.

new Thread() {
public void run() {
System.out.println("menggunakan anonymous class");
}
}.start();


kerugian menggunakan cara ini adalah membuat code yang kita bikin menjadi sulit untuk dibaca dan dipahami.
gunakan method join agar thread yang kita gunakan masuk keantrian dan mengunggu sampai thread yang lain selesai.

myhtread.join();

method ini sangat berguna ketika kita membuat games dan player ingin keluar dari permainan, untuk memastikan bahwa semua thread telah selesai sebelum menjalankan cleanup. Kita juga melakukan sleep untuk thread yang sedang berjalan dengan presisi waktu dalam milidetik.
mythread.sleep(1000);


Sinkronisasi
Misalkan kita mau membuat maze game (games untuk mencari jalan keluar). Thread manapun bisa mengubah posisi pemain/player, dan thread manapun bisa melakukan pemeriksaan apakah ada pemain yang sudah menemukan pintu keluar. Untuk mempermudah mari kita lihat pada ilustrasi berikut, kita asumsikan bahwa jalan keluar/exit berada di x=0, y=0 :
public class Maze {
private int playerX;
private int playerY;

public boolean isAtExit() {
return (playerX==0 && playerY==0);
}

public void setPosition(int x, int y) {
playerX=x;
playerY=y;
}
}

secara garis besar code diatas tidak akan bermasalah, tapi bagai mana jika terjadi preemtive (banyak thread yang mengakses dan mana yang didahulukan untuk dapat mengubah resource). Misalkan kita ambil sekenario sebagai berikut, pamain berpindah tempat dari (1,0) ke (0,1):
1.Dimulai dari posisi (1,0), variabel playerX=1 dan playerY=0
2.Thread A memanggil setPosition(0,1)
3.Ketika line playerX=x dieksekusi maka playerX bernilai 0
4.Tiba-tiba Thread B melakukan pengecekan pada isAtExit() sebelum A sempat mengubah nilai playerY maka B akan mendapatkan kembalian bernilai true, karena playerX dan playerY sedang dalam keadaan yang sama yaitu bernilai 0.

sekarang kita akan melakukan sinkronisasi untuk mencegah terjadinya hal diatas. Kodenya akan berubah menjadi sebagai berikut :

public class Maze {
private int playerX;
private int playerY;
public synchronized boolean isAtExit() {
return (playerX == 0 && playerY == 0);
}
public synchronized void setPosition(int x, int y) {
playerX = x;
playerY = y;
}
}

ketika JVM mengeksekusi method yang beratribut sinchronized, maka akan terjadi ackquire lock pada method tersebut dan hanya akan mengijinkan untuk dieksekusi oleh satu object pada suatu waktu.
Jadi jika suatu sinchronized method belum selesai dieksekusi maka method sinchronized lain tidak akan bisa dieksekusi.

public synchronized void setPosition(int x, int y) {
playerX = x;
playerY = y;
}


code diatas bisa juga ditulis dalam bentuk berikut:
public void setPosition(int x, int y) {
synchronized(this) {
playerX = x;
playerY = y;
}
}


pada code mempunyai jumlah bytecode yang lebih banyak. Sinkronisasi object seperti kode kedua diatas berguna jika kita mengijinkan lebih dari satu lock, dan tidak membutuhkan untuk sinkronisasi untuk methodnya.

Lock bisa dilakukan pada object apapun kecuali pada tipe primitive, berikut adalah contoh bagaimana lock dilakukan pada object :

Object myLock = new Object();
...
synchronized (myLock) {
...
}


Saat bagaimanakah diperlukan sinkronisasi ?
Jawabannya adalah setiap waktu ketika ada dua atau lebih thread melakukan akses pada suatu object/field.

Hal yang perlu diingat jangan pernah melakukan oversinkronisasi (melakukan sinkronisasi pada object/method/field yang telah disinkronisasi). Sebagai contoh jangan lakukan sinkronisasi pada sebuah method jika hanya field tertentu saja yang akan disinkronisasi dalam method tersebut. Sebagai contoh method berikut, lakukan sinkronisasi pada block yang diperlukan saja.

public void myMethod() {
synchronized(this) {
// code that needs to be synchronized
}
// code that is already thread-safe
}


jangan lakukan sinkronisasi pada method yang hanya menggunakan local variable, karena local variable akan ditaruh di stack, sedangkan thread punya stack untuk tiap2 thread, jadi tidak perlu untuk dilakukan sinkronisasi. Berikut adalah contoh method yang tidak perlu dilakukan sinkronisasi karena hanya menggunakan variable local.

public int square(int n) {
int s = n * n;
return s;
}


jika kita tidak yakin thread mana yang sedang mengakses kode kita, kita bisa mendapatkan nama dari thread tersebut dengan :
Thread.currentThread().getName();

Perlu diwaspadai juga adanya deadlock, deadlock adalah adanya thread yang tidak bisa melanjutkan proses karena thread saling menunggu thread lain sampai melepaskan resource. Misalkan pada contoh berikut :
1. Thread A acquire lock 1.
2. Thread B acquire lock 2.
3. Thread B menunggu sampai lock 1 dilepaskan.
4. Thread A menunggu sampai lock 2 dilepaskan.

Dapat kita lihat dari process diatas kedua thread saling menunggu samapai suatu waktu yang tidak bisa ditentukan. Kita bisa mengatasinya dengan cara melakukan sinkronisasi yang tepat sesuai dengan urutannya.

Menggunakan wait() dan notify()
Misalkan kita ambil sekenario sebagai berikut, ada dua thread yang akan saling berkomunikasi satu sama lain, sebagai contoh ada thread A menunggu sampai thread B mengirimkan pesan :

// Thread A
public void waitForMessage() {
while (hasMessage == false) {
Thread.sleep(100);
}
}
// Thread B
public void setMessage(String message) {
...
hasMessage = true;
}


kode diatas bukan suatu contoh yang baik, karena thread A melakukan pengecekan setiap 100 milisecond atau 10 kali dalam satu detik. Thread A dapat oversleep dan terlambat dalam mendapatkan pesan.
Alangkah lebih baik jika A idle sampai ada notifikasi dari B bahwa pesan sudah bisa dikonsumsi, dan ini bisa dilakukan dengan pasangan method wait() dan notify().

Method wait() digunakan didalam blok synchronized. Ketika method wait() dieksekusi, lock akan dilepaskan dan menunggu sampai ada notifikasi.
Method notify() juga digunakan didalam block synchronized. Method notify akan memberikan notifikasi pada thread yang menunggu pada lock yang sama. Jika ada banyak thread yang menunggu maka hanya akan ada satu notifikasi dan akan dipilih satu thread secara acak. Berikut adalah kode yang telah diperbaiki :

// Thread A
public synchronized void waitForMessage() {
try {
wait();
}
catch (InterruptedException ex) { }
}
// Thread B
public synchronized void setMessage(String message) {
...
notify();
}

jika kita ingin memberika notifikasi untuk semua thread yang sendang menunggu kita bisa menggunakan notifyAll(), method wait() juga menerima parameter dalam milisecond sebagai waktu tunggu, misalnya kita ingin memberikan timeout sampai 100milisecond maka kita bisa menggunakan wait(100).
Method wait(), notify(), dan notifyAll() merupakan method dari class object, sehingga semua java object mempunyai method2 tersebut.

Kapan kita seharusnya menggunakan thread ?
Jadi begini dari pendekatan games, ketika games play loading untuk kenyamanan pengguna sebaiknya ketika loading dibuatkan thread sendiri sehingga player tidak menyangka bahwa gamesnya sedang ngehang. Kalo dari pendekatan lain sebenarnya juga untuk kenyamanan dan optimasi, nyaman untuk pengguna karena pengguna merasa menggunakan program yang cepet loadingnya (tricky), optimal karena bisa memanfaatkan resource CPU yang belum termanfaatkan.

Sum it up
Oke dengan informasi thread yang telah dibahas sebelumnya, mari kita buat sesuatu yang berguna yaitu thread pool. Thread pool merupakan sebuah group dari thread yang didesain untuk mengeksekursi tugas yang bermacam-macam. Pada thread pool kita bisa memilih jumlah dari thread didalam pool dan menjalankan task yang didefinisikan sebagai Runnable. Berikut adalah contoh menggunakan ThreadPool dengan membuat 8 thread dalam pool, menjalankan task sederhana dan kemudian menunggu samapai task selesai dijalankan.

ThreadPool myThreadPool = new ThreadPool(8); myThreadPool.runTask(new Runnable() {
public void run() {
System.out.println("Do something cool here.");
}
});
myThreadPool.join();


Method runTask() akan dijalankan. Jika semua thread di dalam pool sedang sibuk menproses task, ketika memanggil runTask() akan memasukkan task kedalam antrian sampai ada thread yang mengeksekusinya. Berikut adalah kode ThreadPool.java :

import java.util.LinkedList;
/**
A thread pool is a group of a limited number of threads that are used to execute tasks.
*/

public class ThreadPool extends ThreadGroup {
private boolean isAlive;
private LinkedList taskQueue;
private int threadID;
private static int threadPoolID;

/**
Creates a new ThreadPool.
@param numThreads The number of threads in the pool.
*/

public ThreadPool(int numThreads) {
super("ThreadPool-" + (threadPoolID++));
setDaemon(true);
isAlive = true;
taskQueue = new LinkedList();
for (int i=0; i

Tasks start execution in the order they are received.
@param task The task to run. If null, no action is taken.
@throws IllegalStateException if this ThreadPool is already closed.
*/

public synchronized void runTask(Runnable task) {
if (!isAlive) {
throw new IllegalStateException();
}
if (task != null) {
taskQueue.add(task);
notify();
}
}

protected synchronized Runnable getTask() throws InterruptedException
{
while (taskQueue.size() == 0) {
if (!isAlive) {
return null;
}
wait();
}
return (Runnable)taskQueue.removeFirst();
}

/**
Closes this ThreadPool and returns immediately.
All threads are stopped, and any waiting tasks are not executed.
Once a ThreadPool is closed, no more tasks can be run on this ThreadPool.
*/

public synchronized void close() {
if (isAlive) {
isAlive = false;
taskQueue.clear();
interrupt();
}
}

/**
Closes this ThreadPool and waits for all running threads to finish.
Any waiting tasks are executed.
*/

public void join() {
// notify all waiting threads that this ThreadPool is no
// longer alive
synchronized (this) {
isAlive = false;
notifyAll();
}

// wait for all threads to finish
Thread[] threads = new Thread[activeCount()];
int count = enumerate(threads);
for (int i=0; i



Sekarang kita akan mencoba untuk melakukan test pada ThreadPool class, berikut adalah kode untuk melakukan test yaitu ThreadPoolTest class.
Berikut adalah cara untuku menjalankan ThreadPoolTest :
java ThreadPoolTest 8 4

8 merupakan jumlah task yang akan dijalankan, 4 adalah jumlah thread yang akan dijalankan. Berikut kode ThreadPoolTest.java :

public class ThreadPoolTest {
public static void main(String[] args) {
if (args.length != 2) {
System.out.println("Tests the ThreadPool task.");
System.out.println(
"Usage: java ThreadPoolTest numTasks numThreads");
System.out.println(
" numTasks - integer: number of task to run.");
System.out.println(
" numThreads - integer: number of threads " +
"in the thread pool.");
return;
}
int numTasks = Integer.parseInt(args[0]);
int numThreads = Integer.parseInt(args[1]);
// create the thread pool
ThreadPool threadPool = new ThreadPool(numThreads);
// run example tasks
for (int i=0; i


oke all thing are finished.