Руководство Android 2D Game для начинающих
1. Введение
Данная статья основана на:
Android Studio 3.6.1
Цель данной статьи ознакомить вас с простой техникой программирования Game 2D Android. Включает:
- Использовать SuffaceView.
- Рисовать на Canvas.
- Движение персонажей в игре.
- Взаимодействие с действиями игрока
В данной статье я покажу вам пошаговое программирование, поэтому вам нужно прочитать и выполнить свержу вниз. Мы напишем каждую версии игры с 1 по последней версии (Release).
2. Создать Game Project
На Android Studio создайте новый project:
- Name: Android2DGame
- Package name: org.o7planning.android2dgame
Хорошо, ваш Project создан.
Далее, вам нужно создать Activity. На Android Studio выберите:
- File > New > Activity > Empty Activity
- Activity Name: MainActivity
Заметьте, что вы создаете игру 2D на Android, поэтому интерфейс игры должен быть нарисован вами, следовательно вам не нужен файл activity_main.xml.
3. Приготовить изображения и звук
Вам нужно несколько файлов изображений.
chibi1.png | chibi2.png |
explosion.png |
Аудиофайл взрыва.
Фоновый звук:
Нажмите на правую кнопку мыши в папку "res" у project и выберите:
- New > Folder > Raw Resource Folder
Скопируйте эти картины в папку drawable в project. Создайте папку raw, и скопируйте файлы explosion.wav & background.mp3 в эту папку.
4. Настроить fullscreen (Version:1)
С игрой, вам нужно настроить фоновую картину и важно так же настроить режим FullScreen.
Ваш класс MainActivity должен быть extends (расширен) из класса 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);
}
}
Далее, настройте экран на альбомный режим (Landscape). Вам нужно настроить в 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>
Запустить приложение:
Заметка: на Windows вы тоже можете менять ориентацию экрана с помощью команды Ctrl + F11.
5. Отображение персонажа в игре (Version:2)
Далее вам нужно наисать код, чтобы появился персонаж игры на экране и переместить его слева на право с определенной скоростью.
С персонажем в игре вам нужен только один файл картины, но картина должна быть разделена на несколько секторов, изображающих разные действия персонажа.
Используя код вы можеет нарисовать картину на Canvas игры, у координатов x, y. Используйте цикл чтобы непрерывно рисовать на Canvas , вы можете создать движение персонажа.
При программировании игры вам нужно обратить внимание на направление движения персонажей в игре, скорость персонажа.
Создайте класс GameObject, объекты этой игры будет расширен из данного класса.
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;
}
}
Класс ChibiCharactor стимулирует персонажа в игре.
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 это поток (thread) управляющий обновления интерфейса в игре.
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;
}
}
Класс GameSurface полностью стимулирует поверхность игры, этот класс расширен из SurfaceView, SurfaceView содержит объект Canvas, объекты в игре будут нарисованы на 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));
}
}
Итак, 2-я версия завершена, вы можете запустить игру.
6. Интерактивность с игроком (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.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;
}
}
}
Перезапустить игру:
7. Игра со многими персонажами (Version: 4)
Вы можете создать других персонажей в игре, здесь я добавил 2-го персонажа. Поправьте код класса 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;
}
}
}
Перезапустить игруi.
8. Эффекты в игре (Version: 5)
Иногда вам нужно обработать некоторые эффекты игры, например, вы управляете самолетом, при падении самолета он должен взорваться, взрыв и есть эффект. В этой части я покажу когда вы кликаете (click) на персонажа Chibi, он взрывается.
Класс Explosion стимулирует взрыв, когда вы кликаете на персонаж Chibi, он выбывает из игры и объет Explosion будет добавлен в игру на позицию выбывшего персонажаđ Chibi.
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;
}
}
Поменять код класса 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;
}
}
}
Перезапустить игру:
9. Звуковые эффекты в игре (Version: Release)
Далее вам нужно добавить звуковой эффект в игру, например фоновый звук игры, звук взрыв персонажа Chibi при уничтожении.
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;
}
}
}
Теперь вы можете перезапустить игру и услышать звуковые эффекты в игре.
Заметка: Можете посмотреть документы звуковых эффектовi:
Pуководства Android
- Настроить Android Emulator в Android Studio
- Руководство Android ToggleButton
- Создать простой File Finder Dialog в Android
- Руководство Android TimePickerDialog
- Руководство Android DatePickerDialog
- Что мне нужно для начала работы с Android?
- Установите Android Studio в Windows
- Установите Intel® HAXM для Android Studio
- Руководство Android AsyncTask
- Руководство Android AsyncTaskLoader
- Руководство Android для начинающих - основные примеры
- Как узнать номер телефона Android Emulator и изменить его?
- Руководство Android TextInputLayout
- Руководство Android CardView
- Руководство Android ViewPager2
- Получить номер телефона в Android с помощью TelephonyManager
- Руководство Android Phone Call
- Руководство Android Wifi Scanning
- Руководство Android 2D Game для начинающих
- Руководство Android DialogFragment
- Руководство Android CharacterPickerDialog
- Руководство Android для начинающих - Hello Android
- Использование Android Device File Explorer
- Включить USB Debugging на устройстве Android
- Руководство Android UI Layouts
- Руководство Android SMS
- Руководство Android SQLite Database
- Руководство Google Maps Android API
- Руководство Текст в речь на Android
- Руководство Android Space
- Руководство Android Toast
- Создание пользовательских Android Toast
- Руководство Android SnackBar
- Руководство Android TextView
- Руководство Android TextClock
- Руководство Android EditText
- Руководство Android TextWatcher
- Форматирование номера кредитной карты с помощью Android TextWatcher
- Руководство Android Clipboard
- Создать простой File Chooser в Android
- Руководство Android AutoCompleteTextView и MultiAutoCompleteTextView
- Руководство Android ImageView
- Руководство Android ImageSwitcher
- Руководство Android ScrollView и HorizontalScrollView
- Руководство Android WebView
- Руководство Android SeekBar
- Руководство Android Dialog
- Руководство Android AlertDialog
- Руководство Android RatingBar
- Руководство Android ProgressBar
- Руководство Android Spinner
- Руководство Android Button
- Руководство Android Switch
- Руководство Android ImageButton
- Руководство Android FloatingActionButton
- Руководство Android CheckBox
- Руководство Android RadioGroup и RadioButton
- Руководство Android Chip и ChipGroup
- Использование Image assets и Icon assets Android Studio
- Настройка SD Card для Android Emulator
- Пример ChipGroup и Chip Entry
- Как добавить внешние библиотеки в Android Project в Android Studio?
- Как отключить разрешения, уже предоставленные приложению Android?
- Как удалить приложения из Android Emulator?
- Руководство Android LinearLayout
- Руководство Android TableLayout
- Руководство Android FrameLayout
- Руководство Android QuickContactBadge
- Руководство Android StackView
- Руководство Android Camera
- Руководство Android MediaPlayer
- Руководство Android VideoView
- Воспроизведение звуковых эффектов в Android с помощью SoundPool
- Руководство Android Networking
- Руководство Android JSON Parser
- Руководство Android SharedPreferences
- Руководство Android Internal Storage
- Руководство Android External Storage
- Руководство Android Intents
- Пример явного Android Intent, вызов другого Intent
- Пример неявного Android Intent, откройте URL, отправьте email
- Руководство Android Services
- Использовать оповещения в Android - Android Notification
- Руководство Android DatePicker
- Руководство Android TimePicker
- Руководство Android Chronometer
- Руководство Android OptionMenu
- Руководство Android ContextMenu
- Руководство Android PopupMenu
- Руководство Android Fragment
- Руководство Android ListView
- Android ListView с Checkbox с помощью ArrayAdapter
- Руководство Android GridView
Show More