- Giới thiệu
- Tạo một Game Project
- Chuẩn bị hình ảnh và âm thanh
- Sét đặt chế độ fullscreen (Version:1)
- Hiển thị nhân vật trên trò chơi (Version:2)
- Tương tác với người chơi (Version: 3)
- Trò chơi với nhiều nhân vật (Version: 4)
- Hiệu ứng trong trò chơi (Version: 5)
- Hiệu ứng âm thanh trong trò chơi (Version: Release)
Hướng dẫn lập trình Android Game 2D cho người mới bắt đầu
1. Giới thiệu
Tài liệu được viết dựa trên:
Android Studio 3.6.1
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
Trên Android Studio tạo mới một project:
- Name: Android2DGame
- Package name: org.o7planning.android2dgame
OK, Project của bạn đã được tạo ra.
Tiếp theo bạn cần tạo một Activity. Trên Android Studio chọn:
- File > New > Activity > Empty Activity
- Activity Name: MainActivity
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 như là activity_main.xml.
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:
Nhấn phải chuột vào thư mục "res" của project và chọn:
- New > Folder > Raw Resource Folder
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.android2dgame;
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);
// Set No Title
this.requestWindowFeature(Window.FEATURE_NO_TITLE);
}
}
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.
** AndroidManifest.xml **
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="org.o7planning.android2dgame">
<application
android:screenOrientation="landscape"
... >
...
</application>
</manifest>
Chạy thử ứ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 lớp 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.android2dgame;
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;
}
}
Lớp ChibiCharactor mô phỏng một nhân vật trong trò chơi.
ChibiCharacter.java (Version: 2)
package org.o7planning.android2dgame;
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;
// Row index of Image are being used.
private int rowUsing = ROW_LEFT_TO_RIGHT;
private int colUsing;
private Bitmap[] leftToRights;
private Bitmap[] rightToLefts;
private Bitmap[] topToBottoms;
private Bitmap[] bottomToTops;
// Velocity of game character (pixel/millisecond)
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;
}
// Current time in nanoseconds
long now = System.nanoTime();
// Never once did draw.
if(lastDrawNanoTime==-1) {
lastDrawNanoTime= now;
}
// Change nanoseconds to milliseconds (1 nanosecond = 1000000 milliseconds).
int deltaTime = (int) ((now - lastDrawNanoTime)/ 1000000 );
// Distance moves
float distance = VELOCITY * deltaTime;
double movingVectorLength = Math.sqrt(movingVectorX* movingVectorX + movingVectorY*movingVectorY);
// Calculate the new position of the game character.
this.x = x + (int)(distance* movingVectorX / movingVectorLength);
this.y = y + (int)(distance* movingVectorY / movingVectorLength);
// When the game's character touches the edge of the screen, then change direction
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 ;
}
// 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);
// Last draw time.
this.lastDrawNanoTime= System.nanoTime();
}
public void setMovingVector(int movingVectorX, int movingVectorY) {
this.movingVectorX= movingVectorX;
this.movingVectorY = movingVectorY;
}
}
GameThread là một luồng (thread) đ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.android2dgame;
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 {
// Get Canvas from Holder and lock it.
canvas = this.surfaceHolder.lockCanvas();
// Synchronized
synchronized (canvas) {
this.gameSurface.update();
this.gameSurface.draw(canvas);
}
}catch(Exception e) {
// Do nothing.
} finally {
if(canvas!= null) {
// Unlock Canvas.
this.surfaceHolder.unlockCanvasAndPost(canvas);
}
}
long now = System.nanoTime() ;
// Interval to redraw game
// (Change nanoseconds to milliseconds)
long waitTime = (now - startTime)/1000000;
if(waitTime < 10) {
waitTime= 10; // Millisecond.
}
System.out.print(" Wait Time="+ waitTime);
try {
// Sleep.
this.sleep(waitTime);
} catch(InterruptedException e) {
}
startTime = System.nanoTime();
System.out.print(".");
}
}
public void setRunning(boolean running) {
this.running= running;
}
}
Lớp 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.android2dgame;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
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);
// Make Game Surface focusable so it can handle events. .
this.setFocusable(true);
// Sét callback.
this.getHolder().addCallback(this);
}
public void update() {
this.chibi1.update();
}
@Override
public void draw(Canvas canvas) {
super.draw(canvas);
this.chibi1.draw(canvas);
}
// Implements method of 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();
}
// Implements method of SurfaceHolder.Callback
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
}
// Implements method of SurfaceHolder.Callback
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
boolean retry= true;
while(retry) {
try {
this.gameThread.setRunning(false);
// Parent thread must wait until the end of GameThread.
this.gameThread.join();
}catch(InterruptedException e) {
e.printStackTrace();
}
retry= true;
}
}
}
MainActivity.java (Version: Release)
package org.o7planning.android2dgame;
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);
// Set No Title
this.requestWindowFeature(Window.FEATURE_NO_TITLE);
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ó xử 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 xử lý sự kiện này trên lớp 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.android2dgame;
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);
// Make Game Surface focusable so it can handle events.
this.setFocusable(true);
// Set callback.
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);
}
// Implements method of 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();
}
// Implements method of SurfaceHolder.Callback
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
}
// Implements method of SurfaceHolder.Callback
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
boolean retry= true;
while(retry) {
try {
this.gameThread.setRunning(false);
// Parent thread must wait until the end of GameThread.
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 lớp GameSurface:
GameSurface.java (Version: 4)
package org.o7planning.android2dgame;
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);
// Make Game Surface focusable so it can handle events.
this.setFocusable(true);
// Set callback.
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);
}
}
// Implements method of 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();
}
// Implements method of SurfaceHolder.Callback
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
}
// Implements method of SurfaceHolder.Callback
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
boolean retry= true;
while(retry) {
try {
this.gameThread.setRunning(false);
// Parent thread must wait until the end of GameThread.
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 xử 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ổ.
Lớp 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.android2dgame;
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 lớp GameSurface:
GameSurface.java (Version: 5)
package org.o7planning.android2dgame;
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);
// Make Game Surface focusable so it can handle events.
this.setFocusable(true);
// Sét callback.
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();
while(iterator.hasNext()) {
ChibiCharacter chibi = iterator.next();
if( chibi.getX() < x && x < chibi.getX() + chibi.getWidth()
&& chibi.getY() < y && y < chibi.getY()+ chibi.getHeight()) {
// Remove the current element from the iterator and the list.
iterator.remove();
// Create Explosion object.
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()) {
// If explosion finish, Remove the current element from the iterator & list.
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);
}
}
// Implements method of 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();
}
// Implements method of SurfaceHolder.Callback
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
}
// Implements method of SurfaceHolder.Callback
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
boolean retry= true;
while(retry) {
try {
this.gameThread.setRunning(false);
// Parent thread must wait until the end of GameThread.
this.gameThread.join();
}catch(InterruptedException e) {
e.printStackTrace();
}
retry= true;
}
}
}
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.android2dgame;
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.android2dgame;
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);
// Make Game Surface focusable so it can handle events.
this.setFocusable(true);
// Sét callback.
this.getHolder().addCallback(this);
this.initSoundPool();
}
private void initSoundPool() {
// With 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();
}
// With Android API < 21
else {
// SoundPool(int maxStreams, int streamType, int srcQuality)
this.soundPool = new SoundPool(MAX_STREAMS, AudioManager.STREAM_MUSIC, 0);
}
// When SoundPool load complete.
this.soundPool.setOnLoadCompleteListener(new SoundPool.OnLoadCompleteListener() {
@Override
public void onLoadComplete(SoundPool soundPool, int sampleId, int status) {
soundPoolLoaded = true;
// Playing background sound.
playSoundBackground();
}
});
// Load the sound background.mp3 into SoundPool
this.soundIdBackground= this.soundPool.load(this.getContext(), R.raw.background,1);
// Load the sound explosion.wav into 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;
// Play sound 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;
// Play sound 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();
while(iterator.hasNext()) {
ChibiCharacter chibi = iterator.next();
if( chibi.getX() < x && x < chibi.getX() + chibi.getWidth()
&& chibi.getY() < y && y < chibi.getY()+ chibi.getHeight()) {
// Remove the current element from the iterator and the list.
iterator.remove();
// Create Explosion object.
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()) {
// If explosion finish, Remove the current element from the iterator & list.
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);
}
}
// Implements method of 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();
}
// Implements method of SurfaceHolder.Callback
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
}
// Implements method of SurfaceHolder.Callback
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
boolean retry= true;
while(retry) {
try {
this.gameThread.setRunning(false);
// Parent thread must wait until the end of GameThread.
this.gameThread.join();
}catch(InterruptedException e) {
e.printStackTrace();
}
retry= true;
}
}
}
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.
Chú ý: Bạn có thểm tham khảo thêm tài liệu về hiệu ứng âm thanh tại:
Các hướng dẫn lập trình Android
- Cấu hình Android Emulator trong Android Studio
- Hướng dẫn và ví dụ Android ToggleButton
- Tạo một File Finder Dialog đơn giản trong Android
- Hướng dẫn và ví dụ Android TimePickerDialog
- Hướng dẫn và ví dụ Android DatePickerDialog
- Bắt đầu với Android cần những gì?
- Cài đặt Android Studio trên Windows
- Cài đặt Intel® HAXM cho Android Studio
- Hướng dẫn và ví dụ Android AsyncTask
- Hướng dẫn và ví dụ Android AsyncTaskLoader
- Hướng dẫn lập trình Android cho người mới bắt đầu - Các ví dụ cơ bản
- Làm sao biết số số điện thoại của Android Emulator và thay đổi nó
- Hướng dẫn và ví dụ Android TextInputLayout
- Hướng dẫn và ví dụ Android CardView
- Hướng dẫn và ví dụ Android ViewPager2
- Lấy số điện thoại trong Android sử dụng TelephonyManager
- Hướng dẫn và ví dụ Android Phone Call
- Hướng dẫn và ví dụ Android Wifi Scanning
- Hướng dẫn lập trình Android Game 2D cho người mới bắt đầu
- Hướng dẫn và ví dụ Android DialogFragment
- Hướng dẫn và ví dụ Android CharacterPickerDialog
- Hướng dẫn lập trình Android cho người mới bắt đầu - Hello Android
- Hướng dẫn sử dụng Android Device File Explorer
- Bật tính năng USB Debugging trên thiết bị Android
- Hướng dẫn và ví dụ Android UI Layouts
- Hướng dẫn và ví dụ Android SMS
- Hướng dẫn lập trình Android với Database SQLite
- Hướng dẫn và ví dụ Google Maps Android API
- Hướng dẫn chuyển văn bản thành lời nói trong Android
- Hướng dẫn và ví dụ Android Space
- Hướng dẫn và ví dụ Android Toast
- Tạo một Android Toast tùy biến
- Hướng dẫn và ví dụ Android SnackBar
- Hướng dẫn và ví dụ Android TextView
- Hướng dẫn và ví dụ Android TextClock
- Hướng dẫn và ví dụ Android EditText
- Hướng dẫn và ví dụ Android TextWatcher
- Định dạng số thẻ tín dụng với Android TextWatcher
- Hướng dẫn và ví dụ Android Clipboard
- Tạo một File Chooser đơn giản trong Android
- Hướng dẫn và ví dụ Android AutoCompleteTextView và MultiAutoCompleteTextView
- Hướng dẫn và ví dụ Android ImageView
- Hướng dẫn và ví dụ Android ImageSwitcher
- Hướng dẫn và ví dụ Android ScrollView và HorizontalScrollView
- Hướng dẫn và ví dụ Android WebView
- Hướng dẫn và ví dụ Android SeekBar
- Hướng dẫn và ví dụ Android Dialog
- Hướng dẫn và ví dụ Android AlertDialog
- Hướng dẫn và ví dụ Android RatingBar
- Hướng dẫn và ví dụ Android ProgressBar
- Hướng dẫn và ví dụ Android Spinner
- Hướng dẫn và ví dụ Android Button
- Hướng dẫn và ví dụ Android Switch
- Hướng dẫn và ví dụ Android ImageButton
- Hướng dẫn và ví dụ Android FloatingActionButton
- Hướng dẫn và ví dụ Android CheckBox
- Hướng dẫn và ví dụ Android RadioGroup và RadioButton
- Hướng dẫn và ví dụ Android Chip và ChipGroup
- Sử dụng các tài sản ảnh và biểu tượng của Android Studio
- Thiết lập SD Card cho Android Emulator
- Ví dụ với ChipGroup và các Chip Entry
- Làm sao thêm thư viện bên ngoài vào dự án Android trong Android Studio?
- Làm sao loại bỏ các quyền đã cho phép trên ứng dụng Android
- Làm sao loại bỏ các ứng dụng ra khỏi Android Emulator?
- Hướng dẫn và ví dụ Android LinearLayout
- Hướng dẫn và ví dụ Android TableLayout
- Hướng dẫn và ví dụ Android FrameLayout
- Hướng dẫn và ví dụ Android QuickContactBadge
- Hướng dẫn và ví dụ Android StackView
- Hướng dẫn và ví dụ Android Camera
- Hướng dẫn và ví dụ Android MediaPlayer
- Hướng dẫn và ví dụ Android VideoView
- Phát hiệu ứng âm thanh trong Android với SoundPool
- Hướng dẫn lập trình mạng trong Android - Android Networking
- Hướng dẫn xử lý JSON trong Android
- Lưu trữ dữ liệu trên thiết bị với Android SharedPreferences
- Hướng dẫn lập trình Android với bộ lưu trữ trong (Internal Storage)
- Hướng dẫn lập trình Android với bộ lưu trữ ngoài (External Storage)
- Hướng dẫn sử dụng Intent trong Android
- Ví dụ về một Android Intent tường minh, gọi một Intent khác
- Ví dụ về Android Intent không tường minh, mở một URL, gửi một email
- Hướng dẫn sử dụng Service trong Android
- Hướng dẫn sử dụng thông báo trong Android - Android Notification
- Hướng dẫn và ví dụ Android DatePicker
- Hướng dẫn và ví dụ Android TimePicker
- Hướng dẫn và ví dụ Android Chronometer
- Hướng dẫn và ví dụ Android OptionMenu
- Hướng dẫn và ví dụ Android ContextMenu
- Hướng dẫn và ví dụ Android PopupMenu
- Hướng dẫn và ví dụ Android Fragment
- Hướng dẫn và ví dụ Android ListView
- Android ListView với Checkbox sử dụng ArrayAdapter
- Hướng dẫn và ví dụ Android GridView
Show More