Thumbnail image

GRAVL: Gravity Puzzle Platformer for Android

!
Warning: This post is over 5 years old. The information may be out of date.
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.

Visit the Website Amazon App Store Google Play Store

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.

Screenshots

Related Posts

Comments