Sprites temporales. Programación de Juegos en Android 8.
Si recuerdan del primer tutorial cuando se hace click sobre un caracter desaparece y aparece una mancha de sangre. Después de unos milisegundos esta sangre también desaparece. En este tutorial vamos a desarrollar esta funcionalidad utilizando un nuevo tipo de sprites.
Estos nuevos sprites no van a tener ni movimiento ni velocidad. Su posición va a ser definida en tiempo de construcción y vamos a tener uno cuando hagamos click. Estos sprites son, en muchos sentidos, mucho más simples que los primeros que creamos. La única nueva complejidad viene con el hecho de que tienen que desaparecer después de que pasa un tiempo.
Vamos a empezar con la creación de una nueva clase llamada TempSprite.
package com.edu4java.android.killthemall;
import java.util.List;
import android.graphics.Bitmap;
import android.graphics.Canvas;
public class TempSprite {
private float x;
private float y;
private Bitmap bmp;
private int life = 15;
private List<TempSprite> temps;
public TempSprite(List<TempSprite>
temps, GameView gameView, float x,
float y, Bitmap bmp) {
this.x = Math.min(Math.max(x - bmp.getWidth() / 2, 0),
gameView.getWidth() - bmp.getWidth());
this.y = Math.min(Math.max(y - bmp.getHeight() / 2, 0),
gameView.getHeight() - bmp.getHeight());
this.bmp = bmp;
this.temps = temps;
}
public void onDraw(Canvas canvas) {
update();
canvas.drawBitmap(bmp, x, y, null);
}
private void update() {
if (--life < 1) {
temps.remove(this);
}
}
}
Para hacer que el sprite desaparezca, introducimos un nuevo campo llamado life. Va a contener el número de ticks en los que el sprite va a estar vivo. Un tick es una marca para cada vez que vez que se llama al método update dentro del game loop. Las imagenes por segundo (FPS) es una marca para cada llamada al método onDraw en un segundo. En nuestro juego FPS y los ticks de update están sincronizados pero no tiene porque ser así.
Cada vez que se llama al método update, disminuimos el valor de la vida en uno y cuando es menor que 1, eliminamos nuestro sprite de la lista de sprites temporales temps. La lista temps guarda todos los sprites temporales y es pasado como parámetro por el view en el constructor de TempSprite.
El constructor recibe además de los parámetros; una referencia al view, la posición (x,y) de donde hacemos click y el bitmap. La posición (x,y) se ajusta en el constructor teniendo en cuenta varios factores;
- El centro del bitmap de la sangre tiene que estar en el eje de coordenadas (x,y). Si no lo está, vamos a tener la sensación de que la sangre está abajo a la izquierda de donde hacemos click.
Conseguimos esto con: x - bmp.getWidth() / 2
- (x,y) no pueden salir de la pantalla. Si ocurre tendremos comportamientos inesperados y errores. Por lo tanto:
- x e y tienen que ser mayor que 0. Math.max(x - bmp.getWidth() / 2, 0)
- x tiene que ser menor que gameView.getWidth(), descontando el bitmap e y tiene que ser menor que gameView.getHeight() descontando la altura del bitmap. Math.min( ... , gameView.getWidth() - bmp.getWidth());
this.x = Math.min(Math.max(x - bmp.getWidth()
/ 2, 0), gameView.getWidth() - bmp.getWidth());
this.y = Math.min(Math.max(y - bmp.getHeight()
/ 2, 0), gameView.getHeight() - bmp.getHeight());
El bitmap se imprime completo en la posición calculada de (x,y) en el método onDraw.
Todavía hacen falta unos cambios más;
- Hay que copiar la siguiente imagen a la carpeta recursos:
- Añadir la siguiente línea de código como última línea en el constructor de view.
bmpBlood = BitmapFactory.decodeResource(getResources(), R.drawable.blood1);
- Incluir el campo temps
private List<TempSprite> temps = new ArrayList<TempSprite>();
- Y añadir los dibujos de los sprites temps al método onDraw.
for (int i = temps.size() - 1; i
>= 0; i--) {
temps.get(i).onDraw(canvas);
}
Iteramos hacia atrás para evitar errores cuando los sprites sean eliminados. Escribimos este código antes de dibujar los otros sprites para dar la sensación de que la sangre está en un segundo plano.
-
Y por último añadimos
temps.add(new TempSprite(temps, this, x, y, bmpBlood));
justo después de que cada sprite sea eliminado en el método onTouchEventy.
package com.edu4java.android.killthemall;
import java.util.ArrayList;
import java.util.List;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.view.MotionEvent;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
public class GameView extends SurfaceView
{
private GameLoopThread gameLoopThread;
private List<Sprite> sprites = new ArrayList<Sprite>();
private List<TempSprite> temps = new ArrayList<TempSprite>();
private long lastClick;
private Bitmap bmpBlood;
public GameView(Context context) {
super(context);
gameLoopThread = new GameLoopThread(this);
getHolder().addCallback(new SurfaceHolder.Callback() {
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
boolean retry = true;
gameLoopThread.setRunning(false);
while (retry) {
try {
gameLoopThread.join();
retry = false;
}
catch (InterruptedException e) {}
}
}
@Override
public void surfaceCreated(SurfaceHolder holder) {
createSprites();
gameLoopThread.setRunning(true);
gameLoopThread.start();
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format,
int width, int height) {
}
});
bmpBlood = BitmapFactory.decodeResource(getResources(), R.drawable.blood1);
}
private void createSprites() {
sprites.add(createSprite(R.drawable.bad1));
sprites.add(createSprite(R.drawable.bad2));
sprites.add(createSprite(R.drawable.bad3));
sprites.add(createSprite(R.drawable.bad4));
sprites.add(createSprite(R.drawable.bad5));
sprites.add(createSprite(R.drawable.bad6));
sprites.add(createSprite(R.drawable.good1));
sprites.add(createSprite(R.drawable.good2));
sprites.add(createSprite(R.drawable.good3));
sprites.add(createSprite(R.drawable.good4));
sprites.add(createSprite(R.drawable.good5));
sprites.add(createSprite(R.drawable.good6));
}
private Sprite createSprite(int resouce) {
Bitmap bmp = BitmapFactory.decodeResource(getResources(), resouce);
return new Sprite(this, bmp);
}
@Override
protected void onDraw(Canvas canvas) {
canvas.drawColor(Color.BLACK);
for (int i = temps.size() - 1; i
>= 0; i--) {
temps.get(i).onDraw(canvas);
}
for (Sprite sprite : sprites) {
sprite.onDraw(canvas);
}
}
@Override
public boolean onTouchEvent(MotionEvent event) {
if (System.currentTimeMillis() - lastClick > 300) {
lastClick = System.currentTimeMillis();
float x = event.getX();
float y = event.getY();
synchronized (getHolder()) {
for (int i = sprites.size() - 1; i
>= 0; i--) {
Sprite
sprite = sprites.get(i);
if (sprite.isCollision(x, y)) {
sprites.remove(sprite);
temps.add(new TempSprite(temps, this, x, y, bmpBlood));
break;
}
}
}
}
return true;
}
}
<< Eventos de Touch y detección de colisiones de Sprite | Indice >> |