/*
* Copyright (C) 2016 Google Inc. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.android.apps.santatracker.games.gumball;
import android.content.Context;
import android.content.res.Resources;
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.util.AttributeSet;
import android.view.View;
import com.google.android.apps.santatracker.R;
import org.jbox2d.collision.shapes.CircleShape;
import org.jbox2d.collision.shapes.EdgeShape;
import org.jbox2d.collision.shapes.Shape;
import org.jbox2d.common.Vec2;
import org.jbox2d.dynamics.Body;
import org.jbox2d.dynamics.BodyType;
import org.jbox2d.dynamics.Fixture;
import java.util.Random;
/**
* Custom view which contains the elements used in the physics word.
* It handles the painting of all bitmaps for the game, including all levels (canes),
* the pipe and gumballs.
*/
public class TiltGameView extends View {
public static final float GUMBALL_DENSITY = 185.77f;
public static final float GUMBALL_RADIUS = 0.258f;
public static final float GUMBALL_BOUNCE = 0.2f;
public static final float GUMBALL_FRICTION = 0.8f;
/**
* The physics world for the gumball game.
*/
private PhysicsWorld mWorld;
/**
* Bitmaps for all elements on screen.
*/
private Bitmap mGumballBlue;
private Bitmap mGumballYellow;
private Bitmap mGumballRed;
private Bitmap mGumballGreen;
private Bitmap mGumballOrange;
private Bitmap mGumballPurple;
private Bitmap mCaneMainLong;
private Bitmap mCaneMainLongReverse;
private Bitmap mCaneMainMed;
private Bitmap mCaneMainMedReverse;
private Bitmap mCaneMainSmall;
private Bitmap mCaneMainSmallReverse;
private Bitmap mCaneMainTiny;
private Bitmap mCaneMainTinyReverse;
private Bitmap mCaneHook;
private Bitmap mCaneHookFlip;
private Bitmap mCaneHookReverse;
private Bitmap mCaneHookReverseFlip;
private Bitmap mCaneEnd;
private Bitmap mCaneEndFlip;
private Bitmap mCaneEndReverse;
private Bitmap mCaneEndReverseFlip;
private Bitmap mCaneMainSmallAngleNine;
private Bitmap mCaneMainSmallAngleSix;
private Bitmap mCaneMainSmallAngleTwelve;
private Bitmap mCaneMainReverseTinyAngleTwelve;
private Bitmap mCaneMainLargeAngleSix;
private Bitmap mCaneMainMedAngleSix;
/**
* Bitmap of a pipe where gumballs drop.
*/
private Bitmap mPipeSides;
/**
* Default paint object that is used to draw all bitmaps to the screen.
*/
private Paint mPaint = new Paint();
/**
* Identifiers for objects in the world.
*/
public static final int GUMBALL_RED = 0;
public static final int GUMBALL_BLUE = 1;
public static final int GUMBALL_YELLOW = 2;
public static final int GUMBALL_GREEN = 3;
public static final int GUMBALL_ORANGE = 4;
public static final int GUMBALL_PURPLE = 5;
public static final int[] GUMBALLS = new int[]{GUMBALL_RED, GUMBALL_BLUE, GUMBALL_YELLOW,
GUMBALL_GREEN, GUMBALL_ORANGE, GUMBALL_PURPLE};
public static final int CANE_MAIN_LONG = 6;
public static final int CANE_MAIN_LONG_REVERSE = 7;
public static final int CANE_MAIN_MEDIUM = 8;
public static final int CANE_MAIN_MEDIUM_REVERSE = 9;
public static final int CANE_MAIN_SMALL = 10;
public static final int CANE_MAIN_SMALL_REVERSE = 11;
public static final int CANE_MAIN_TINY = 12;
public static final int CANE_MAIN_TINY_REVERSE = 13;
public static final int CANE_HOOK = 14;
public static final int CANE_HOOK_FLIP = 15;
public static final int CANE_HOOK_REVERSE = 16;
public static final int CANE_HOOK_REVERSE_FLIP = 17;
public static final int CANE_END = 18;
public static final int CANE_END_FLIP = 19;
public static final int CANE_END_REVERSE = 20;
public static final int CANE_END_REVERSE_FLIP = 21;
public static final int CANE_MAIN_SMALL_ANGLE_NINE = 22;
public static final int CANE_MAIN_SMALL_ANGLE_SIX = 23;
public static final int CANE_MAIN_SMALL_ANGLE_TWELVE = 24;
public static final int CANE_MAIN_REVERSE_TINY_ANGLE_SIX = 25;
public static final int CANE_MAIN_LARGE_ANGLE_SIX = 26;
public static final int CANE_MAIN_MED_ANGLE_SIX = 27;
public static final int PIPE_SIDES = -1;
/**
* Bottom of the pipe user data, this is separate from the side because the gumball is removed
* from the scene on collision with it.
*/
public static final int PIPE_BOTTOM = -2;
public static final int GAME_FLOOR = -3;
private static Random sRandomGenerator = new Random();
private TiltGameFragment.GameCountdown mGameCountDown;
public TiltGameView(Context context) {
super(context);
init();
}
public TiltGameView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
private void init() {
setClickable(true);
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
// Load the bitmaps as soon as we have the size of the view to scale them appropriately.
if (mGumballBlue == null) {
loadBitmaps();
}
}
private void loadBitmaps() {
// set the bitmaps
Resources res = getResources();
int[] gumballBlue = {R.drawable.gbg_gumball_blue_1920,
R.drawable.gbg_gumball_blue_800, R.drawable.gbg_gumball_blue_480};
int[] gumballRed = {R.drawable.gbg_gumball_red_1920,
R.drawable.gbg_gumball_red_800, R.drawable.gbg_gumball_red_480};
int[] gumballYellow = {R.drawable.gbg_gumball_yellow_1920,
R.drawable.gbg_gumball_yellow_800,
R.drawable.gbg_gumball_yellow_480};
int[] gumballGreen = {R.drawable.gbg_gumball_green_1920,
R.drawable.gbg_gumball_green_800,
R.drawable.gbg_gumball_green_480};
int[] gumballOrange = {R.drawable.gbg_gumball_orange_1920,
R.drawable.gbg_gumball_orange_800,
R.drawable.gbg_gumball_orange_480};
int[] gumballPurple = {R.drawable.gbg_gumball_purple_1920,
R.drawable.gbg_gumball_purple_800,
R.drawable.gbg_gumball_purple_480};
int[] caneMain = {R.drawable.gbg_candycane_main_1920,
R.drawable.gbg_candycane_main_800,
R.drawable.gbg_candycane_main_480};
int[] caneMainReverse = {R.drawable.gbg_candycane_main_reverse_1920,
R.drawable.gbg_candycane_main_reverse_800,
R.drawable.gbg_candycane_main_reverse_480};
int[] caneHook = {R.drawable.gbg_candycane_hook_1920,
R.drawable.gbg_candycane_hook_800,
R.drawable.gbg_candycane_hook_480};
int[] caneHookReverse = {R.drawable.gbg_candycane_hook_reverse_1920,
R.drawable.gbg_candycane_hook_reverse_800,
R.drawable.gbg_candycane_hook_reverse_480};
int[] caneEnd = {R.drawable.gbg_candycane_end_1920,
R.drawable.gbg_candycane_end_800, R.drawable.gbg_candycane_end_480};
int[] caneEndReverse = {R.drawable.gbg_candycane_end_reverse_1920,
R.drawable.gbg_candycane_end_reverse_800,
R.drawable.gbg_candycane_end_reverse_480};
int[] pipes = {R.drawable.gbg_gumball_funnel_1920,
R.drawable.gbg_gumball_funnel_800, R.drawable.gbg_gumball_funnel_480};
int[] caneMainAngleNine = {R.drawable.gbg_candycane_main_angle_nine_1920,
R.drawable.gbg_candycane_main_angle_nine_960,
R.drawable.gbg_candycane_main_angle_nine_480};
int[] caneMainAngleSix = {R.drawable.gbg_candycane_small_angle_six_1920,
R.drawable.gbg_candycane_small_angle_six_960,
R.drawable.gbg_candycane_small_angle_six_480};
int[] caneMainAngleTwelve = {R.drawable.gbg_candycane_small_angle_twelve_1920,
R.drawable.gbg_candycane_small_angle_twelve_960,
R.drawable.gbg_candycane_small_angle_twelve_480};
int[] caneMainTinyReverseAngleSix = {R.drawable.gbg_candycane_tiny_reverse_angle_six_1920,
R.drawable.gbg_candycane_tiny_reverse_angle_six_960,
R.drawable.gbg_candycane_tiny_reverse_angle_six_480};
int[] caneMainLargeAngleSix = {R.drawable.gbg_candycane_large_angle_six_1920,
R.drawable.gbg_candycane_large_angle_six_960,
R.drawable.gbg_candycane_large_angle_six_480};
int[] caneMainMedAngleSix = {R.drawable.gbg_candycane_med_angle_six_1920,
R.drawable.gbg_candycane_med_angle_six_960,
R.drawable.gbg_candycane_med_angle_six_480};
int[] sizes = {1920, 960, 480};
final int viewWidth = getWidth();
int size = 0;
for (int i = 1; i <= sizes.length; i++){
if (viewWidth <= sizes[i-1]){
size = i-1;
} else {
break;
}
}
mGumballBlue = resizeImage(res, gumballBlue, sizes, size, viewWidth, -360f, true, 1);
mGumballRed = resizeImage(res, gumballRed, sizes, size, viewWidth, -360f, true, 1);
mGumballYellow = resizeImage(res, gumballYellow, sizes, size, viewWidth, -360f, true, 1);
mGumballGreen = resizeImage(res, gumballGreen, sizes, size, viewWidth, -360f, true, 1);
mGumballOrange = resizeImage(res, gumballOrange, sizes, size, viewWidth, -360f, true, 1);
mGumballPurple = resizeImage(res, gumballPurple, sizes, size, viewWidth, -360f, true, 1);
mCaneMainLong = resizeImage(res, caneMain, sizes, size, viewWidth, 180f, false, 1);
mCaneMainLongReverse = resizeImage(res, caneMainReverse, sizes, size, viewWidth, 180f, false,
1);
mCaneMainMed = resizeImage(res, caneMain, sizes, size, viewWidth, 180f, false, .75f);
mCaneMainMedReverse = resizeImage(res, caneMainReverse, sizes, size, viewWidth, 180f, false,
.75f);
mCaneMainSmall = resizeImage(res, caneMain, sizes, size, viewWidth, 180f, false, .50f);
mCaneMainSmallReverse = resizeImage(res, caneMainReverse, sizes, size, viewWidth, 180f,
false, .50f);
mCaneMainTiny = resizeImage(res, caneMain, sizes, size, viewWidth, 180f, false, .25f);
mCaneMainTinyReverse = resizeImage(res, caneMainReverse, sizes, size, viewWidth, 180f, false,
.25f);
mCaneMainSmallAngleNine = resizeImage(res, caneMainAngleNine, sizes, size, viewWidth, 180f,
true, 1f);
mCaneMainSmallAngleSix = resizeImage(res, caneMainAngleSix, sizes, size, viewWidth, 180f,
true, 1f);
mCaneMainSmallAngleTwelve = resizeImage(res, caneMainAngleTwelve, sizes, size, viewWidth,
180f, true, 1f);
mCaneMainReverseTinyAngleTwelve = resizeImage(res, caneMainTinyReverseAngleSix,
sizes, size, viewWidth, 180f, true, 1f);
mCaneMainLargeAngleSix = resizeImage(res, caneMainLargeAngleSix, sizes, size, viewWidth,
180f, true, 1f);
mCaneMainMedAngleSix = resizeImage(res, caneMainMedAngleSix, sizes, size, viewWidth, 180f,
true, 1f);
mCaneHook = resizeImage(res, caneHook, sizes, size, viewWidth, 180f, false, 1);
mCaneHookFlip = resizeImage(res, caneHook, sizes, size, viewWidth, 180f, true, 1);
mCaneHookReverse = resizeImage(res, caneHookReverse, sizes, size, viewWidth, 180f, false, 1);
mCaneHookReverseFlip = resizeImage(res, caneHookReverse, sizes, size, viewWidth, 180f, true,
1);
mCaneEnd = resizeImage(res, caneEnd, sizes, size, viewWidth, 180f, false, 1);
mCaneEndFlip = resizeImage(res, caneEnd, sizes, size, viewWidth, 180f, true, 1);
mCaneEndReverse = resizeImage(res, caneEndReverse, sizes, size, viewWidth, 180f, false, 1);
mCaneEndReverseFlip = resizeImage(res, caneEndReverse, sizes, size, viewWidth, 180f, true,
1);
mPipeSides = resizeImage(res, pipes, sizes, size, viewWidth, 180f, true, 1);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
// Advance the countdown
if (mGameCountDown != null) {
mGameCountDown.tick();
}
// Load bitmaps if they haven't been initialised yet
if (mGumballBlue == null) {
loadBitmaps();
}
// reset and initialise the canvas for a fresh draw
canvas.drawColor(Color.TRANSPARENT);
canvas.translate(0, getHeight());
canvas.scale(1.0f, -1.0f);
float scale = getWidth() / 10.0f;
mPaint.setAntiAlias(true);
if (isInEditMode()) {
return;
}
// Iterate through all of the bodies in the game world and draw the corresponding bitmaps
Body body = mWorld.getWorld().getBodyList();
while (body != null) {
if (body.m_userData == null || body.m_userData.equals(PIPE_BOTTOM)) {
body = body.getNext();
continue;
}
// Skip bodies with empty fixtures or shapes
Fixture fixture = body.getFixtureList();
if (fixture == null) {
body = body.getNext();
continue;
}
Shape shape = fixture.getShape();
if (shape == null) {
body = body.getNext();
continue;
}
// Get the position.
Vec2 position = body.getPosition();
// Get the bitmap of this body.
Bitmap bitmap = null;
if (body.getUserData() instanceof Gumball) {
// For a gumball, load the correct color
Gumball gumball = (Gumball) body.getUserData();
if (gumball.mGumballColorId == GUMBALL_BLUE) {
bitmap = mGumballBlue;
} else if (gumball.mGumballColorId == GUMBALL_YELLOW) {
bitmap = mGumballYellow;
} else if (gumball.mGumballColorId == GUMBALL_RED) {
bitmap = mGumballRed;
} else if (gumball.mGumballColorId == GUMBALL_GREEN) {
bitmap = mGumballGreen;
} else if (gumball.mGumballColorId == GUMBALL_ORANGE) {
bitmap = mGumballOrange;
} else if (gumball.mGumballColorId == GUMBALL_PURPLE) {
bitmap = mGumballPurple;
}
} else if (body.m_userData.equals(CANE_MAIN_LONG)) {
bitmap = mCaneMainLong;
} else if (body.m_userData.equals(CANE_MAIN_LONG_REVERSE)) {
bitmap = mCaneMainLongReverse;
} else if (body.m_userData.equals(CANE_MAIN_MEDIUM)) {
bitmap = mCaneMainMed;
} else if (body.m_userData.equals(CANE_MAIN_MEDIUM_REVERSE)) {
bitmap = mCaneMainMedReverse;
} else if (body.m_userData.equals(CANE_MAIN_SMALL)) {
bitmap = mCaneMainSmall;
} else if (body.m_userData.equals(CANE_MAIN_SMALL_REVERSE)) {
bitmap = mCaneMainSmallReverse;
} else if (body.m_userData.equals(CANE_MAIN_TINY)) {
bitmap = mCaneMainTiny;
} else if (body.m_userData.equals(CANE_MAIN_TINY_REVERSE)) {
bitmap = mCaneMainTinyReverse;
} else if (body.m_userData.equals(CANE_HOOK)) {
bitmap = mCaneHook;
} else if (body.m_userData.equals(CANE_HOOK_FLIP)) {
bitmap = mCaneHookFlip;
} else if (body.m_userData.equals(CANE_HOOK_REVERSE)) {
bitmap = mCaneHookReverse;
} else if (body.m_userData.equals(CANE_HOOK_REVERSE_FLIP)) {
bitmap = mCaneHookReverseFlip;
} else if (body.m_userData.equals(CANE_END)) {
bitmap = mCaneEnd;
} else if (body.m_userData.equals(CANE_END_FLIP)) {
bitmap = mCaneEndFlip;
} else if (body.m_userData.equals(CANE_END_REVERSE)) {
bitmap = mCaneEndReverse;
} else if (body.m_userData.equals(CANE_END_REVERSE_FLIP)) {
bitmap = mCaneEndReverseFlip;
} else if (body.m_userData.equals(PIPE_SIDES)) {
bitmap = mPipeSides;
} else if (body.m_userData.equals(CANE_MAIN_SMALL_ANGLE_NINE)) {
bitmap = mCaneMainSmallAngleNine;
} else if (body.m_userData.equals(CANE_MAIN_SMALL_ANGLE_SIX)) {
bitmap = mCaneMainSmallAngleSix;
} else if (body.m_userData.equals(CANE_MAIN_SMALL_ANGLE_TWELVE)) {
bitmap = mCaneMainSmallAngleTwelve;
} else if (body.m_userData.equals(CANE_MAIN_REVERSE_TINY_ANGLE_SIX)) {
bitmap = mCaneMainReverseTinyAngleTwelve;
} else if (body.m_userData.equals(CANE_MAIN_LARGE_ANGLE_SIX)) {
bitmap = mCaneMainLargeAngleSix;
} else if (body.m_userData.equals(CANE_MAIN_MED_ANGLE_SIX)) {
bitmap = mCaneMainMedAngleSix;
}
if (shape instanceof CircleShape && bitmap != null) {
// Draw a gumball
CircleShape circleShape = (CircleShape) shape;
canvas.save();
canvas.rotate((float) (180 * body.getAngle() / Math.PI), scale * position.x,
scale * position.y);
canvas.drawBitmap(bitmap, scale * (position.x - circleShape.m_radius),
scale * (position.y - circleShape.m_radius), mPaint);
canvas.restore();
} else if (shape instanceof EdgeShape && bitmap != null) {
// Draw all other objects
canvas.save(Canvas.MATRIX_SAVE_FLAG);
canvas.rotate((float) (180 * body.getAngle() / Math.PI), scale * position.x,
scale * position.y);
canvas.drawBitmap(bitmap, scale * (position.x), scale * (position.y), mPaint);
canvas.restore();
}
// Continue drawing with the next body in the world
body = body.getNext();
}
}
public void setGameCountDown(TiltGameFragment.GameCountdown gameCountDown) {
mGameCountDown = gameCountDown;
}
/**
* Returns the index of a randomly colored gumball.
*/
public static int getRandomGumballId() {
int index = sRandomGenerator.nextInt(GUMBALLS.length);
int id = GUMBALLS[index];
return id;
}
/**
* Gets the correct bitmap based on screen size and rotates and flips the image.
*/
private static Bitmap resizeImage(Resources res, int[] resourceId, int[] sizes, int size, int viewWidth,
float rotationDegrees, boolean isFlipped, float caneScale) {
Matrix matrix = new Matrix();
Bitmap bmp = BitmapFactory.decodeResource(res, resourceId[size]);
if (rotationDegrees != 361f) {
matrix.setRotate(rotationDegrees, bmp.getWidth() / 2, bmp.getHeight() / 2);
}
if (isFlipped) {
matrix.preScale(-1, 1);
}
float scale = ((float) viewWidth) / sizes[size];
matrix.postScale(scale, scale);
bmp = Bitmap
.createBitmap(bmp, 0, 0, (int) (bmp.getWidth() * caneScale), bmp.getHeight(),
matrix, true);
return bmp;
}
/**
* Sets the Box 2D physics world to draw.
*/
public void setModel(PhysicsWorld world) {
mWorld = world;
}
/**
* Adds a gumball for drawing.
*/
public void addGumball(Gumball gumball) {
gumball.mGumballColorId = getRandomGumballId();
mWorld.addGumball(gumball.mXInitPos, gumball.mYInitPos, gumball, GUMBALL_DENSITY,
GUMBALL_RADIUS, GUMBALL_BOUNCE, GUMBALL_FRICTION, BodyType.DYNAMIC);
}
}