Руководство Android 2D Game для начинающих

1- Introduction

Данная статья основана на:
  • Android Studio 1.5

Цель данной статьи ознакомить вас с простой техникой программирования  Game 2D Android. Включает:
  • Использовать SuffaceView
  • Рисовать на Canvas
  • Движение персонажей в игре.
  • Взаимодействие с действиями игрока
В данной статье я покажу вам пошаговое программирование, поэтому вам нужно прочитать и выполнить свержу вниз. Мы напишем каждую версии игры с 1 по последней версии (Release).

2- Create a Game Project

Заметьте, что вы создаете игру 2D на Android, поэтому интерфейс игры должен быть нарисован вами, следовательно вам не нужен файл  activity_main.xml.
Хорошо, ваш Project создан.

3- Preparing Images and sounds

Вам нужно несколько файлов картинок.
  • chibi1.png

  • chibi2.png


 
  • explosion.png





 
Аудиофайл взрыва.
Фоновый звук:
Скопируйте эти картины в папку  drawable в project. Создайте папку  raw, и скопируйте файлы  explosion.wav & background.mp3 в эту папку.

4- Setting fullscreen (Version:1)

С игрой, вам нужно настроить фоновую картину и важно так же настроить режим FullScreen.
Ваш класс  MainActivity должен быть extends (расширен) из класса  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);

        // Set No Title
        this.requestWindowFeature(Window.FEATURE_NO_TITLE);

    }

}
Запустить приложение:
Далее, настройте экран на альбомный режим (Landscape). Вам нужно настроить в  AndroidManifest.xml.
android:screenOrientation="landscape"
Перезапустите приложение:
Заметка: на Windows вы тоже можете менять ориентацию экрана с помощью команды  Ctrl + F11.

5- Show character in the game (Version:2)

Далее вам нужно наисать код, чтобы появился персонаж игры на экране и переместить его слева на право с определенной скоростью.
С персонажем в игре вам нужен только один файл картины, но картина должна быть разделена на несколько секторов, изображающих разные действия персонажа.
Используя код вы можеет нарисовать картину на  Canvas игры, у координатов x, y. Используйте цикл чтобы непрерывно рисовать на  Canvas , вы можете создать движение персонажа.
При программировании игры вам нужно обратить внимание на направление движения персонажей в игре, скорость персонажа.
Создайте класс  GameObject, объекты этой игры будет расширен из данного класса.
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;
    }

}
Класс  ChibiCharactor стимулирует персонажа в игре.
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;

    // 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 это поток (thread) управляющий обновления интерфейса в игре.
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 {
                 // 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;
    }
}
Класс  GameSurface полностью стимулирует поверхность игры, этот класс расширен из  SurfaceView, SurfaceView содержит объект  Canvas, объекты в игре будут нарисованы на 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);

       // 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.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);

        // Set No Title
        this.requestWindowFeature(Window.FEATURE_NO_TITLE);

      
        this.setContentView(new GameSurface(this));
    }

}
Итак, 2-я версия завершена, вы можете запустить игру.

6- Interact with player (Version: 3)

Далее вы можете обрабатывать события при прикосновении пользователя на экран, персонаж игры будет бежать в направлении нажатия. Вам нужно обработать это событие на классе  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;
}
Посмотреть код полностью:
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);

        // 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;
        }
    }

}
Перезапустить игру:

7- Game with multiple characters (Version: 4)

Вы можете создать других персонажей в игре, здесь я добавил 2-го персонажа. Поправьте код класса  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);

        // Make Game Surface focusable so it can handle events.
        this.setFocusable(true);

        // Sét 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;
        }
    }

}
Перезапустить игруi.

8- Effects in the Game (Version: 5)

Иногда вам нужно обработать некоторые эффекты игры, например, вы управляете самолетом, при падении самолета он должен взорваться, взрыв и есть эффект. В этой части я покажу когда вы кликаете (click) на персонажа Chibi, он взрывается.
Класс  Explosion стимулирует взрыв, когда вы кликаете на персонаж  Chibi, он выбывает из игры и объет Explosion будет добавлен в игру на позицию выбывшего персонажаđ  Chibi.
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;
    }

}
Поменять код класса  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);

      // 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;
      }
  }

}
  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;
  }
 
}
Перезапустить игру:

9- Sound effects in game (Version: Release)

Далее вам нужно добавить звуковой эффект в игру, например фоновый звук игры, звук взрыв персонажа Chibi при уничтожении.
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);

        // 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;
        }
    }

}
Заметка: Можете посмотреть документы звуковых эффектовi:
Теперь вы можете перезапустить игру и услышать звуковые эффекты в игре.