Implementasi Game/Animation Loop di Android Bagian 2

4 August 2013 at 00:26 | Posted in android | 1 Comment

Pada posting sebelumnya, telah dibahas implementasi game loop di Android.  Tetapi implementasi tersebut masih memiliki kelemahan. JIka jumlah objek banyak sehingga proses update posisi membutuhkan waktu lama,  ditambah lagi delay yang fix 0.2 detik maka loop akan berlangsung lebih lama dari seharusnya. Kecepatan animasi juga dapat berbeda untuk berbagai smartphone.

Ada dua cara untuk memperbaiki: pertama delay dilakukan secara dinamik, semakin lambat smartphone, semakin lambat loop maka sleep juga akan semakin sebentar. Yang kedua, jika loop membutuhkan waktu lebih lama dari minimal waktu yang ditentukan, maka untuk mengejar ketertinggalan update posisi akan dilakukan tanpa ditampilkan (hanya updateposisi tanpa draw). Penjelasan lebih rinci dapat dilihat di http://obviam.net/index.php/the-android-game-loop/   Kode dibawah juga saya adaptasi dari contoh pada web tersebut.  Berikut kode-nya, banyak komentar yang saya tambahkan pada kode ini, semoga bisa lebih memperjelas.

import com.example.animasicanvas2.R;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.util.Log;
import android.view.SurfaceHolder;

//menggunakan runnable, karena hanya run yang dibutuhkan untuk dioverride
//baca: http://manikandanmv.wordpress.com/tag/extends-thread-vs-implements-runnable/
//tentang bedanya extends thread vs runnable

//sebagian kode diambil dari : http://obviam.net/index.php/the-android-game-loop/

public class GameRunnable implements Runnable {

	 //TAMBAHAN==========

	// nilai frame per detik (fps) yang diinginkan
	// jika 25 FPS artinya satu loop yang berisi update+draw akan dieksekusi setiap 1/25 detik
	private final static int 	MAX_FPS = 25;

	// frame period, waktu satu loop dieksekusi dalam satuan mili detik (ms)
	// jadi kalau 25 FPS, maka frame perode adalah 1/25 detik atau 1000/25 ms = 40ms
	private final static int	FRAME_PERIOD = 1000 / MAX_FPS;

	// jumlah frame yang boleh dilewat saat proses update posisi terlalu lama
	// Contoh: untuk 25 FPS, satu loop berarti diekkseskusi 40ms
	// misalnya jumlah objek yang harus diupdate posisinya banyak sehingga
	// melewati 40ms, maka akan dilakukan updateposisi *TANPA* proses draw (di-skip).
	// kenapa spt itu? karena lebih baik FPS turun daripada posisi objek berantakan.
	// konstanta berikut menyatakan berapa frame proses draw boleh di-skip
	// detilnya silahkan lihat method run()
	private final static int	MAX_FRAME_SKIPS = 5;

	 //jika true, loop di run() berakhir
	 boolean mRun=false;
	 float posX=10;
	 float posY=100;
	 Bitmap bmp;
	 private Paint cat = new Paint();

	 //akses ke surface dan canvas
	 private SurfaceHolder mSurfaceHolder;

	 //akses ke context (untuk ambil resource)
	 Context mContext;

	 long lastTick;

	 private void updatePosisi(long tick) {
		 //1 tick = 1/25 detik (untuk 25FPS)
		 //update akan dilakukan jika sudah berselang 1 detik dari update terakhir
		 if (tick-lastTick > 25) {
			 //geser ke kiri dan kalau sudah max, kembali ke awal
			 posX= posX+10;
			 if (posX>200) {
				 posX=10;
			 }
			 lastTick = tick;
		 }
	 }

	 //siapkan resources berupa bitmap,
	 //ambil bitmap icon launcher supaya mudah
	 public GameRunnable(SurfaceHolder vSurfaceHolder, Context vContext) {
		  mSurfaceHolder = vSurfaceHolder;
		  mContext = vContext;
		  Resources res = mContext.getResources();
		  bmp = BitmapFactory.decodeResource(res, R.drawable.ic_launcher);
	 }

	 //bersihkan layar dan gambar objek
	 public void doDraw(Canvas c) {
		//canvas perlu dicek apakah null, karena pada saat
		 //user pindah ke activity lain (user tekan home), c bisa
		 //berisi null (proses shutdown belum selesai dan
		 //thread masih jalan, tapi canvas sudah didestroy)
		 if (c!=null) {
			//clear screen
			c.drawColor(Color.WHITE);
			//gambar bitmap
			c.drawBitmap(bmp,posX,posY,cat);
		}
	 }

	 @Override
     public void run() {

		 //TAMBAHAN
		 // waktu mulai awal loop
		 long beginTime;

		 // waktu real yang dibutuhkan untuk loop
		 long timeDiff;

		 // waktu tidur, misal FPS 25, berarti 1 loop = 40ms. Jika timeDiff = 30 ms, maka
		 // sleeptime 10ms (sisa waktu digunakan untuk sleep)
		 // tapi jika timeDiff 60ms, maka sleepTime akan bernilai negatif (-
		 int sleepTime;

		 // jumlah frame (draw) yang sudah di-skip, lihat penjelasan tentang
		 // MAX_FRAME_SKIPS diatas
		 int framesSkipped;

		 //satuan waktu update, setiap update nilai tick akan bertambah
		 //jika objek dalam suatu game banyak, nilai tick digunakan sebagai waktu global
		 //sehingga setiap objek sinkron
		 long tick = 0;

		 //loop forever selama tidak dishutdown (mRun diset false)
		 while (mRun) {
             Canvas c = null;
             try {
                 c = mSurfaceHolder.lockCanvas(null);
                 //lock surface agar tidak diakses thread lain dulu
                 synchronized (mSurfaceHolder) {
                     //TAMBAHAN
                	 //catat waktu skr
                	 beginTime = System.currentTimeMillis();
     				 framesSkipped = 0;

                	 updatePosisi(tick);
                	 tick++;
                     doDraw(c);

                     //TAMBAHAN
                     //hitung waktu yg dibutuhkan untuk satu loop
                     timeDiff = System.currentTimeMillis() - beginTime;
     				 //hitung waktu tidur, kalau positif artinya loop
                     //lebih cepat dari max FPS dan thread akan tidur
                     //tapi kalau nilainya negatif artinya loop berjalan
                     //lebih dari waktu seharusnya shg akan dilakukan penyesuaian
                     //yaitu update posisi tanpa draw.
     				 sleepTime = (int)(FRAME_PERIOD - timeDiff);

     				 //sleep sebanyak sleepTime kalau masih ada siswa waktu (sleeptime>0)
     				 if (sleepTime > 0) {
	     				 try {
	     						Thread.sleep(sleepTime);
	     				 } catch (InterruptedException e) {
	     						e.printStackTrace();
	     						Log.e("yw","error saat mencoba sleep"+e.getMessage());

	     				 }
     				 }

     				 //jika sleeptime negatif (proses update dan draw terlalu lama)
     				 //maka masuk loop berikut
     				 //di dalam loop update dilakukan *TANPA* draw
     				 while (sleepTime < 0 && framesSkipped < MAX_FRAME_SKIPS) {

    					// update posisi saja
     					updatePosisi(tick);
     					tick++;
    					//tambah, jika sleepTime>=0 keluar
    					sleepTime += FRAME_PERIOD;

    					//jumlah draw yang di-skip, dibatasi agar FPS tidak terlalu
    					//drop
    					framesSkipped++;
    				 }

                 }
             } finally {
                 //pastikan diunlock
                 if (c != null) {
                     mSurfaceHolder.unlockCanvasAndPost(c);
                 }
             }
         }
     }

	//jika diset false maka akan shutdown (lihat method run())
	public void setRunning(boolean b) {
		mRun = b;
	}
}

1 Comment »

RSS feed for comments on this post. TrackBack URI

  1. makasih tutorialnya kolo boleh di lanjut sya harap tutorial berikutnya adalah bagaimana membuat karakter game melompat maksih sebelumnya


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Blog at WordPress.com.
Entries and comments feeds.

%d bloggers like this: