Search code examples
androidmatrixbitmapsurfaceview

Bitmap explosion animation spinning in one place, not moving outward


I have a short game using a SurfaceView. My "ship" is just a white rectangle, and the obstacles are bitmaps of asteroids. When an asteroid collides with the ship, an explosion animation is supposed to take place where 20 pieces of debris (just a 10 x 10 white dot) fly out in all directions.

The problem is that when the collision occurs, all the debris just sits in one place spinning in a circle, as shown here:

screenshot of explosion

The explosion does indeed start at the exact position of my ship. The particles just don't move outward. Have I placed the code in the wrong place? Does anyone know where I have gone wrong with this?

Here is the class:

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Point;
import android.graphics.Rect;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.MotionEvent;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import java.util.Random;


public class GameView extends SurfaceView implements Runnable, SurfaceHolder.Callback {

    private Thread gameViewThread = null;
    SurfaceHolder surfaceHolder;
    boolean okToRun;

    /*
    SHIP AND ASTEROID STUFF
     */
    private Ship ship;
    private Point shipPoint;
    private Rect textRect = new Rect();
    private float oldX;
    private float oldY;
    private AsteroidController asteroidController;
    /*
    END SHIP AND ASTEROID STUFF
     */

    /*
    GAME OVER STUFF
     */
    private boolean isMoving = false;
    private boolean isGameOver = false;
    private long waitTime;
    /*
    END GAME OVER STUFF
     */

    /*
    EXPLOSION STUFF
     */
    Bitmap explosionBMP;
    private Matrix[] explosion = new Matrix[20];
    boolean isDestroyed = false;
    boolean explosionStarted = false;
    float[] explosionXPosition = new float[20];
    float[] explosionYPosition = new float[20];
    float explosionSpeed = 20.0f;
    float[] explosionRotation = new float[20];
    /*
    END EXPLOSION STUFF
     */

     /*
    SCREEN DIMENSIONS
     */
    DisplayMetrics displayMetrics = getResources().getDisplayMetrics();
    int gameWidth = displayMetrics.widthPixels;
    int gameHeight = displayMetrics.heightPixels;
    /*
    END SCREEN DIMENSIONS
     */

    public GameView(Context context) {
        super(context);

        surfaceHolder = this.getHolder();
        okToRun = true;

        ship = new Ship(new Rect(100, 100, 200, 200), Color.WHITE);
        shipPoint = new Point(gameWidth / 2, 3 * gameHeight / 4);
        ship.update(shipPoint);

        asteroidController = new AsteroidController(context);

        //JUST SPAWNING ONE ASTEROID FOR NOW
        asteroidController.createAsteroid(1);

        //JUST A 10 x 10 WHITE DOT
        explosionBMP = BitmapFactory.decodeResource(getResources(), R.drawable.explosion_debris);

        //DON'T WANT A NULL POINTER RIGHT OFF THE BAT
        for(int i = 0; i < explosion.length; i++) {
            explosion[i] = new Matrix();
        }

        setFocusable(true);
    }

    @Override
    public void run() {
        while(okToRun) {
            if(!surfaceHolder.getSurface().isValid()) {
                continue;
            }
            Canvas gameCanvas = surfaceHolder.lockCanvas();

            this.update();
            this.draw(gameCanvas);
            surfaceHolder.unlockCanvasAndPost(gameCanvas);
        }
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {

        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:

                isMoving = true;
                oldX = event.getX();
                oldY = event.getY();
                if (isGameOver && System.currentTimeMillis() - waitTime >= 3000) {
                    isGameOver = false;
                    resetGame();
                }
                break;
            case MotionEvent.ACTION_MOVE:
                if (!isGameOver && isMoving) {

                    /*
                    THIS CODE ALLOWS TOUCH EVENT TO BE ANYWHERE ON SCREEN WHILE
                    SHIP MOVES ACCORDINGLY
                     */
                    float newX = event.getX();
                    float newY = event.getY();
                    float deltaX = getOffsetCoords(oldX, newX, shipPoint.x);
                    float deltaY = getOffsetCoords(oldY, newY, shipPoint.y);
                    shipPoint.set((int) deltaX, (int) deltaY);
                    oldX = newX;
                    oldY = newY;
                }
                return false;
        }
        return true;
    }

    /*
    GET THE DIFFERENCE IN SHIP COORDS AND TOUCH COORDS
     */
    private float getOffsetCoords(float oldVal, float newVal, float current) {
        return current + (newVal - oldVal);
    }




    @Override
    public void draw(Canvas canvas) {
        super.draw(canvas);

        //RANDOMS FOR STARS
        Random random0 = new Random();
        Random random1 = new Random();

        //BACKGROUND
        canvas.drawColor(Color.BLACK);
        Bitmap single_pixel_star = BitmapFactory.decodeResource(getResources(), R.drawable.single_pixel_star);
        Bitmap three_pixel_star = BitmapFactory.decodeResource(getResources(), R.drawable.three_pixel_star);

        //Draw random stars on the canvas
        canvas.drawBitmap(single_pixel_star, random0.nextInt(canvas.getWidth() - single_pixel_star.getWidth()), random0.nextInt(canvas.getHeight() - three_pixel_star.getHeight()), null);
        canvas.drawBitmap(three_pixel_star, random1.nextInt(canvas.getWidth() - three_pixel_star.getWidth()), random1.nextInt(canvas.getHeight() - three_pixel_star.getHeight()), null);

        //DRAW ASTEROID(s)
        asteroidController.render(canvas);
        //DRAW SHIP
        ship.draw(canvas);


        if (isGameOver) {

            ship.removeShip();

            isDestroyed = true;
            explosionStarted = true;

            Paint gameOverPaint = new Paint();
            gameOverPaint.setTextSize(200);
            gameOverPaint.setColor(Color.RED);
            showGameOver(canvas, gameOverPaint, "YOU LOSE!");

            /*
            SHIP GOES KABOOM!
             */
            for(int i = 0; i < explosion.length; i++) {
                canvas.drawBitmap(explosionBMP, explosion[i], null);
            }

        }
    }
    public void update() {
        if (!isGameOver) {
            ship.update(shipPoint);
            asteroidController.update();

            if (asteroidController.isCollision(ship)) {
                isGameOver = true;
                waitTime = System.currentTimeMillis();
            }
        }
        Matrix[] localExplosionDebris = new Matrix[20];
        if(explosionStarted) {
            for(int i = 0; i < explosionXPosition.length; i++) {
                explosionXPosition[i] = getShipPointX();
                explosionYPosition[i] = getShipPointY();
                Random rand = new Random();
                explosionRotation[i] = (float) rand.nextInt(360);
            }
            explosionStarted = false;
        }
        for(int i = 0; i < localExplosionDebris.length; i++) {
            localExplosionDebris[i] = new Matrix();
        }
        if(isDestroyed) {
            for(int i = 0; i < localExplosionDebris.length; i++) {
                float debrisXSpeed = (float) Math.sin(explosionRotation[i]*(Math.PI/180)) * explosionSpeed - .2f;
                float debrisYSpeed = (float) Math.cos(explosionRotation[i]*(Math.PI/180)) * explosionSpeed - .2f;

                explosionXPosition[i] += debrisXSpeed;
                explosionYPosition[i] -= debrisYSpeed;

                localExplosionDebris[i].postRotate(0, explosionBMP.getWidth()/2, explosionBMP.getHeight()/2);
                localExplosionDebris[i].postTranslate(explosionXPosition[i], explosionYPosition[i]);

                explosion[i].set(localExplosionDebris[i]);
            }
        }
    }

    /*
    SHOW GAME OVER TEXT ON THE CENTER OF THE SCREEN UNTIL 3 SECONDS HAVE PASSED AND TOUCH EVENT OCCURS
     */
    private void showGameOver(Canvas canvas, Paint paint, String gameOverString) {
        paint.setTextAlign(Paint.Align.LEFT);
        canvas.getClipBounds(textRect);
        int canvasH = textRect.height();
        int canvasW = textRect.width();
        paint.getTextBounds(gameOverString, 0, gameOverString.length(), textRect);
        float textX = canvasW / 2f - textRect.width() / 2f - textRect.left;
        float textY = canvasH / 2f - textRect.height() / 2f - textRect.bottom;
        canvas.drawText(gameOverString, textX, textY, paint);
    }

    /*
    RESET SHIP TO ORIGINAL LOCATION AND COLOR
     */
    public void resetGame() {
        ship = new Ship(new Rect(100, 100, 200, 200), Color.WHITE);
        shipPoint = new Point(gameWidth / 2, 3 * gameHeight / 4);

        //TODO STOP BG MUSIC ONCE FILE IS ADDED
        //TODO RESET SCORE ONCE ADDED
    }


    public void pause() {
        okToRun = false;
        while(true) {
            try {
                gameViewThread.join();
            } catch(InterruptedException e) {
                Log.d("ERROR", e.getMessage());
            }
            break;
        }
        gameViewThread = null;
    }

    public void resume() {
        okToRun = true;
        gameViewThread = new Thread(this);
        gameViewThread.start();

    }

    /*
    SELF EXPLANATORY
     */
    public int getShipPointX() {
        return shipPoint.x;
    }

    public int getShipPointY() {
        return shipPoint.y;
    }

    @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {

    }

    @Override
    public void surfaceCreated(SurfaceHolder holder) {

    }

    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {

    }
}

Solution

  • I rearranged some boolean variables and I now have an explosion. In the following if statement, you can see what I commented out:

    if (isGameOver) {
    
            ship.removeShip();
    
            //isDestroyed = true;
            //explosionStarted = true;
            Paint gameOverPaint = new Paint();
            gameOverPaint.setTextSize(200);
            gameOverPaint.setColor(Color.RED);
            showGameOver(canvas, gameOverPaint, "YOU LOSE!");
    
            /*
            SHIP GOES KABOOM!
             */
            for(int i = 0; i < explosion.length; i++) {
                canvas.drawBitmap(explosionBMP, explosion[i], null);
            }
    
        }
    

    And in another if statement, I added a condition, placed the commented out booleans (shown above) to that statement, then reset them in an else:

    if (!isGameOver) {
            ship.update(shipPoint);
            asteroidController.update();
    
            if (asteroidController.isCollision(ship) && !isDestroyed) { //<--added the && !isDestroyed
                isGameOver = true; 
                isDestroyed = true; //<-- added this
                explosionStarted = true; //<-- and this
                waitTime = System.currentTimeMillis();
            } else {
                isGameOver = false; //<-- then reset here
                isDestroyed = false; //<-- and here
                explosionStarted = false; //<-- and here
            }
        }
    

    All done. :-)

    Showing proof that it works now