Hướng dẫn lập trình Android Game 2D cho người mới bắt đầu
Công ty Vĩnh Cửu tuyển dụng lập trình viên Java

1- Giới thiệu

Tài liệu được viết dựa trên:
  • Android Studio 1.5

Mục tiêu của tài liệu hướng dẫn bạn làm quen với một vài kỹ thuật đơn giản trong lập trình Game 2D Android. Bao gồm:
  • Sử dụng SuffaceView
  • Vẽ trên Canvas
  • Chuyển động của các nhân vật game.
  • Tương tác với cử chỉ của người chơi
Trong tài liệu này tôi sẽ hướng dẫn bạn lập trình từng bước, chính vì vậy bạn cần đọc và thực hành từ trên xuống dưới. Chúng ta sẽ viết từng phiên bản của trò chơi từ 1 cho tới phiên bản cuối cùng (Release).

2- Tạo một Game Project

Chú ý rằng bạn đang tạo một trò chơi 2D trên Android, chính vì vậy giao diện của trò chơi phải do bạn vẽ ra, chính vì vậy bạn không cần một file kiểu activity_main.xml.
OK, Project của bạn đã được tạo ra.

3- Chuẩn bị hình ảnh và âm thanh

Bạn cần một vài file ảnh.
  • chibi1.png

  • chibi2.png


 
  • explosion.png





 
Âm thanh tiếng nổ.
Âm thanh nền:
Copy các ảnh này vào thư mục drawable của project. Tạo mới thư mục raw, và copy file explosion.wav & background.mp3 vào thư mục này.

4- Sét đặt chế độ fullscreen (Version:1)

Với trò chơi bạn cần phải sét đặt ảnh nền và một điều quan trọng bạn cần phải sét chế độ FullScreen (Đầy màn hình).
Class MainActivity của bạn phải extends từ class Activity.
MainActivity.java (Version: 1)
package org.o7planning.androidgame2d;

import android.app.Activity;
import android.os.Bundle;
import android.view.Window;
import android.view.WindowManager;

public class MainActivity extends Activity {

  @Override
  protected void onCreate(Bundle savedInstanceState) {
      super.onCreate(savedInstanceState);

      // Set fullscreen
      this.getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
              WindowManager.LayoutParams.FLAG_FULLSCREEN);

      // Loại bỏ tiêu đề.
      this.requestWindowFeature(Window.FEATURE_NO_TITLE);

  }

}
Chạy thử ứng dụng:
Tiếp theo sét đặt kiểu màn hình nằm ngang (Landscape). Bạn cần phải sét đặt trong AndroidManifest.xml.
android:screenOrientation="landscape"
Chạy lại ứng dụng:
Chú ý: Trên Windows bạn cũng có thể xoay chiều của thiết bị mô phỏng bởi lệnh Ctrl + F11.

5- Hiển thị nhân vật trên trò chơi (Version:2)

Tiếp theo bạn cần viết code để hiển thị nhân vật trò chơi trên màn hình và di chuyển nó từ trái qua phải theo một vận tốc nào đó.
Với một nhân vật trong trò chơi bạn chỉ có một file ảnh, nhưng file ảnh được chia ra làm nhiều vùng mô tả các hành động khác nhau của nhân vật.
Sử dụng code bạn có thể vẽ một vùng ảnh lên đối tượng Canvas của trò chơi, tại tạo độ x, y. Sử dụng vòng lặp for để liên tục vẽ lại trên Canvas bạn có thể tạo ra sự chuyển động của nhân vật.
Trong khi lập trình Game bạn cũng cần phải tính đến hướng di chuyển của nhân vật trong trò chơi, vận tốc của nhân vật.
Tạo một class GameObject, các đối tượng của trò chơi mở rộng từ class này.
GameObject.java (Version: Release)
package org.o7planning.androidgame2d;


import android.graphics.Bitmap;

public abstract class GameObject {

    protected Bitmap image;

    protected final int rowCount;
    protected final int colCount;

    protected final int WIDTH;
    protected final int HEIGHT;

    protected final int width;


    protected final int height;
    protected int x;
    protected int y;

    public GameObject(Bitmap image, int rowCount, int colCount, int x, int y)  {

        this.image = image;
        this.rowCount= rowCount;
        this.colCount= colCount;

        this.x= x;
        this.y= y;

        this.WIDTH = image.getWidth();
        this.HEIGHT = image.getHeight();

        this.width = this.WIDTH/ colCount;
        this.height= this.HEIGHT/ rowCount;
    }


    protected Bitmap createSubImageAt(int row, int col)  {
        // createBitmap(bitmap, x, y, width, height).
        Bitmap subImage = Bitmap.createBitmap(image, col* width, row* height ,width,height);
        return subImage;
    }

    public int getX()  {
        return this.x;
    }

    public int getY()  {
        return this.y;
    }


    public int getHeight() {
        return height;
    }

    public int getWidth() {
        return width;
    }

}
Class ChibiCharactor mô phỏng một nhân vật trong trò chơi.
ChibiCharacter.java (Version: 2)
package org.o7planning.androidgame2d;


import android.graphics.Bitmap;
import android.graphics.Canvas;

public class ChibiCharacter extends GameObject {

private static final int ROW_TOP_TO_BOTTOM = 0;
private static final int ROW_RIGHT_TO_LEFT = 1;
private static final int ROW_LEFT_TO_RIGHT = 2;
private static final int ROW_BOTTOM_TO_TOP = 3;


// Dòng ảnh đang được sử dụng.
private int rowUsing = ROW_LEFT_TO_RIGHT;

private int colUsing;

private Bitmap[] leftToRights;
private Bitmap[] rightToLefts;
private Bitmap[] topToBottoms;
private Bitmap[] bottomToTops;


// Vận tốc di chuyển của nhân vật (pixel/milisecond).
public static final float VELOCITY = 0.1f;

private int movingVectorX = 10;
private int movingVectorY = 5;

private long lastDrawNanoTime =-1;

private GameSurface gameSurface;

public ChibiCharacter(GameSurface gameSurface, Bitmap image, int x, int y) {
    super(image, 4, 3, x, y);

    this.gameSurface= gameSurface;

    this.topToBottoms = new Bitmap[colCount]; // 3
    this.rightToLefts = new Bitmap[colCount]; // 3
    this.leftToRights = new Bitmap[colCount]; // 3
    this.bottomToTops = new Bitmap[colCount]; // 3

    for(int col = 0; col< this.colCount; col++ ) {
        this.topToBottoms[col] = this.createSubImageAt(ROW_TOP_TO_BOTTOM, col);
        this.rightToLefts[col]  = this.createSubImageAt(ROW_RIGHT_TO_LEFT, col);
        this.leftToRights[col] = this.createSubImageAt(ROW_LEFT_TO_RIGHT, col);
        this.bottomToTops[col]  = this.createSubImageAt(ROW_BOTTOM_TO_TOP, col);
    }
}

public Bitmap[] getMoveBitmaps()  {
    switch (rowUsing)  {
        case ROW_BOTTOM_TO_TOP:
            return  this.bottomToTops;
        case ROW_LEFT_TO_RIGHT:
            return this.leftToRights;
        case ROW_RIGHT_TO_LEFT:
            return this.rightToLefts;
        case ROW_TOP_TO_BOTTOM:
            return this.topToBottoms;
        default:
            return null;
    }
}

public Bitmap getCurrentMoveBitmap()  {
    Bitmap[] bitmaps = this.getMoveBitmaps();
    return bitmaps[this.colUsing];
}


public void update()  {
    this.colUsing++;
    if(colUsing >= this.colCount)  {
        this.colUsing =0;
    }

    // Thời điểm hiện tại theo nano giây.
    long now = System.nanoTime();


    // Chưa vẽ lần nào.
    if(lastDrawNanoTime==-1) {
        lastDrawNanoTime= now;
    }

    // Đổi nano giây ra mili giây (1 nanosecond = 1000000 millisecond).
    int deltaTime = (int) ((now - lastDrawNanoTime)/ 1000000 );


    // Quãng đường mà nhân vật đi được (fixel).
    float distance = VELOCITY * deltaTime;

    double movingVectorLength = Math.sqrt(movingVectorX* movingVectorX + movingVectorY*movingVectorY);


    // Tính toán vị trí mới của nhân vật.
    this.x = x +  (int)(distance* movingVectorX / movingVectorLength);
    this.y = y +  (int)(distance* movingVectorY / movingVectorLength);


    // Khi nhân vật của game chạm vào cạnh của màn hình thì đổi hướng.

    if(this.x < 0 )  {
        this.x = 0;
        this.movingVectorX = - this.movingVectorX;
    } else if(this.x > this.gameSurface.getWidth() -width)  {
        this.x= this.gameSurface.getWidth()-width;
        this.movingVectorX = - this.movingVectorX;
    }

    if(this.y < 0 )  {
        this.y = 0;
        this.movingVectorY = - this.movingVectorY;
    } else if(this.y > this.gameSurface.getHeight()- height)  {
        this.y= this.gameSurface.getHeight()- height;
        this.movingVectorY = - this.movingVectorY ;
    }


    // Tính toán rowUsing.
    if( movingVectorX > 0 )  {
        if(movingVectorY > 0 && Math.abs(movingVectorX) < Math.abs(movingVectorY)) {
            this.rowUsing = ROW_TOP_TO_BOTTOM;
        }else if(movingVectorY < 0 && Math.abs(movingVectorX) < Math.abs(movingVectorY)) {
            this.rowUsing = ROW_BOTTOM_TO_TOP;
        }else  {
            this.rowUsing = ROW_LEFT_TO_RIGHT;
        }
    } else {
        if(movingVectorY > 0 && Math.abs(movingVectorX) < Math.abs(movingVectorY)) {
            this.rowUsing = ROW_TOP_TO_BOTTOM;
        }else if(movingVectorY < 0 && Math.abs(movingVectorX) < Math.abs(movingVectorY)) {
            this.rowUsing = ROW_BOTTOM_TO_TOP;
        }else  {
            this.rowUsing = ROW_RIGHT_TO_LEFT;
        }
    }
}

public void draw(Canvas canvas)  {
    Bitmap bitmap = this.getCurrentMoveBitmap();
    canvas.drawBitmap(bitmap,x, y, null);

    // Thời điểm vẽ cuối cùng (Nano giây).
    this.lastDrawNanoTime= System.nanoTime();
}

public void setMovingVector(int movingVectorX, int movingVectorY)  {
    this.movingVectorX= movingVectorX;
    this.movingVectorY = movingVectorY;
}
}
GameThread là một luồng điều khiển việc cập nhập lại giao diện của trò chơi.
GameThread.java (Version: Release)
package org.o7planning.androidgame2d;


import android.graphics.Canvas;
import android.view.SurfaceHolder;

public class GameThread extends Thread {

  private boolean running;
  private GameSurface gameSurface;
  private SurfaceHolder surfaceHolder;

  public GameThread(GameSurface gameSurface, SurfaceHolder surfaceHolder)  {
       this.gameSurface= gameSurface;
       this.surfaceHolder= surfaceHolder;
  }

  @Override
  public void run()  {
       long startTime = System.nanoTime();

       while(running)  {
           Canvas canvas= null;
           try {
               // Lấy ra đối tượng Canvas và khóa nó lại.
               canvas = this.surfaceHolder.lockCanvas();

     
               // Đồng bộ hóa.
               synchronized (canvas)  {
                   this.gameSurface.update();
                   this.gameSurface.draw(canvas);
               }
           }catch(Exception e)  {
               // Không làm gì
           } finally {
               if(canvas!= null)  {
                   // Mở khóa cho Canvas.
                   this.surfaceHolder.unlockCanvasAndPost(canvas);
               }
           }
           long now = System.nanoTime() ;
           // Thời gian cập nhập lại giao diện Game
           // (Đổi từ Nanosecond ra milisecond).
           long waitTime = (now - startTime)/1000000;
           if(waitTime < 10)  {
               waitTime= 10; // Millisecond.
           }
           System.out.print(" Wait Time="+ waitTime);

           try {
               // Ngừng chương trình một chút.
               this.sleep(waitTime);
           } catch(InterruptedException e)  {

           }
           startTime = System.nanoTime();
           System.out.print(".");
       }
  }

  public void setRunning(boolean running)  {
      this.running= running;
  }
}
Class GameSurface mô phỏng toàn bộ bề mặt của trò chơi, class này mở rộng từ SurfaceView, SurfaceView chứa một đối tượng Canvas, các đối tượng trong trò chơi sẽ được vẽ lên Canvas.
GameSurface.java (Version: 2)
package org.o7planning.androidgame2d;


import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.view.MotionEvent;
import android.view.SurfaceHolder;
import android.view.SurfaceView;

public class GameSurface extends SurfaceView implements SurfaceHolder.Callback {


  private GameThread gameThread;

  private ChibiCharacter chibi1;

  public GameSurface(Context context)  {
      super(context);


      // Đảm bảo Game Surface có thể focus để điều khiển các sự kiện.
      this.setFocusable(true);

 
      // Sét đặt các sự kiện liên quan tới Game.
      this.getHolder().addCallback(this);
  }

  public void update()  {
      this.chibi1.update();
  }

 

  @Override
  public void draw(Canvas canvas)  {
      super.draw(canvas);

      this.chibi1.draw(canvas);
  }


  // Thi hành phương thức của interface SurfaceHolder.Callback
  @Override
  public void surfaceCreated(SurfaceHolder holder) {
      Bitmap chibiBitmap1 = BitmapFactory.decodeResource(this.getResources(),R.drawable.chibi1);
      this.chibi1 = new ChibiCharacter(this,chibiBitmap1,100,50);

      this.gameThread = new GameThread(this,holder);
      this.gameThread.setRunning(true);
      this.gameThread.start();
  }

  // Thi hành phương thức của interface SurfaceHolder.Callback
  @Override
  public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {

  }


  // Thi hành phương thức của interface SurfaceHolder.Callback
  @Override
  public void surfaceDestroyed(SurfaceHolder holder) {
      boolean retry= true;
      while(retry) {
          try {
              this.gameThread.setRunning(false);


              // Luồng cha, cần phải tạm dừng chờ GameThread kết thúc.
              this.gameThread.join();
          }catch(InterruptedException e)  {
              e.printStackTrace();
          }
          retry= true;
      }
  }

}
MainActivity.java (Version: Release)
package org.o7planning.androidgame2d;

import android.app.Activity;
import android.os.Bundle;
import android.view.Window;
import android.view.WindowManager;

public class MainActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        // Set fullscreen
        this.getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
                WindowManager.LayoutParams.FLAG_FULLSCREEN);

       
        // Loại bỏ tiêu đề.
        this.requestWindowFeature(Window.FEATURE_NO_TITLE);

        // Sét đặt giao diện của Activity.
        this.setContentView(new GameSurface(this));
    }

}
OK, phiên bản 2 đã hoàn thành, bạn có thể chạy trò chơi.

6- Tương tác với người chơi (Version: 3)

Tiếp theo bạn có sử lý sự kiện khi người dùng chạm vào màn hình, nhân vật trò chơi sẽ chạy theo hướng bạn đã chạm. Bạn cần sử lý sự kiện này trên class GameSurface.
@Override
public boolean onTouchEvent(MotionEvent event) {
    if (event.getAction() == MotionEvent.ACTION_DOWN) {
        int x=  (int)event.getX();
        int y = (int)event.getY();

        int movingVectorX =x-  this.chibi1.getX() ;
        int movingVectorY =y-  this.chibi1.getY() ;

        this.chibi1.setMovingVector(movingVectorX,movingVectorY);
        return true;
    }
    return false;
}
Xem code đầy đủ:
GameSurface.java (Version: 3)
package org.o7planning.androidgame2d;


import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.view.MotionEvent;
import android.view.SurfaceHolder;
import android.view.SurfaceView;

public class GameSurface extends SurfaceView implements SurfaceHolder.Callback {


   private GameThread gameThread;

   private ChibiCharacter chibi1;

   public GameSurface(Context context)  {
       super(context);

   
       // Đảm bảo Game Surface có thể focus để điều khiển các sự kiện.
       this.setFocusable(true);

 
       // Sét đặt các sự kiện liên quan tới Game.
       this.getHolder().addCallback(this);
   }

   public void update()  {
       this.chibi1.update();
   }

   @Override
   public boolean onTouchEvent(MotionEvent event) {
       if (event.getAction() == MotionEvent.ACTION_DOWN) {
           int x=  (int)event.getX();
           int y = (int)event.getY();

           int movingVectorX =x-  this.chibi1.getX() ;
           int movingVectorY =y-  this.chibi1.getY() ;

           this.chibi1.setMovingVector(movingVectorX,movingVectorY);
           return true;
       }
       return false;
   }

   @Override
   public void draw(Canvas canvas)  {
       super.draw(canvas);

       this.chibi1.draw(canvas);
   }

 
   // Thi hành phương thức của interface SurfaceHolder.Callback
   @Override
   public void surfaceCreated(SurfaceHolder holder) {
       Bitmap chibiBitmap1 = BitmapFactory.decodeResource(this.getResources(),R.drawable.chibi1);
       this.chibi1 = new ChibiCharacter(this,chibiBitmap1,100,50);

       this.gameThread = new GameThread(this,holder);
       this.gameThread.setRunning(true);
       this.gameThread.start();
   }

  
   // Thi hành phương thức của interface SurfaceHolder.Callback
   @Override
   public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {

   }

 
   // Thi hành phương thức của interface SurfaceHolder.Callback
   @Override
   public void surfaceDestroyed(SurfaceHolder holder) {
       boolean retry= true;
       while(retry) {
           try {
               this.gameThread.setRunning(false);

           
               // Luồng cha, cần phải tạm dừng chờ GameThread kết thúc.
               this.gameThread.join();
           }catch(InterruptedException e)  {
               e.printStackTrace();
           }
           retry= true;
       }
   }

}
Chạy lại trò chơi:

7- Trò chơi với nhiều nhân vật (Version: 4)

Bạn có thể tạo thêm các nhân vật khác vào trò chơi, ở đây tôi thêm một nhân vật thứ 2. Sửa lại code của class GameSurface:
GameSurface.java (Version: 4)
package org.o7planning.androidgame2d;


import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.view.MotionEvent;
import android.view.SurfaceHolder;
import android.view.SurfaceView;

import java.util.ArrayList;
import java.util.List;

public class GameSurface extends SurfaceView implements SurfaceHolder.Callback {

   private GameThread gameThread;

   private final List<ChibiCharacter> chibiList = new ArrayList<ChibiCharacter>();

   public GameSurface(Context context)  {
       super(context);
 
       // Đảm bảo Game Surface có thể focus để điều khiển các sự kiện.
       this.setFocusable(true);
 
       // Sét đặt các sự kiện liên quan tới Game.
       this.getHolder().addCallback(this);
   }

   public void update()  {
       for(ChibiCharacter chibi: chibiList) {
           chibi.update();
       }
   }

   @Override
   public boolean onTouchEvent(MotionEvent event) {
       if (event.getAction() == MotionEvent.ACTION_DOWN) {
           int x=  (int)event.getX();
           int y = (int)event.getY();

           for(ChibiCharacter chibi: chibiList) {
               int movingVectorX =x-  chibi.getX() ;
               int movingVectorY =y-  chibi.getY() ;
               chibi.setMovingVector(movingVectorX, movingVectorY);
           }
           return true;
       }
       return false;
   }

   @Override
   public void draw(Canvas canvas)  {
       super.draw(canvas);

       for(ChibiCharacter chibi: chibiList)  {
           chibi.draw(canvas);
       }

   }
 
   // Thi hành phương thức của interface SurfaceHolder.Callback
   @Override
   public void surfaceCreated(SurfaceHolder holder) {
       Bitmap chibiBitmap1 = BitmapFactory.decodeResource(this.getResources(),R.drawable.chibi1);
       ChibiCharacter chibi1 = new ChibiCharacter(this,chibiBitmap1,100,50);

       Bitmap chibiBitmap2 = BitmapFactory.decodeResource(this.getResources(),R.drawable.chibi2);
       ChibiCharacter chibi2 = new ChibiCharacter(this,chibiBitmap2,300,150);

       this.chibiList.add(chibi1);
       this.chibiList.add(chibi2);

       this.gameThread = new GameThread(this,holder);
       this.gameThread.setRunning(true);
       this.gameThread.start();
   }

 
   // Thi hành phương thức của interface SurfaceHolder.Callback
   @Override
   public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {

   }

 
   // Thi hành phương thức của interface SurfaceHolder.Callback
   @Override
   public void surfaceDestroyed(SurfaceHolder holder) {
       boolean retry= true;
       while(retry) {
           try {
               this.gameThread.setRunning(false);

 
               // Luồng cha, cần phải tạm dừng chờ GameThread kết thúc.
               this.gameThread.join();
           }catch(InterruptedException e)  {
               e.printStackTrace();
           }
           retry= true;
       }
   }

}
Chạy lại trò chơi.

8- Hiệu ứng trong trò chơi (Version: 5)

Đôi khi bạn cần phải sử lý một vài hiệu ứng cho trò chơi, chẳng hạn bạn đang điều khiển một cái máy bay, khi nó rơi xuống đất máy bay phát nổ, vậy nổ là một hiệu ứng. Trong phần này tôi sẽ mô phỏng khi bạn chạm (click) vào nhân vật Chibi, nó sẽ phát nổ.
Class Explosion mô phỏng một vụ nổ, khi bạn click vào nhân vật Chibi, nó bị loại ra khỏi trò chơi và một đối tượng Explosion được thêm vào trò chơi tại vị trí của nhân vật Chibi vừa bị loại bỏ.
Explosion.java (Version: 5)
package org.o7planning.androidgame2d;


import android.graphics.Bitmap;
import android.graphics.Canvas;

public class Explosion extends GameObject {

    private int rowIndex = 0 ;
    private int colIndex = -1 ;

    private boolean finish= false;
    private GameSurface gameSurface;

    public Explosion(GameSurface GameSurface, Bitmap image, int x, int y) {
        super(image, 5, 5, x, y);

        this.gameSurface= GameSurface;
    }

    public void update()  {
        this.colIndex++;

        if(this.colIndex >= this.colCount)  {
            this.colIndex =0;
            this.rowIndex++;

            if(this.rowIndex>= this.rowCount)  {
                this.finish= true;
            }
        }
    }

    public void draw(Canvas canvas)  {
        if(!finish)  {
            Bitmap bitmap= this.createSubImageAt(rowIndex,colIndex);
            canvas.drawBitmap(bitmap, this.x, this.y,null);
        }
    }

    public boolean isFinish() {
        return finish;
    }

}
Bạn cần sửa code của Class GameSurface:
GameSurface.java (Version: 5)
package org.o7planning.androidgame2d;


import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.view.MotionEvent;
import android.view.SurfaceHolder;
import android.view.SurfaceView;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

public class GameSurface extends SurfaceView implements SurfaceHolder.Callback {

 private GameThread gameThread;

 private final List<ChibiCharacter> chibiList = new ArrayList<ChibiCharacter>();
 private final List<Explosion> explosionList = new ArrayList<Explosion>();

 public GameSurface(Context context)  {
     super(context);

     // Đảm bảo Game Surface có thể focus để điều khiển các sự kiện.
     this.setFocusable(true);

     // Sét đặt các sự kiện liên quan tới Game.
     this.getHolder().addCallback(this);
 }


 @Override
 public boolean onTouchEvent(MotionEvent event) {
     if (event.getAction() == MotionEvent.ACTION_DOWN) {

         int x=  (int)event.getX();
         int y = (int)event.getY();

         Iterator<ChibiCharacter> iterator= this.chibiList.iterator();

         // Kiểm tra xem có click vào nhân vật nào không.
         while(iterator.hasNext()) {
             ChibiCharacter chibi = iterator.next();
             if( chibi.getX() < x && x < chibi.getX() + chibi.getWidth()
                 && chibi.getY() < y && y < chibi.getY()+ chibi.getHeight())  {

                 // Loại bỏ nhân vật Game hiện tại ra khỏi iterator và list.
                 iterator.remove();

                 // Tạo mới một đối tượng Explosion.
                 Bitmap bitmap = BitmapFactory.decodeResource(this.getResources(),R.drawable.explosion);
                 Explosion explosion = new Explosion(this, bitmap,chibi.getX(),chibi.getY());

                 this.explosionList.add(explosion);
             }
         }


         for(ChibiCharacter chibi: chibiList) {
             int movingVectorX =x-  chibi.getX() ;
             int movingVectorY =y-  chibi.getY() ;
             chibi.setMovingVector(movingVectorX, movingVectorY);
         }
         return true;
     }
     return false;
 }

 public void update()  {
     for(ChibiCharacter chibi: chibiList) {
         chibi.update();
     }
     for(Explosion explosion: this.explosionList)  {
         explosion.update();
     }

     Iterator<Explosion> iterator= this.explosionList.iterator();
     while(iterator.hasNext())  {
         Explosion explosion = iterator.next();

         if(explosion.isFinish()) {

             // Nếu explosion đã hoàn thành, loại nó ra khỏi iterator & list.
             // (Loại bỏ phần tử hiện thời ra khỏi bộ lặp).
             iterator.remove();
             continue;
         }
     }
 }

 @Override
 public void draw(Canvas canvas)  {
     super.draw(canvas);

     for(ChibiCharacter chibi: chibiList)  {
         chibi.draw(canvas);
     }

     for(Explosion explosion: this.explosionList)  {
         explosion.draw(canvas);
     }

 }

 // Thi hành phương thức của interface SurfaceHolder.Callback
 @Override
 public void surfaceCreated(SurfaceHolder holder) {
     Bitmap chibiBitmap1 = BitmapFactory.decodeResource(this.getResources(),R.drawable.chibi1);
     ChibiCharacter chibi1 = new ChibiCharacter(this,chibiBitmap1,100,50);

     Bitmap chibiBitmap2 = BitmapFactory.decodeResource(this.getResources(),R.drawable.chibi2);
     ChibiCharacter chibi2 = new ChibiCharacter(this,chibiBitmap2,300,150);

     this.chibiList.add(chibi1);
     this.chibiList.add(chibi2);

     this.gameThread = new GameThread(this,holder);
     this.gameThread.setRunning(true);
     this.gameThread.start();
 }

 // Thi hành phương thức của interface SurfaceHolder.Callback
 @Override
 public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {

 }

 // Thi hành phương thức của interface SurfaceHolder.Callback
 @Override
 public void surfaceDestroyed(SurfaceHolder holder) {
     boolean retry= true;
     while(retry) {
         try {
             this.gameThread.setRunning(false);

             // Luồng cha, cần phải tạm dừng chờ GameThread kết thúc.
             this.gameThread.join();
         }catch(InterruptedException e)  {
             e.printStackTrace();
         }
         retry= true;
     }
 }

}
 public void update()  {
     this.colIndex++;

     if(this.colIndex >= this.colCount)  {
         this.colIndex =0;
         this.rowIndex++;

         if(this.rowIndex>= this.rowCount)  {
             this.finish= true;
         }
     }
 }

 public void draw(Canvas canvas)  {
     if(!finish)  {
         Bitmap bitmap= this.createSubImageAt(rowIndex,colIndex);
         canvas.drawBitmap(bitmap, this.x, this.y,null);
     }
 }

 public boolean isFinish() {
     return finish;
 }

}
Chạy lại trò chơi của bạn:

9- Hiệu ứng âm thanh trong trò chơi (Version: Release)

Tiếp theo bạn cần thêm hiệu ứng âm thanh vào trò chơi, chẳng hạn âm thanh nền của trò chơi, âm thanh tiếng nổ khi nhân vật Chibi bị phá hủy.
Explosion.java (Version: Release)
package org.o7planning.androidgame2d;


import android.graphics.Bitmap;
import android.graphics.Canvas;

public class Explosion extends GameObject {

    private int rowIndex = 0 ;
    private int colIndex = -1 ;

    private boolean finish= false;
    private GameSurface gameSurface;

    public Explosion(GameSurface GameSurface, Bitmap image, int x, int y) {
        super(image, 5, 5, x, y);

        this.gameSurface= GameSurface;
    }

    public void update()  {
        this.colIndex++;

        // Play sound explosion.wav.
        if(this.colIndex==0 && this.rowIndex==0) {
            this.gameSurface.playSoundExplosion();
        }

        if(this.colIndex >= this.colCount)  {
            this.colIndex =0;
            this.rowIndex++;

            if(this.rowIndex>= this.rowCount)  {
                this.finish= true;
            }
        }
    }

    public void draw(Canvas canvas)  {
        if(!finish)  {
            Bitmap bitmap= this.createSubImageAt(rowIndex,colIndex);
            canvas.drawBitmap(bitmap, this.x, this.y,null);
        }
    }

    public boolean isFinish() {
        return finish;
    }

}
GameSurface.java (Version: Release)
package org.o7planning.androidgame2d;


import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.media.AudioAttributes;
import android.media.AudioManager;
import android.media.SoundPool;
import android.os.Build;
import android.view.MotionEvent;
import android.view.SurfaceHolder;
import android.view.SurfaceView;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

public class GameSurface extends SurfaceView implements SurfaceHolder.Callback {

  private GameThread gameThread;

  private final List<ChibiCharacter> chibiList = new ArrayList<ChibiCharacter>();
  private final List<Explosion> explosionList = new ArrayList<Explosion>();

  private static final int MAX_STREAMS=100;
  private int soundIdExplosion;
  private int soundIdBackground;

  private boolean soundPoolLoaded;
  private SoundPool soundPool;



  public GameSurface(Context context)  {
      super(context);
 
      // Đảm bảo Game Surface có thể focus để điều khiển các sự kiện.
      this.setFocusable(true);

      // Sét đặt các sự kiện liên quan tới Game.
      this.getHolder().addCallback(this);

      this.initSoundPool();
  }

  private void initSoundPool()  {
      // Với phiên bản Android API >= 21
      if (Build.VERSION.SDK_INT >= 21 ) {

          AudioAttributes audioAttrib = new AudioAttributes.Builder()
                  .setUsage(AudioAttributes.USAGE_GAME)
                  .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
                  .build();

          SoundPool.Builder builder= new SoundPool.Builder();
          builder.setAudioAttributes(audioAttrib).setMaxStreams(MAX_STREAMS);

          this.soundPool = builder.build();
      }
      // Với phiên bản Android API < 21
      else {
          // SoundPool(int maxStreams, int streamType, int srcQuality)
          this.soundPool = new SoundPool(MAX_STREAMS, AudioManager.STREAM_MUSIC, 0);
      }


      // Sự kiện SoundPool đã tải lên bộ nhớ thành công.
      this.soundPool.setOnLoadCompleteListener(new SoundPool.OnLoadCompleteListener() {
          @Override
          public void onLoadComplete(SoundPool soundPool, int sampleId, int status) {
              soundPoolLoaded = true;

              // Phát nhạc nền
              playSoundBackground();
          }
      });

      // Tải file nhạc tiếng nổ (background.mp3) vào SoundPool.
      this.soundIdBackground= this.soundPool.load(this.getContext(), R.raw.background,1);

      // Tải file nhạc tiếng nổ (explosion.wav) vào SoundPool.
      this.soundIdExplosion = this.soundPool.load(this.getContext(), R.raw.explosion,1);


  }

  public void playSoundExplosion()  {
      if(this.soundPoolLoaded) {
          float leftVolumn = 0.8f;
          float rightVolumn =  0.8f;

          // Phát âm thanh explosion.wav
          int streamId = this.soundPool.play(this.soundIdExplosion,leftVolumn, rightVolumn, 1, 0, 1f);
      }
  }

  public void playSoundBackground()  {
      if(this.soundPoolLoaded) {
          float leftVolumn = 0.8f;
          float rightVolumn =  0.8f;

          // Phát âm thanh background.mp3
          int streamId = this.soundPool.play(this.soundIdBackground,leftVolumn, rightVolumn, 1, -1, 1f);
      }
  }

  @Override
  public boolean onTouchEvent(MotionEvent event) {
      if (event.getAction() == MotionEvent.ACTION_DOWN) {

          int x=  (int)event.getX();
          int y = (int)event.getY();

          Iterator<ChibiCharacter> iterator= this.chibiList.iterator();

          // Kiểm tra xem có click vào nhân vật nào không.
          while(iterator.hasNext()) {
              ChibiCharacter chibi = iterator.next();
              if( chibi.getX() < x && x < chibi.getX() + chibi.getWidth()
                  && chibi.getY() < y && y < chibi.getY()+ chibi.getHeight())  {
 
                  // Loại bỏ nhân vật Game hiện tại ra khỏi iterator và list.
                  iterator.remove();

                  // Tạo mới một đối tượng Explosion.
                  Bitmap bitmap = BitmapFactory.decodeResource(this.getResources(),R.drawable.explosion);
                  Explosion explosion = new Explosion(this, bitmap,chibi.getX(),chibi.getY());

                  this.explosionList.add(explosion);
              }
          }


          for(ChibiCharacter chibi: chibiList) {
              int movingVectorX =x-  chibi.getX() ;
              int movingVectorY =y-  chibi.getY() ;
              chibi.setMovingVector(movingVectorX, movingVectorY);
          }
          return true;
      }
      return false;
  }

  public void update()  {
      for(ChibiCharacter chibi: chibiList) {
          chibi.update();
      }
      for(Explosion explosion: this.explosionList)  {
          explosion.update();
      }

      Iterator<Explosion> iterator= this.explosionList.iterator();
      while(iterator.hasNext())  {
          Explosion explosion = iterator.next();

          if(explosion.isFinish()) {
              // Nếu explosion đã hoàn thành, loại nó ra khỏi iterator & list.
              // (Loại bỏ phần tử hiện thời ra khỏi bộ lặp).
              iterator.remove();
              continue;
          }
      }
  }

  @Override
  public void draw(Canvas canvas)  {
      super.draw(canvas);

      for(ChibiCharacter chibi: chibiList)  {
          chibi.draw(canvas);
      }

      for(Explosion explosion: this.explosionList)  {
          explosion.draw(canvas);
      }

  }


  // Thi hành phương thức của interface SurfaceHolder.Callback
  @Override
  public void surfaceCreated(SurfaceHolder holder) {
      Bitmap chibiBitmap1 = BitmapFactory.decodeResource(this.getResources(),R.drawable.chibi1);
      ChibiCharacter chibi1 = new ChibiCharacter(this,chibiBitmap1,100,50);

      Bitmap chibiBitmap2 = BitmapFactory.decodeResource(this.getResources(),R.drawable.chibi2);
      ChibiCharacter chibi2 = new ChibiCharacter(this,chibiBitmap2,300,150);

      this.chibiList.add(chibi1);
      this.chibiList.add(chibi2);

      this.gameThread = new GameThread(this,holder);
      this.gameThread.setRunning(true);
      this.gameThread.start();
  }

  // Thi hành phương thức của interface SurfaceHolder.Callback
  @Override
  public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {

  }


  // Thi hành phương thức của interface SurfaceHolder.Callback
  @Override
  public void surfaceDestroyed(SurfaceHolder holder) {
      boolean retry= true;
      while(retry) {
          try {
              this.gameThread.setRunning(false);
 
              // Luồng cha, cần phải tạm dừng chờ GameThread kết thúc.
              this.gameThread.join();
          }catch(InterruptedException e)  {
              e.printStackTrace();
          }
          retry= true;
      }
  }

}
Chú ý: Bạn có thểm tham khảo thêm tài liệu về hiệu ứng âm thanh tại:
OK, bây giờ bạn có thể chạy lại trò chơi và nghe hiệu ứng âm thanh của trò chơi.

10- Các kỹ thuật lập trình cơ bản Game 2D

Tài liệu tiếp theo: Các kỹ thuật lập trình cơ bản 2D Game.
  • TODO