- Introduction
- Créer un projet Game
- La préparation des images et des sonores
- Réglage plein écran (Version:1)
- Afficher le personnage dans le jeu (Version:2)
- Interagir avec le joueur (Version: 3)
- Le jeu avec plusieurs personnages (Version: 4)
- Des effets dans le jeu (Version: 5)
- Des effets de sonore dans le jeu (Version: Release)
Le Tutoriel de programmation de jeux Android 2D pour débutant
1. Introduction
Ce document est basé sur:
Android Studio 3.6.1
L'objectif de ce document est de vous aider de se famialiser avec quelques simples techniques de programmation Game 2D Android. Inclut:
- L'utilisation SuffaceView
- Le dessin sur un Canvas
- Le mouvement du personnage du jeu .
- Des interactions avec les gestes de joueurs
Dans ce document, je vous indique étape par étape, donc vous devez lire et pratiquer progressivêmnt. Nous allons écrire chaque version du jeu de la premère version jusqu'à la dernière version (Release).
2. Créer un projet Game
On Android Studio create a new project:
- Name: Android2DGame
- Package name: org.o7planning.android2dgame
OK, votre projet est créé.
Next you need to create an Activity. On Android Studio select:
- File > New > Activity > Empty Activity
- Activity Name: MainActivity
Notez que nous créons un jeu 2D sur Android, donc vous devez dessiner l'interface du jeu, vous n'avez pas besoin d'un fichier activity_main.xml.
3. La préparation des images et des sonores
Vous avez besoin de quelques photos.
chibi1.png | chibi2.png |
explosion.png |
Le fichier d'audio de l'explosion.
Le fond sonore:
Right-click on the project "res" folder and choose:
- New > Folder > Raw Resource Folder
Copiez des images dans le dossier drawable du projet. Créez un nouveau fichier raw, et copiez le fichier explosion.wav & background.mp3 dans ce dossier.
4. Réglage plein écran (Version:1)
Avec des jeux, vous devez mettre l'image de fond et une chose importante est que vous devez mettre le mode plein écran (Full Screen).
Votre classe MainActivity doit s'étendre de la classe 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);
}
}
Ensuite, mettez l'écran de paysage (Landscape). Vous devez le définir dans 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>
Exécutez l'application:
Remarque: Sur Windows vous pouvez changer la direction du simulateur par Ctrl + F11.
5. Afficher le personnage dans le jeu (Version:2)
Ensuite, vous devez écrire le code pour exposer le personnage du jeu sur l'écran et le déplacer de gauche à droit avec certaine vitesse.
Avec un personnage dans le jeu, vous avez seulement un fichier image mais l'image est divisée en plusieurs régions décrites les différentes actions du personnage.
En utilisant le code, vous pouvez dessiner une image de Canvas dans le jeu, aux coordonnées x, y. Utilisez une boucle pour dessiner en continu sur le Canvas, vous pouvez créer le mouvement du personnage.
En programmant un jeu, vous devez également faire attention à la direction du déplacement des personnages dans le jeu, la vitesse des personnages.
Créez la classe GameObject, les objets du jeu est étendus à partir de cette classe.
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;
}
}
La classe ChibiCharactor simule un personnage dans le jeu.
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 est un fil (thread) qui contrôle la mise à jour de l'interface du jeu.
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;
}
}
La classe GameSurface simule toute la surface du jeu. Cette classe s'étend à partir de SurfaceView, SurfaceView comprend un objet Canvas, les objets dans le jeu seront dessinés dans 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, la deuxième version est terminée, vous pouvez exécuter le jeu.
6. Interagir avec le joueur (Version: 3)
Ensuite, vous pouvez gérer des événements lorsque l'utilisateur touche l'écran, le personnage du jeu va courir vers ce que vous avez touché. Vous devez gérer cet événement dans la classe 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;
}
Voir le code complet:
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;
}
}
}
Réexécutez le jeu:
7. Le jeu avec plusieurs personnages (Version: 4)
Vous pouvez ajouters autres personnage du jeu, ici j'ajoute le deuxième personnage. Vous devez éditer le code de la classe 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;
}
}
}
Réexécutez le jeu:
8. Des effets dans le jeu (Version: 5)
Parfois, vous devez gérer quelques effets du jeu, par exemple, vous conduisez un avion, lorsque l'avion frappe le sol, il va exploser, donc ici l'explosion est un effet. Dan cette section, je vais simuler quand vous cliquez le personnage Chibi, il va exploser.
La classe Explosion simule une explosion, lorsque vous cliquez au personnage Chibi, il est retiré du jeu et une Explosion sera ajoutée à la place où le personnage Chibi vient d'être éliminé.
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;
}
}
Changez le code de la classe 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;
}
}
}
Réexécutez le jeu:
9. Des effets de sonore dans le jeu (Version: Release)
Ensuite, vous devez ajouter des effets de son du jeu, tels que des fond sonores du jeu, le sonore de l'explosition lorsque le personnage Chibi est détruit.
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, maintenant, vous pouvez relancer le jeu et écouter les effets sonores du jeu.
Remarque: Vous pouvez consulter les documents sur les effets sonores à l'adresse suivante:
Tutoriels de programmation Android
- Configurer Android Emulator en Android Studio
- Le Tutoriel de Android ToggleButton
- Créer un File Finder Dialog simple dans Android
- Le Tutoriel de Android TimePickerDialog
- Le Tutoriel de Android DatePickerDialog
- De quoi avez-vous besoin pour démarrer avec Android?
- Installer Android Studio sur Windows
- Installer Intel® HAXM pour Android Studio
- Le Tutoriel de Android AsyncTask
- Le Tutoriel de Android AsyncTaskLoader
- Tutoriel Android pour débutant - Exemples de base
- Comment connaître le numéro de téléphone d'Android Emulator et le changer?
- Le Tutoriel de Android TextInputLayout
- Le Tutoriel de Android CardView
- Le Tutoriel de Android ViewPager2
- Obtenir un numéro de téléphone dans Android à l'aide de TelephonyManager
- Le Tutoriel de Android Phone Call
- Le Tutoriel de Android Wifi Scanning
- Le Tutoriel de programmation de jeux Android 2D pour débutant
- Le Tutoriel de Android DialogFragment
- Le Tutoriel de Android CharacterPickerDialog
- Le Tutoriel Android pour débutant - Hello Android
- Utiliser Android Device File Explorer
- Activer USB Debugging sur un appareil Android
- Le Tutoriel de Android UI Layouts
- Le Tutoriel de Android SMS
- Le Tutoriel de Android et SQLite Database
- Le Tutoriel de Google Maps Android API
- Le Tutoriel de texte pour parler dans Android
- Le Tutoriel de Android Space
- Le Tutoriel de Android Toast
- Créer un Android Toast personnalisé
- Le Tutoriel de Android SnackBar
- Le Tutoriel de Android TextView
- Le Tutoriel de Android TextClock
- Le Tutoriel de Android EditText
- Le Tutoriel de Android TextWatcher
- Formater le numéro de carte de crédit avec Android TextWatcher
- Le Tutoriel de Android Clipboard
- Créer un File Chooser simple dans Android
- Le Tutoriel de Android AutoCompleteTextView et MultiAutoCompleteTextView
- Le Tutoriel de Android ImageView
- Le Tutoriel de Android ImageSwitcher
- Le Tutoriel de Android ScrollView et HorizontalScrollView
- Le Tutoriel de Android WebView
- Le Tutoriel de Android SeekBar
- Le Tutoriel de Android Dialog
- Le Tutoriel de Android AlertDialog
- Tutoriel Android RatingBar
- Le Tutoriel de Android ProgressBar
- Le Tutoriel de Android Spinner
- Le Tutoriel de Android Button
- Le Tutoriel de Android Switch
- Le Tutoriel de Android ImageButton
- Le Tutoriel de Android FloatingActionButton
- Le Tutoriel de Android CheckBox
- Le Tutoriel de Android RadioGroup et RadioButton
- Le Tutoriel de Android Chip et ChipGroup
- Utilisation des Image assets et des Icon assets d'Android Studio
- Configuration de la Carte SD pour Android Emulator
- Exemple ChipGroup et Chip Entry
- Comment ajouter des bibliothèques externes à Android Project dans Android Studio?
- Comment désactiver les autorisations déjà accordées à l'application Android?
- Comment supprimer des applications de Android Emulator?
- Le Tutoriel de Android LinearLayout
- Le Tutoriel de Android TableLayout
- Le Tutoriel de Android FrameLayout
- Le Tutoriel de Android QuickContactBadge
- Le Tutoriel de Android StackView
- Le Tutoriel de Android Camera
- Le Tutoriel de Android MediaPlayer
- Le Tutoriel de Android VideoView
- Jouer des effets sonores dans Android avec SoundPool
- Le Tutoriel de Android Networking
- Analyser JSON dans Android
- Le Tutoriel de Android SharedPreferences
- Le Tutorial de stockage interne Android (Internal Storage)
- Le Tutoriel de Android External Storage
- Le Tutoriel de Android Intents
- Exemple d'une Android Intent explicite, appelant une autre Intent
- Exemple de Android Intent implicite, ouvrez une URL, envoyez un email
- Le Tutoriel de Android Service
- Le Tutoriel Android Notifications
- Le Tutoriel de Android DatePicker
- Le Tutoriel de Android TimePicker
- Le Tutoriel de Android Chronometer
- Le Tutoriel de Android OptionMenu
- Le Tutoriel de Android ContextMenu
- Le Tutoriel de Android PopupMenu
- Le Tutoriel de Android Fragment
- Le Tutoriel de Android ListView
- Android ListView avec Checkbox en utilisant ArrayAdapter
- Le Tutoriel de Android GridView
Show More