Jul 22 2008
Jetman and the Helicopter Game Tutorial - Part 1
I remember playing the Helicopter game and thinking it was extremely complex. Looking back on it now, I realize it is probably one of the easiest flash games on the internet to clone. Jetman, a popular game on Facebook is actually a clone of the helicopter game. In fact, it is so easy, it can be programmed in less than 400 lines of code! So let’s get started.
The Title Screen:
The title screen is supposed to disappear when you click it and start the game.
Document Class:
package { import flash.display.Sprite; import flash.display.MovieClip; import flash.events.MouseEvent; public class Main extends Sprite { private var TitleScreen:StartScreen; private var bGameStarted:Boolean; public function Main ( ) : void { addTitleScreen(); stage.addEventListener ( MouseEvent.MOUSE_DOWN, mouseDownHandler ); stage.addEventListener ( MouseEvent.MOUSE_UP, mouseUpHandler ); } private function addTitleScreen ( ) : void { TitleScreen = new StartScreen (); TitleScreen.x = stage.stageWidth/2 + 100; TitleScreen.y = stage.stageHeight/2; this.addChild(TitleScreen); bGameStarted = false; } private function startGame ( ) : void { } private function mouseDownHandler ( ME:MouseEvent ) : void { if ( bGameStarted == false ) { bGameStarted = true; this.removeChild(TitleScreen); } } private function mouseUpHandler ( ME:MouseEvent ) : void { } } }
Lines 1-8: Importing the classes I need, setting up the class.
Lines 9-10: Creating two variables. The first holds the TitleScreen. It is of type StartScreen which is a movieclip with linkage in the library. The second is a boolean to let us know if the game has started.
Lines 12-18: Running the function to add the TitleScreen and adding a MOUSE_DOWN and a MOUSE_UP event listener in the constructor.
Lines 20-28: Adding the TitleScreen to the stage and setting the bGameStarted variable to false.
Lines 35-42: The MOUSE_DOWN handler function. If bGameStarted is false, then start the game and remove the title screen.
The Walls:
The walls are randomly generated in the game. This is probably one of the most complex parts of the game, but very doable. The wallls need their own class. There is a square Brick movieclip in the library with the linkage Wall.
Wall Class:
package { import flash.display.MovieClip; import flash.events.Event; public class Wall extends MovieClip { public var bPlaying:Boolean = false; public function Wall ( BStarting:Boolean ) : void { bPlaying = BStarting; this.addEventListener ( Event.ENTER_FRAME, onEnterFrameHandler ); } private function onEnterFrameHandler ( E:Event ) : void { if ( bPlaying ) { this.x -= 15; } } } }
Lines 1-7: Setting up the class.
Line 8: Creating a boolean variable called bPlaying that lets the Wall know if the game is going or not.
Lines 10-15: The constructor passes a parameter to tell whether the block is one of the starting Wall that shows up as the background before the game starts, or if it is game Wall that is added once the game is started. Setting bPlaying to BStarting, adding and ENTER_FRAME event to each Wall.
Lines 17-25: ENTER_FRAME handler. bPlaying dictates whether the Wall moves or not. The wall is moved -5 pixels every frame.
Document Class:
package { import flash.display.Sprite; import flash.display.MovieClip; import flash.events.MouseEvent; import flash.events.Event; import flash.utils.setInterval; public class Main extends Sprite { private var TitleScreen:StartScreen; private var bGameStarted:Boolean; private var mcWallContainer:MovieClip; private var uintAddABrick:uint; private var PreviousBrickTop:Wall; private var PreviousBrickBottom:Wall; public function Main ( ) : void { mcWallContainer = new MovieClip (); this.addChild(mcWallContainer); addTitleScreen(); stage.addEventListener ( MouseEvent.MOUSE_DOWN, mouseDownHandler ); stage.addEventListener ( MouseEvent.MOUSE_UP, mouseUpHandler ); } private function addTitleScreen ( ) : void { addStartingBricks(); TitleScreen = new StartScreen (); TitleScreen.x = stage.stageWidth/2 + 100; TitleScreen.y = stage.stageHeight/2; this.addChild(TitleScreen); bGameStarted = false; } private function startGame ( ) : void { stage.addEventListener ( Event.ENTER_FRAME, onEnterFrameHandler ); uintAddABrick = setInterval ( addABrick, 100 ); addABrick(); for ( var i = 0; i < mcWallContainer.numChildren; i++ ) { var mcWall:Object = mcWallContainer.getChildAt(i); mcWall.bPlaying = true; } } private function mouseDownHandler ( ME:MouseEvent ) : void { if ( bGameStarted == false ) { bGameStarted = true; startGame(); this.removeChild(TitleScreen); } } private function mouseUpHandler ( ME:MouseEvent ) : void { } private function addStartingBricks ( ) : void { for ( var i = 0; i < Math.floor( ( stage.stageWidth * 2 )/ 50); i++ ) { var Brick:Wall = new Wall ( false ); if ( i < Math.floor( ( stage.stageWidth * 2 ) / 50) / 2) { Brick.y = 0; Brick.x = i * 50 + 25; PreviousBrickTop = Brick; } else { Brick.y = stage.stageHeight; Brick.x = i * 50 + 25 - stage.stageWidth; PreviousBrickBottom = Brick; } mcWallContainer.addChild(Brick); } } private function onEnterFrameHandler ( E:Event ) : void { for ( var i = 0; i < mcWallContainer.numChildren; i++ ) { if ( mcWallContainer.getChildAt(i).x < -20 ) { mcWallContainer.removeChildAt(i); } } } private function addABrick ( ) : void { var nRandom:Number = Math.random(); var BrickTop:Wall = new Wall( true ); BrickTop.x = stage.stageWidth + 25; var BrickBottom:Wall = new Wall( true ); BrickBottom.x = stage.stageWidth + 25; if ( nRandom < .5 ) { if ( PreviousBrickTop.y > 3 ) { BrickTop.y = PreviousBrickTop.y - Math.floor ( Math.random() * 3 ); BrickBottom.y = PreviousBrickBottom.y - Math.floor ( Math.random() * 3 ); } else { BrickTop.y = PreviousBrickTop.y + Math.floor ( Math.random() * 3 ); BrickBottom.y = PreviousBrickBottom.y + Math.floor ( Math.random() * 3 ); } } else { if ( PreviousBrickBottom.y < 397 ) { BrickTop.y = PreviousBrickTop.y + Math.floor ( Math.random() * 3 ); BrickBottom.y = PreviousBrickBottom.y + Math.floor ( Math.random() * 3 ); } else { BrickTop.y = PreviousBrickTop.y - Math.floor ( Math.random() * 3 ); BrickBottom.y = PreviousBrickBottom.y - Math.floor ( Math.random() * 3 ); } } PreviousBrickTop = BrickTop; PreviousBrickBottom = BrickBottom; mcWallContainer.addChild(BrickTop); mcWallContainer.addChild(BrickBottom); } } }
Lines 13-16: Creating variables.
Lines 20-21: Creating an empty movieclip that is going to “hold” the walls. This is important as it will let us loop through all the walls easily, and will also help with depth management.
Line 31: Running the addStartingBricks function.
Lines 41-54: Function to start the game. Adds an ENTER_FRAME to the stage. Sets an interval called uintAddABrick to add a brick every 1/10 of a second. Also runs the addABrick function right away, otherwise there would be a gap between the prepopulated wall bricks and the first brick added on the interval. The function also runs a for loop to change the bPlaying boolean in all the bricks to true. This will make the bricks start to move and simulate the helicopter moving forward.
Lines 70-91: Puts the starting bricks on the stage in a straight line across the top and bottom. Just does this based on the width and height of the stage. 50 is the width and height of the Brick movieclip. This may seem a bit complicated at first, but if you think about the math behind it, it actually makes a lot of sense.
Lines 93-102: the handler function for the ENTER_FRAME. Looks at all the bricks inside the mcWallContainer and checks if the x position is less than -20. If it is, it removes the Brick. It doesn’t matter that the brick is removed because it is off the screen by that point.
Lines 104-148: The add a brick function. It depends heavily on random events. It may seem complicated, but it isn’t. A random decimal is generated. Then if statements are run and it places the next brick on the top 1-3 pixels higher or lower than the previous brick on the top. Does the same for the bottom bricks. It also makes sure the bricks never go lower or higher than the screen. At the end, it sets the brick that was just added to the previous brick, so that the next brick has a base Y position to work off of.
The Levels and Random Juts:
If the game stayed the same difficulty the whole time, it would be really boring. In the Helicopter game, the passage gets narrower and narrower. Also, random pieces of the wall jut out higher than the other pieces. For this section the wall class stays the same.
The Document Class:
package { import flash.display.Sprite; import flash.display.MovieClip; import flash.events.MouseEvent; import flash.events.Event; import flash.utils.setInterval; public class Main extends Sprite { private var TitleScreen:StartScreen; private var bGameStarted:Boolean; private var mcWallContainer:MovieClip; private var uintAddABrick:uint; private var PreviousBrickTop:Wall; private var PreviousBrickBottom:Wall; private var uintLevelUp:uint; private var nLevel:Number = 1; public function Main ( ) : void { mcWallContainer = new MovieClip (); this.addChild(mcWallContainer); addTitleScreen(); stage.addEventListener ( MouseEvent.MOUSE_DOWN, mouseDownHandler ); stage.addEventListener ( MouseEvent.MOUSE_UP, mouseUpHandler ); } private function addTitleScreen ( ) : void { addStartingBricks(); TitleScreen = new StartScreen (); TitleScreen.x = stage.stageWidth/2 + 100; TitleScreen.y = stage.stageHeight/2; this.addChild(TitleScreen); bGameStarted = false; } private function startGame ( ) : void { stage.addEventListener ( Event.ENTER_FRAME, onEnterFrameHandler ); uintAddABrick = setInterval ( addABrick, 100 ); uintLevelUp = setInterval ( levelUp, 3000 ); addABrick(); for ( var i = 0; i < mcWallContainer.numChildren; i++ ) { var mcWall:Object = mcWallContainer.getChildAt(i); mcWall.bPlaying = true; } } private function mouseDownHandler ( ME:MouseEvent ) : void { if ( bGameStarted == false ) { bGameStarted = true; startGame(); this.removeChild(TitleScreen); } } private function mouseUpHandler ( ME:MouseEvent ) : void { } private function addStartingBricks ( ) : void { for ( var i = 0; i < Math.floor( ( stage.stageWidth * 2 )/ 50); i++ ) { var Brick:Wall = new Wall ( false ); if ( i < Math.floor( ( stage.stageWidth * 2 ) / 50) / 2) { Brick.y = 0; Brick.x = i * 50 + 25; PreviousBrickTop = Brick; } else { Brick.y = stage.stageHeight; Brick.x = i * 50 + 25 - stage.stageWidth; PreviousBrickBottom = Brick; } mcWallContainer.addChild(Brick); } } private function onEnterFrameHandler ( E:Event ) : void { for ( var i = 0; i < mcWallContainer.numChildren; i++ ) { if ( mcWallContainer.getChildAt(i).x < -20 ) { mcWallContainer.removeChildAt(i); } } } private function addABrick ( ) : void { var nRandom:Number = Math.random(); var BrickTop:Wall = new Wall( true ); BrickTop.x = stage.stageWidth + 25; BrickTop.scaleY *= nLevel; var BrickBottom:Wall = new Wall( true ); BrickBottom.x = stage.stageWidth + 25; BrickBottom.scaleY *= nLevel; if ( nRandom < .5 ) { if ( PreviousBrickTop.y > 3 ) { BrickTop.y = PreviousBrickTop.y - Math.floor ( Math.random() * 3 ); BrickBottom.y = PreviousBrickBottom.y - Math.floor ( Math.random() * 3 ); } else { BrickTop.y = PreviousBrickTop.y + Math.floor ( Math.random() * 3 ); BrickBottom.y = PreviousBrickBottom.y + Math.floor ( Math.random() * 3 ); } } else { if ( PreviousBrickBottom.y < 397 ) { BrickTop.y = PreviousBrickTop.y + Math.floor ( Math.random() * 3 ); BrickBottom.y = PreviousBrickBottom.y + Math.floor ( Math.random() * 3 ); } else { BrickTop.y = PreviousBrickTop.y - Math.floor ( Math.random() * 3 ); BrickBottom.y = PreviousBrickBottom.y - Math.floor ( Math.random() * 3 ); } } PreviousBrickTop = BrickTop; PreviousBrickBottom = BrickBottom; mcWallContainer.addChild(BrickTop); mcWallContainer.addChild(BrickBottom); } private function levelUp ( ) : void { if ( nLevel < 3 ) { nLevel += .2; } } } }
Lines 17-18: Variables for leveling up.
Line 47: Setting the interval that is going to run the levelUp funciton. It runs every 3 seconds.
Lines 112+116: Multiplying the BrickTop and BrickBottom scaleY by the nLevel. nLevel will always be over 1, so it will make the bricks longer and longer.
Lines 145-149: If nRandom is greater or equal to .98 then make a “jut”. This is a random time when the walls get larger. There is a 2% chance this will happen.
Lines 152-158: Every 3 seconds, levelUp gets run. All it does it checks to see whether nLevel is less than 3, and if it is it adds .2 to the nLevel, the variable that is used to make the scaleY bigger of the bricks.
The Helicopter:
Time to add the helicopter!
The Document Class:
package { import flash.display.Sprite; import flash.display.MovieClip; import flash.events.MouseEvent; import flash.events.Event; import flash.utils.setInterval; import flash.utils.clearInterval; public class Main extends Sprite { private var TitleScreen:StartScreen; private var bGameStarted:Boolean; private var mcWallContainer:MovieClip; private var uintAddABrick:uint; private var PreviousBrickTop:Wall; private var PreviousBrickBottom:Wall; private var uintLevelUp:uint; private var nLevel:Number = 1; private var Helicopter:Chopper; private var nPower:Number = 2; private var nSpeedY:Number = 0; private var bMouseDown:Boolean; public function Main ( ) : void { mcWallContainer = new MovieClip (); this.addChild(mcWallContainer); addTitleScreen(); stage.addEventListener ( MouseEvent.MOUSE_DOWN, mouseDownHandler ); stage.addEventListener ( MouseEvent.MOUSE_UP, mouseUpHandler ); } private function addTitleScreen ( ) : void { addStartingBricks(); TitleScreen = new StartScreen (); TitleScreen.x = stage.stageWidth/2 + 100; TitleScreen.y = stage.stageHeight/2; this.addChild(TitleScreen); Helicopter = new Chopper (); Helicopter.x = stage.stageWidth/3; Helicopter.y = stage.stageHeight/2; this.addChild(Helicopter); Helicopter.rotation = 5; bGameStarted = false; } private function startGame ( ) : void { stage.addEventListener ( Event.ENTER_FRAME, onEnterFrameHandler ); uintAddABrick = setInterval ( addABrick, 100 ); uintLevelUp = setInterval ( levelUp, 3000 ); addABrick(); for ( var i = 0; i < mcWallContainer.numChildren; i++ ) { var mcWall:Object = mcWallContainer.getChildAt(i); mcWall.bPlaying = true; } } private function mouseDownHandler ( ME:MouseEvent ) : void { if ( bGameStarted == false ) { bGameStarted = true; startGame(); bMouseDown = true; Helicopter.rotation = -5; this.removeChild(TitleScreen); } bMouseDown = true; Helicopter.rotation = -5; } private function mouseUpHandler ( ME:MouseEvent ) : void { bMouseDown = false; Helicopter.rotation = 5; } private function addStartingBricks ( ) : void { for ( var i = 0; i < Math.floor( ( stage.stageWidth * 2 )/ 50); i++ ) { var Brick:Wall = new Wall ( false ); if ( i < Math.floor( ( stage.stageWidth * 2 ) / 50) / 2) { Brick.y = 0; Brick.x = i * 50 + 25; PreviousBrickTop = Brick; } else { Brick.y = stage.stageHeight; Brick.x = i * 50 + 25 - stage.stageWidth; PreviousBrickBottom = Brick; } mcWallContainer.addChild(Brick); } } private function onEnterFrameHandler ( E:Event ) : void { if ( bMouseDown ) { if ( nSpeedY > -8 ) { nSpeedY -= nPower; } } else { if ( nSpeedY < 12 ) { nSpeedY += 3; } } Helicopter.y += nSpeedY; for ( var i = 0; i < mcWallContainer.numChildren; i++ ) { if ( mcWallContainer.getChildAt(i).x < -20 ) { mcWallContainer.removeChildAt(i); } } } private function addABrick ( ) : void { var nRandom:Number = Math.random(); var BrickTop:Wall = new Wall( true ); BrickTop.x = stage.stageWidth + 25; BrickTop.scaleY *= nLevel; var BrickBottom:Wall = new Wall( true ); BrickBottom.x = stage.stageWidth + 25; BrickBottom.scaleY *= nLevel; if ( nRandom < .5 ) { if ( PreviousBrickTop.y > 3 ) { BrickTop.y = PreviousBrickTop.y - Math.floor ( Math.random() * 3 ); BrickBottom.y = PreviousBrickBottom.y - Math.floor ( Math.random() * 3 ); } else { BrickTop.y = PreviousBrickTop.y + Math.floor ( Math.random() * 3 ); BrickBottom.y = PreviousBrickBottom.y + Math.floor ( Math.random() * 3 ); } } else { if ( PreviousBrickBottom.y < 397 ) { BrickTop.y = PreviousBrickTop.y + Math.floor ( Math.random() * 3 ); BrickBottom.y = PreviousBrickBottom.y + Math.floor ( Math.random() * 3 ); } else { BrickTop.y = PreviousBrickTop.y - Math.floor ( Math.random() * 3 ); BrickBottom.y = PreviousBrickBottom.y - Math.floor ( Math.random() * 3 ); } } if ( nRandom >= .98 ) { BrickTop.scaleY += 2.5 - nLevel/2; BrickBottom.scaleY += 2.5 - nLevel/2 ; } PreviousBrickTop = BrickTop; PreviousBrickBottom = BrickBottom; mcWallContainer.addChild(BrickTop); mcWallContainer.addChild(BrickBottom); } private function levelUp ( ) : void { if ( nLevel < 3 ) { nLevel += .2; } } } }
Lines 20-23: Creating variables of the helicopter.
Lines 45-49: Adding the helicopter.
Lines 71-80: Setting the bMouseDown to true when the mouse is down. This only happens when the game is actually started. Also, it changes teh rotation of the helicopter to simulate it falling and rising.
Lines 86-90: bMouseDown to false and change the rotation when the mouse is up.
Lines 117-132: Actually changing the helicopters Y position. Checks if the mouse is down, then checks whether the helicopter is over and under certain speeds. If you don’t have the speed checks, the helicopter can start going really fast really quickly.
That’s it for now. I will put the second half of the tutorial up tomorrow. I will also post the complete source files tomorrow. Please leave comments and ask questions.