Let’s make a game from scratch like ‘Flappy Bird’. Our game is a side-scroller where the player controls a copter, attempting to fly between columns of green obstacles without hitting them. Before we start, check out the youtube clip below that shows how our final game will look like-
If you cannot wait to see how it would look like on your device, you can download the apk or aia file from Github project and see for yourself.
I am assuming you have some experience using App Inventor. If not, it is highly recommended that you first do the very basic tutorial.
Let’s first design the interface. Download the barebone project that already has UI layout but no blocks. Import it and take a look at the different components in place. You can change the look and feel however you want later. This is how mine looks like-
We have the following components in Screen1:
1. A Canvas component which we renamed to GameCanvas with Height and Width both set to 100% to take the entire device screen. Also, notice a background image is set. Font size is set to 22 to display scores.
2. We have a TinyDB to store high scores. We renamed it to ScoreDB. It’s very important that you name different components with some meanings or purpose so that later you can easily recognize what they are for.
3. We have various ImageSprite components for our copter, green pipes like obstacles, for showing header and tap instruction animation, and a Flash. This Flash image sprite is to show a quick white flash (death impact) when the player hits an obstacle and dies. All these tiny effects and details make gameplay more exciting and entertaining.
4. We also have different Clock components – for copter’s flap/spin animation, tap instruction animation, clocks for copter to go up and down, etc. Later in the tutorial as we’d use them, it will become obvious which is for what purpose. Important thing to notice here is the value of TimerInterval property of these clocks are set to different values. For some, we want them to fire more frequently than others.
5. Lastly, we have some Sound effects for flapping/spinning, scoring, death, and hit sound.
Now, let’s define some variables that we’ll need throughout the game. I will explain what they are for, in a bit.
ALL_HEIGHTS : This variable holds a predefined list of heights of a top and a bottom pipe combination such a way that each height combination allows certain amount of vertical gap. If you look at the first image below, there’s one top pipe and a bottom pipe. The top pipe’s height is bigger than the bottom’s, leaving some vertical gap for the copter to pass through. We have 5 different combinations like this to have different gaps and gap locations.
RUNNING: Indicates if player started playing the game (not in idle or end state)
READY: If in idle state, this indicates waiting for player to tap and start the game
PIPE_GAP: Indicates horizontal gap between two pipes as shown in the image above. We have to make sure there’s enough gap for our copter to fit and able to move horizontally before another pipe appears. Four times of the copter width should be ok and challenging. You can make the gap even bigger to make the game easier.
PIPE_WIDTH: Width of a pipe. This should be defined considering screen width so that in various screens, difficulty stays the same
SPEED: How fast pipes should move leftwards. Note we do not make the copter move, we instead make the pipes move.
DB_SCORE_TAG: This is a tag to use for storing a score and also reading a saved score.
heights: This holds values from ALL_HEIGHTS variable that we showed above but in random order. Random order so that player doesn’t learn the pattern and the game stays challenging in each play.
all_pipes: Stores all pipe image sprites
active_pipes: Pipes that haven’t crossed the left edge of the screen yet.
upDown: Indicates if the copter is moving up or falling down.
state: Indicates current state of the game, can be either READY or RUNNING.
bestScore: Highest score of the player.
score: Current score of the game.
isNewBest: Checks if the current score is higher than best score
lastPipeScored: To validate if we already awarded a score to player for passing through current pipe so we don’t score multiple for the same pipe
gravity: Pull of gravity.
firstGame: Indicates if it is the first game ever
wait: How long to wait before tap instruction animation starts showing after death
temp1, temp2, temp3: Temporary variables.
Let’s setup our pipes and some starting data-
In SetPipeReferences, we put an empty list to active_pipes variable for later use and assign all available pipes to all_pipes list.
Different phones have different sizes. So, we can’t just put random numbers to define width and heights of pipes and copters as this would result to distorted images in different phones. The same goes for speed as well since you do not want in some phones, pipes move too fast and in some other, too slow. There’s a very simple tutorial that explains this better. In SetStartData procedure block, we set SPEED to screen width divided by 10% of screen height which means speed is relative to the size of the copter. Later, we will see copter’s width and height is set to 10% of screen height. This is a way to support various devices sizes while keeping the gameplay in tact. We use 20% of the screen width as the PIPE_WIDTH. We read if there is a saved score. If none, we get zero as default. We then call SetPipeReferences procedure we discussed above. Lastly we set Flash image sprite’s size to Screen’s width and height.
Now, let’s setup canvas, title header, and our beloved copter!
In SetupCanvas, we set the width of the canvas to screen width plus pipe width. We added pipe width because when we need to spawn a pipe, we will have to do that offscreen at the right edge of the screen. Otherwise, pipe will pop up right away a rather than making it look like the pipe is entering the phone’s visible area from right side of the screen. Cool, eh?
In SetupHeader, we get header image’s aspect ratio and store it in a temporary variable. We set header’s width to 80% of screen width and adjust height using aspect ratio so that our header image doesn’t get distorted. We also place the header image on the center of the screen and finally make it visible.
In SetupCopter, we use our principle of supporting various phone sizes and set things accordingly. As aforementioned, we set the size of the copter to 10% of screen height. Placed our copter at left side of the screen with a little gap, half of its width so that copter is not touching the left edge of the screen, and a bit above center of the screen on y-axis. We also enable the copter for animation and make it visible. We set the horizontal gap of pipes to 4 times of the copter’s width. We also enabled FlapClock and UpDown so that we can use them for animation and moving up and down respectively.
In idle state meaning player has not started a game, we want copter to go up and down a bit and repeat. When UpDown clock’s Timer event is triggered, we first check current upDown value. If less than 20, we want copter to keep moving up, if below 40, we want it to go down. Else, we reset upDown counter to 0.
We have to do similar setup for other components like tap animation, displaying of scores, etc. Let’s create another procedure called SetupInfo as below:
For Hand and Tap images we do the same as we did for setting up Header image sprite. The important part here is displaying the score. First we use a temporary variable, temp1 to form a score display text. We put an emply string to start with. If the game is just launched, we only show best score if available as obviously there’s no current score yet. If not just launched, we check if there’s a current score and if it is higher than best score. If so, we show it as ‘NEW BEST‘, otherwise we label it as ‘BEST‘. We also set the font size here to 20. This means whatever we set before in design view, would be overwritten by new value which is 20. Finally we display the score by drawing the text stored in temp1 on canvas.
Time to setup the obstacles, those green pipes. We will have to shuffle our ALL_HEIGHTS list here to randomize the gameplay and store in heights variable because we want to keep the original list (ALL_HEIGHTS) in tact. Let’s start with creating a procedure called Swap which swaps two values in a list, then a ShuffleList procedure to shuffle a list, followed by SetPipe to setup a combination of top-down pipes, and finally SetupPipes procedure. We will discuss them in a bit. It would be better if you go through how to shuffle a list tutorial first to get some idea as we won’t discuss that here.
In SetupPipes procedure, first we shuffle our predefined list of pipes positions – ALL_HEIGHTS. We want each game to have a randomize appearance of pipes meaning we do not want to start with the same pattern and positions of the pipes in every game. If we do that, then our players will eventually memorize the locations of pipes, thus the gaps in between, and won’t find the game challenging anymore.
After we shuffle the list, we simply setup each combination of top and bottom pipes by calling SetupPipe procedure. In SetupPipe, we set the height, width, and position on y-axis of top and bottom pipe combination. The reason we divide the predefined value in heights by 100, so we can get the percentage. For example, if the predefined value is 25 and we divide by 100, we get .25 or in other words, 25%. Then we would get 25% of canvas height to setup pipe’s height. We do the same for bottom pipe. Note that canvas height is set to 100% of the screen meaning the same as device or phone height where the game is played.
Since we have setup all components needed, finally we can initialize the screen (Screen1) by dragging Initialize procedure found under Screen1 in Blocks window and calling various setup procedures we have just implemented:
We can setup now FlapClock and HandClock:
Every time FlapClock gets triggered which we setup to fire in every 100 milliseconds in design view, we just change copter image to a different one than what currently being used. We do the same for Hand image sprite when HandClock fires.
What happens when player taps on the screen? We start the game.
When a player taps on the screen, GameCanvas.Touched event is triggered. If we are not in wait mode meaning current game hasn’t just ended, we check if the game is in ready state. If so, we set the state to running. We set some variables to default values in prepartaion for a new game. We set the font size to 40 to print the score in big font. Then we call a procedure named PrintScore. We pass zero as the value since the game is about to start and no score yet. We also call SpawnPipe procedure to spawn pipes from offside of right edge of the screen. We will see both procedures in a bit. We also play flap sound effect here. We hide components that are for our game menu like header, tap, and hand image sprites. We enable WidthClock and Score clocks.
When a tap is registered, player means for the copter to go up as well. So, we play the flap sound. Enable the Ascend clock to fly up and disable Descend clock to not fall down. We register current Y position of the copter and add 10 units to move the copter upwards. In canvas, top-leftmost position is (0,0) and bottom-leftmost is (0, canvas height).
In SpawnPipe, our objective is to spawn a top and a bottom pipes from outside of right edge of the screen. We need to define another procedure called Spawn where we set properties of a given sprite. In our case, we set the width to PIPE_WIDTH, we set position of the pipe on x-axis to width of the canvas meaning it will be outside of the screen. We also set the speed. Lastly, we make the pipe visible and enable it. Afterwards in SpawnPipe, we store the top and bottom pipes in our active_pipes list and remove them from all_pipes. By the way, do you know why some variables are declared with all capital letters? This is to show that values of the variables with all capital letters will not change, they are constant.
In Ascend timer event, we move the copter a little up. If it reaches the edge, we activate Descend clock to fall down and deactivate ascend to stop moving further up. We put a bit more ‘weight’ to gravity pull.
We do the opposite in Descend‘s Timer event.
In PrintScore, we first clear the canvas and then print the score that is passed to the procedure.
Our pipes move from right to left. What happens when a pipe reaches the left edge? Should we just make them vanish? That won’t look so good. We need to make it look like it is going into left of the screen. To have that effect, we could have a bigger canvas width, more than the device size so that pipes van move off the left edge of screen. Most people implement that way. However, let’s try something different. We can manipulate the width of the pipes and shrink them so they will look like they are going off-screen. Sounds fun? Let’s take a look at the image below to understand how this can be achieved.
We have a timer called WidthClock whose purpose is to shrink a pipe slowly to zero width when a pipe reaches left edge and spawn a new pipe when distance between a pipe on screen and right edge of screen is more than allowable gap.
First we store currently active top and bottom pipes in some temporary variables. Then we check if the top pipe reached the left edge and width is still bigger than zero. If both conditions are true, we lessen both top and bottom pipes’ width by pipe’s movement speed. While doing so, if we see our about to set value might be negative, we just set to zero since you cannot set width to a negative value. Since top and bottom pipes are on top of each other, we do not test if bottom also reached the edge since they have the same position on x-axis.
If the width is already zero, we disable the pipe meaning we hide the pipe and set Enabled property to false by calling DisablePipe procedure and passing the pipe. Afterwards, we put them back in all_pipes list and remove from active_pipes list as they are no longer active.
If either of the two conditions we check above is false, we simply check if it is time to spawn a new pipe by checking if distance between screen right edge and last active pipe’s position on x-axis plus its width is equal to or greater than the horizontal gap we can have which is stored in PIPE_GAP variable. If equal or greater, we spawn a new top-bottom pipe combination by calling our SpawnPipe procedure.
We also need to give a score to the player if the copter successfully passes through a top and a bottom pipes.
In Score.Timer event, we get the current top pipe first. If the state is running, and we haven’t awareded a score for the same active pipe, we just check if the pipe is 30% behind relative to copter’s position on x-axis. If so, we add a score to current total score and draw it on canvas, also play a score sound.
When our copter reaches top edge of the screen, we enable Descend clock to make it go down. We also add 10 units to its position on y-axis to put a bit more ‘weight’ to gravity pull. We call EndGame procedure if hits the bottom edge.
In CollidedWith event, we simply call EndGame procedure as the copter hit an obstacle pipe.
In EndGame procedure, we first check if the game is running. If so, we show our Flash image. We also enable DieFlash clock so that when it triggers we can hide the flash image. We play both Hit and Die sound effects. We set wait flag on so that we don’t show score and idle copter right away. We set the state to ready. We reset most components to prepare for a new game. We also check if the current score is the best score of all time for the player so we can show them accordingly.
We call different procedures like we did before when we initialized the Screen1. We show header image. We disable all pipes as no pipe should be visible or move.
In Wait.Timer event, we disable itself. We also remove the wait flag. We show Hand and Tap image sprites.
In DieFlash.Timer event, we also disable itself and hide the flash image sprite.
Congratuilations! You did it! I appreciate your learning spirit. Never stop doing more.
If you want me to keep making tutorials like this, support me by downloading and rating our Games.
NOTICE: Please note graphics, fonts, and sound used in this tutorial may have license restrictions. So, please check online for licenses if you are planning to use them in your app/game.