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
25Mar/080

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.

15Mar/080

Review: Pinball Hall Of Fame:Williams Collection for the Wii

A week or so back I wrote a pissed-off blog entry about how it was to find Pinball Hall Of Fame Williams Collection for the Wii out in the wild. That has not changed, and I still have not heard or found any real explanation for it. I ordered my copy from Amazon.com, and it arrived in 2 days, which seemed reasonable, and I've been playing the game ever since.

If I was bewildered about the lack of availability, promotion, etc. for Pinball Hall Of Fame Williams Collection before I played it, I'm simply astonished over it now that I own it. This game is by far, the best simulation of pinball ever created for a home console or computer. I have played nearly all "video pinball" games (save for  "videogame-ized " titles that include well-known Nintendo or Sega characters...sue me) over the past 30 or so years: Video Pinball on the VCS and in the arcade, Davids' Midnight Magic on the Apple IIe, Pinball Construction Set on the Atari 800, Macadam Bumper on the Atari ST, Tristan Pinball on the PC, the Epic Pinball series on the PC, Eight Ball Deluxe on the PC, the Pro Pinball series on the PC, the pre-cursor to this title, Pinball Hall Of Fame The Gottlieb Collection on the PS2, as well as countless others in-between. All of these titles have, in some small way, improved upon their earlier cousins creating digital simulations of the very analog game of pinball that were closer and closer to the real thing. However, all of these earlier games suffered from some or all of the same problems: the camera view (top-down for the 2D games, odd and weird angles for the 3D games) was never quite right, the physics were not simulated to allow for"real pinball" strategies, the controls did not have the "feel" of a real table, the layouts were decent, but did not always model real-world and well-known designs, and even if they did try to model real-world tables, the graphics were not powerful enough to pull genuine nostalgia through the player's digital cognitive dissonance.

I'm happy to say that Pinball Hall Of Fame Williams Collection for the Wii solves nearly all of the aforementioned problems. The intelligent camera follows the ball (and when unnecessary, doesn't) in nearly the same way a trained "pinball-eye" watches the ball while playing. an actual game. Multiple camera angles are available for people who don't like this intelligent system, but to me, the default is the best I've ever seen. The physics seem spot-on. Different tables have the same feel as their real-world counter-parts, and this feel is significant enough to make each table a unique game unto themselves (just like in real-life). Furthermore, the physics allow for most of the same strategies that you would use while playing the actual tables. The controls of the Wii version (nunchuck in the left hand using Z-button for the left flipper, the stick to launch the ball, Wiimote in the right-hand using B-button for the right flipper, shake either one to nudge) are the closest to real pinball that I have ever experienced. There is something unique about having each hand free from the other (pinball control schemes for other systems and computers either have both hands on one control pad, or both on the same keyboard) that allows for the same kind of independent flipper strategies used in the real world. Furthermore the kinetic rumble feedback from Wiimote does a great job of simulating the feel of a well-aimed shot hitting it's target. Since Williams created some of the most well-known tables of the 70's, 80's and 90's you will find games that you either played in the arcade, passed-by with a token hot-in-your hand for a Tempest machine, or at very least , heard their distinctive sound-work emanating around you. Finally,the graphics are certainly the best ever for a pinball simulation, and arguably, some of the best to ever grace the Nintendo Wii. With the camera pulled-back the tables sometimes look cramped and jaggy, but up-close the table modeling is nothing short of breath-taking.

There are 10 memorable Williams tables modeled in the game. Unlike the Gottlieb collection from a few years back, these are the games I actually played in the arcades. While I can appreciate the unique features and craftsmanship of Gottlieb's games, the over-all theme and table design of Williams's games in their 80's (roughly) hey-day have never been, nor do I believe will ever be, matched. The tables in this collection are no exception, Jive Time, Gorgar, Firepower, Black Knight, Space Shuttle, Sorcerer,Pinbot, Taxi, Whirlwind, and Funhouse were created by legends of pinball design like John Trudeau , Barry Oursler, Python Anghelo, Mark Ritchie, Norm Clark, Pat Lawlor, and Steve Ritchie (Who  actually got his start at Atari's ill fated pinball division) as well as countless other artists, designers and programmers. If the only thing this title was successful at was highlighting the artistry and craftsmanship of William's best games, then it would have been a success in it's own right. The fact that the creators at Farsignt Studios did the games true justice by making the best pinball simulation ever, makes this game a an unqualified triumph. A salute needs to go out to Farsight lead engineer Ryan Broner who did an amazing job with the technical simulation of these pinball tables. .

Now, there are a couple small little problems with the game that don't detract much, and might even be figments of my graying memory more than faults with the simulations. Most of the games (besides Jive Time) only allow for 3 balls to be played. I seem to recall at least a few of these games (i.e. Gorgar, Firepower and Black Knight) having a full 5-balls per play in the arcade. As well, it seems like the "grace period" in some of the modern games that allowed a ball to stay in-play if it drained too early is missing. Again, these things might be my own projections and not missing features. As well, there are a few missing Williams games I would have loved to see in the collection. Comet, Cyclone. High Speed, F-14, Hurricane, Police Force, Black Knight 2000 and the phenomenal Machine Bride Of Pinbot could easily for the basis of a sequel..and God-willing, hopefully will.

Pinball Hall Of Fame:Williams Collection for the Wii is the finest pinball simulation ever created. I still have no idea why it has been (mostly) ignored by the press and at retail. With the Wii audience supposedly made-up of many new and older games, I'd think that a simulation of fast-action yet mostly defunct and nostalgic type of electronic gaming would fit nicely along-side Carnival Games and Wii Sports on a Mid-Core/Casual gamer's Wii shelf...but what do I know? A decent 8.0 review did appear at IGN yesterday, which an amazing score for IGN, but it still seems a bit low for me. 8.8 or 9.0 would be much more accurate even for them. For our Mid-core Gamer purposes, I will score this one a 95 out of 100. If own a Wii (or PS2 for that matter) and ever loved to play actual real-life pinball, you should get this game right now.

(No disclosure: I am not related to nor do I personally know any of the companies, designers, programmers, etc. mentioned in this review. I simply as "giddy as a school girl" over this game and wanted to make sure the names of some of the otherwise anonymous yet talented people involved received a mention)

14Mar/080

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

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

Michael Baczynski, on his blog at http://lab.polygonal.de/ds/, has great new set of AS3 Data Structures for game Developers. The structures include: Multi-Dimensional Arrays, Queue, Stack, Tree, Binary Tree, Binary Search, Linked List, Heap and Priority Queue, Graph, and Bit Vector classes. It looks like AS3 now has the equivalent of the data structures in the C++ STL. For those of us that have been forced to look to C++ books and literature for advice on data structures, we now have our own versions of the STL ones we have been envious of for years! This is a great project, and it part of the Google Code library.

Looking around Michael's site, I also found his MOTOR2 engine, A rigid-body, iterative, impulse-based 2D physics engine for ActionScript 3 based on Box2D. This looks very promising! While building this engine, he came up with a great list of optimizations for AS3 code based on the theory that you should use as few instructions as possible to optimized your AS3 code.

  • precompute as much as possible
  • avoid frequent object creation/deconstruction, instead create objects once and reuse them
  • constrain function calls, inline critical parts
  • inline vector and matrix math
  • use linked lists for data structures that are constantly under modification
  • limit array access (especially nested arrays)
  • accept code duplication instead of trying to encapsulate everything
  • avoid using flash’s build in methods, e.g. the Math class
  • prefer ‘extend and override’ instead of defining interfaces (template pattern)
  • don’t cast objects often

Mochiland has put up Steve's latest tutorial, Anatomy of a Flash Game: Lesson 3 - MochiAds, MochiBot and MochiAds Leaderboards.

New 8bitrocket Friend, and now day-job partner in crime, Bryson Whiteman, as a very interesting post about Inspiring 3d Flash tech demos. Squize (at www.gameingyourway.com) also has a link to the same http://cubo.cc/ demo in Bryson's post. It is an amazing example of what Flash can do in capable hands.

Another free and very impressive engine is the FFilmation Engine. The engine is based on and pays tribute to the filmation engine used in 8 bit adventure games. It is an AS3 engine for making isometric style games.It looks very promising and I encourage anyone interested in making Flash Games to check out and support the effort.

Filed under: Book Reviews No Comments
6Mar/080

Where the @#$! Is Pinball Hall Of Fame, Williams Collection?

I'm a pinball fan. there, I've said. I love playing actual pinball tables, and I love playing simulated pinball on computers and consoles. My wife and I spent the first two years dating in the early 90s playing a Williams machine named Machine Bride Of Pinbot at the local Pizza Hut. I used to rack up high scores for hours using the bonus/nudge trick on the Atari VCS version of Video Pinball. One of my favorite Atari 800 games was Bill Budge's Pinball Construction Set (which just might be the greatest 8-bit game ever made). One of my favorite early 90's PC games was Tristan Pinball. Heck, I even own the Lynx cartridge with the Elivra pinball game on it. A couple years back I bought the the first Pinball Hall Of Fame Gottleib Collection for the PS2 and was very pleased.

When Pinball Hall Of Fame Williams Collection was announced for the Wii last year I thought I had gone to heaven. Williams was arguably the best pinball company to ever make pinball machines, and the highly detailed and exacting simulations from this series promised some of the best simulated pinball action ever. Furthermore, since it is harder to find a working pinball machine in the wild these days than it is find a slot machine wit han open continer on top in Salt Lake City, I was pretty jazzed to have some of my old favorites at my fingertips once again. I waited while the game was delayed for Christmas release, then again as it never showed for it's rescheduled release near my birthday in January. I waited until the 3rd posted released date of Feb. 13th, and then the updated release date of Feb. 20th. When I finally saw it was supposedly on the "the street" I hit every store in the area and no one carried the game. Even Gamestop, who stocks stuff like Cosmic Family, doesn't even have it in their system. Yeah, you read that right, Gamestop claims to have never heard of the game.

However, the game does exist. An informal and very positive review popped-up today at the Quarter To Three Forums and the game is listed in-stock at Amazon.com with some great reviews. However the developer Crave seems to have completely disowned it (however they are pushing this), and almost every online review site has ignored the game. So what has happened? How did a fairly high profile release from an established developer coming out in the dregs of February get such shabby treatment? Is pinball, even simulated pinball really that ghettoized these days? If anyone has any information about the fate of this game, please post a comment or write to us. We'd love to follow this story to its conclusion. Meanwhile, I'll order the game from Amazon.com and wait a week for it to show-up. I'd rather be playing the game tonight, but no brick and mortar store, it seems, in interested in taking my $29.99 from my wallet...and it'd damned shame.

Filed under: Game Reviews No Comments
3Mar/080

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

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

Mochiland, always a great place for the Flash Game Developer, has a very good new article on designing thumbnails for games on portals. It discusses using faces, contrasting colors, embellishment (like Atari 2600 cart graphic art) and more. It is a great read by indie game author, Sam Horton.

nGFX at the GamingYourWay.com blog has started a new 3D game. It should be interesting to follow his progress as his skills are top notch.

Steve Street, an accomplished 3D Engine Builder, has a nice little article on his first Flash Game. It is a impressive little lunar lander style game. It shows what a good developer can do in a few hours with the tools at his disposal. Steve, get some Mochi Ads in that game!

I haven't done any real 3D in Flash, but the subject fascinates me. Just Another Game Development Blog has a fascinating entry on Bump Mapping In Flash. For the uninitiated (me included),bump mapping is a technique used to create highly detailed surfaces on 3D objects. Maybe I should get my butt in gear and try out Papervision.

Flashgameu.com has a host of new entries to delight the novice game developer. Here, Gary teaches how to create a random number between -1 and 1 (not as simple as it first sounds especially for a novice). He has a video tutorial on how to use mouse movement to fade a movieclip. And Finally, he explains the often needed getDefinitionByName method, which can be used to instantiate a class with a String value.

That's is for this week. If you have a game development blog (or even an entry) and want to be included in these aggregate postings, please send an email to info@8bitrocket.com.

Oh yeah, we added 2 new arcade diversions for you to try out:
The first is a classic shooter where you use the Atari 800 computer to blast rivals in the 80's. It is called Home Computer Wars: Alpha Mission.
Also, we added a game I have been working on for a few months called 8bitrocket Pumpkinman. It is a retro-enhanced version of Pac Man.

   
This site is protected by Comment SPAM Wiper.