8bitrocket.com
31Mar/080

Pro Evolution Soccer 2008 for the Wii

Soccer video games have not changed very much since the the first 3D version of FIFA appeared on the 3DO almost 15 years ago. The graphics and models have gotten much better, and the licensed player names have improved, but the actual game-play has stayed (relatively) the same. The controls of nearly all modern soccer games go something like this:

  • 1 button passes
  • 1 button lob passes
  • 1 button shoots
  • 1 button is used to switch control to player closest to the ball
  • 1 button is designed to be mashed as quickly as possible to make the above player run as fast as possible to or with the ball
  • 1 of the above buttons is used to volley the ball from a pass
  • Defense consists of the above running and switching of players, plus an abundance of slide-tackling

Of course there are other controls, many of which use shoulder-buttons and combos that are nearly impossible to remember lest actually execute in heat of the game. After the initial learning curve of each new game is completed, play usually falls into a rut where you use one or two "super-man" players who do nearly everything on the field, taking shots at the goal from a set of standard positions that you know have a high probability of getting you a tick on scoreboard. While this can be fun for a while, it's really not soccer at all, no matter what the Cockney accented color commentator would have you believe. The real problem is that the interface to the game (the gamepad) does not allow for the complex interactions that make soccer an interesting sport to watch and play. The games simply capture a shadow of what really makes a soccer match a great contest: the immersive nuances in the run of play.

Because of these limitations designers have created games that eshued nuance altogether. They place games in dark alleys with power-ups and weapons, or on mini-fields where the proper combo can launch 8 balls at an unsuspecting goalie. They make rarities in an actual game (like a bicycle-kick) into sought-after power-ups and special moves that replace tactics with gimmicks. Sure, they make the game fun to play, but they also pave over the actual game of soccer in the process. Sadly however, without a new way to implement the basics of the game, it seemed like soccer games had gotten just about as good as they could possibly get. The game could not get any more immersive as long the control scheme stayed the same.

On a tip from the weekly IGN Wii Podcast I picked-up Pro Evolution Soccer 2008 for the Wii last week. The podcast (and subsequent reviews) told a very intriguing story about a new control scheme for a soccer game that could only be accomplished on the Wii with a Wiimote. To me, this seemed like it could be the answer to immersion problem. The reviewers described the game as using a "John Madden tele-strator"-like interface. Even though these descriptions made it sound like the game would be played in slow motion, I was intrigued enough to buy the game and see for myself.

When Pro Evolution Soccer 2008 for the Wii starts for the first-time, you are thrust into a tutorial about the controls. This is appropriate because the controls are like nothing I have ever experienced before in any kind of sports game. All action on the screen is directed using the Wiimote and the Nunchuck, but not in any garden-variety way. Like other soccer games, you have direct control of one-player at a time. By pressing down the (A) button, an arrow appears on the screen. By controlling the length and direction of that arrow, you control the player. It might sound weird at first, but after a couple tries is appears to work almost flawlessly. Instead of mashing a button to run, pressing a shoulder-button for a step-over, and controlling the player movement with an analog stick, you (almost) effortlessly glide the controlled player through the defensive-line and into scoring position. All the way you are weaving, dribbling, stepping-over, etc, but these tactics come from intuitive flicks of wrist instead of multiple button combos. Shooting the ball at the goal comes from a flick of the Nunchuck. This itself is significant, as it actually separates shooting from passing and dribbling: something that most other soccer games get completely wrong. By separating shooting to it's own unique action, it becomes much harder to make mistakes in-front of the net. This should be welcome news to anyone who has played a soccer game in the past and has furiously yelled at the TV to "shoot shoot shoot damn it" only to realize they have been shttoing at all, but repeatedly telling the game to "lob" the ball back to the wing.

While improving the control of single player is welcome, in and of itself it is not enough of an improvement to warrant calling this game "revolutionary". You might be thinking: "Sure, you can shoot easier, but how does that offer more immersion and nuances than a gamepad? In fact, it seems like the gamepad might be more flexible and nuance than the Wiimote." If the improvements in single-player control were all that Pro Evolution Soccer 2008 had to offer, then these thoughts would be correct. however, it is the passing game truly sets this game above all that have come before it. In most other soccer games, passing the ball is relatively "magical" process. Since you can only control one player, you must rely on A.I. to direct the other players on where to stand and when to run for a pass. While some games offer a modicum of control of the player that will be passed the ball, going beyond single passes, one-twos or pass-volleys is nearly impossible. Those games take a full-field game of soccer, and crunch it down to a series of one-on-ones and one-on- two and match-ups. It's like a mini game of one on one basketball on a giant green field. However soccer is not basketball, and the makers Pro Evolution Soccer 2008 for the Wii figured out a way to take the essential but seemingly simple tactic of passing the ball in soccer revolutionize it.

By pressing the (B) button on the Wiimote an second arrow appears. By clicking on another player, while pressing (B) you will pass them the ball. simple right? How is that revolutionary? Well, here comes the best part. Before you pass the ball, you can press the (B) button over more players. This does not cancel-out your first pass, it adds to it. Very quickly you will find yourself lining-up 3, 4 and 5 pass plays that result in shots on goal. If you press both (A) and (B) at the same time, you can direct players into one-two plays around defenders. You can even direct players to run for an open space to receive a leading pass. As far as I know, this has been essentially impossible, or at least improbable with other soccer games. I might have accomplished these feats a few times with all other games combined in my lifetime, but with Pro Evolution Soccer 2008 for the Wii I can make them happen on every play. Furthermore set-piece passes can be set-up in much the same way. With other soccer games, a corner-kick was most likely a "prayer" pass while mashing the "shoot" button for a hopeful volley into the net. While you can still do that with this game, a bit more careful planning will lead you to directing a corner kick volley as pass to a 3rd player and possibly a 4th before swinging the Nunchuck for a shot on goal. The results are truly astonishing. All of a sudden you will find yourself using the entire field to play a soccer video game.

While offense is modeled amazingly well in Pro Evolution Soccer 2008, defense, while still good, doesn't offer the same significant level improvements. You can mark specific players, direct players to intercept passes, call an off sides trap, direct the goalie to come off his line, and call for slide-tackles. It's all fine, but simply not as immersive or enjoyable as offense. Some people might argue such is the nature of defense, and I'd tend to agree, if it was not for the nagging want to gain control a single players and go after those any bastard that tries to attack my goal! Still, defense if certainly not a deal breaker, and as far as staying with the intended game design, I could not think of a better implementation.

The game offers a slew of play options and modes. A wi-fi online mode is available, but the most enjoyable mode to me is called "Champions Road". This option allows you to select a team, and play in a series of tournaments of increasing difficulty. After every game your players increase in their abilities, and if you win, you get the chance to pinch the best players from the other team. In this way, you get to mold and form your team as the you play the game and immerse yourself in the details of team management. The significant immersion and nuance of on-field play, added to this addictive and interesting tournament mode, make this one of the best soccer games available today. If you even think you might like to play a soccer game, but have been put-off or frustrated by the controls of earlier soccer games, be sure to check this one out. Pro Evolution Soccer 2008 for the Wii offers the type of innovative controls and game play that I expected from the Wii in 2006, but slogged through 2007 without finding. I'm happy to see that in 2008 developers are finally finding ways to move Wii's unique control scheme away from hand flipping mini-game gimmicks, and towards new and innovative methods to control and immerse the player into games that I once (mistakenly) thought had reached their apex.

30Mar/080

Flash Game Development Inter-web mash up : Mar. 30, 2008

The latest in Blog entries and articles that might interest Flash game developers.

Forrest's AS3 / Flash Blog has an entry that is right up my alley. It is entitled What is the fastest way to draw pixels in AS3? He found that the fastest method was to call setPixel once per pixel using y as the outer loop and x for the inner loop. He also locks the output BitmapData object before the loops and unlocks afterward. He was able to get 20FPS writing out an 800x600 bitmap on every frame tick. Take a look at the entire article, as there is a lot of good work involved.

A brand spankin' new site has launched, and by god its not another game portal that won't accept my games. It is great new site called Game Poetry, and its first entry is devoted to making games for FREE on the Flash platform - primarily using Flash Develop and Flex.

http://www.jesshansen.com/ returns to this round-up with a very nice tutorial on creating a shattered glass effect in AS3. He has the class available for YOU to use right now, so go check it out.

I finished the 4th and final part of my Atari 7800 Asteroids Tutorial. In the final part, we build in pixel perfect collisions, and a simple particle emitter and object pool to draw particles from.

28Mar/080

Flash Retro Remake Round Up : March 27, 2008

This is by no means an exhaustive survey of the universe of retro Flash games available on the infobaun, but here are some fun, unique takes on some of my favorite old games. Most of these aren't even recent, but we have to start somewhere.

We've all seen the games by Paul Neave plastered over almost every game site on the internet. My favorite is his Frogger re-make. All of his games are wonderfully close to the originals, and even though I am not trying to be a completist, my list would be bare without one of his games. Please play them at his site because he gets the ad revenue. He also has versions of Asteroids, Tetris, Snake, and more.

Andre Michelle has a nice collection of retro games. For me, his best, by far is the Donkey Kong game he made in FLASH 5. How he was able to pull this off, I'll never know. Other fun ones are his early take on Moon Patrol and his Flash MX take on Super Mario Bros.

Ninja Kiwi has been very successful recently with some retro inspired games. I don't think these are really remakes , but they have an 80's 16-bit feeling to them. We start the Bloons juggernaut with Even More Bloons - a game that I am absolute crap at. It's great fun though.Then there is Zeba, a block pushing / shooting puzzler that is even more fun than Bloons.

GameTeam has a very nice selection of retro inspired games. My favorite, by far is Navy Fighter, a 1941 style game that is a blast to play. Others to check out are: the similar, but totally awesome Sky Warrior; Galaxy Invaders, a game that is oddly similar to Better Dead Than Alien (Atari ST, Amiga) that I am sure they have never played, but I could be wrong. The final one I will list, but they have at least 5 more brilliant games, is a title called Onslaught Online. This is Tower defense combined with Operation Wolf (and all the fun gun set pieces from Half Life and Medal of Honor).

The final game for today is an Omega race style blaster called Virus by the genius duo (at least nGfx is credited) that is Gamingyourway.com. It is beautifully retro in every way possible, especially the perfectly rendered glowing mono vector graphics (they look like the are coming from a Vectrex machine) and the SAM style digital voice.

That's it for this time. Please send an email to info[at]8bitrocket[dot]com is you have any retro games you would like me to cover for next time.

Filed under: Game Reviews No Comments
27Mar/080

Midcoregamer.com Midcore RSS Aggregation Features

Midcoregamer.com has been scouring the web for the past few weeks to locate blogs that are relevant to the Midcore audience.  We found an increasing number of homegrown bloggers that have taken the concept of  the "midcore gamer" (as well has other like designations) to heart and have decided to collect their respective RSS feeds on our home page.  Now you can find out the current state of the "Midcore World" with  one look at http://www.midcoregamer.com.   Some of the blogs we are now featuring include:

  • Seanbajuice - Mid-Core Gamer Reviews and Tech News
  • Hardcore Casual
  • Hardcasual
  • CasualHardcore

As well, we have added feeds for some other like-minded sites that are not necessarily "mid-core"

  • Gaming With Children
  • GAMEparent
  • Armchair Arcade

You can read all the latest blog entries from this growing segment of the blogosphere by visiting http://www.midcoregamer.com.

Also, if you have know of another like-minded blog, or would like us to review your own blog to see if it would be appropriate for the home page of midcoregamer.com, please leave a message below, or email us at info@8bitrocket.com

25Mar/082

Tutorial: Using Flash CS3 and Actionscript 3 to create Atari 7800 Asteroids Part 4

Tutorial: Using Flash CS3 and Actionscript 3 to create Atari 7800 Asteroids Part 4

In this 4th and final part we will cover 2 main topics. First, we will use pixel perfect BitmapData collision detection to detect missile and ship collisions with the rocks. And second, we will create a very simple particle engine and use a pre-created farm/pool of particles to draw from. This will allow us to have some nice particle effects but also keep optimization in mind.

In Part 3 we covered firing missiles from the player ship. We used a png sprite sheet for the missile animation and demonstrated how to blit the missiles to the BitmapData canvas.

In Part 2 we covered adding the asteroids to the screen. The asteroid animation was cached into an array in a different manner then the ship rotation. With the asteroids, we took the frames of a timeline animation and drew each one into a BitmapData image that was placed into an array.

In Part 1 we covered creating an array of pre-calculated vector values for animating our player ship. We also covered caching the rotated frames of the ship in an array of BitmapData objects. We also added the ship to the screen and move it with the keyboard.

First, here is the the final game we will create. It isn't fully fleshed out game-wise, but it contains all of the basic elements necessary for you to apply to your own games. There is only 1 level, and the ship will not die. It will collide with rocks and provide a particle explosion though. In the upper part of the screen you will see Active Graph showing the game's current memory usage. When you click to start a new game, I don't explicitly null out all of the created objects, so you will see a slight increase in memory use on 2nd and subsequent plays. If this were a real game, I would set every object to null and call dispose() on all BitmapData objects. You will also see a frameRate counter next to the Active Graph. It is included in the zip file below.

Arrow keys rotate left and right and the up thrusts forward. The [z] key fires. The the window is embeded with a wmode=window. It will work at about 5 more FPS if I could use use the transparent or opaque settings. I don't because some browsers will not work with with it yet.. The below example starts with 20 rocks and each use up 40 particles for each explosion. Even using the BitmapData collision detection (which uses more resources than math-based) we can still keep a pretty constant 30 FPS frame rate. The .fla and all class files are provide so you can play with it and change things around if you like.

The source files are here: 7800 Asteroids Tutorial Part 4 source files

In the first 3 parts of this tutorial I have exhaustively gone through all of the code for each section of the game. I am not going to do that in part 4. My reasoning is simple. I don't think the tutorials layout has been as effective as they can be up to this point. The code box has been easy to read, but the explanations have not. I am currently fiddling with the layout and content of this an future tutorials, so I will do this one a little different. I will explain in detail as much as I feel is sufficient, but I will focus on a little more of the details behind these two theories. I definitely will show the code here, but you will need to unzip the files and play with them to see absolutely everything in action. I have added in some optimizations in this version that I will also explain at the end.

BitmapData pixel perfect collision detection
The theory about collision detection up to this point (especially in Flash) has been that math based detection is the best manner in which to detect. I agree with this, but I have seen some pretty un optimized math based collisions in my time. This is especially true if you are going to use the new Point() class and check the radius of each object against the other objects to see if they overlap. While this is an excellent way to do collisions, most new AS3 programmers make the mistake of in-line creating new Point objects on every frame tick. This wastes a lot of execution time because Object creation is very time consuming in Actionscript. Now, I'm not saying that checking each non-transparent pixel on a BitmapData object is faster, but as you will see it works fine and maintains a decent frame rate. So, since I wanted to try and make a game with pixel perfect collision detection I went ahead with this later route. What I am saying here really is no matter what type of collision detection you choose, if you write un-optimized code, it can perform worse than you might have imagined.

BitmapData objects are basically a collection of colored and transparent pixels. When two BitmapData objects overlap, if any non-transparent pixels are in the overlapped portion, a hit will be detected. This is very different from the standard Bounding Box to Bounding Box and Bounding Box to single screen point that is available with standard display objects. Now, you can also test pixel perfect collisions between a BitmapData object and a Sprite or MovieClip, but we will keep it simple as check to BitmapData objects against one another.

References: A Paradox with BitmapData.hitTest?
Keep in mid that when you create a BitmapData object from another BitmapData object through the assignment operation (=), you have just created a reference to the original BitmapData object. You don't physically have two different pieces of BitmapData, but two references to the same object. This is important because our game is made up of a lot of objects that all share the same array of BitmapData objects for display. If we were to modify one of the frames of animation for an asteroid on the fly somehow (by setting a pixel color for instance), all of the asteroids would get the change because they all share the same original BitmapData objects for each animation frame. Since all of the asteroids share the same array of BitmapData objects, detecting collisions between objects based on the BitmapData of each object would seem impossible, but it isn't. You see, if you remember from earlier parts of this tutorial, on each frame tick, an object might animate by changing to the next frame of BitmapData in the shared array of a animation frames. Each asteroid holds just a index int that represents the location in the array of asteroid frames to copyPixels from for display to the canvas. There is no way we can use the same index and reference for collision detection. So, we simply need to make sure that each asteroid also contains another variable called bitmapData. This variable will hold a reference to the current BitmapData object that is displayed by the asteroid object. It doesn't sound logical, but it works. If you have any experience with the BitmapData object, you might think that you need to use the clone() method to create a new object for the hitTest, but you do not. I was surprised by this myself, and will keep exploring how this might be beneficial in the future.

This new checkCollisions() method is below. I will not be discussing each line of the code but the most significant functionality will be discussed in detail.

[cc lang="javascript" width="550"]
private function checkCollisions():void {
missileLen=aMissile.length-1;
rockLen=aRock.length-1;

rocks: for (rockCtr=rockLen;rockCtr>=0;rockCtr--) {
tempRock=aRock[rockCtr];
rockPoint.x=tempRock.x;
rockPoint.y=tempRock.y;
missiles: for (missileCtr=missileLen;missileCtr>=0;missileCtr--) {
tempMissile=aMissile[missileCtr];
missilePoint.x=tempMissile.x;
missilePoint.y=tempMissile.y;
//trace("1");
try{
if (tempMissile.bitmapData.hitTest(missilePoint,255,tempRock.bitmapData,rockPoint,255)) {
// trace("hit!");
createExplode(tempRock.x+18,tempRock.y+18);
tempMissile=null;
tempRock=null;
aMissile.splice(missileCtr,1);
aRock.splice(rockCtr,1);
break rocks;
break missiles;

}
}catch(e:Error) {
trace("error in missle test");
}
}

//trace("2");
playerPoint.x=playerObject.x;
playerPoint.y=playerObject.y;
try{
if (tempRock.bitmapData.hitTest(rockPoint,255,playerObject.bitmapData,playerPoint,255)){
//trace("ship hit");
createExplode(tempRock.x+18,tempRock.y+18);
tempRock=null;
aRock.splice(rockCtr,1);
}
}catch(e:Error) {
trace("error in ship test");
}
}

//trace("3");
}
[/cc]

Labels
We need to loop through all of the rocks and all of the missiles to see if they hit another. We don't care if the missiles hit other missiles, and we don't care if rocks hit other rocks, but we do care of rocks hit the playerShip. For that reason, we loop through the rocks in our outer loop and the missiles in our inner loop. If we looped through the missiles as our outer loop, then for each missile we would need to check each rock AND also check the ship against each rock. That would be a waste of time, so we loop through each rock in the outer loop - first looking to see if a rock has hit a missile, and if it does NOT, then check the rock against the ship. We do this be setting up two labels called:

rocks:
missiles:

You can use labels in nested loops to help discern the loop you want to break out of. We do that here when a rock and a missile collide. The break rocks; and break missiles; lines tell the run-time to stop looking at this rock and this missile and start with the next in each loop.

Looping Backward
Why the heck does Jeff loop backward through his arrays? Is he one of the THOSE people who can use ++i and i++ correctly? Sadly, I cannot, but that is beside the point. We loop backward through the rocks and the missiles because we need to splice them out of their respective arrays when a collision is detected. If we looped forward through the array, and we had to splice say the 10th element in the missile array, something unexpected would happen. We would actually SKIP checking the 11th element in our array. That is because if the missileCtr is on 10 and we splice the element at index 10 in our array, right away, the 11th element shifts into the 10th spot (and all other elements after 11 shift one spot also). The missileCtr would then increase from 10 to 11, and the former element at position 11 (now at 10) is never checked. By looping backward through the array, when we splice, we don't cause this same problem. If we splice the 10th element in the array, 11 certainly moves in to fill its place, but the next one we check is the element in position 9 (we are looping backward), so we never skip an element.

The BitmpData.hittest method.
[cc lang="javascript" width="550"]if (tempMissile.bitmapData.hitTest(missilePoint,255,tempRock.bitmapData,rockPoint,255))
[/cc]
The hitTest() method of the BitmapData object is a powerful one. When called on a BitmpData object, it looks at all of the pixels on the object to see if they are overlapping with pixels on the other object. If those pixels are not transparent (or don't fall over the opacity threshold value) and hit is detected. As an optimization, I created generic point objects called rockPoint and missilePoint as global class variables. By doing so, I use a little extra memory up front, but don't have to instantiate the point objects on each hitTest. The missilePoint and rockPoint must be the upper left-hand corner of each object on the scene in global coordinates. The 255 basically tells the hitTest that NO alpha areas are to be considered opaque for the rock. Setting this number lower we change areas with alpha values above it to be considered opaque for this test.

The tempMissle.bitmapData and tempRock.bitmpaData hold the current objects referenced to the bitmapData needed for the hitTest. It is essential that this be changed each time the BitmapData of the object changes.

For example, in the code below, I have modified the previous drawMissiles methods from Part 3 to include one more line at the bottom. (see the //added in part 4 section).

[cc lang="javascript" width="550"]
private function drawMissiles():void {
missileLen=aMissile.length-1;

for each (tempMissile in aMissile) {

//trace("tempRock.animationIndex=" + tempRock.animationIndex);
//trace("aAsteroidAnimation[tempRock.animationIndex]=" + aAsteroidAnimation[tempRock.animationIndex].width);
missilePoint.x=tempMissile.x;
missilePoint.y=tempMissile.y;

canvasBD.copyPixels(aMissileAnimation[tempMissile.animationIndex],missileRect, missilePoint);

tempMissile.animationIndex++;
if (tempMissile.animationIndex > missileArrayLength-1) {
tempMissile.animationIndex = 0;
}
//added in part 4
tempMissile.bitmapData=aMissileAnimation[tempMissile.animationIndex];

}

}[/cc]

//added in part 4

tempMissile.bitmapData=aMissileAnimation[tempMissile.animationIndex

As we update the missile on the screen we also change the piece of BitmapData that used to display it on the screen. (it animates from red to yellow). When this change is made, we store a reference to the new BitmapData object representing the animation in our tempMissile.bitmapData variable.

We have added similar lines to the the drawRocks() method (see the bottom).

 

[cc lang="javascript" width="550"]
private function drawRocks() {
rockLen=aRock.length-1;

for each (tempRock in aRock) {

//trace("tempRock.animationIndex=" + tempRock.animationIndex);
//trace("aAsteroidAnimation[tempRock.animationIndex]=" + aAsteroidAnimation[tempRock.animationIndex].width);
rockPoint.x=tempRock.x;
rockPoint.y=tempRock.y;

canvasBD.copyPixels(aAsteroidAnimation[tempRock.animationIndex],rockRect, rockPoint);

tempRock.frameCount++;
if (tempRock.frameCount > tempRock.frameDelay){

tempRock.animationIndex++;
if (tempRock.animationIndex > asteroidFrames-1) {
tempRock.animationIndex = 0;
}
tempRock.frameCount=0;
//added in part 4
tempRock.bitmapData=aAsteroidAnimation[tempRock.animationIndex];
}
}

}
[/cc]

For the playerObject. we needed to add the code only when the frame of animation changed by turning the ship:

[cc lang="javascript" width="550"]
private function checkKeys():void {
if (aKeyPress[38]){
//trace("up pressed");
playerObject.dx=aRotation[playerObject.arrayIndex].dx;
playerObject.dy=aRotation[playerObject.arrayIndex].dy;

var mxn:Number=playerObject.movex+playerObject.acceleration*(playerObject.dx);
var myn:Number=playerObject.movey+playerObject.acceleration*(playerObject.dy);

var currentSpeed:Number = Math.sqrt ((mxn*mxn) + (myn*myn));
if (currentSpeed < playerObject.maxVelocity) {
playerObject.movex=mxn;
playerObject.movey=myn;
} // end speed check

}
if (aKeyPress[37]){
playerObject.arrayIndex--;
if (playerObject.arrayIndex <0) playerObject.arrayIndex=shipAnimationArrayLength-1;
//added in part 4
playerObject.bitmapData=aShipAnimation[playerObject.arrayIndex];

}
if (aKeyPress[39]){
playerObject.arrayIndex++;
if (playerObject.arrayIndex ==shipAnimationArrayLength) playerObject.arrayIndex=0;
//added in part 4
playerObject.bitmapData=aShipAnimation[playerObject.arrayIndex];

}
//*** added for part 3
if (aKeyPress[90]){
fireMissile();

}

}
[/cc]

You can se in the above code we simply added
playerObject.bitmapData=aShipAnimation[playerObject.arrayIndex]


when a change is made to the animationIndex.

So, that's basically pixel perfect collision detection between bitmapData objects in a nut shell. You need 4 pieces of data:
1. A reference to the bitmapData object for the first object you want to check.
2. A reference to the bitmapData object for the second object you want to check.
3. A point object representing the current upper left corner x and y values of the first object.
4. A point object representing the current upper left corner x and y values for the second object.

I usually leave the Alpha threshold values at 255, but I can see a need for them with more complicated objects. So, here is what the code looks like for the hitTest between a rock and the playerObject:

[cc lang="javascript" width="550"]

playerPoint.x=playerObject.x;
playerPoint.y=playerObject.y;
try{
if (tempRock.bitmapData.hitTest(rockPoint,255,playerObject.bitmapData,playerPoint,255)){
//trace("ship hit");
createExplode(tempRock.x+18,tempRock.y+18);
tempRock=null;
aRock.splice(rockCtr,1);
}
}catch(e:Error) {
trace("error in ship test");
}

[/cc]

This is very similar to the check between the rocks and the missiles. When a rock is hit by either the ship or a missile, we set the tempRock to be null, splice it from our array of rocks (we do this with the missile and the missile array when a rock - missile collision is detected also), and then we create an explosion.

createExplode(tempRock.x+18,tempRock.y+18);

The new createExplode function takes two parameter and x and y value. Here, since the x and y values for the rock are the upper left hand corner, and the rock is a 36x36 bitmap, we add 18 to the x and y before we pass them in. That will put the explosion roughly in the middle of the rock.

The Particle Explosion and Particle Pool/Farm
Since the particles that make up our explosion are merely for decoration, I have decided to limit the number available for display. I have done this through the implementation of a farm or pool or particles. This is a set of pre-created particle objects in an array called aFarmParticle. A the beginning of the game, I pre-create 500 particles objects and place them in this array. The code is below:

[cc lang="javascript" width="550"]
private function createFarmParticles() {
var particleCtr:int;
for (particleCtr=0;particleCtr<maxParticles;particleCtr++) {
var tempPart:Object={};
tempPart.lifeCount=0;
tempPart.life=0;
tempPart.x=0;
tempPart.y=0;
tempPart.dx=0;
tempPart.dy=0;
tempPart.speed=0;
tempPart.bitmapData=null;
aFarmParticle.push(tempPart);
}
}
[/cc]

I have a maxParticles variable set to 500 in my variable definition section. This code merely loops 500 times, creates some dummy particles and places them in the array for later use. This way, I don't have to create particles on fly, thus saving valuable execution time.

When a particle explosion is needed, we call the createExplode function. This function will start a loop and try to move maxPartsPerExplode (currently set to 40) from the aFarmParticle to the aActiveParticle array.

[cc lang="javascript" width="550"]
private function createExplode(xval:Number,yval:Number):void {

//trace("aFarmParticle.length=" + aFarmParticle.length);
//trace("aActiveParticle.length=" + aActiveParticle.length);

for (explodeCtr=0;explodeCtr<maxPartsPerExplode;explodeCtr++) {
farmLen=aFarmParticle.length-1;
if (farmLen >0){
tempPart=aFarmParticle[farmLen];
aFarmParticle.splice(farmLen,1);
tempPart.lifeCount=0;
tempPart.life=int(Math.random()*partMaxLife)+partMinLife;;
tempPart.x=xval;
tempPart.y=yval;
tempPart.speed=(Math.random()*partMaxSpeed)+1;
randIntFrame=int(Math.random()*10);
tempPart.bitmapData=aMissileAnimation[randIntFrame];
randIntVector=int(Math.random()*36);
tempPart.dx=aRotation[randIntVector].dx;
tempPart.dy=aRotation[randIntVector].dy;
aActiveParticle.push(tempPart);
}
}
}
[/cc]

On each iteration through the the loop we first check to make sure our farm/pool length is not 0. If it is above 0, there are particles that can be moved from the farm to the active array. We do this by creating a tempPart (temporary particle) from the last element in the aFarmParticle array. We then splice that particle from the farm, set random properties for the tempPart and add it to the aActiveParticle array.

We set random properties for the movement vector (dx and dy) values by randomly picking a number between 0-36 and them using that as the index for our pre-calculated aRotation array. We also set a random speed, and a random frame from our missile tile sheet (array of BitmapData) aMissileAnimation - because we have those already and they are easy to use. We also set a random life between partMaxLife (currently 50) and partMinLife (currently 10). We do all of this to make the particles have some minor differences and seem more organic. There are many more things we could do, but this is our simple particle emitter.

Now, once we have active particles, we need to update them on each frame. This is very similar to updating the rocks or the missiles. We loop through them, and copy the current bitmapData of the particle to the canvasBD.

[cc lang="javascript" width="550"]
private function drawParts():void {

activeLen=aActiveParticle.length-1;

for (partCtr=activeLen;partCtr>=0;partCtr--) {
removePart=false;
tempPart=aActiveParticle[partCtr];
tempPart.x+=tempPart.dx*tempPart.speed;;
tempPart.y+=tempPart.dy*tempPart.speed;

if ((tempPart.x > stage.width) || (tempPart.x < 0)) {
removePart=true;
}

if ((tempPart.y > stage.height) || (tempPart.y < 0)){
removePart=true;
}

tempPart.lifeCount++;
if (tempPart.lifeCount > tempPart.life) {

}

if (removePart) {
aFarmParticle.push(tempPart);
aActiveParticle.splice(partCtr,1);

}else{
//trace("tempRock.animationIndex=" + tempRock.animationIndex);
//trace("aAsteroidAnimation[tempRock.animationIndex]=" + aAsteroidAnimation[tempRock.animationIndex].width);
partPoint.x=tempPart.x;
partPoint.y=tempPart.y;

canvasBD.copyPixels(tempPart.bitmapData,partRect, partPoint);

}

}

}
[/cc]

A particle is removed from the screen is its life count is greater than its life value or it leaves the screen boundaries. We have 500 particles and only a hand full of asteroids on the screen. We will never use all 500 at one time, but this just sets us up for higher level of complex space battles and explosions later in the game. When a particle is removed, it is added back to the aFarmParticle array and spliced from the the aActiveParticle array:

aFarmParticle.push(tempPart);
aActiveParticle.splice(partCtr,1);

So, we have now completed all of the features for this Asteroids game. Our new game loop looks like this:

[cc lang="javascript" width="550"]
private function runGame(e:TimerEvent) {

checkKeys();
updatePlayer();
updateRocks();
updateMissiles();
checkCollisions();
drawBackground();
drawPlayer();
drawRocks();
drawMissiles();
drawParts();
frameTimer.countFrames();
frameTimer.render();
if (aRock.length ==0 && aActiveParticle.length==0) stopRunningGame();
}
[/cc]

I have added a simple frameRate counter and the class is in the .zip above. It is easy to use, you just need to make sure that you call its countFrames() and render() methods on each frame tick.

Below is the entire code listing, including the Main class, StartBox class, and FrameTimer class.

 

[cc lang="javascript" width="550"]

/**
* ...
* @author Default
* @version 0.1
*/

package {

import flash.display.Bitmap;
import flash.display.MovieClip;
import flash.events.Event;
import flash.events.TimerEvent;
import flash.utils.Timer;
import flash.display.BitmapData;
import flash.geom.*;
import flash.events.*;
import ActiveGraph.*;
//** added for part 3
import flash.utils.getTimer;
//*** added for part 4
import FrameTimer;

public class Main extends MovieClip {

var aRotation:Array=[];
var aShipAnimation:Array=[];
var shipAnimationArrayLength:int;
var ship:Ship;
var shipHolder:MovieClip;
var animationTimer:Timer;
var animationCounter:int;
var playerObject:Object;
var canvasBD:BitmapData;
var canvasBitmap:Bitmap;
var backgroundBD:BitmapData;
var backgroundSource:Background;
var gameTimer:Timer;
var backgroundRect:Rectangle;
var backgroundPoint:Point;
var playerRect:Rectangle;
var playerPoint:Point;
var aKeyPress:Array=[];
var spriteHeight:int=20;
var spriteWidth:int=20;

//** part 2 variables
var aAsteroidAnimation:Array=[];
var asteroidAnimationTimer:Timer;
var asteroidHolder:RockToCache;
var asteroidFrames:int=12;
var aRock:Array=[];
var level:int=1;
var rockPoint:Point=new Point(0,0);
var rockRect:Rectangle=new Rectangle(0,0,36,36);
var levelRocksCreated:Boolean=false;

//*** part 3 variables
var missleTileSheet:BitmapData;
var aMissileAnimation:Array;
var aMissile:Array; //holds missile objects fires
var missileSpeed:int=4;
var missileWidth:int=4;
var missileHeight:int=4;
var missileArrayLength=10;
var missilePoint:Point=new Point(0,0);
var missileRect:Rectangle=new Rectangle(0,0,4,4);
var missileMaxLife:int=50;
var missileFireDelay:Number=100;
var lastMissileShot:Number=getTimer();

//*** part 4 variables
var missileLen:int;
var missileCtr:int;
var tempMissile:Object;
var tempRock:Object;
var rockLen:int;
var rockCtr:int;
var aFarmParticle:Array=[];
var aActiveParticle:Array=[];
var maxParticles:int=500;
var maxPartsPerExplode:int=40;
var explodeCtr:int;
var tempPart:Object;
var farmLen:int;
var randIntVector:int;
var randIntFrame:int;
var activeLen:int;
var partCtr:int;
var partMaxSpeed:int=2;
var partMaxLife:int=50;
var partMinLife:int=10;
var removePart:Boolean=false;
var partPoint:Point=new Point(0,0);
var partRect:Rectangle=new Rectangle(0,0,4,4);

//part 4 optimizations
var randInt:int;
var randInt2:int;
var ctr:int;
var frameTimer:FrameTimer;
var startBox:StartBox;
var ag:ActiveGraph;

public function Main() {
trace("main");
createObjects();
createRotationArray();
createShipAnimation();
startBox=new StartBox(this);
ag=new ActiveGraph(0,false,true,1);

}

private function startBoxOn():void{
startBox.startit();

}

private function createObjects():void {
//create generic object for the player
playerObject=new Object();
playerObject.arrayIndex=0;
playerObject.x=200;
playerObject.y=200;
playerObject.dx=0;
playerObject.dy=0;
playerObject.movex=0;
playerObject.movey=0;
playerObject.acceleration=.3;
playerObject.maxVelocity=8;
playerObject.friction=.01;
playerObject.centerx=playerObject.x+spriteWidth;
playerObject.centery=playerObject.y+spriteHeight;
playerRect=new Rectangle(0,0,spriteWidth*2,spriteHeight*2);
playerPoint=new Point(playerObject.x,playerObject.y);

//init canvas and display bitmap for canvas
canvasBD=new BitmapData(400,400,false,0x000000);
canvasBitmap=new Bitmap(canvasBD);

//init background
backgroundSource=new Background();
backgroundBD=new BitmapData(400,400,false,0x000000);
backgroundBD.draw(backgroundSource,new Matrix());
backgroundRect=new Rectangle(0,0,400,400);
backgroundPoint=new Point(0,0);

//part 3 init tilesheet for missiles
aMissile=[];
aMissileAnimation=[];
missleTileSheet=new missile_sheet(40,4);
var tilesPerRow:int=10;
var tileSize:int=4;
for (var tileNum=0;tileNum<10;tileNum++) {
var sourceX:int=(tileNum % tilesPerRow)*tileSize;
var sourceY:int=(int(tileNum/tilesPerRow))*tileSize;
var tileBitmapData:BitmapData=new BitmapData(tileSize,tileSize,true,0x00000000);
tileBitmapData.copyPixels(missleTileSheet,
new Rectangle(sourceX,sourceY,tileSize,tileSize),new Point(0,0));
aMissileAnimation.push(tileBitmapData);
}

//added in part 4
createFarmParticles();
frameTimer=new FrameTimer(this,140,0,canvasBD);

}

private function createFarmParticles() {
var particleCtr:int;
for (particleCtr=0;particleCtr< playerObject.maxVelocity) {
playerObject.movex=mxn;
playerObject.movey=myn;
} // end speed check

}
if (aKeyPress[37]){
playerObject.arrayIndex--;
if (playerObject.arrayIndex <0) playerObject.arrayIndex=shipAnimationArrayLength-1;
//added in part 4
playerObject.bitmapData=aShipAnimation[playerObject.arrayIndex];

}
if (aKeyPress[39]){
playerObject.arrayIndex++;
if (playerObject.arrayIndex ==shipAnimationArrayLength) playerObject.arrayIndex=0;
//added in part 4
playerObject.bitmapData=aShipAnimation[playerObject.arrayIndex];

}
//*** added for part 3
if (aKeyPress[90]){
fireMissile();

}

}

private function updatePlayer():void {

//add friction

if (playerObject.movex > 0) {
playerObject.movex-=playerObject.friction;
}else if (playerObject.movex < 0) {
playerObject.movex+=playerObject.friction;
}

if (playerObject.movey > 0) {
playerObject.movey-=playerObject.friction;
}else if (playerObject.movey < 0) {
playerObject.movey+=playerObject.friction;
}

playerObject.x+=(playerObject.movex);
playerObject.y+=(playerObject.movey);

playerObject.centerx=playerObject.x+spriteWidth;
playerObject.centery=playerObject.y+spriteHeight;

if (playerObject.centerx > stage.width) {
playerObject.x=-spriteWidth;
playerObject.centerx=playerObject.x+spriteWidth;
}else if (playerObject.centerx < 0) {
playerObject.x=stage.width - spriteWidth;
playerObject.centerx=playerObject.x+spriteWidth;
}

if (playerObject.centery > stage.height) {
playerObject.y=-spriteHeight;
playerObject.centery=playerObject.y+spriteHeight;
}else if (playerObject.centery < 0) {
playerObject.y=stage.height-spriteHeight;
playerObject.centery=playerObject.y+spriteHeight;
}

//trace("centerx=" + playerObject.centerx);
}

private function updateRocks():void {
if (!levelRocksCreated) {
for (ctr=0;ctr<level+5;ctr++) {
//create a rock;
tempRock=new Object();
randInt=int(Math.random()*36);
randInt2=int(Math.random()*asteroidFrames);
tempRock.dx=aRotation[randInt].dx;
tempRock.dy=aRotation[randInt].dy;
tempRock.x=20;
tempRock.y=20;
tempRock.animationIndex=randInt2;
//added for part 4
tempRock.bitmapData=aAsteroidAnimation[tempRock.animationIndex];
tempRock.frameDelay=3;
tempRock.frameCount=0;
tempRock.speed = (Math.random()*level)+1;
//trace("tempRock.speed=" + tempRock.speed);
aRock.push(tempRock);
}
levelRocksCreated=true;
}
for each (tempRock in aRock) {
tempRock.x+=tempRock.dx*tempRock.speed;;
tempRock.y+=tempRock.dy*tempRock.speed;

if (tempRock.x > stage.width) {
tempRock.x=0;
}else if (tempRock.x < 0) {
tempRock.x=stage.width;
}

if (tempRock.y > stage.height) {
tempRock.y=0;
}else if (tempRock.y < 0) {
tempRock.y=stage.height
}

}
}

private function drawBackground():void {
canvasBD.copyPixels(backgroundBD,backgroundRect, backgroundPoint);
}

private function drawPlayer():void {
playerPoint.x=playerObject.x;
playerPoint.y=playerObject.y;
canvasBD.copyPixels(aShipAnimation[playerObject.arrayIndex],
playerRect, playerPoint);
}

private function drawRocks() {
rockLen=aRock.length-1;

for each (tempRock in aRock) {

rockPoint.x=tempRock.x;
rockPoint.y=tempRock.y;

canvasBD.copyPixels(aAsteroidAnimation[tempRock.animationIndex],
rockRect, rockPoint);

tempRock.frameCount++;
if (tempRock.frameCount > tempRock.frameDelay){

tempRock.animationIndex++;
if (tempRock.animationIndex > asteroidFrames-1) {
tempRock.animationIndex = 0;
}
tempRock.frameCount=0;
//added in part 4
tempRock.bitmapData=aAsteroidAnimation[tempRock.animationIndex];
}
}

}

private function fireMissile():void {

if (getTimer() > lastMissileShot + missileFireDelay) {
tempMissile=new Object();
tempMissile.x=playerObject.centerx;
tempMissile.y=playerObject.centery;
tempMissile.dx=aRotation[playerObject.arrayIndex].dx;
tempMissile.dy=aRotation[playerObject.arrayIndex].dy;
tempMissile.speed=missileSpeed;
tempMissile.life=50;
tempMissile.lifeCount=0;
tempMissile.animationIndex=0;
tempMissile.bitmapData=aMissileAnimation[tempMissile.animationIndex];
aMissile.push(tempMissile);
lastMissileShot=getTimer();
}
}

private function updateMissiles():void {
missileLen=aMissile.length-1;
for (ctr=missileLen;ctr>=0;ctr--) {
tempMissile=aMissile[ctr];
tempMissile.x+=tempMissile.dx*tempMissile.speed;;
tempMissile.y+=tempMissile.dy*tempMissile.speed;

if (tempMissile.x > stage.width) {
tempMissile.x=0;
}else if (tempMissile.x < 0) {
tempMissile.x=stage.width;
}

if (tempMissile.y > stage.height) {
tempMissile.y=0;
}else if (tempMissile.y < 0) {
tempMissile.y=stage.height
}

tempMissile.lifeCount++;
if (tempMissile.lifeCount > tempMissile.life) {
aMissile.splice(ctr,1);
tempMissile=null;
}

}
}

private function drawMissiles():void {
missileLen=aMissile.length-1;

for each (tempMissile in aMissile) {

missilePoint.x=tempMissile.x;
missilePoint.y=tempMissile.y;

canvasBD.copyPixels(aMissileAnimation[tempMissile.animationIndex],
missileRect, missilePoint);

tempMissile.animationIndex++;
if (tempMissile.animationIndex > missileArrayLength-1) {
tempMissile.animationIndex = 0;
}
//added in part 4
tempMissile.bitmapData=aMissileAnimation[tempMissile.animationIndex];

}

}
//added in part 4
private function checkCollisions():void {
missileLen=aMissile.length-1;
rockLen=aRock.length-1;

rocks: for (rockCtr=rockLen;rockCtr>=0;rockCtr--) {
tempRock=aRock[rockCtr];
rockPoint.x=tempRock.x;
rockPoint.y=tempRock.y;
missiles: for (missileCtr=missileLen;missileCtr>=0;missileCtr--) {
tempMissile=aMissile[missileCtr];
missilePoint.x=tempMissile.x;
missilePoint.y=tempMissile.y;
//trace("1");
try{
if (tempMissile.bitmapData.hitTest(missilePoint,255,
tempRock.bitmapData,rockPoint,255)) {
// trace("hit!");
createExplode(tempRock.x+18,tempRock.y+18);
tempMissile=null;
tempRock=null;
aMissile.splice(missileCtr,1);
aRock.splice(rockCtr,1);
break rocks;
break missiles;

}
}catch(e:Error) {
trace("error in missle test");
}
}

//trace("2");
playerPoint.x=playerObject.x;
playerPoint.y=playerObject.y;
try{
if (tempRock.bitmapData.hitTest(rockPoint,255,
playerObject.bitmapData,playerPoint,255)){
//trace("ship hit");
createExplode(tempRock.x+18,tempRock.y+18);
tempRock=null;
aRock.splice(rockCtr,1);
}
}catch(e:Error) {
trace("error in ship test");
}
}

//trace("3");
}

private function createExplode(xval:Number,yval:Number):void {

//trace("aFarmParticle.length=" + aFarmParticle.length);
//trace("aActiveParticle.length=" + aActiveParticle.length);

for (explodeCtr=0;explodeCtr<maxPartsPerExplode;explodeCtr++) {
farmLen=aFarmParticle.length-1;
if (farmLen >0){
tempPart=aFarmParticle[farmLen];
aFarmParticle.splice(farmLen,1);
tempPart.lifeCount=0;
tempPart.life=int(Math.random()*partMaxLife)+partMinLife;;
tempPart.x=xval;
tempPart.y=yval;
tempPart.speed=(Math.random()*partMaxSpeed)+1;
randIntFrame=int(Math.random()*10);
tempPart.bitmapData=aMissileAnimation[randIntFrame];
randIntVector=int(Math.random()*36);
tempPart.dx=aRotation[randIntVector].dx;
tempPart.dy=aRotation[randIntVector].dy;
aActiveParticle.push(tempPart);
}
}
}

private function drawParts():void {

activeLen=aActiveParticle.length-1;

for (partCtr=activeLen;partCtr>=0;partCtr--) {
removePart=false;
tempPart=aActiveParticle[partCtr];
tempPart.x+=tempPart.dx*tempPart.speed;;
tempPart.y+=tempPart.dy*tempPart.speed;

if ((tempPart.x > stage.width) || (tempPart.x < 0)) {
removePart=true;
}

if ((tempPart.y > stage.height) || (tempPart.y < 0)){
removePart=true;
}

tempPart.lifeCount++;
if (tempPart.lifeCount > tempPart.life) {

}

if (removePart) {
aFarmParticle.push(tempPart);
aActiveParticle.splice(partCtr,1);

}else{

partPoint.x=tempPart.x;
partPoint.y=tempPart.y;

canvasBD.copyPixels(tempPart.bitmapData,partRect, partPoint);

}

}

}

}

}
[/cc]

 

[cc lang="javascript" width="550"]
package {
import flash.display.MovieClip;
import flash.events.*;
import flash.geom.Point;
import flash.geom.Rectangle;
import flash.utils.Timer;
import flash.text.TextField;
import flash.text.TextFormat;
import flash.display.BitmapData;

public class FrameTimer {
private var format:TextFormat=new TextFormat();
private var messageText:String;
private var messageBitmapData:BitmapData;
private var messageTextField:TextField = new TextField();
public var frameTimer:Timer;
public var framesCounted:int=0;
public var parent:MovieClip;
public var x:int;
public var y:int;
public var canvasBD:BitmapData;
public var messagePoint:Point;
public var messageRect:Rectangle;

public function FrameTimer(parentVal:MovieClip,xval:int,yval:int,canvasval:BitmapData):void {
x=xval;
y=yval;
canvasBD=canvasval;
format.size=12;
format.font="Arial";
format.color="0xffffff";
format.bold=true;
messageText="0";
messageTextField.text=messageText;
messageTextField.setTextFormat(format);
//messageTextField.width=(messageText.length+2)*int(format.size);
messageTextField.width=30;
//messageTextField.height=int(format.size)*2;
messageTextField.height=20;
messageBitmapData=new BitmapData(messageTextField.width,
messageTextField.height,true,0xffff0000);
parent=parentVal;
frameTimer= new Timer(1000,0);
frameTimer.addEventListener(TimerEvent.TIMER,frameCounter,false,0,true);
frameTimer.start();
messagePoint=new Point(x,y);
messageRect=new Rectangle(0,0,messageTextField.width,messageTextField.height);

}

function frameCounter(e:TimerEvent):void {
messageText=framesCounted.toString();
messageTextField.text=messageText;
//trace("frameRate:" + framesCounted.toString());
framesCounted=0;
}

function countFrames():void {
framesCounted++;
}

function render():void {
format.size=12;
format.font="Arial";
format.color="0xffffff";
format.bold=true;
messageTextField.setTextFormat(format);
messageBitmapData.fillRect(messageRect,0xffff0000);
//trace("messageTextField.text=" + messageTextField.text);
messageBitmapData.draw(messageTextField);
canvasBD.copyPixels(messageBitmapData,messageRect, messagePoint);
}

} // end class

} // end package

 



package {
import flash.display.MovieClip;
import flash.events.*;

public class StartBox extends MovieClip {
var parentClass:MovieClip;

public function StartBox(parentVal:MovieClip) {
parentClass=parentVal;

}

public function startit():void {
parentClass.addChild(this);
trace("starting startBox");
x=80;
y=100;
start_mc.addEventListener(MouseEvent.CLICK, startButtonListener,false,0,true);
}

public function stopit():void {
start_mc.removeEventListener(MouseEvent.CLICK, startButtonListener);
trace("stopping startBox");
parentClass.removeChild(this);
}

private function startButtonListener(e:Event) {
stopit();
parentClass.startGame();
}
}

}
[/cc]

Final notes and optimizations

If you have read through all 4 parts of this tutorial, you will note that I didn't go as deep into making a finalized game as I initially intended. I decided to focus more on optimizations than adding in Mochi Ads and Leader Boards because those might not be relevant to all readers. If you compare the code provided for the Main class in part 3 with the code above for part 4, you notice quite a few changes. All have been noted in the variable definition section and inside the code where needed. Mostly, I moved many variables that were being locally defined over and over in loops and made them global variables. This gives the game a larger initial memory foot print, but will save much execution time while the game is processing.

That's it, if you have any questions or comments, please email info[at]8bitrocket[dot]com, leave a message below, or visit the forums and post.

Jeff Fulton (8bitjeff).

20Mar/080

Flash Game Development Inter-web mash up : Mar. 20, 2008

The latest in Blog entries and articles that might interest Flash game developers.

Mochiland and James Robinson bring you an absolutely essential article on Marketing Flash Games: The Other Half of the Battle. Please read the entire article, but in summary, Mark suggests that vigorous marketing of your game is essential once it is complete. His basic 3 points are:
1. Create a distribution pack for email submit portals. It should include the protected swf file(s); logos and thumbnails; as well as a document with descriptions in various lengths, width and height information, and any other essential information about your creation.
2. Create a professional distribution email that says enough, but not too much about your game. Make sure to pinpoint what makes your game so special..
3. Make sure to maintain a list of portals (emails and submit urls) that you can attack once your games are finished.

Steve has posted his latest opus, an article called Flash Game Design Kitchen Nightmares, where he details how to apply the Gordon Ramsey Kitchen Nightmares advice to Flash Game design.

Google Docs has a slide show presentation by Brad Merritt, Lead Game Designer, Cartoon Network called Common Flash Game Mistakes. It is a 187 slide presentation that I was made aware of by browsing an evilzug Live Journal posting. It is a very detailed discussion on 2d camera issues and mistakes, keyboard control mistakes, lack of consistent communication and feedback to the player, problems with relying on random numbers for game variation, issues with emulating previously published designs, and problems with setting game difficulty. He also goes into a number of smaller issues such as implementing power ups properly, making games too realistic, letting non-game players have too much say in a game design, using unfamiliar play mechanics, forcing the player to watch long intros before a game starts and much more.

19Mar/083

Flash Game Design Kitchen Nightmares

Flash Game Design Kitchen Nightmares

I've been watching a lot of the BBC America (plus the sub-par USA Fox version) of Chef Gordon Ramsey's reality show Kitchen Nightmares. You might know Chef Ramsey from the "foul-mouthed chef" reality contest series Hell's Kitchen. While that series is the standard by-the-numbers "Survivor meets The Apprentice" show, but with food, Kitchen Nightmares (especially the BBC version) is something else entirely. On that show, Chef Ramsey visits failing restaurants  to help them before they fall into financial ruin. He visits the restaurant, eats their food, comments on their menu, meets and watches the staff while they are working, and then gives his recommendations (usually peppered with the word "f*ck"). Most of the time, these recommendations fall into the same categories:

  1. Simplify the menu
  2. Sell what the public wants
  3. Streamline your operations
  4. Make sure the word gets out

As it turns out, most restaurants (the ones featured on the show anyway) have similar problems, and these problems almost always lead to failure. Their menu  has too many items because they are trying to be all things to all people. The food they sell is too fancy or not the right mix for their location because the owner or chef are too emotionally involved in their own ideas and creations to see what will actually make a profit. Finally, they have too many people working in the wrong jobs and are creating artificial bottle-necks that drive customers away because they have had a bad experience. Chef Ramsey finds these flaws like clockwork, and bluntly tells the establishment what is required to heal their ills. Finally, after changes are made, Ramsey offers innovative ideas on how to let the public know about the new direction for the restaurant to get people through the door. The audience then watches to see if the establishment is willing or able to change it's ways fast enough to stay in-business.

Why am I writing about a "cooking show" on a blog dedicated to Flash game programming? Because as I've watched the show I've realized that the advice Chef Ramsey gives to failing restaurant entrepreneurs is great advice to anyone who is trying to create a business around offering their own creations that will be consumed by the public. I believe this extends to game design, and in particular, Flash Game Design.

  1. Simplify The Menu: Not the menu exactly, but the elements of the game. Don't try to throw-in too much at once to cloud the main object of what you are trying to accomplish. In most cases, Flash Game designers are pretty good at this one. However, many games might be actually "too simple". They are based on a funny and or interesting but thin concept that is appealing at first, but has little staying value. Finding the right mix of features and ideas that will work might take some trial and error, but it seems that the most successful games have an appealing level of depth, but at the same time don't try to do too much at once. For instance a "tower defense" game at its heart is an addictive, one-sided game of "Capture the flag". The idea is simple, the execution is what makes the game a success.
  2. Sell What The Public Wants: This might be hard to swallow, but it is possible that your "grand idea for a new concept in games and game play" is really not all that grand or new, and possibly, is not all that appealing. If you are in the business to make a living from your games, you might have to sacrifice the "big concept" for something that will appeal to the general public. This does not necessarily mean that you should copy a game that is current a success. Popular games are good guide as to what the public currently wants, but a cheap knock-off won't buy you much staying power. Instead you should should glean the elements that you think make make the popular, and make sure to fold similar elements or game play into your creations. The game should be accessible to the masses, but have just enough unique elements to make it stand-out.
  3. Streamline Your Operations: This doesn't not apply to staffing, because most Flash game developers are very small operations. However, this does apply to resources you include in game itself. For instance, many games skimp on small but important elements like well thought-out controls, animation, sound, and music. The "idea" of your game might capture people initially, but the nuances of these other elements will keep them playing. My game Hot Wheels Trackmod, has been played now over 50,000,000 times. Most of the feedback I have received is along the lines of "I love the sublime game play, but the hypnotic music keeps me playing". Ira Willey has great blog entry named How to make a successful Flash Game that details many of these important elements that will help set a game apart from the pack.
  4. Make sure the word gets out: No matter now good your game might be, getting people to play it takes more than just posting it on your site and crossing your fingers. James Robinson's recent article on Mochiland, Marketing Flash Games: The Other Half of the Battle is a fantastic guide on how to get the "word out" about your game. As well, the similar article on Mochiland , Sam Horton's Thumbnail Design: A Picture Is Worth A Thousand Plays details another important element in getting people to notice your game.
  5. Franchise: OK, I made this one up myself mostly because the scope of the show Kitchen Nightmares can't address this, but most successful restaurants aim to branch out with more locations and franchises based on their good name and reputation. Your game should be no different. Create games and characters that you own (as soon as you create them they are copy-writed to yourself), that will (hopefully) become familiar to players. Create game engines that can be easily "re-skinnable" or re-used for more games of the same type or with the same characters of your own creation. This might take some advanced programming to accomplish (creating a re- usable state-machine game engine is good start), but the benefits will outweigh the investment in the time you take to develop your game code. People don't necessarily eat at McDonald;s because the food is exotic or gourmet, they eat at McDonald's because they know what they are getting.
18Mar/080

Site Down This Morning

We're not sure why, but the site has been down all morning. The hosting company was finally able to fix it, but we don't have an explanation as to why it occurred. We are using .net 2.0 and share an application pool with other sites on the same server, it could be that another site caused a problem, but we have no idea.

Anyway, we want apologize to anyone who tried to visit the site this morning. It is especially troubling because Pumpkin Man is getting a GIANT boost in plays today (10K already) and any clicks over to over to our site from portals were lost.

 

16Mar/080

Mission Leprechaun

Gourdon Gruesome needs money. His untimely defeat at the hands of Pumpkin Man has left him broke and without minions to do his bidding. Help Gourdon finance his future evil deeds by rescuing Leprechauns who have been imprisoned in the potato field mazes. Gourdon must eat all of the clovers, and find all of the powers ups to complete each maze. Find keys to unlock the prison doors and use rainbow transporters to get inside enclosed areas.

Watch out for rats and snakes, but be on the lookout for colored clovers, 4 leaf clovers, and mugs of ale. These will give you temporary powers to aide your quest.

Filed under: Games, Maze No Comments
16Mar/080

The Mission Leprechaun Chronicles II

I did get one pretty good exclusive offer, but I have re-thought my need to sell off my IP and have looked for more places to post the game. Because the game has some tie-in to a holiday that is actually tomorrow, I figured that I should put it up myself, so I have changed the license offer to Non-Exclusive Only, and have started to upload the game to sites like UGOPlayer, Kongregate, NewGrounds, MochiAds for distribution, and abcarcade.com.

You can play Mission Leprechaun Here or check out the license opportunity at Flashgmelicense.com.