
GRAVL: Gravity Puzzle Platformer for Android
Timeline: | Summer 2014 | |
Languages Used: | Java (Android), SQL, HTML, CSS | |
Role: | Independent Android Developer, Graphic Designer, Level Designer, Story Writer, and Game Creator | |
Reason: | To learn Java and Android Development |
Watch the Trailer
More than 800 hours over the course of 6 months went into making GRAVL, my first independently developed Android game, from start to finish. With over 10,000 downloads across all platforms and 4.8/5 rating on Google Play at its peak, GRAVL was more successful than I could’ve ever hoped. It’s a platform game (like Super Mario Bros.), except each level is a puzzle that can only be solved using one thing… gravity.
You play as Phil, a stranded astronaut who’s trying to collect fuel for his spaceship, while traversing the bizarre icy terrain of an unknown planet. You encounter many different types of Gravity Switches along the way, which allow Phil to freely manipulate the laws of physics, to help you avoid falling to your death.
This game was more of a programming experiment for me than anything else. I chose not to use any game or physics engines like Unity, and instead chose to program every aspect of the game in pure Java, starting from scratch. This is the primary reason it took so damn long, and also why I wouldn’t recommend this approach to anyone whose goal is to efficiently develop a functional game. Don’t reinvent the wheel unless you have to, or in my case, unless you want to just for the lolz.
All the graphics in the game are completely original, created by me. The music/sounds are also original; they were composed by my brother Kirby. GRAVL has had its fair share of performance bugs, which have been a pain to fix, but also kinda fun to figure out. I now know the inner workings of Android like never before. You’d be surprised how many different programming and math concepts go into developing a game.
How I Built a Game Engine and Physics Simulator from Scratch
Writing a game engine with custom physics and level editors from the ground up is no joke. Here are some of the key technical components, along with real code samples from the GRAVL codebase, that showcase how I made it all work:
Gravity System Implementation
The core gravity mechanics are handled in the Ball
class, which manages the player character’s physics:
1public class Ball {
2 private boolean gravityDown = true;
3 private boolean gravityShifting = false;
4 private boolean gravityBeingTimed = false;
5 private int gravityTimerCount = 0;
6 private int gravityTimerMax = 0;
7
8 public void setGravityDown(boolean grdn) {
9 gravityDown = grdn;
10 if(gravityDown) Assets.playFX(3, Gravl.areSoundsMuted());
11 else Assets.playFX(4, Gravl.areSoundsMuted());
12 }
13
14 public void incGravityTimer() {
15 if(gravityTimerCount > 0 && !finishingLevel) {
16 if(gravityTimerCount > 1) {
17 gravityTimerCount--;
18 if(!gravityBeingTimed) gravityBeingTimed = true;
19 } else {
20 if(!gravityDown) {
21 gravityShifting = true;
22 gravityDown = true;
23 bouncingV = false;
24 } else {
25 gravityShifting = true;
26 gravityDown = false;
27 bouncingV = false;
28 }
29 if(gravityDown) Assets.playFX(3, Gravl.areSoundsMuted());
30 else Assets.playFX(4, Gravl.areSoundsMuted());
31 gravityBeingTimed = false;
32 gravityTimerCount = 0;
33 }
34 }
35 }
36}
Tile Types and Gravity Switches
The game features various tile types, including different gravity switches. Here’s how they’re defined:
1public class Tile {
2 // Tile types for different gravity mechanics
3 public static final int TYPE_FLOOR = 1;
4 public static final int TYPE_GRAVITY_SWITCH_UP = 2;
5 public static final int TYPE_GRAVITY_SWITCH_DOWN = 3;
6 public static final int TYPE_GRAVITY_SWITCH_ROTATE = 10;
7 public static final int TYPE_TIMED_GRAVITY_SWITCH_UP_5_SEC = 17;
8 public static final int TYPE_TIMED_GRAVITY_SWITCH_UP_3_SEC = 18;
9 public static final int TYPE_TIMED_GRAVITY_SWITCH_DOWN_5_SEC = 19;
10 public static final int TYPE_TIMED_GRAVITY_SWITCH_DOWN_3_SEC = 20;
11 public static final int TYPE_FLOAT_SLIDE_LEFT_RIGHT = 12;
12 public static final int TYPE_FLOAT_SLIDE_UP_DOWN = 13;
13 public static final int TYPE_SPRING_LAUNCH = 15;
14 public static final int TYPE_TELEPORT = 26;
15}
Collision Detection and Gravity Switch Logic
The collision detection system handles interactions with gravity switches:
1public void checkVerticalCollision(Rect ballvert) {
2 if (Rect.intersects(ballvert, r) && type != TYPE_NOTHING) {
3 // Shift gravity UP
4 if((type==TYPE_GRAVITY_SWITCH_UP ||
5 (!ball.isGravityBeingTimed() &&
6 (type==TYPE_TIMED_GRAVITY_SWITCH_UP_5_SEC ||
7 type==TYPE_TIMED_GRAVITY_SWITCH_UP_3_SEC))) &&
8 ball.getCenterY() <= tileY + Values.TILE_HEIGHT &&
9 ball.isGravityDown() &&
10 !ball.isExploding() && !ball.isImploding() &&
11 !ball.isRotatingGravity()) {
12
13 ball.setGravityDown(false);
14 ball.setGravityShifting(true);
15 ball.setBouncingV(false);
16 activeCounter = 1;
17
18 if(type==TYPE_TIMED_GRAVITY_SWITCH_UP_5_SEC ||
19 type==TYPE_TIMED_GRAVITY_SWITCH_UP_3_SEC)
20 ball.setGravityTimerCount(GRAVITY_TIMER_MAX);
21
22 ((GameScreen) thisGameScreen).startArrow(0);
23 thisGameScreen.getGameReference().vibrate(0);
24 ((GameScreen) thisGameScreen).addMove();
25 }
26 // Shift gravity DOWN
27 else if((type==TYPE_GRAVITY_SWITCH_DOWN ||
28 (!ball.isGravityBeingTimed() &&
29 (type==TYPE_TIMED_GRAVITY_SWITCH_DOWN_5_SEC ||
30 type==TYPE_TIMED_GRAVITY_SWITCH_DOWN_3_SEC))) &&
31 ball.getCenterY() >= tileY && !ball.isGravityDown() &&
32 !ball.isExploding() && !ball.isImploding() &&
33 !ball.isRotatingGravity()) {
34
35 ball.setGravityDown(true);
36 ball.setGravityShifting(true);
37 ball.setBouncingV(false);
38 activeCounter = 1;
39
40 if(type==TYPE_TIMED_GRAVITY_SWITCH_DOWN_5_SEC ||
41 type==TYPE_TIMED_GRAVITY_SWITCH_DOWN_3_SEC)
42 ball.setGravityTimerCount(GRAVITY_TIMER_MAX);
43
44 ((GameScreen) thisGameScreen).startArrow(1);
45 thisGameScreen.getGameReference().vibrate(0);
46 ((GameScreen) thisGameScreen).addMove();
47 }
48 }
49}
Game Constants and Physics Values
The game uses carefully tuned physics constants for smooth gameplay:
1public class Values {
2 // Ball physics constants
3 public static int BALL_YSPEED = 2; // Gravity acceleration
4 public static int BALL_MOVESPEED = 7; // Horizontal movement speed
5 public static int BALL_MAX_BOUNCE_SPEED = 4; // Maximum bounce velocity
6 public static float BALL_LAUNCH_SPEED = 27; // Spring launch velocity
7
8 // Tile dimensions
9 public static int TILE_WIDTH = 60;
10 public static int TILE_HEIGHT = 60;
11
12 // Background scrolling
13 public static int BGSPEED = 1; // Background scroll speed
14
15 // Frame compensation for different device speeds
16 public static final float FRAME_COMPENSATOR_MULTIPLIER = 0.3f;
17}
Custom Game Engine Architecture
The game uses a custom framework built from scratch:
1public abstract class AndroidGame extends BaseGameActivity implements Game {
2 AndroidFastRenderView renderView;
3 Graphics graphics;
4 Audio audio;
5 Input input;
6 FileIO fileIO;
7 Screen screen;
8
9 @Override
10 public void onCreate(Bundle savedInstanceState) {
11 super.onCreate(savedInstanceState);
12
13 // Initialize custom game engine components
14 renderView = new AndroidFastRenderView(this);
15 graphics = new AndroidGraphics(this, getAssets());
16 fileIO = new AndroidFileIO(this);
17 audio = new AndroidAudio(this);
18 input = new AndroidInput(this, renderView);
19 screen = getInitScreen();
20 }
21}
Level Loading and Map System
Levels are loaded from text-based map files:
1private void loadMap() {
2 String mapString = Gravl.getMap(currentLevel);
3 Scanner scanner = new Scanner(mapString);
4
5 int tileY = 0;
6 while(scanner.hasNextLine()) {
7 String line = scanner.nextLine();
8 for(int tileX = 0; tileX < line.length(); tileX++) {
9 char tileChar = line.charAt(tileX);
10 int tileType = Character.getNumericValue(tileChar);
11
12 if(tileType > 0) {
13 Tile newTile = new Tile(tileX, tileY, tileType, this);
14 tilearray.add(newTile);
15
16 // Special tile handling
17 if(tileType == Tile.TYPE_TELEPORT) {
18 teleporterTiles.add(newTile);
19 } else if(tileType == Tile.TYPE_POWER_TOGGLE_ALL) {
20 toggleTiles.add(newTile);
21 }
22 }
23 }
24 tileY++;
25 }
26 scanner.close();
27}
In order to complete this project (which was really my first attempt to create something substantial using Java), I had to learn about:
- Programming for Android
- Background threads
- Runnables
- Bitmap scaling & rendering (pain in the ass!)
- Game controller integration
- Surface view canvas drawing
- Cloud syncing and conflict resolution with Amazon GameCircle and Google Play Services
- Libraries and dependency order
- Slow framerate compensation (and throttling for devices that were TOO fast)
- Hardware acceleration
- Using abstract Java classes for framework layers
- Device compatibility workarounds
- Audio/sound integration
- and tons more.
I also had to use some concepts from trigonometry, like Pythagorean Theorem (a big middle finger to all my high school classmates who said we’d never use it outside of school), and I had to come up with some pretty hardcore algorithmic solutions for various issues I ran into along the way.
If you want to read or hear more, I was interviewed by Literary Video Games about GRAVL; feel free to check that out. I must say, this is one of my projects I’m most proud of, just because of how ridiculous of an undertaking it was. The day I released GRAVL was one of the most satisfying days of my life. But I can guarantee you, I will never attempt make a game without an engine again. Still, it was fun.