Video Tutoriales

Android Game Programming 8. Temporary Sprites.

If you remember from the first tutorial in the series when you press over a character it disappears and a blood stain appears. After some milliseconds this blood disappears. In this tutorial we are going to develop this functionality using a new kind of sprites.

These new sprites are going to have no movement or speed. Their position is going to be defined at construction time and it is going to be the one where we press. There is no animation or movie. These sprites are in most senses simpler than the first we created. The only new complexity comes from the fact that they have to disappear after a while. Let's start with a new class called 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);

             }

       }

}

In order to make the sprite disappear we introduce a new field called life. It will contain the number of ticks the sprite is going to be alive. A tick is a mark for each time the update method is called in our game loop. The frame per seconds FPS is a mark for each onDraw method call in a second. In our game FPS and update ticks are synchronized but it hasn´t got to be like that.

Each time the update method is called we decrease the life value by one and when it gets under 1 we remove our sprite from the temporary sprite list temps. The temps list holds all temporal sprites and it is passed as a parameter by the view in the TempSprite constructor.

The constructor receives in addition as parameters; a reference to the view, the (x,y) position (where we press) and a bitmap. The (x,y) position is adjusted in the constructor taking in consideration various factors:

  • The blood bitmap center has to be in the (x,y) coordinates. If it is not we are going to get the sensation that the blood is down and left from where we press. We get this with: x - bmp.getWidth() / 2
  • (x,y) cannot be passed outside of the screen. If we do that we can get unexpected behaviors and errors. Then:
  1. x and y have to be greater than 0. Math.max(x - bmp.getWidth() / 2, 0)
  2. x has to be minor than gameView.getWidth() discounting the bitmap width and y has to be minor than gameView.getHeight() discounting the bitmap height. 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());

 

Then bitmap is printed entirely in the calculated (x,y) position in the onDraw method.

A few more changes are necessary. We have to copy the next image to the resource folder

and add the next code line as the last line in the view constructor

bmpBlood = BitmapFactory.decodeResource(getResources(), R.drawable.blood1);

Include the temps field

private List<TempSprite> temps = new ArrayList<TempSprite>();

Add the temps sprites drawing to the onDraw method.

      for (int i = temps.size() - 1; i >= 0; i--) {

            temps.get(i).onDraw(canvas);

      }

 

We iterate backwards to avoid errors when the sprites are removed. We write this code before the others sprites drawing to give the sensation that the blood is in the background.

Lastly we add

temps.add(new TempSprite(temps, this, x, y, bmpBlood));

just after the sprite is removed in the onTouchEventy method.

 

 

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;

       }

}