Here you will find the source code for the Kitty Hero game, as described in the book Beginning Graphics Programming with Processing 3
Copyright 2018 Antony Lees
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.
import android.content.Context;
import android.hardware.Sensor;
import android.hardware.SensorManager;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.view.WindowManager;
import android.os.Bundle;
import android.os.Vibrator;
static final int NUMBER_OF_COLUMNS = 5;
static final int GRACE_PERIOD = 1;
static final int STANDARD_TILE = 0;
static final int HOLE_TILE = 1;
static final int SEALED_HOLE_TILE = 2;
static final int SPECIAL_TILE = 3;
static final int INITIAL_SCORE = 0;
static final int INITIAL_LIVES = 9;
static final int LOWEST_SPEED = 5;
static final int GREATEST_SPEED = 20;
static final String SCORE_TEXT = "Score: ";
static final String LIVES_TEXT = "Lives: ";
static final String GAME_OVER_TEXT = "GAME OVER";
static final String END_SCORE_TEXT = "You scored: ";
static final String RESTART_TEXT = "Press To Restart";
static final String START_TEXT = "Press To Start";
static final String GAME_TITLE_1 = "Kitty Hero:";
static final String GAME_TITLE_2 = "The Adventures of";
static final String GAME_TITLE_3 = "Mr Kittenson";
Context context;
SensorManager manager;
Sensor sensor;
AccelerometerListener listener;
Vibrator vibe;
float ax;
float ay;
float az;
int numberOfRows;
int tileSize;
int offsetX;
Cat cat;
int lives;
int score;
int speed;
boolean started;
boolean gameOver;
ArrayList<Tile> tiles;
void setup() {
//size(600, 600, P3D);
fullScreen(P3D);
orientation(PORTRAIT);
tileSize = width/NUMBER_OF_COLUMNS/2;
numberOfRows = (height/tileSize) * 2;
// set gap at sides
offsetX = (width-tileSize * NUMBER_OF_COLUMNS)/2;
// initialise game
initialise();
// setup accelerometer
context = getActivity();
manager = (SensorManager)context.getSystemService(Context.SENSOR_SERVICE);
sensor = manager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
listener = new AccelerometerListener();
// SENSOR_DELAY_FASTEST
// SENSOR_DELAY_GAME
manager.registerListener(listener,
sensor, SensorManager.SENSOR_DELAY_FASTEST);
// register vibrator
vibe = (Vibrator) getActivity().getSystemService(Context.VIBRATOR_SERVICE);
}
void draw() {
background(0);
if (!started) {
showStartScreen();
} else if (gameOver) {
showGameOver();
} else {
play();
}
}
void initialise() {
lives = INITIAL_LIVES;
score = INITIAL_SCORE;
speed = LOWEST_SPEED;
cat = new Cat(tileSize);
// initial position
cat.x = width/2;
cat.y = height - (height/4);
// set bounds
cat.minX = offsetX;
cat.maxX = width - offsetX;
cat.minY = tileSize;
cat.maxY = height-(height/8);
// initialise tiles
tiles = new ArrayList<Tile>();
for (int i = 0; i < numberOfRows; i++) {
// set y coordinates (-y so scrolls downwards)
int y = -i * tileSize;
for (int j = 0; j < NUMBER_OF_COLUMNS; j++) {
// set x coordinate
int x = j * tileSize + offsetX;
int tileType = STANDARD_TILE;
// allow grace period of normal tiles
if (i > GRACE_PERIOD) {
// random tile types
tileType = generateRandomTileType();
}
tiles.add(new Tile(x, y, tileType));
}
}
}
void startGame() {
started = true;
gameOver = false;
}
void play() {
if (lives < 1) {
gameOver = true;
vibe.vibrate(500);
}
lights();
// scoreboard
textAlign(LEFT);
fill(255);
textSize(tileSize/2.5);
text(SCORE_TEXT + score, 10, tileSize/2);
text(LIVES_TEXT + lives, width-(tileSize*1.5), tileSize/2);
// set speed but keep within limits
speed = constrain(score/2, LOWEST_SPEED, GREATEST_SPEED);
pushMatrix();
// rotate for 3D
rotateX(radians(45));
// draw tiles
stroke(0);
for (Tile tile : tiles) {
// move tiles
tile.y = tile.y + speed;
if (tile.type == HOLE_TILE) {
fill(0);
// check location of kitty
if (hitTile(tile.x, tile.y, cat.x, cat.y)) {
// hit a hole, do something
lives--;
vibe.vibrate(100);
tile.type = SEALED_HOLE_TILE;
}
} else if (tile.type == SPECIAL_TILE) {
fill(255, 255, 0);
// check location of kitty
if (hitTile(tile.x, tile.y, cat.x, cat.y)) {
// hit a special tile, do something
score++;
tile.type = STANDARD_TILE;
}
} else if (tile.type == SEALED_HOLE_TILE) {
fill(255, 0, 0);
} else {
fill(255);
}
rect(tile.x, tile.y, tileSize, tileSize);
}
// draw kitty
cat.draw();
popMatrix();
// recycle tiles
recycleTiles();
}
// show game over screen
void showGameOver() {
fill(167, 0, 174);
int offset = width/10;
rect(offset, offset, width-offset*2, height-offset*2);
fill(0);
textAlign(CENTER);
int fontSize = textToFontSize(GAME_OVER_TEXT);
textSize(fontSize);
text(GAME_OVER_TEXT, width/2, offset + fontSize);
fontSize = textToFontSize(END_SCORE_TEXT + score);
textSize(fontSize);
text(END_SCORE_TEXT + score, width/2, height/2);
fontSize = textToFontSize(RESTART_TEXT);
textSize(fontSize);
text(RESTART_TEXT, width/2, height - offset - fontSize);
cat.draw();
}
// show start screen
void showStartScreen() {
fill(167, 0, 174);
int offset = width/10;
rect(offset, offset, width-offset*2, height-offset*2);
fill(0);
textAlign(CENTER);
int fontSize = textToFontSize(GAME_TITLE_2);
textSize(fontSize);
text(GAME_TITLE_1, width/2, offset + fontSize);
text(GAME_TITLE_2, width/2, offset + fontSize * 2);
text(GAME_TITLE_3, width/2, offset + fontSize * 3);
textSize(textToFontSize(START_TEXT));
text(START_TEXT, width/2, height - offset - fontSize);
cat.draw();
}
// calculates the font size for a piece of text
int textToFontSize(String text) {
// work out space per character
int chars = text.length();
int availableSpace = width;
int characterSpace = availableSpace / chars;
// convert pixels to font size
int size = (int) (characterSpace * 1.3);
return size;
}
// generate a random type of tile
int generateRandomTileType() {
int tileType = STANDARD_TILE;
// randomised tile chances
if (random(0, 5) > 4) {
tileType = HOLE_TILE;
}
if (random(0, 50) > 45) {
tileType = SPECIAL_TILE;
}
return tileType;
}
// recycle tiles off the screen
void recycleTiles() {
// get y coord of last tile
int y = 0;
for (Tile tile : tiles) {
if (tile.y < y) {
y = tile.y;
}
}
// moves tiles to the end (x coord can stay the same)
int newY = y - tileSize;
for (Tile tile : tiles) {
// if off the screen
if (tile.y > height) {
tile.y = newY;
tile.type = generateRandomTileType();
}
}
}
// check if we hit a tile
boolean hitTile(int tileX, int tileY, int locationX, int locationY) {
return (locationY >= tileY && locationY <= tileY + tileSize
&& locationX >= tileX && locationX <= tileX + tileSize);
}
// class for tiles
class Tile {
int x;
int y;
int type;
Tile(int x, int y, int type) {
this.x = x;
this.y = y;
this.type = type;
}
}
// class for drawing kitty
class Cat {
final int tailLength;
final int tailThickness;
final color CAT_COLOUR = color(255, 200, 0);
final int size;
// tail segments
PVector[] tail;
// location
int x;
int y;
// bounds
int minX;
int maxX;
int minY;
int maxY;
Cat(int size) {
this.size = size;
this.tailLength = size/4;
this.tailThickness = size/7;
tail = new PVector[tailLength];
for (int i = 0; i < tailLength; i++) {
tail[i] = new PVector(0, 0);
}
}
void draw() {
// draw body in separate matrix so can rotate
pushMatrix();
noStroke();
translate(x, y);
rotateX(frameCount/20.0); // roll
sphereDetail(7);
fill(CAT_COLOUR);
sphere(size * 0.4);
popMatrix();
pushMatrix();
noStroke();
// draw head
translate(x, y - (size/2));
sphereDetail(8);
sphere(size * 0.2);
// draw ears
// left
pushMatrix();
//fill(0, 0, 255);
translate(-size*0.1, -size*0.05, size*0.15);
rotateY(0.5);
rotateX(4);
sphereDetail(1);
sphere(size*0.1);
popMatrix();
// right
pushMatrix();
translate(size*0.1, -size*0.05, size*0.15);
rotateY(3);
rotateX(2);
sphereDetail(1);
sphere(size*0.1);
popMatrix();
// draw tail
sphereDetail(8);
tail[tailLength-1].x = (sin(frameCount * 0.2) * 10);
tail[tailLength-1].y = size/2;
for (int i = 0; i < tailLength-1; i++) {
tail[i].x = tail[i+1].x;
tail[i].y = tail[i+1].y + (tailThickness/2);
pushMatrix();
translate(tail[i].x, tail[i].y);
sphere(tailThickness);
popMatrix();
}
popMatrix();
}
}
void mousePressed() {
// ignore if game is playing
if (gameOver || !started) {
initialise();
startGame();
}
}
public void onCreate(Bundle bundle) {
super.onCreate(bundle);
// keep the screen on
getActivity().getWindow().addFlags(
WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
}
public void onResume() {
super.onResume();
if (manager != null) {
manager.registerListener(listener,
sensor, SensorManager.SENSOR_DELAY_FASTEST);
}
}
public void onPause() {
super.onPause();
if (manager != null) {
manager.unregisterListener(listener);
}
}
class AccelerometerListener implements SensorEventListener {
public void onSensorChanged(SensorEvent event) {
ax = event.values[0];
ay = event.values[1];
az = event.values[2];
if (cat.x - ax > cat.minX && cat.x - ax < cat.maxX) {
cat.x -= ax;
}
if (cat.y + ay > cat.minY && cat.y + ay < cat.maxY) {
cat.y += ay;
}
}
public void onAccuracyChanged(Sensor sensor, int accuracy) {
// do nothing
}
}