LEGO MINDSTORMS Robot Inventor
Flying Bird Game
This project uses only the hub as a small handheld device to play a simple game inspired by the famous mobile phone game Flappy Bird. You control a flying bird that moves up and down on the left edge of the screen, and you try to catch targets as they fly by.
Press the right arrow button to "flap" the bird up a pixel. The bird falls down by itself, so you need to time your presses to keep the bird flying and get it to the right height.
Targets fly by horizontally one at a time from right to left. Your goal is to intercept and catch as many targets as possible. Each target you catch is worth 1 point, and the game ends when you miss 3 targets in a row. The targets start to fly gradually faster as the game progresses, so it keeps getting harder. How long can you last?
The program is not that complex and fits easily on one screen, but it is also presented here by building it up in stages with a brief tutorial to help explain the key concepts so you can understand the structure and learn how to program your own games.
Understanding the Program
The key concepts in the game program code are explained briefly here. Many of these same concepts are used in real games for mobile phones and other platforms. To make it easier to follow, the program is build up in stages. The concepts added in each stage are explained below.
The "Target" Program
This starter program shows the basic structure of an action game based on frame-based animation. The graphics are displayed as a series of "frames". A frame is a visual display that lasts for a fixed duration before the next frame is displayed. The frames appear one after the other quickly enough that the illusion of motion is created, like the frames in a movie. When programming a game, you can pick any appropriate frame rate, but it is best to then stick to it and stay consistent. Since the resolution of the LEGO hub is so low (5 x 5 pixels), the frame rate used here is 10 frames per second (0.1 second per frame). Typical mobile phone games use 30 or 60 frames per second but a similar technique.
Variables are the key to programming an action game. The positions of moving objects, and anything else that can change over time, are represented by variables, so most games will define several variables.
Moving objects are represented by their position in an (x, y) coordinate system. Here on the LEGO hub, the coordinates for pixels (drawn by the "set pixel" block) are mapped onto a 5 x 5 grid. The x coordinate increases from 1 on the left to 5 on the right, and y increases from 1 on the top to 5 on the bottom. Note that the y-direction is the opposite of that used in normal math (this is a computer graphics convention).
In the Target program, the only thing that changes is the x-coordinate of the target (it always has the same height at y = 3), so the current position of the target is stored in the targetX variable.
Although pixel coordinates are integers from 1-5, it is often best to think of the position of objects as real/fractional ("floating point") numbers that can change continuously, such as 2.3 or 1.76, but then when drawn on the screen are rounded to the nearest pixel. So here the targetX variable is a continuous (non-integer) variable, but another variable targetXPixel is the position rounded to the nearest pixel.
The main structure of a frame-based action game is a "forever" loop that repeats the following actions for each frame:
Erase the previous frame
Draw the current frame using the current object position(s) stored in variables
Test the positions of objects for any actions that should result
Move objects to their positions for the next frame (by changing their variables)
Wait for the duration of the frame interval (here 0.1 second) before the loop continues to the next frame.
You can see this structure in the Target program. The only action for step 3 is to test to see if the target has flown off-screen to the left (its x-coordinate is less than 1), and if so, then it is moved back to the right side for the next frame.
The "Targets" Program
This next version of the program extends the target flying logic to add two things: Each new pass of the target is at a new random height (a random y-coordinate from 1 to 5), and with each new pass the target flys slightly faster, so the targets gradually fly faster and faster over time.
There is still only one target (which keeps getting recycled), so we only need to store the position of a single target in our variables, but the movement will give the illusion of a series of different targets flying by. But, in addition to the x-coordinate of this target changing, two other things now change over time: the y-coordinate that it flys at, and the speed that it flys. These are stored in two new variables targetY and targetSpeed.
Note that mathmatically, a speed is measured as a change in position between two frames. So here, the targetSpeed starts at -0.2, which means that between two frames, targetX will change by 0.2 pixels (negative makes it go to the left, positive would move to the right) in a 0.1 second frame, which works out to 2 pixels per second.
This program also introduces a custom My Block called New Target, which helps the structure of the code. The process of getting a target ready to fly is now 3 blocks long (setting its 3 variables), and this code needs to appear twice in the program (once for the first target, and again when a target is recycled in the loop), so this code is moved into the New Target My Block. In addtion to preventing repeated code, using My Blocks like this also makes a longer program easier to read and understand.
The "Bird and Targets" Program
This program adds the bird object, which you control with the right arrow button. Now that the basic structure of the frame animation is in place, adding additional objects is quite easy.
Due to the design of this game, the bird always flys straight up and down always at x = 1, so the only thing that changes is its y-coordinate, so the new variable birdY is added to store that, along with the birdYPixel version rounded to the nearest pixel. The various steps of the animation loop now just process both the target and bird object variables.
How the birdY variable changes is the key to how the game plays. In this simple version of the game, the right arrow button just decreases birdY by 1 to move it up 1 pixel, and in the main program loop, the bird falls on its own at a rate of 0.2 pixels per frame (which works out to 2 pixels per second).
The Completed "Bird Game" Program
After the "Bird and Targets" program, all of the animation is in place for the bird and targets, but these objects don't interact (hitting a target doesn't do anything, there is no score, etc), so that is all that is left to add.
Given the design of the rules of this game, we need two new variables to keep track of the score and game progress: score counts the total number of hits, and missStreak counts the current number of misses in a row. These both start out 0, score just increases by 1 for each hit detected, and missStreak is increased by 1 when a miss is detected and set back to 0 when a hit is detected.
With light and sound feedback as well as these new variables to change, there are several things to do when a hit or miss is detected, so the Hit and Miss My Blocks are defined to break up the program and make it easier to read (otherwise the main loop keeps getting longer and harder to read).
The main loop now has two additional tasks that it needs to perform during its "step 3" (testing for actions for the objects): detecting hits and detecting misses, so those are the two "if" blocks in the loop.
A hit occurs when the bird and target objects occupy the same (x, y) position during a frame. The pixel-rounded positions are tested instead of the continous variables to make the action align with what the user sees (otherwise what appears as a hit might be mathematically a close miss and the game would be too hard).
A miss occurs when a target goes off-screen, which is the same test that targets were already using to know when it's time to recycle for another pass, so that now triggers the Miss My Block.
Finally, the Miss My Block also tests to see if this is the 3rd miss in a row and then triggers the new Game Over block if so. Note that the setting up of all the various variables for the start of a game is now moved into the New Game My Block, so that it can be used for both the beginning of the program and also a restart after a Game Over.