Danny Burbol's How to make a falling blocks style Flash Game Tutorials
Lesson 2: Drawing Blocks & Keyboard Input
A Link To: All Lessons' Table Of Contents
A Link To: The Original Flash Game that started all this
So here's where we left off at the end of lesson 1. We have a couple self-contained screens that we can navigate between by clicking buttons.
You'll notice that I changed my title text, scaled up the title screen buttons with the free transform tool (Tool bar, the black cursor with the box, under the white cursor and above the Lasso) and I've added an extra screen with links to these tutorials. You should be able to make changes like this if you went through lesson 1. The only thing you'll needs is the function to call to open a web link in a new window. Here it is:
Note the: import flash.net.*;
In this lesson we are going to:
- Learn how to draw a sprite with a rectangle using ActionScript .
- Learn how to create our main loop callback event (timer events).
- Learn how to use Sprites for containers.
- Learn how to use 2 dimensional arrays as an adjacency matrix (the game's board).
- Learn how to detect simple keyboard keys presses.
- Learn how to rotate our game's pieces using some simple vector math trick.
I'm also not going to be going in to as much depth as I did in Lesson 1. Lesson 1 was a "starting from nothing" introduction to flash, but here in Lesson 2, we're going to build on what we've learned rather than reiterate all of it. So don't be afraid to pop open Lesson 1 and review a couple steps when you feel the need. I know it's annoying, but realizing you don't quite know how to do something and having to go dig up the info is actually a great way to train yourself to remember.
1. How To Draw a Rectangle With ActionScript
Flash uses vector art, which basically mean it holds most of it's visual data as math. For example, if you wanted to draw a smiley face in flash, you'd draw a yellow circle, then on top of that you'd draw two smaller black circles (for eyes), and a maybe a half circle for a mouth.
Flash doesn't keep that final image of the smiley face anywhere. Instead, it keeps the list of shapes it needs to draw in order to construct the smiley face every time we want to draw it combine with some information on where to draw these shapes, how big to draw them, and/or at what angle to draw them.
This is what Sprites are for. Sprites are basically just a list of shapes combine with a position, rotation, and scale for drawing those shapes. You can also think of Sprites like a container full of child objects/shapes. If you move the container, everything inside it moves too.
So basically, we're going to create a Sprite. Then we are going to add a bunch of shapes to that sprite. Finally, we are going to tell flash that we want that sprite to be drawn on the screen.
In order to make this example as simple and bare-bones as possible, I'm just going to make a little test function and drop it into your MainApp's constructor.
Notice that we don't have to add the import command for the Sprite because we already have it. MainApp was extended from Sprite. So we just create a new sprite like we'd create any new object.
Next we make the color white by using the RGB hex value FFFFFF. (Do a web search on "HTML colors" or "RGB colors" if you'd like a more in-depth explanation on why FFFFFF is white). Then we use a few graphics commands to add a shape to the sprite. We use beginFill to specify the color, drawRect to add a rectangle and endFill to say we're done. The values we passed to drawRect were the x and y positions (0 and 0) followed by the width and the height of the rectangle we want (20 and 20).
Finally we tell Flash we want to draw this sprite. We do this with addChild(). But why does this work? What are we actually doing here? Remember, sprites are like containers. If we have a sprite that is being drawn to the screen, we can add more children to that sprite and it will be drawn to the screen as well. MainApp is a Sprite. MainApp is already being drawn because it's our "Document class" (remember, "AGreatGame.fla" > "Properties" > "Document class: MainApp"). We've already hooked up MainApp to be drawn, so when we add children to MainApp, they get drawn too. This is because Flash works "display lists". When MainApp is told to draw, it tells all it's children to draw and they in turn tell their children to draw, etc.
So addChild() adds our sprTestBlock to MainApp which is a sprite that's already being drawn. After that we just set the test block's position to (20, 20).
Don't forget to call DrawATestBlock(); in your constructor, then save your files and test the movie.
Note, I made my block white because my background was black. I trust if you've made it to lesson 2 you'd be wise enough to make the block a color that will stand out against your IntroScreen's background.
Also note, the block is in the top left corner of the screen and offset to the right and downward by 20 pixels. That how screens work. The origin is in the top left corner of the screen. Positive x is to the right. Positive y is down.
So we drew a square and we did it by breaking our nice self-contained AppScreen framework. I only did this because I wanted to make sure you saw all three steps for drawing a block before we separate them.
Let's start by making a function for drawing a block to any sprite in any color. I'm going to do this by making a small DrawBlock class with a static function member.
Create a new *.as file. Save it as "DrawBlock.as" and type this into it:
(My header comment got clipped a little bit, but it's something to the affect of: "// a class to hold drawing functions.")
We do our standard "package" wrapper, followed by importing any api's we are going to use, then we make our DrawBlock class. The only function is a "static" function, which means we will be able to call "WithBorder()" without having to create a "DrawBlock" variable first.
//so instead of:
var tmpDrawBlock:DrawBlock = new DrawBlock();
tmpDrawBlock.WithBorder();
//the static function lets us do it like this:
DrawBlock.WithBorder();
In WithBorder we're drawing more than one square now. We draw a black square. Then we draw a specific color right on top of it. The .5 in beginFill means we're drawing at 50% alpha. So we're drawing a semi-transparent color on top of a black square... which gives us a dark colored square. Then we inset the size of the square and do it again, giving us another shade of the intended color. Finally we inset the size of the square one more time and draw the color we were instructed to use. It's all wrapped in a test for uBlockSize just in case someone calls it with a uBlockSize that will cause us trouble.
Now I want to bring your attention to the clear() function with my all-caps comment. Say you had a sprite, and you called DrawBlock.WithBorder() on it. It's going to add 4 rectangles to the sprite's list of things it should be drawing. If you don't clear() the list of shapes before adding more, then those 4 rectangles will just keep getting added to the end of the list. It will look fine on screen, but before long you could be drawing a list of hundreds of shapes each time Flash draws your sprite to the screen. It may sound trivial, but if you don't call clear() you could very easily find your game grinding to a halt the longer you play it.
Okay, now lets go back to MainApp.as and update our test function:
Notice that I switch the color to green so you can actually see what our DrawBlock.WithBorder() function does.
Save your files and test the movie.
Next, let's add the block to the right screen in the game, the GameScreen. Pull the "DrawATestBlock();" out of MainApp's constructor and put it in GameScreen_mc's constructor. Also, move the DrawATestBlock() function to the GameScreen_mc class as well. Also, don't forget to add the import for the Sprite.
Now you should have this:
Note: remember, the block draws because our GameScreen is added as a child of MainApp in MainApp's CreateGameScreen() function. So the test sprite is a child of GameScreen which is a child of MainApp.
2. Laying out some game framework
Before we move to drawing game objects, let's start setting up a framework for our game to operate in. We'll start by addressing these hard-coded numbers in DrawATestBlock().
We're going to do this by making a class in a separate file that has nothing but const member variables. So create a new file and call it: FallingBlocks_GameSettings.as
You can see that I've done three new things. I've imported Point. I've changed our block size from 20 to 18, and I'm calling our position mvBoardScreenPos and I'm moved it to the middle of the screen.
As the name implies, these will only be the settings for the Falling Blocks Game we're going to make. We won't be cluttering it up with constant variables that are needed on other screen or in other games. Also, the game settings will be completely separate from the game's logic. Later, when we create the game's main class, we will be passing it our FallingBlocks_GameSettings object.
Why do this? Why not just have a bunch of constant variable members at the top of our game's main class? Well, first of all, it makes it really, really easy to locate and modify the game's settings since they're not mixed in with other game variables. Second, if we design the game's main class to run off settings that we hand it, that means we can hand it different settings very easily. We could extend FallingBlocks_GameSettings with a class like FallingBlocks_GameSettings_DirectorsCut or FallingBlocks_GameSettings_ForTheMacOS. (note, when doing that, you'd need to change the 'const' to 'var' for any variables you want to override in the new class.)
Make sure to hook up these game setting inside the GameScreen_mc class.
Save your files and test your movie. You should have this:
Now let's make the game's main class. We'll call it: FallingBlocks_Main.as
Again you see we've extended from Sprite. I did this because I wanted to take advantage of Sprite's container-like qualities as well as the fact that it's a display object.
You see we have a pointer to the game settings, but we don't create a new FallingBlocks_GameSettings ourselves, we use the one that's passed to us in the constructor.
I also moved our DrawATestBlock() from GameScreen_mc to here. Note that "addChild()" still works because FallingBlocks_Main extends Sprite.
Now go back and update GameScreen_mc:
We create the new FallingBlocks_Main and used the settings we created on the line before it. We also call addChild( mGameMain ); If you don't do this, you won't see our test block being drawn. Also Note, the DrawATestBlock() function has been deleted and relocated.
Save your files and test your movie. It should look exactly the same as last time. Same result, better structure.
3. Creating a Main Loop with a Timer Event
Now let's hookup and test our main loop. If you're used to main loops, you'll notice that this one is slightly different. Normally a main loop is called once per frame at 60 frames per second (well, we shoot for 60 frames per second). However, when working in console and pc games, we do everything ourselves. We do all the drawing, we handle all the input, we decide what happens in the game on *every* frame.
Flash handles most of that for us. Plus, it uses events to handle input, so we don't ever have to poll it ourselves every frame. So, in our falling blocks game, all we're ever going to do is react to player input events and make the player's current piece fall. Since input is handled with events, all our main loop will ever do is sit around waiting for enough time to go by so we can make the game piece fall down.
Rather than spend all those cycles in a main loop that just waits on a timer, we're going to do the opposite. We're going to set a timer event that will get called each time the game piece is supposed to fall a row. As the game gets harder, and the game piece needs to fall faster, we'll just updated that timer to be a little faster. So basically, this game doesn't need a main loop that gets called every frame, we just need a timer event to drop the block every second or so.
Here's how we create a timer event in our FallingBlocks_Main.as
So we import our Timer and TimerEvent, then we create a new member variable that is a timer. In our constructor we create the timer and set how much time it takes before it calls the callback (you'll see in a moment, muFallSpeed_Start is set to 1 second).
After that we addEventListener so we can call our callback function OnMakePieceFall when the timer goes off. Then we start the timer.
Next you'll see our OnMakePieceFall function needs to have a parameter of type TimerEvent. This makes sense because addEventListener was using a TimerEvent.TIMER. Finally, just for testing purposes, I dropped the DrawATestBlock() function in there.
NOTE: take a good look at DrawATestBlock(). Every time it's called we create a new Sprite and attach it to the FallingBlocks_Main object. This is a prime example of a memory leak. So don't leave your program running over night in this state because it will eat up memory. Remember, DrawATestBlock() is for testing purposes, not final game purposes.
I've also changed DrawATestBlock to use a random color each time it's called.
Now jump over to the FallingBlocks_GameSettings file and add in the muFallSpeed_Start.
Again, 1000 milliseconds is 1 second.
Save your files and test your movie. You should have a block that changes color once a second.
Bonus use for timer: delay
While we're on timers, let's go make our intro screen time-out if we don't press the skip button soon enough. Why don't you go give it a shot and then come back to check your work?
Okay, so here's what I did in IntroScreen_mc.as:
So again, we import our Timer and TimerEvents, create a timer member variable and a const to control the timer's delay. Then we create it and hook it up in our constructor.
In our callback, we do what the skip button would have done, so I've reloaded that dispatchEvent into a SkipIntro() function that both event callbacks will call. Now we change SkipIntro() and both the timer event and the button clicked event will stay in sync.
However, did you notice that you have to stop the timer yourself otherwise it will keep going off and bringing you to the TitleScreen. It seems buggy to me that flash would continue to call a callback in a member function from an object that's been put on the garbage collection list, but the thing about garbage collection is that it's not instant, so it's better to just play it safe, just in this case.
(You will probably have to refresh the page to see the following swf actually move from the intro to the title screen.)
Also Note: right about now you're going to want to go back to your MainApp constructor and comment out the CreateIntroScreen() and replace it with CreateGameScreen(), so you don't have to click through to the game screen each time you test your movie. When the game is all finished, we can turn the other screens back on. This is totally optional, but makes debugging faster.
4. Making the Game's Board
Before we go on, let me just be clear on some of my terminology, so we're all on the same page.
- Block: is a colored square in the game board or in a game piece.
- GamePiece: is made up of 4 blocks, this is what the player is in control of as it's falling.
- GameBoard: is a grid that represents the area the game is played on.
- GameBoardCell: is one cell of the gameboard's grid. It's more than just a block. A cell holds information about the block that may or may not be in this grid cell.
With those terms cleared up, let's talk about how to use a 2 dimensional array to represent the game's board.
If You don't Know What an Array is, read this:
- I'd describe an array as "a bunch of something in a row." If you're a Star Trek fan, you may recall the captain saying "fire the forward array," to say "fire all the cannons across the front of the ship."
- In a computer programming language an array is a bunch of numbers that are lined up in a row. The syntax often uses "[" and "]". So something like "myPhotos[0]" would get the first element in the array of myPhotos, while "myPhotos[1]" would get the second element in the array of myPhotos.
- Arrays are handy, if you don't know much about them, please do a web search on them because they are pretty basic programmer knowledge and they are pretty crucial to know how to make and use.
If You don't know what a 2d array is, read this:
- This is basically an array of arrays. So if we had an array called "columns" which had 10 elements (i.e., 10 columns). Each of these columns might also hold an array (i.e. an array of cells, one for each row in the column). So for example, grid[4] would be the 5th column in our grid. What's in the 5th column? An array of cells. gird[4][1] would get us the grid cell from the 5th column, and the 2nd row.
- Again, multi-dimensional arrays are handy and if you don't know much about them, please do a web search on them because they are also pretty crucial programmer knowledge.
Okay, if you're here, you know what 2d arrays are, so here's what they look like in ActionScript3 syntax. (you don't have to type this anywhere, just observe and take it in.)
Basically, we're going to make the FallingBlocks_GameBoard nothing more than a grid. To keep things easy to read and work with, we're going to make each cell in the grid an object called FallingBlocks_GameBoard_Cell
So the GameBoard only holds a bunch of cells, and each cell will hold it's own data, things like, "is there a block here or not?" "what color is it?" "is it special?"
Let's start by defining some more constants. Most falling blocks games use a grid that's 10 wide by 20 tall. So we're just going to stick with that.
Update your FallingBlocks_GameSettings.as:
Next, I'm going to setup a mostly blank FallingBlocks_GamePiece class simply because I want to define the colors and ids for the pieces that the GameBoard_Cells are going to be using.
Create a FallingBlocks_GamePiece.as:
We used "static const" so we could access these variables without needing an instance of the class. Also note, the colors are not const because we're going to make the colors change after each level.
Create a FallingBlocks_GameBoard_Cell.as:
So again we have some static consts to keep track of the cell's states for "IS_EMPTY" and "IS_FULL".
We have a sprite, which will where we do our DrawBlock.WithBorder().
We have a BlockId, which refers to the gamepiece who's block is in this cell. We keep this id rather than a color because we can always lookup the color of the block as long as we know which game piece we're part of. This way, when colors of game pieces change, the cell can easily look-up and update to the new color.
When we create the cell, it automatically creates a sprite and set's is position. Keep in mind this position is in pixels and is measured in relation to the GameBoard's origin.
Finally, we make some IsEmpty() and IsFull() functions to make our lives easier later.
Now create a FallingBlocks_GameBoard.as... we'll do this file in two pieces so I can explain as we go:
Again we import Sprit and Point.
The class extends Sprite because we want to treat our game board like a sprite. It will be drawn, it will have a screen position and it will contain children.
We have 3 member variables, the Grid is the most important of the data. The GridSize and the BlockSize are just cached values we'll be getting from FallingBlocks_GameSettings, which are passed to use in the constructor.
The constructor saves the settings we just mentioned and then set's the position of the sprite we extended. After that we create our 2d array for the gird. Notice how we create a FallingBlocks_GameBoard_Cell for each slot in the grid and we give it the pixel offsets from the board's origin.
Now let's look at the second half of FallingBlocks_GameBoard.as:
Here are some basic functions that we will be using with our GameBoard, a FillCell(), a ClearCell(), and a debug function to FillRandomCell().
FillCell() takes an offset in the grid, and the id of which piece we want to fill it with. The first thing we do is check if the cell is empty. If so, we need to add it to the GameBoard as a child so it will draw. NOTE: if you call addChild when the child already exists, Flash will be unhappy with you. This is why we check first rather than just blindly call addChild. After all that, we set the Game Piece id to use and Draw that block into our cell's sprite. Note: how we looked up the color in the PIECES_COLORS array.
Clearing a cell is as simple as not drawing it and clearing out it's IS_FULL state. This is the first time we've used removeChild(), but it's just the opposite of addChild(). If the sprite is no longer a child, it will no longer be drawn.
Finally, FillRandomCell() gets a random index in the grid and fills the cell with a random piece. The "-1))+1" on the random piece is because we don't want zero as one of the random values returned.
Okay, now let's hook it all up in FallingBlocks_Main.as
We add our mGameBoard class member variable, and then create a new one in the constructor. When creating it, we use all GameSettings data. After that we have to add the GameBoard (which extended Sprite) as a child of FallingBlocks_Main (which is also a Sprite), so the board will actually get displayed.
I commented out all references to DrawATestBlock() because we no longer need it. If you look, creating a new FallingBlocks_GameBoard() replaces the line where were created a Sprite in DrawATestBlock, and we also called addChild(), just like in DrawATestBlock.
Finally, to make sure the DrawBlock.WithBorder() still happens, we add mGameBoard.FillRandomCell() to our OnMakePieceFall() callback.
Save your files and test the movie. You should have a gameboard that randomly files with block of color, like this:
5. Setting up and Drawing Game Pieces
There is much to do in FallingBlocks_GamePiece.as, however much of it is repetitive. The GamePiece is much like the GameBoard, just an array of blocks that make up the various shapes.
There's two ways we can go about this. We can make a 2d array that works like a little lookup table or adjacency matrix that tells us, one for one, what the block looks like, or we can make an array of 4 elements that hold the position of each block.
Let's compare. First, here's what a one for one look up table would look like:
But if we're only ever going to have 4 blocks in a piece, why make a 2d array that has a total of 16 slots?
This way, we don't have all those empty slots in a 2d array lying around doing nothing.
But how about we take it one step further? Let think about this. The piece is always going to have a "center" block that the piece rotates around. And since none of the pieces have an empty block that they rotate around, we can guarantee that there will always be one block that all of the pieces have in common.
If you imaging all the pieces rotating around "c" in our look-up grids, you'll see that they all have this one block in common. So let's do everything in relation to that block. Let's say, "c" is at Point( 0, 0 ) instead of Point( 1, 1 ).
Now, we can probably write our FallingBlocks_GamePiece with an array of *3* block locations because the block at (0,0) never changes.
However, we're not going to do that. I know some people love to optimize every chance they get, but I like to keep things general and flexible and optimize at the end (rather than optimize ASAP and code myself into a corner)
Besides, I want you to be able to reuse this gamepiece code in other Falling Blocks style games that might have totally different shaped pieces. So let's keep it general and keep it an array for 4 block locations, but let's capitalize on knowing that they will have a center that they all revolve around. If we make the center block always sit in slot[0] of our array, we can optimize by skipping that slot when ever we can (in loops for all blocks in the game piece), and always fall back on NOT skipping it if we find the need for it. (trust me, all that crap about making "c" equal to Point(0,0) wasn't a waste of time, we'll use it later in this lesson.)
Okay, so we're going to start filling in FallingBlocks_GamePiece.as. All the stuff we've been talking about will be in the function InitBlocks(), but we're just going to start at the top of the class and work our way down a chunk at a time.
I imported Sprite and Point, and then made our game piece extend Sprite for the same reason we always do (display & container qualities)
I added some member variables for keeping track of our piece's id, the array of block locations, an array of block sprites, and the block size in pixels.
Notice our constructor defaults to "EMPTY" as a block type. We'll talk about that in a moment.
We create our array of 4 block locations and also an array of 4 sprites for displaying the blocks. Then we give them an int and add them to the FallingBlocks_GamePiece sprite.
Now we test what id was passed to us so we can either create the block, or create a random block if nothing was passed to us.
The MakeNewRandom function is pretty straight forward. We pick a random piece and then Init it.
CopyPiece is pretty much the same thing, only instead of picking a random piece, we use the id from another piece.
Okay, now here comes InitBlocks(), which carries out everything we talked about at the beginning of this section.
So I started with a comment that looks like what we talked about in the beginning. If there are any "elite" programmers out there who can't stand seeing diagrams in code, I'd just like to say "you're out numbered by the rest of us" :) . It's called "self-documenting code", get on board.
Okay, so as we've discussed, slot 0 in maBlockLocs is always 0 and 0. Notice that I didn't say: maBlockLocs[0] = new Point(0,0); I don't care what programming/scripting language you're using, it's usually not wise to create new objects when you don't really need to. As you can already see by the last couple functions, InitBlocks is going to get called more than once, so there's no reason to have it create 4 new Points every time it's called.
After we init out first slot, the rest of the blocks are init'ed selectively by which piece we're making. It's really not that complicated (especially with that handy diagram comment :)
I expect everyone will gloss over that "btw" comment about the game piece's screen position, but don't worry, I plan on making that visual.
Finally, we call an UpdateDisplay() function. Basically, when the blocks change, we need to make sure the sprites that are being used for each block are updated to reflect the change. So let's look at those functions next:
Our UpdateDiplay() function does two things, updates the colors and the positions of the block sprites.
In UpdateDisplayColor() we just run through all our block sprites and Draw the correct color in the sprite.
In UpdateDisplayPos(), we set the position of the block sprites in relation to the GamePiece itself.
Okay, now all that's left to do is hook it up in FallingBlocks_Main.as and we're good to go.
Why create one FallingBlock_GamePiece when you can create 2, the current piece and the next piece. (I figured it'd be easier to just do them both at once)
We create out pieces in the constructor and we addChild() with them because, like always, we want these things to be seen.
I've given the next piece a dummy screen location, but I've left the current piece at (0,0) because I want to show you where (0,0) actually is.
Finally, down in our timed callback OnMakePieceFall(), we are going to create a new random piece and copy it to the current piece (yes, you may already have noticed those lines seem backwards as far as gameplay goes, but I'm trying to show you something).
Okay, save your files and test your movie and you should get this:
Notice how the current block sits up in the corner and is half cut off sometimes. This is what my "btw" comment in InitBlocks() was referring to. Since we made that center block the (0,0) block, then the screen position of 0,0 is the top left corner of that center block.
6. Getting Keyboard Input
Keyboard input in flash can be slightly tricky. You need to keep these things in mind:
- Only the object that has "focus" will get keyboard input.
- If you use actual letter keys in your game, when you test your movie, you'll have to got up to the "Control" menu and select "Disable Keyboard Shortcuts", otherwise your key presses won't seem to be firing correctly.
- Windows has something called "Sticky Keys" that automatically enable themselves when you press the shift key 5 times. They are not fun. They will cause you to loose in the middle of a game. Stay away from using the Shift key in your games.
I've seen some cool keyboard input libraries floating around on line, however they are overkill for our game. Since this tutorial series started off as an introduction to flash, I'm going to implement keyboard input in a very basic, very simple way. (feel free to explore the web for the more complex options if you're interested.)
Okay, let's keep this section short and sweet. Here's the code for catching keyboard events. Open up MainApp.as and add this:
We're going to have KeyboardEvents so we have to import them first.
The "stage.focus = this;" is what I mentioned earlier. Regardless of how many objects are listening to keyboard events, only the object that has focus will actually hear those events when they are fired. It can be a little frustrating. You'll be pressing keys and wondering why the game is not working, only to find out the wrong object has focus. I'll show you more of that first hand in a second.
Next we add our keyboard event listeners. One for detecting key down and one for detecting key up. I've hooked them up to callback functions that just throw out some debug text.
Save your files and test your movie. (I'd show you an swf, but it's pointless because it's all in the debug print anyway.)
Notice that as you press some keys, you'll see the key down and key up print outs. Even when the game automatically movies from the intro screen to the title screen. Also notice that if you hold down a key, it will repeatedly fire the key down event.
Now, click the "Play" or "Credits" button (or maybe you clicked the "Skip" button and have already noticed). After you click a button, you stop getting keyboard events because clicking the button stole our focus. I understand the reason for Flash to work this way, but I wish it didn't. To get the focus back, you can just click somewhere on the game's screen and your input will start working again. But, do you expect the player to figure that out or go find another game to play?
Basically we have two options. We can give the focus back to the stage manually or we can hardwire it. This is why I attached the KEY_DOWN and KEY_UP event listeners to the "stage" and not the MainApp. The state is a keyword that's accessible from anywhere. So if you want to call "stage.focus = stage;" from any file at any time, you can. Thus we overcome this issue of loosing focus on a case by case basis.
We can also hardwire it like this:
Now every time the stage gets a "FOCUS_OUT" event, it just sets focus back to itself.
BE CAREFUL WITH THIS. It may seem nice now but it can bite you later. For example, does your game have any text boxes for type names or messages in to? If so, you may be rendering them all useless.
Now, let's get that input to our game screen. I'm going to do this via overriding functions in the class AppScreen. It's very simple. Open up AppScreen.as. Notice how the class is empty. Remember the whole point of this class was so we can easily add functionality to all classes that extend it (as in, all of our screens). We're going to drop in the ActionScript equivalent of what C++ calls "a virtual function."
Basically when the callbacks in main app catch a keyboard event, they are simply going to pass it on to the current screen. There will be a stubbed out function in AppScreen for onKeyDownEvent and onKeyUpEvent that will do nothing.
Then any screen that inherits from AppScreen will have the option of overriding these two functions to actually do something. So TitleScreen_mc can ignore keyboard events, while GameScreen_mc can use them.
Here we go, starting with AppScreen.as:
We've imported our KeyboardEvent and we've created two callbacks, but no event listeners.
Now in MainApp.as
We update our event callback to pass the event on to the current screen.
Save your files and test your movie and you should now get the print outs from AppScreen.
Next we go to GameScreen_mc and override the key event functions using the override keyword, like this:
Save your files and test the movie. Now you'll get that annoyingly long printout from AppScreen on every screen but the Game Screen, which will give us the GameScreen_mc's trace text.
Next, let's take it one last step and pass the event onto your FallingBlocks_Main class.
FallingBlocks_Main.as:
Again, we import our KeyboardEvents, we create two functions for key up and key down, and then we drop a trace in there. (Note, if you copy and pasted those functions from the previous file, make sure you delete the "override" keyword).
Now go back to GameScreen_mc.as and pass along the events:
Save your files and test your movie. It should look just like last time only now the print outs are from FallingBlocks_Main's functions.
Finally, let's use it to actually change something on screen!
First let's define our keys in FallingBlocks_GameSettings:
Note; RotateCW stands for "Clock-wise" while RotateCCW stands for "Counter Clock-wise"
Also Note: remember my warning at the beginning about not using the SHIFT key. The first versions of my game did and I got lots of complaints. That's why we're using SPACE to RotateCCW.
Now let's hook these keys up in FallingBlocks_Main.as to actually do something. We'll move the current game piece around a block's with at a time.
So we have a switch statement based on the key that was passed in the event. Each case is a key *from our GameSettings*, NOT a direct test for things like Keyboard.LEFT. (This is so we can remap keys easily).
Then we just update the mCurPiece's x and y positions (which is just the Sprite x and y from the Sprite class we extended). Since all the blocks in the piece are children of the piece, they will automatically say relative to the pieces new location.
Save your files and test your move. You should be able to move the current piece around.
Also, note how the piece moves if you hold a key down. Flash will send the key down event repeatedly, so the piece will move once, then pause, then keep moving. Which is ideal for our falling blocks game. This is one of the reasons we didn't use one of the fancy keyboard input classes I've seen floating around the web. It would be overkill for this game and we'd have to write extra code just to get that "move once, pause, keep moving" functionality.
7. Rotating Game Pieces
As I mentioned earlier, we went through the trouble to mark the block we rotate around as (0,0) so we could use a simple vector math trick.
In vector math, you can find the normal to any vector by swapping the x and y values and negating one. It's a pretty common trick for anyone working with 3d games. It looks like this:
What's interesting is that the negative determines if we are going in a clockwise or counterclockwise direction from the original vector.
We're just going to use this to rotate the blocks in our piece.
FallingBlocks_GamePiece.as:
First thing to note is that both of these functions are identical except for the negative signs on line 213 vs. 237.
The second thing to note is that I've got the function returning a Boolean. If I haven't already mentioned it (and you don't already know) a Boolean is a variable that will only ever be "true" or "false.
At the beginning of the functions we test for the special case of a Square piece. There's no need for Square pieces to rotate. We return "true" to say "rotation was successful." Because a square rotating 90 degrees around it's actual center point still looks like a square. So there's no reason to return false.
Next we loop for the 3 block locations that are not our center block. In the loop, we swap the x and y values and negate one, just like we do with vectors. I know some of you may feel the urge to optimize my code and replace "vTmpPoint" with as single "nTmpNumber"... but don't. I did that because later we're going to test our vTmpPoint to make sure the block isn't colliding with something (as in, "is the block *allowed* to rotate?").
After that, we update the blocks' display position (no need to update the colors, right?). Then we return true for "rotation was successful".
Again, I'm just planning ahead here with the returning of true. Currently, our function doesn't return false, but in later lessons it might.
Now go back to FallingBlocks_Main.as and hook these functions up to actual key presses:
Save your files and test your movie. You should be able to move and rotate your current piece.
And there you have it. The shortest section so far.
Summary
So what have we learned? We've done so much, I'm sure it's easy to forget what we did in the first section.
We have:
- Learned how to draw rectangles in a Sprite.
- Learned how to make a class with a static function (DrawBlock.WithBorder())
- Learned how to create a GameSettings class and why we take the time to do so (because it lets us create alternate game settings without having to change our actual game)
- Learned how to use Timer Events to gave a periodic event for running the game with and for one time delays.
- Learned the syntax for arrays and 2d arrays.
- Learned how to make a GameBoard with Cells to hold block data.
- Learned how to represent Game Pieces with an array of block locations
- Learned a simple way for getting keyboard input (which is a complex topic you should look into some time)
- Learned how to apply a small vector concept to the rotation of game pieces.
I hope by now you've gained a lot of respect for these falling block games. These first two Lessons have been pretty long, and so far, we don't really have a game yet. However we do have all the pieces for a game. In the next lesson we're going to start putting the pieces together and writing the actual game's logic. It's going to be another long lesson, but by the end, you'll have something to play.
Also, I think most of us would refer to this kind of game as "simple" arcade game -and it is. This is a simple game to play, and a simple game to make. However, now you might be seeing how much programming is involved in a "simple" arcade game. Don't be discouraged, just know that this is a really great "my first game" to be working on. Plus, it's a framework that can be reused and tweaked. Once you finish this game, you'll be able to reuse it to make many different styles of action-puzzle games.
On top of that, you'll be able to take the next step and make a more "Pac-Man" style games. If you think about it, the 2d grid we're using for our gameboard is much like the grid we'd use to track where a character is and is not allowed to step. (The big hurdle is adding the enemies.)
For some reason, many people try to make an old-school RPG for their first game. Trust me, RPGs are NOT simple, they are HUGE and complex. I feel for you if you tried to make one before making a falling blocks game... after all, I made that same mistake myself. :)
A Link To: All Lessons' Table Of Contents
A Link To: The Original Flash Game that started all this
No comments:
Post a Comment