Intro to the Flex SDK
The Flash Display ModelThe "traditional" strategy for drawing games is: erase the whole screen, redraw everything, repeat every frame. But in Flash, we don't do that. Flash retains a display list of all the sprites on the screen. If we want to start drawing something, we call the function addChild() to add it to the display list. If we want to erase this sprite, we call removeChild(). If we want to move it, we change the "x" and "y" properties (like the code does above). Of course, behind the scenes, the Flash player will be constantly erasing and redrawing the screen. But we don't have to worry about that. This model takes a little getting used to. For example, in the "traditional" model, if you want something to disappear, you just... stop drawing it. But in the display list model, you need to remember to explicitly remove it. This introduces the possibility of a new kind of bug: a "leak" type bug, where an item remains on the screen after it should have been removed. Fortunately, display list leaks are pretty easy to spot during testing. This model might also lead you to use different strategies for organizing your code. The "traditional" way to organize game code, is to keep the display-related code and game-logic-related code as two completely separate entities, with as little overlap as possible. However, in Flash, I find it's easiest to weave display-related code and game-logic-related code into the same place. Instead of having an "update" step and a "draw" step, we're just going to have an "update" step. So in a nutshell, the above code draws a purple circle onto "ball", and adds "ball" to the screen. Now when we change ball.x and ball.y, our purple circle will move around the screen. Back to the code...
// set up paddle sprite
paddle.graphics.beginFill(0x000000);
paddle.graphics.drawRect(-PADDLE_WIDTH/2, -PADDLE_HEIGHT/2, PADDLE_WIDTH, PADDLE_HEIGHT);
paddle.graphics.endFill();
paddle.y = height - PADDLE_DIST_FROM_BOTTOM;
addChild(paddle);
// Figure out some metrics for placing the blocks
var reference_block:DisplayObject = new GoldBlock();
var block_field_width:int = NUM_BLOCK_COLS * reference_block.width
+ (NUM_BLOCK_COLS-1)*BLOCK_GUTTER_X;
var block_field_x:int = (width - block_field_width) / 2;
var current_block_index:int = 0;
// Create and position the blocks
for (var x:int = 0; x < NUM_BLOCK_COLS; x++)
{
for (var y:int = 0; y < NUM_BLOCK_ROWS; y++)
{
// Create a new Block object
var current_image:Class = allBlocks[current_block_index];
var sprite:DisplayObject = new current_image();
// Create sprite, position it, and add it to the display list.
sprite.x = block_field_x + x * (reference_block.width + BLOCK_GUTTER_X);
sprite.y = BLOCK_FIELD_Y + y * (reference_block.height + BLOCK_GUTTER_Y);
addChild(sprite);
// Add the Block to our list
blocks.push(sprite);
// This piece of code causes us to cycle through the available block images.
current_block_index++;
if (current_block_index >= allBlocks.length)
current_block_index = 0;
}
}
This code is more complicated, but we are essentially doing the same thing: creating sprites, adding them to the display list, and positioning them.
Note that our block image objects aren't actually Sprites, they are technically Bitmaps. Bitmap is a child of DisplayObject, so I refer to them as DisplayObjects in the code. // Create a Timer object, tell it to call our onTick method, and start it ticker = new Timer(10); ticker.addEventListener(TimerEvent.TIMER, onTick); ticker.start();Here we declare a Timer, and tell it to call our onTick method (which we haven't defined yet) every 10 milliseconds. Pay attention to the addEventListener syntax, as this method shows up a lot. The first parameter is a constant describing which event we want to listen to, and the second is the method that we want to be called when it occurs (fortunately we're allowed to pass functions as arguments in Actionscript). You can check the documentation to learn which events are provided by which objects. There are quite a few. You can listen for when a certain sprite is clicked or mouse-overed, when objects are moved, when a display object is just about to be rendered, and more. You can also extend EventDispatcher and pass your own events. Event passing is pretty powerful and useful, so take advantage of it.
public function onTick(evt:TimerEvent):void
{
// Place the paddle (based on mouse location)
paddle.x = mouseX;
Here's the onTick function. The first thing we do is update the position of the player's paddle. The variable "mouseX" is a property that's built-in to every DisplayObject. It contains the current mouse X position (relative to the position of the DisplayObject). So, making the paddle move with the mouse is absurdly easy.
// Check paddle collision with ball
if (ball.hitTestObject(paddle))
{
// Adjust X momentum of ball based on where on the paddle we hit
var new_ball_dx:Number = (ball.x - paddle.x) / (PADDLE_WIDTH/2.0)
* INITIAL_BALL_SPEED;
// Blend this new momentum with the old
ball_dx = (ball_dx + new_ball_dx) / 2;
ball_dy = -ball_dy;
}
Here we check if the paddle has hit the ball. "hitTestObject" is built-in, and checks if the two objects overlap. If we were going to get more serious, we would probably eschew "hitTestObject" and instead do a circle-rectangle collision check.
// Check collision with every block
for (var i:int = 0; i < blocks.length; i++)
{
var block:DisplayObject = blocks[i];
if (block == null) continue;
// Check collision
if (ball.hitTestObject(block))
{
// We collided, remove this sprite
removeChild(block);
blocks[i] = null;
// Figure out which way the ball should bounce
var ball_distance_norm_x:int = (ball.x - block.x) / block.width;
var ball_distance_norm_y:int = (ball.y - block.y) / block.height;
// Don't bounce in a direction that the ball is already going
if (ball_distance_norm_x * ball_dx > 0)
ball_dy = -ball_dy;
else if (ball_distance_norm_y * ball_dy > 0)
ball_dx = -ball_dx;
// If we get here then either bounce is valid, pick one based on ball loc
else if (ball_distance_norm_x > ball_distance_norm_y)
ball_dx = -ball_dx;
else
ball_dy = -ball_dy;
// Increase ball speed by 2.5% to make things interesting!
ball_dx *= 1.025;
ball_dy *= 1.025;
}
}
Here's where we check if the ball has hit any blocks. Aside from some funky math, this part is pretty straightforward.
// Check collision with sides of screen
if (ball.x - BALL_RADIUS < 0) ball_dx = Math.abs(ball_dx);
if (ball.x + BALL_RADIUS > width) ball_dx = -Math.abs(ball_dx);
if (ball.y - BALL_RADIUS < 0) ball_dy = Math.abs(ball_dy);
// Check if ball was lost
if (ball.y - BALL_RADIUS > height)
{
if (lives > 0) lives -= 1;
resetBall();
// todo: check for game over
}
// Move ball
ball.x += ball_dx;
ball.y += ball_dy;
}
public function resetBall():void
{
ball.x = .25 * width;
ball.y = .5 * height;
ball_dx = INITIAL_BALL_SPEED * .70;
ball_dy = INITIAL_BALL_SPEED * .70;
}
}
}
And that's it!
Now we fire up our command line to compile this sucker. > mxmlc tutorial.mxml And here's the SWF that it produced. Our game still needs a few features before it's a complete game (such as a win state, a game-over state, scoring, and maybe even a main menu), but I left that stuff out to make this as succinct as possible. Hopefully by now you would have an idea of how to do this. If you'd like the source to the mini Breakout clone, it's available here And we're done. There's of course a lot more left to learn about the Flex platform, but hopefully you enjoyed getting your feet wet. About the Author Andy Fischer is a software engineer living in San Francisco. He has previously worked in the game industry and is currently at a non-games job. Over the years, he has played with many different platforms for web-based games, and Flex is now his favorite. He has no affiliation with Adobe. His personal website is http://crayfishden.net
|
|