Flash Tutorial Links:
Play Games: HTML5 Tutorials:

Make Music Games

Blocks are falling, music is pounding, can your hands keep up?

Rise of Music Games

Musical games gained immense popularity of the late when Guitar Hero, Rock Band, etc took the gaming world by storm. These games test the players reflexes by listening to some pop music, and watching little bricks or game objects fall. When these objects fall, the player is supposed to hit the right buttons, and get points according to how perfect his timing is.

We've seen some similar game mechanics back then when Dance Dance Revolution was pretty popular in arcade centres (and made its way to the homes of some people). That involved hitting the correct directional buttons.

In this tutorial, we'll try to recrate a simplified game with this form of game mechanics. I'll leave out the music and addition of sound effects to another tutorial. We'll focus solely on the coding of the game for now.

Game Scenario

Do we really need a game scenario for this kind of game? Ok, how about ... musical bricks are dropping down, and the player needs to hit the right keys according to the kind of musical brick, at the right time.

Game Details

  1. The player hits the keys Q, W, O and P for the Blue, Red, Green, and Yellow bricks respectively.
  2. Each perfect timing gets the player 50 points, a good timing 10 points, and a bad timing a penalty of -20 points.

Download the Game Files

The files you need to create this game can be downloaded from here.

Step 1 - Managing your FLA file

In this tutorial, a lot of graphics have been laid out on the screen to supplement the game.

musicGamestage

The text fields, the outlines of the bricks, as well as the letters (which tell the player which key to press for which brick) are all parked under the UI layer. Remember that this layer is there for such purposes of holding UI elements like these, as they will always be above the game assets (since UI layer is one layer higher than Contents)

One Brick movie cilp is used to hold all the four colour types of bricks, and they are differentiated by the frame they are currently at.

brick Structure

When we create the bricks, we just need to gotoAndStop at the correct frame label to get the correct colour of the brick displayed to the player.

If you look at the centre of the game stage, you will see another text field labelled txtStatus. This text field is used to display to the player how accurate his gameplay is. It will show Perfect, Good or Bad, and the text will slowly fade after it pops up for a while.

Step 2 - Starting up the game in your GameController.as

Let us see what goes on in the startGame function.

47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
public function startGame()
{
    playerScore = C.PLAYER_START_SCORE;
			
    bricks = new Array();
    showText = "";
    showTextTimer = 0;
    
    txtStatus.blendMode = BlendMode.LAYER;
    
    mcGameStage.addEventListener(Event.ENTER_FRAME,update);
    
    //Handle event when this game is being preloaded
    addEventListener(Event.ADDED_TO_STAGE, gameAddedToStage ); 
    
    //Handle situations when this game is being run directly
    if (stage != null)
    {
        stage.addEventListener(KeyboardEvent.KEY_DOWN,keyDownHandler);
        stage.addEventListener(KeyboardEvent.KEY_UP,keyUpHandler);
    }
}

The only interesting code here is probably line 55. For now, let's just say that the reason for this line of code is to enable the transparency fading of the txtStatus text field. I think this code would not be required anymore if it is targeted to Flash Player 10, but anything lesser, the dynamic text fields are not really able to change in their alpha property properly it seems.

Step 3 - Key Handlers

The keyboard input to the game will be the letters Q, W, O and P. We chose two sets of letters that are grouped far apart (group for Q and W, and a group for O and P) for obvious reasons of hand positioning. It is intended that the player plays with his two hands on the keyboard, left hand for Q and W, while the right hand for the other two.

76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
private function keyDownHandler(evt:KeyboardEvent):void
{
    if (evt.keyCode == 81) //Letter 'Q' 
    {
        pressQ = true;
    }
    if (evt.keyCode == 87) //Letter 'W'
    {
        pressW = true;
    }
    if (evt.keyCode == 79) //Letter 'O'
    {
        pressO = true;
    }
    if (evt.keyCode == 80) //Letter 'P'
    {
        pressP = true;
    }
}

We set the keypresses accordingly in the variables pressQ, pressW, pressO and pressP.

Step 4 - Game Loop - User Input

Now, let us consider the user input portion in the game loop. You can see that it is clearly broken up into several if statements, each handling the situation of a specific keypress. They all call the same function, checkBrick, which takes in a string that is either "blue", "red", "green" or "orange", accordingly.

101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
public function update(evt:Event)
{
    //******************			
    //Handle User Input
    //******************
    if (pressQ)
    {
        checkBrick(C.COLOUR_FOR_Q);
    }
    
    if (pressW)
    {
        checkBrick(C.COLOUR_FOR_W);
    }
    
    if (pressO)
    {
        checkBrick(C.COLOUR_FOR_O);
    }
    
    if (pressP)
    {
        checkBrick(C.COLOUR_FOR_P);
    }
    
    pressQ = false;
    pressW = false;
    pressO = false;
    pressP = false;

After doing the checks, we set all the variables to false so as not to have the game over-react to the player's input.

So what actually happens in the checkBrick function?

180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
public function checkBrick(colour:String)
{
    var foundABrick = false;
        
    for (var i=0; i < bricks.length; i++)
    {
        if (bricks[i].getColour() == colour)
        {
            foundABrick = true;
            
            //Check how close this brick is to the PERFECT location
            var distToPerfect = C.PERFECT_YPOS - bricks[i].y;
            
            if ((distToPerfect < C.PERFECT_DISTANCE) && 
                (distToPerfect >= C.HIT_TOLERANCE))
            {
                //Brick is at a perfect distance
                playerScore += C.PERFECT_POINTS;
                showText = C.STATUS_PERFECT;
                showTextTimer = C.TXTSTATUS_MAX_TIMER;
                
                //Remove Brick
                mcGameStage.removeChild(bricks[i]);
                bricks.splice(i,1);
            }
            else if ((distToPerfect < C.GOOD_DISTANCE) && 
                    (distToPerfect >= C.HIT_TOLERANCE))
            {
                //Brick is at a good distance
                playerScore += C.GOOD_POINTS;
                showText = C.STATUS_GOOD;
                showTextTimer = C.TXTSTATUS_MAX_TIMER;
                
                //Remove Brick
                mcGameStage.removeChild(bricks[i]);
                bricks.splice(i,1);
            }
            else
            {
                //Very bad timing for hits
                playerScore += C.BAD_POINTS;
                showText = C.STATUS_BAD;
                showTextTimer = C.TXTSTATUS_MAX_TIMER;
            }
            
            //already found 1 brick, exit
            break;
        }
    }
    
    if (!foundABrick)
    {
        //Hit key when there isn't a brick
        playerScore += C.BAD_POINTS;
        showText = C.STATUS_BAD;
        showTextTimer = C.TXTSTATUS_MAX_TIMER;
    }
}

Line 184 and 186 loops through all the bricks that are stored in the bricks array, and filters out only those bricks that belong to the colour which we're going to check. If the function is called with checkBrick(C.COLOUR_FOR_Q), it effectively means we're doing checkBrick("blue"). So, even when we loop through all the bricks in line 184, line 186 ensures that we only look at the blue bricks.

Because we only look at 1 brick each time the user presses a key, the variable foundABrick controls that.

Also notice that although we've always been doing the loops from the back of the array to the front, here, we're actually looping from the front to the back. The reason is that we want to select the bricks lowest in the game with the highest priority. As the bricks are created, they are pushed into the bricks array. This means that bricks with the lower array indices, are the bricks that are lowest in the game.

bricks priority illustration

This illustration above should show you what I was trying to highlight earlier. If the player presses the W key, pressW will be true, and that in turn calls the function checkBrick("red"). Inside the checkBrick function, we have to loop from the front of the array so that we correctly selects bricks[0] instead of bricks[1].

190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
            //Check how close this brick is to the PERFECT location
            var distToPerfect = C.PERFECT_YPOS - bricks[i].y;
            
            if ((distToPerfect < C.PERFECT_DISTANCE) && 
                (distToPerfect >= C.HIT_TOLERANCE))
            {
                //Brick is at a perfect distance
                playerScore += C.PERFECT_POINTS;
                showText = C.STATUS_PERFECT;
                showTextTimer = C.TXTSTATUS_MAX_TIMER;
                
                //Remove Brick
                mcGameStage.removeChild(bricks[i]);
                bricks.splice(i,1);
            }
            else if ((distToPerfect < C.GOOD_DISTANCE) && 
                    (distToPerfect >= C.HIT_TOLERANCE))
            {
                //Brick is at a good distance
                playerScore += C.GOOD_POINTS;
                showText = C.STATUS_GOOD;
                showTextTimer = C.TXTSTATUS_MAX_TIMER;
                
                //Remove Brick
                mcGameStage.removeChild(bricks[i]);
                bricks.splice(i,1);
            }
            else
            {
                //Very bad timing for hits
                playerScore += C.BAD_POINTS;
                showText = C.STATUS_BAD;
                showTextTimer = C.TXTSTATUS_MAX_TIMER;
            }
            
            //already found 1 brick, exit
            break;

After we found that brick, we need to check its position from the perfect position. Since nowhere in the game will this perfect position be changed, we've put this perfect y coordinates into the C.as already, under C.PERFECT_YPOS.

In line 191, we take this perfect position - the current y coordinates of the brick. This gives the distance the brick is from the perfect position. While it makes sense to test the player harshly to get a distance of 0 here, meaning for the brick to be EXACTLY at the perfect location, this is actually poor game balancing, because he is going to end up without seeing any Perfect ever. Instead, we set a tolerance distance. As long as the distance falls within this range, we take it as Perfect.

distance diagram

Essentially, what lines 193 and 194 are checking for, is that the brick falls within the PERFECT_DISTANCE and HIT_TOLERANCE area.

If it does, we update the player's score, remove the brick, etc. Else, in lines 217 to 223, we consider this a bad timing, and impose the penalty to the player's score accordingly.

230
231
232
233
234
235
236
237
    if (!foundABrick)
    {
        //Hit key when there isn't a brick
        playerScore += C.BAD_POINTS;
        showText = C.STATUS_BAD;
        showTextTimer = C.TXTSTATUS_MAX_TIMER;
    }
}

These lines of codes handle the scenarios whereby the player wildly mashes his keys just to get the timing right by brute force. If foundABrick is not true, which means that as we loop through all the bricks, we did not find a colour matching the key he pressed, we impose the same bad timing penalties.

This is probably the heaviest user input logic we've seen so far. :)

Step 5 - Game Loop - Game Logic

131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
//******************
//Handle Game Logic
//******************			
//Check to spawn bricks
if (Math.random() < C.SPAWN_BRICK_CHANCE)
{
    var newBrick = new Brick();
    bricks.push(newBrick);
    mcGameStage.addChild(newBrick);
}
 
//Update Bricks
for (var i=bricks.length - 1; i >= 0; i--)
{
    bricks[i].update();
    
    if (bricks[i].notInScreen())
    {
        mcGameStage.removeChild(bricks[i]);
        bricks.splice(i,1);
        
        //Consider a miss
        playerScore += C.BAD_POINTS;
        showText = C.STATUS_BAD;
        showTextTimer = C.TXTSTATUS_MAX_TIMER;
    }
 
}

The amount of game logic required to maintain the game is much shorter this time round. We have the usual code to check for spawning of the bricks from line 135 to 140. There are 4 different types of bricks that could spawn, and this is handled in the constructor of the Brick class.

10
11
12
13
14
15
16
17
18
public function Brick()
{
    var colourIndex = Math.floor(Math.random()*4);
    colour = C.COLOUR_ARRAY[colourIndex];
    this.x = C.COLOUR_START_X[colourIndex];
    this.y = C.COLOUR_START_Y;
    
    this.gotoAndStop(colour);
}

As the brick is created, a random colour is selected by the code shown above. We merely forward the frame of the brick to the appropriate colour. The rest of the code in the Brick class are rather straightforward.

Now, back to the game logic in the game loop.

131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
//******************
//Handle Game Logic
//******************			
//Check to spawn bricks
if (Math.random() < C.SPAWN_BRICK_CHANCE)
{
    var newBrick = new Brick();
    bricks.push(newBrick);
    mcGameStage.addChild(newBrick);
}
 
//Update Bricks
for (var i=bricks.length - 1; i >= 0; i--)
{
    bricks[i].update();
    
    if (bricks[i].notInScreen())
    {
        mcGameStage.removeChild(bricks[i]);
        bricks.splice(i,1);
        
        //Consider a miss
        playerScore += C.BAD_POINTS;
        showText = C.STATUS_BAD;
        showTextTimer = C.TXTSTATUS_MAX_TIMER;
    }
 
}

From line 143 to 156, we loop through each of the brick in the bricks array, and update them accordingly. If you refer back to the Brick class, the update function simply moves the brick downwards.

In line 147, we check if the bricks have reached a certain distance below the perfect y, where the player should have hit the right button. If the player has yet to, the brick will survive past the perfect y, and trigger these code where we consider the player having missed his chance. The usual penalty applies in line 153, where we deduct the player's score.

You'll see why we set this variable showTextTimer in line 155 later on. This variable is used to trigger the text updates where the player sees Perfect, Good, or Bad.

Step 6 - Handling Display

160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
//******************
//Handle Display
//******************
//Display status alerts
showTextTimer -= C.TXTSTATUS_DECREASE_TIMER;
if (showTextTimer > 0)
{
    txtStatus.text = showText;
    txtStatus.alpha = showTextTimer/C.GAME_FPS;
}
else
{
    txtStatus.text = "";
    txtStatus.alpha = 0;
}			
 
//Display new Score
txtScorePlayer.text = String(playerScore);

We have a little extra thing to show besides the usual playerScore. The status alert is shown whenever the showTextTimer is greater than 0. That's why we see earlier on that whenever the player gets a Perfect hit, a Good hit, or a miss, we always set the showTextTimer to C.TXTSTATUS_MAX_TIMER.

We then slowly reduce the alpha of the text field by a little each tick of the loop, until the time when showTextTimer reaches 0, and we'll clear the status text totally by setting the alpha to be 0.

The Game

And here you have it ... the working game! If you like it ... share it!

Download the Game Files

The files you need to create this game can be downloaded from here.

How To Play

Objective: Hit the bricks with the correct key when it falls into the brick outline.

Controls: Press Q for the Blue brick, W for Red, O for Green and P for Yellow.


Content on this page requires a newer version of Adobe Flash Player.

Get Adobe Flash player


Flash Resources
Preloader FPS Display Sounds & Music
Keycodes Name Generator
Game Development Resources
Sprite Sheets