Tuesday, November 3, 2009

Game Code

Game Code


This game has a lot of ActionScript. Don't be intimidated by it, though, because there is not much in the way of techniques that you haven't seen before, and for the most part the code is simple. It's just that in this game, many things can happen that we have to use code to check for. For instance, when detecting a collision of the hero with a platform, we need to do the following:







  1. Check the left side. If hitting, reposition the hero.
  2. Check the right side. If hitting, reposition the hero.
  3. Check the top. If the hero is on the top, then do we also need to move him a little bit if the platform is moving?
  4. Check the bottom. If the hero hits from underneath, are there collectable items hidden in there? If so, how many? Is the hero crouched under the platform? If so, move him out.

None of those things are difficult�there are just many situations to cover. Those are only a few of the platform-detection questions answered with code. Above I didn't even include cloud properties that we need to check for (such as a tree)!


This game takes a very object-oriented (OO) approach. I used my fledgling knowledge of OO programming to create this game, and I'm happy with the results. The code is easy to understand and can be extended without having to rewrite it.



If you don't already have ice_world.fla open, then open it now. We are going to go through some of the major pieces of ActionScript, but not all of it. (As a reminder, we're looking at the game board movie clip.)


ActionScript Not Found in a Function


There are about 50 lines of ActionScript in this game that are not contained within a function (or the onEnterFrame event). These perform the tasks that only need to occur once within the game. We will look at these actions here.


The game object


The game object is the container for all of the information in the game. This includes, but is not limited to, the level data (once it's been loaded from an XML file), the hero object, and all of the constants used throughout the game, such as friction and gravity. The game object is defined first:



game = {};


The bg object


The bg object is created to store information about the background image. The background image is scrolled subtly as the hero runs around the level. In order to calculate how much to scroll the background, we need to know the width and height of the background. All of this information is stored in the following lines of ActionScript:



1 game.bg = {};
2 game.bg.clip = gameClip.bg;
3 game.bg.x = 0;
4 game.bg.y = 0;
5 game.bg.height = game.bg.clip._height;
6 game.bg.width = game.bg.clip._width;


The overlay object


This object is used to store information about the overlay movie-clip instance inside the gameClip instance. That is the movie clip that contains the hero as well as all of the other level elements except the background.


We create an object for the overlay so that we can keep track of its position in an OO way.



1 game.overlay = {};
2 game.overlay.clip = gameClip.overlay;
3 game.overlay.x = 0;
4 game.overlay.y = 0;

You can see in lines 3 and 4 that we initially give the overlay object x and y positions of 0. This is because it starts at that position. When the hero moves around the level, the overlay will also move, and its x and y positions will be updated.



The hero object


The hero object serves many functions. It represents the hero character of the game, and it stores a lot of information about the hero, such as his position, his dimensions, and the number of lives he has. In addition to what you see here, we will define a lot of methods onto this object later in the code. Those methods will allow for events like jumping, walking, and crouching.



1 game.hero = {};
2 game.hero.clip = gameClip.overlay.man;
3 hero = game.hero;
4 hero.lives = game.numLives-1;
5 clip = game.hero.clip;
6 hero.xmov = 0;
7 hero.startx = 50;
8 hero.starty = 320;
9 hero.standingHeight = 50;
10 hero.crouchingHeight = 30;
11 hero.width = 23;
12 hero.height = hero.standingHeight;
13 hero.ymov = 0;
14 hero.minXmov = .75;
15 hero.maxXmov = 12;
16 hero.groundWalkIncrement = 1.7;
17 hero.airWalkIncrement = .85;
18 hero.pushSpeed = 4;
19 hero.walkIncrement = hero.groundWalkIncrement;
20 hero.jumpSpeed = 23;
21 hero.clip.swapDepths(1000000);

Notice that in line 3 we define a shortcut to the hero object. This is done as a convenience and is not required. It is just easier to type hero than game.hero. In line 9 we set a property to the hero object called standingHeight. This is the height of the hero when he is standing. In the next line we set a property called crouchingHeight. This is the height of the hero when he is crouching. When the hero is on the level and he crouches and then stands, internally the code switches the hero.height property between standingHeight and crouchingHeight. This allows the hero to fit into places when crouched that he couldn't when standing.


In line 14 we set minXmov. When the hero is moving at a speed less than this number, we set his speed to 0. In the next line we set maxXmov. This is used as a speed cap�we don't let the hero move at an x speed faster than this number.


In line 16 we set a property called groundWalkIncrement. This is the amount added to your x speed in each frame in which you have the left or right arrow key pressed. Notice that the next line sets a property called airWalkIncrement, and its number is a little smaller than the value in groundWalkIncrement. We do this because you should have control over your character in the air, as in other popular platform games�but not quite as great control as if you were on the ground. (In line 19 there is a walkIncrement property whose value gets changed from the groundWalkIncrement value to the airWalkIncrement value based on the location�ground or air�of the hero).


In line 18 we set a property called pushSpeed. If there is a platform you can only move under by running, crouching, and sliding, then we need to make it so that you can't get stuck. So if you are under a platform and you are crouched, then you slowly get pushed in a certain direction, based on a speed of pushSpeed.


Next we set jumpSpeed. This is the initial speed at which you move upward on the screen when you jump. Finally, we swap the hero movie clip to a very high depth. At this new depth, the hero will always be above everything else.



Miscellaneous Property Definitions of the Game Object


These are the rest of the properties on the game object. Except for lines 6�11, this ActionScript should be easily understood with no further explanation.



1 game.level = 1;
2 game.numLevels = 5;
3 game.runDecay = .85;
4 game.depth = 10000;
5 game.floor = 330;
6 game.viewableHeight = 350;
7 game.viewableWidth = 680;
8 game.walkAbleWidth = 4800;
9 game.walkAbleHeight = 4000;
10 game.xScrollFactor = (game.bg.width-game.viewableWidth)/
game.walkAbleWidth;
11 game.yScrollFactor = game.bg.height/game.walkAbleHeight;
12 game.gravity = 2;
13 game.windResistance = .92;
14 game.numLives = 3;

Let's look at lines 6�11. The goal in these lines is to determine how much the background needs to be scrolled when the hero moves. We do this by storing the dimensions of what we can see and the dimensions of what we can't see. In lines 6 and 7 we store the viewable dimensions of the level. This tells us that at any given time we can see 680 pixels across and 350 pixels up and down. The next two lines define how far (at a maximum) we will allow the hero to walk both horizontally and vertically. These numbers are arbitrary; you can pick any that you want. The idea is, though, that if the character were to walk from one side of the defined area all the way to the other side, then the background image will have scrolled one full scroll … so I'd recommend using large numbers as I did here. The large number will make it so that the background won't scroll too fast and will appear to be moving fairly naturally or realistically. If you think the background is still scrolling too fast, then make these numbers even larger. In lines 10 and 11 we determine the scrolling factors based on a ratio of the background-image dimensions to the walkable areas.




levelLoaded()


The ActionScript used in this function deals almost entirely with parsing through the XML, which is beyond the scope of this book. For information on XML parsing, please see Appendix D, "XML Basics in Flash." But here, we should at least go over the big picture of what this function does. Once the level XML file is loaded, this function is executed. It parses through the XML and creates a few objects and some arrays, and then adds the movie clips to the level. Here are the objects and arrays it creates:


game.flag
This object is used to store the x and y positions as well as a movie-clip reference of the flag.


game.platforms.column
This array stores an object for each platform that contains information about that platform, including the x and y positions, type of platform, width and height of the platform, and a reference to the movie clip.


game.baddies
This array contains an object for each enemy. It contains the enemy's position as well as a reference to the enemy's movie clip.


game.collectables
This array contains an object for each collectable item; the object stores the x and y positions of the item and a reference to its movie clip.


game.grounds
This array stores objects representing the areas of the ground that are walkable (meaning that they are not gaps in the floor).



The onEnterFrame Event


As with most games, in Ice World we have an onEnterFrame event that is set up to repeatedly call a series of functions. Here it is:



1 this.onEnterFrame = function() {
2 if (game.inPlay) {
3 if (!hero.dead) {
4 listenForKeys();
5 }
6 addFrictionAndGravity();
7 createTempPosition();
8 if (!hero.dead) {
9 baddyDetection();
10 platformDetect();
11 collectableDetect();
12 detectFlag();
13 checkFloor();
14 }
15 renderScreen();
16 }
17 };

There is an if statement that contains all of the function calls. It executes the actions within the if statement if game.inPlay is true. The inPlay property of the game object is set to true when the startLevel() function is called.


There is a property on the hero object called dead. When the hero dies, dead is set to true; otherwise it is false. Line 4, listenForKeys(), is executed if hero.dead is not true. The listenForKeys() function checks to see if any of the arrow keys or the spacebar is currently pressed. If one of those keys is pressed, the hero's movement is affected. Lines 6 and 7 decrease the horizontal movement due to friction (either air friction or ground friction) and increase the hero's y velocity by the gravitational amount.


In line 8 we check (again) to see if the hero is dead. If he's not, then we execute lines 9�13. In line 9 we check for collisions between the hero and the enemies. In line 10 we execute platformDetect(), which checks for collisions between the hero and the platforms. We then check for collisions between the hero and the collectable objects by executing collectableDetect() in line 11. In line 12 we check to see if the hero has reached the flag, and in line 13 we check to see if the hero has fallen far enough to stand on the floor.



listenForKeys()


This function is called in every frame by the onEnterFrame event. It checks to see if the directional arrow keys or the spacebar is pressed. If any of those hero-control keys are pressed, then the hero's current state may change (for example, walking changes to a jump). If you think about it from a programming point of view, you can see that controlling the hero and making him look as real as possible can get a little confusing. Here are some rules and behaviors for Ice World.



  • The hero can only jump if he is currently on the ground or on a platform. (In some games the hero can jump in mid-air, but not in this game.)

  • The hero can jump when crouching, but cannot walk when crouching.

  • While in the air (crouching or not), the hero can be controlled to move left or right.

  • The controllability of the hero is less sensitive while in the air. That means that if the hero jumps, the game player can control the hero's direction, but not as easily as if the character were on the ground.


When you can look at a bulleted list of rules, as above, then the functionality doesn't seem as confusing to code. Still, there are a few things that can be tricky to control. For instance, when you press the down arrow button, the hero should crouch. But what if the hero ran, then crouched and slid underneath a platform, and then you released the down arrow button? The hero would then stand up if you (as the programmer) did not foresee this situation. What happens in this situation (in this game and in other similar games) is that the hero stays crouched but is slowly pushed (by code) to one side of the platform or the other. When the hero reaches the edge, he is automatically un-crouched. You will see more about this in the platformDetect() function.


Here is the listenForKeys() function:



1 function listenForKeys() {
2 hero.wasCrouching = hero.isCrouching;
3 if (hero.inAir) {
4 hero.walkIncrement = hero.airWalkIncrement;
5 } else {
6 hero.walkIncrement = hero.groundWalkIncrement;
7 }
8 if (Key.isDown(Key.RIGHT) && (!hero.isCrouching ||
(hero.isCrouching && hero.inAir))) {
9 hero.xmov += hero.walkIncrement;
10 hero.walkRight();
11 } else if (Key.isDown(Key.LEFT) && (!hero.isCrouching ||
(hero.isCrouching && hero.inAir))) {
12 hero.xmov -= hero.walkIncrement;
13 hero.walkLeft();
14 }
15 if ((Key.isDown(Key.SPACE) || Key.isDown(Key.UP)) &&
okToJump) {
16 okToJump = false;
17 if (!hero.isJumping && !hero.inAir) {
18 hero.inAir = true;
19 hero.ymov -= hero.jumpSpeed;
20 hero.jump();
21 }
22 } else if (!Key.isDown(Key.SPACE) &&
!Key.isDown(Key.UP)) {
23 okToJump = true;
24 }
25 if (Key.isDown(Key.DOWN)) {
26 hero.crouch();
27 } else {
28 hero.unCrouch();
29 }
30 }

The first thing we do (line 2) is store the current crouching state of the hero. We do this to help with the situation described above (sliding under a platform and then trying to stand). In the platformDetect() function we will use this stored crouching state, hero.wasCrouching, to determine if the hero should be pushed out.


Next, in lines 3�7, we change the hero.walkIncrement property depending on whether the hero is on the ground or platform, or in the air. The value of hero.walkIncrement determines the character's sensitivity (that is, its acceleration).


In lines 8 and 11 we check for similar conditions. If the right arrow key is pressed and the hero is not crouching, we move the hero to the right. If the right arrow is pressed and the hero is crouching and is in the air, we also move the hero to the right. If any other situation is happening, we do not move the hero to the right. In line 11 we do the same check, except with the left arrow key.


Lines 15�24 control the hero's jumping. In line 15 we check to see if either the spacebar or the up arrow key (both of which control jumping) is pressed, and if okToJump is true. The variable okToJump is set to true when neither the spacebar nor the up arrow key is currently pressed. This makes it so that you cannot just hold down the spacebar or the up arrow key and have the hero jump automatically as soon as he touches the ground�you have to release the jump key and press it again for the hero to jump again. Notice that when the conditions exist for a jump to occur (that is, the condition in line 15 is fulfilled), hero.inAir is set to true. We keep track of when the hero is in the air and when he is on the ground.


In lines 25�29 we check to see if the down arrow key is pressed. If it is, then we tell the hero to crouch by invoking hero.crouch(); otherwise we call hero.uncrouch(). If you are thinking ahead, you may wonder if this interferes with the scenario of sliding under the platform and then un-crouching mentioned above. It doesn't affect it, because we check for this condition in a function that is called after the platformDetect() function. If we detect that the hero should still be crouching, then we call hero.crouch() and the game player never sees the hero stand up (until appropriate).



platformDetect()


If you take the time to understand any one function in this game, let it be this one. It is pretty long (77 lines) but not too difficult. As mentioned earlier in this chapter, there are many situations we have to take into account when our hero is colliding with a platform. This function does all of that. It not only detects if he is colliding with a platform, but it determines the properties of the platform (such as solid, cloud, moving) and makes the hero react accordingly. Here it is:



1 function platformDetect() {
2 var oldOnPlatform = hero.onPlatform;
3 var onPlatform = false;
4 for (var i = 0; i<game.platforms.column.length; ++i) {
5 var platform = game.platforms.column[i];
6 var px = platform.x;
7 var py = platform.y;
8 var pw = platform.width;
9 var ph = platform.height;
10 var type = platform.type;
11 for (var iteration = 1; iteration<=
game.totalIterations; ++iteration) {
12 hero.tempx = hero.x+
(hero.xmov/game.totalIterations)*iteration;
13 hero.tempy = hero.y+
(hero.ymov/game.totalIterations)*iteration;
14 if ((hero.tempx+hero.width>px) &&
(hero.tempx<px+pw) &&
(hero.tempy-hero.height<py+ph) &&
(hero.tempy>py)) {
15 // find which side he hit.
16 if (hero.tempy>py && hero.y<=py+.01 &&
hero.tempy<py+ph && hero.ymov>0) {
17 //landed on top
18 //the .01 is for a comparison error
19 var onPlatform = true;
20 var platformTop = py;
21 landOnPlatform(platformTop);
22 if (platform.mover == "yes") {
23 var xinc = platform.xspeed+
hero.xmov*.5;
24 hero.x += xinc;
25 hero.y += platform.yspeed+
hero.ymov*.5;
26 hero.tempx = hero.x;
27 hero.tempy = hero.y;
28 }
29 } else if (type != "cloud" &&
hero.tempy-hero.height>py &&
hero.tempy-hero.height<py+ph &&
hero.tempx+hero.width/2>px &&
hero.tempx<px+pw-hero.width/2 &&
hero.ymov<0) {
30 //hit from underneath
31 var newy = py+ph+hero.height;
32 bounceOffOfBottom(newy);
33 if (platform.container) {
34 platform.clip.item.gotoAndPlay
("Display");
35 display.num2 = Number(display.num2)+1;
36 playSound("collect");
37 if (--platform.containerCounter<=0) {
38 platform.container = false;
39 }
40 }
41 } else if (type != "cloud" &&
hero.tempx+hero.width>px &&
hero.tempx+hero.width<px+pw) {
42 //hit the left side of the platform
43 if (!hero.wasCrouching) {
44 var newx = px-hero.width;
45 bounceOffOfPlatform(newx);
46 } else {
47 hero.crouch();
48 var crouchMove = true;
49 var left = true;
50 }
51 } else if (type != "cloud" && hero.tempx>px
&& hero.tempx<px+pw) {
52 //hit the right side of the platform
53 if (!hero.wasCrouching) {
54 var newx = px+pw;
55 bounceOffOfPlatform(newx);
56 } else {
57 hero.crouch();
58 var crouchMove = true;
59 var left = false;
60 }
61 }
62 }
63 }
64 }
65 hero.onPlatform = onPlatform;
66 if (crouchMove) {
67 if (left) {
68 hero.tempx -= hero.pushSpeed;
69 } else {
70 hero.tempx += hero.pushSpeed;
71 }
72 }
73 if (!hero.onPlatform && oldOnPlatform) {
74 //he just left a platform
75 hero.inAir = true;
76 }
77 }

First we store the current state of the onPlatform property of the hero object as oldOnPlatform. We use this at the end of the function to determine if the hero has just left a platform without jumping. Next we set the local variable onPlatform to false. We assume that the hero is not on a platform. Then, through the loops and conditions that follow we determine if the hero is actually on a platform.


In line 4 we initiate a for loop that loops through the platform objects in the column array (which stores the objects that represent the platforms). In line 5 we create a reference to the current object in the column array called platform. Then we create some local variables to store some of the properties of this platform (lines 6�10).


In line 11 we do something that may not be immediately obvious. If you remember the description of frame-dependent collision detection in Chapter 5, "Collision Detection," it is like a snapshot in time. If one or both of the objects are moving fast enough, then they can pass through each other without being detected. That is a possibility in this game, too. In fact, I ran into that issue while programming this game and had to implement the technique I'm about to describe. Here is what happens in line 11: We take the position where the hero was on the last frame and the position where he is on this frame, and then divide that distance into steps. We then run through the collision-detection routines on each step. If a collision is found, we stop the looping. Most of the time, you'll find that one loop is enough. It is just when the hero is moving over a certain speed that we need to use more than one loop. So the property that controls the number of loops, game.totalIterations, is controlled in every frame by the createTempPosition() function. That function changes game.totalIterations based on the hero's speed.


If the condition in line 14 is met, then the hero is colliding with a platform. At that point, more conditions must be checked to determine exactly what needs to be done with the hero. If the condition in line 16 is met, then the hero has landed on top of the platform. We also check to see if this platform is one that moves, and if it is, we move the hero a little bit.


In line 29 we check to see if the hero hit the platform from underneath. If he did, then we also check to see if that platform contains a collectable item, and if so, then we display it.


In lines 41 and 51 we check to see if the hero is colliding with either the left or right side of the platform. If he is, and he wasn't just crouching, we move him to the boundary of the platform. If he was just crouching, we keep him crouching and slide him to one side or the other (see lines 66�72).


If the condition in line 73 is met, the hero has just left the platform without jumping. We then set the hero.inAir property to true.



baddyDetection()


This function is called in every frame. It determines whether there is a collision between the hero and an enemy, by using the hitTest() method of the movie-clip object.



1 function baddyDetection() {
2 for (var i = 0; i<game.baddies.length; ++i) {
3 var baddy = game.baddies[i];
4 if (hero.clip.hitTest(baddy.clip.hitArea)) {
5 if (hero.ymov>game.gravity) {
6 //baddy squashed
7 hero.tempy = hero.y;
8 hero.ymov = -10;
9 killBaddy(baddy, i);
10 } else {
11 //hero died
12 hero.ymov = -20;
13 hero.xmov = 15;
14 hero.die();
15 }
16 }
17 }
18 }

Each enemy is represented by an object. These objects are stored in the baddies array. In this function we loop through the baddies array, checking for a collision between the hero and each enemy. If the condition in line 4 is met, there has been a collision between the hero and an enemy. But then we have to determine who dies�the hero or the enemy. If the hero's y velocity is greater than that of gravity (that is, the hero is falling and not standing), then the enemy dies. Otherwise, the hero dies. That's all there is to this detection!



collectableDetect()


In every frame, we call this function and loop through the list of collectable items on the screen to determine if the hero is touching any of them:



1 function collectableDetect() {
2 for (var i = 0; i<game.collectables.length; ++i) {
3 var ob = game.collectables[i];
4 if (hero.clip.hitTest(ob.clip) && !ob.captured) {
5 display.num2 = Number(display.num2)+1;
6 playSound("collect");
7 ob.captured = true;
8 ob.clip.gotoAndPlay("Capture");
9 }
10 }
11 }

The collectable items are stored in an array called collectables. We loop through this array and use hitTest() to determine whether a collision is occurring. If the hero is colliding with a collectable, then the item should be collected, and so we animate the item and play a sound.





No comments: