Deconstruction of a Simple World


You have now seen the geometry and trigonometry needed to understand the isometric system's orientation with respect to the Flash system and how to handle z-sorting in an isometric world. With these concepts under your belt, it's an appropriate time to start thinking about more fun stuff, like the isometric world itself. In this section we deconstruct a file that contains a character, and see how the character interacts with its surroundings.

graphics/cd_icon.gif

Open iso_world.swf from the Chapter08 directory on the CD. You'll quickly see that this file looks almost the same as the depth.fla file we used in the previous section. The only cosmetic difference is that instead of moving a ball, you'll be moving a character. In this movie you can click anywhere on the tiles and the character will start walking toward the point where you clicked. You'll see that a tile can be either grassy or concrete, and can contain either no objects or a plant or block. The character cannot walk through an object and so will stop walking when the next step would bring a collision.

graphics/08fig17.gif

Now open iso_world.fla in the same directory. Here you'll see that not only does the file look similar, but the majority of the ActionScript is the same, too. Before talking about the ActionScript, let's take a look at the character movie clip itself. Open the library and double-click the character library item.

graphics/08fig18.gif

Notice that there are 16 frame labels in this timeline. The artist who created this character created eight different angles of it. For each of the eight angles there is a standing pose and a walking animation (called a walk cycle). The standing pose for the first angle is in the frame labeled stand1, and the corresponding walk cycle is in walk1. In the SWF, when you click somewhere on the tiles, the angle your mouse makes with the character (in the isometric world, not in the Flash system) is calculated. From this angle the script determines which of the eight character angles to display. We then move to the walk cycle for that angle. When the character stops walking, we move to the stand frame for that angle.

Now double-click the tiles library item. This is the movie clip that is attached to the screen several times to create the floor. There are two movie clips within this timeline, called innerTile and objects. The innerTile movie clip contains two frame labels, Grass and Concrete.

The objects movie clip contains two frame labels, Block and Plant. The Plant label has an isometric view of a plant, and the Block label has an isometric view of a thick tile that protrudes from the floor. With this simple setup we can easily create many types of tiles. For instance, we can show a plant but change the floor of that tile to show concrete instead of grass. This simple architecture is very flexible.

Now move back to the main timeline. Select the frame in the Actions layer, and open the Actions panel. We are going to go through the majority of the ActionScript in this frame, much of which was not discussed in the previous section.

First, let's look at the buildWorld() function. This function is called to initialize the world itself. It creates the objects needed to store information about the world, and calls functions to do things like creating the tiles and initializing the character.

 1   function buildWorld(maxx, maxz) { 2      world = new Object(); 3      world.maxx = maxx; 4      world.maxz = maxz; 5      world.cellWidth = 29; 6      world.width = maxx*world.cellWidth; 7      world.length = -maxz*world.cellWidth; 8      world.path = this.floor; 9      var path = world.path; 10     buildFloor(path); 11     buildCharacter(path); 12  } 

In line 2 we create an object called world that is used to store information about the world. It stores the array that represents the tiles (created with the buildFloor() function), properties of the world dimensions (maxx and maxz), and the object that represents the character. This object will be discussed soon, when we cover the buildCharacter() function. Also stored on this object is the tile's width in the isometric world.

How Wide Is Your Tile?

You might wonder how you can find out the width of a square tile in an isometric world. Easy you just use the Properties inspector to find the tile's width in the Flash system, and then divide this number by the square root of 2 (approximately 1.414). The result is the width of this tile in the isometric world. Alternatively, if you are the one creating the tile, then you can do this trick:

  1. Draw a square in Flash (say, 100 by 100).

  2. Rotate the square by 45°.

  3. Scale down this square's height by 50 percent.

What you are left with is precisely what that square looks like in an isometric world. And you know what its width is in the Flash world because you created it (100, in this case).

graphics/08fig19.jpg

Now let's look at the buildCharacter() function (which is called in line 11 of the ActionScript on the previous page):

 1   function buildCharacter(path) { 2      world.char = new Object(); 3      world.char.tempx = 10; 4      world.char.tempy = 0; 5      world.char.tempz = -10; 6      world.char.speed = 4; 7      world.char.feeler = 10; 8      world.char.width = 10; 9      world.char.xmov = 0; 10     world.char.zmov = 0; 11     world.char.moving = false; 12     world.char.clip = path.character; 13     positionCharacter(); 14  } 

This function initializes the object that represents the character. This object is called char and is on the world object. In lines 3 5 we set the temporary position of the character to give it a starting place. Through the course of the other functions that are called, this temporary position becomes the character's current position (that is, tempx, tempy, and tempz become x, y, z). In line 6 we create a variable called speed. When you click a tile, the character will attempt to walk there. The speed at which the character walks is determined by the value of the speed variable.

Next, we set an oddly named variable called feeler. This one requires a little explanation. In our previous file examples (and in this file as well), the object being moved around is represented by one point. We all know that a character in real life is three-dimensional and hence characterized by more than one point. If we use just one point to determine where the character is going, then when the character is on the edge of a tile bordering an object such as a block, some of the character is already on the block's tile. This is because the point that represents the character is still on the previous tile, but some of the graphic elements are overlapping the block. This presents a visual problem, but there is no actual programming problem. Everything still works, but it just may not look as good as you would like. There is more than one way to handle, or eliminate, this visual issue. The most proper way is to treat the character as if it were a cylinder or a cube (as we mentioned in Chapter 6, "Collision Reactions"). We're going to take an even simpler approach we'll use something called feelers. Imagine an insect walking around. Before the majority of the insect's body moves onto a new surface, its feelers first inspect that surface. In our case, we extend the feelers a distance of 10 pixels along the direction in which the character is walking. If the feelers find an object in a cell not yet (but almost) reached, the character stops moving. This works amazingly well in this file. The feelers are nothing you can see; they are just code. You will see this technique used in the detectObjects() and worldClicked() functions.

In line 11 we set a variable called moving. This will always have a value of true or false. If true, then the character is moving.

In line 13 the function positionCharacter() is called. This function handles placing the character on the screen. It is called here but will also be called during every frame in an onEnterFrame event:

 1   function positionCharacter() { 2      world.char.x = world.char.tempx; 3      world.char.y = world.char.tempy; 4      world.char.z = world.char.tempz; 5      var temp = iso.mapToScreen(world.char.x, world.char.y,         world.char.z); 6      world.char.clip._x = temp[0]; 7      world.char.clip._y = temp[1]; 8   } 

In lines 2 4 we set the character's x, y, and z positions based on its current temporary positions in memory. We then use this placement to determine the character's x and y placement on the screen using the mapToScreen() method of the isometricAS object. Finally, we place the character on the screen.

The next logical step would be to discuss the function used to move the character from one point to another. But during this movement, collision detection occurs, using the feelers to see if the character is about to enter a tile that contains an object. So before we talk about how to move the character, let's talk about the functions that create the objects on the screen and change the types of tiles that are displayed.

 1   function makeObject(x, z, object) { 2      world.tiles[x][z].isObject = true; 3      world.tiles[x][z].clip.objects.gotoAndStop(object); 4   } 5   function changeGroundTile(x, z, object) { 6      world.tiles[x][z].clip.innerTile.gotoAndStop(object); 7   } 8   function changeManyGroundTiles(x, xnum, z, znum, object) { 9      for (var i = 0; i<xnum; ++i) { 1          for (var j = 0; j<znum; ++j) { 11            world.tiles[x+i][z+j].clip.innerTile.gotoAndStop                (object); 12         } 13     } 14  } 

These three functions are fairly self-explanatory. The first one, makeObject(), adds an object (either a plant or a block) to a tile. It then also sets the property isObject to true in the tiles array. We use the isObject property with the feelers when detecting a collision (more on this later). The next function, changeGroundTile(), simply changes the type of tile displayed. You can change a tile from grass to concrete or the other way around. The function changeManyGroundTiles() does the same thing as changeGroundTile() except that it applies to many tiles at once. You specify the starting x and z positions and then how far to extend in each direction.

Now that we've got that collision-detection discussion taken care of, we can move on to the "next logical step" I mentioned above moving the character itself. When you click the mouse button, the well-named worldClicked() function is called. This function maps your mouse pointer's x and y positions onto the x, -z plane in the isometric world. It then takes these values and compares them with the world's boundaries. If the mouse was clicked within the world's boundaries and the character was not already moving, then many things happen. Let's look at those things.

 1   function worldClicked(xm, ym) { 2      var temp = iso.mapToIsoWorld(xm, ym); 3      var xm = temp[0]; 4      var zm = temp[1]; 5      if (!world.char.moving && xm>=0 && xm<=world.width         && zm>=world.length && zm<=0) { 6         var x = world.char.x; 7         var z = world.char.z; 8         world.char.startx = x; 9         world.char.startz = z; 10        world.char.endx = xm; 11        world.char.endz = zm; 12        var angleSpan = 360/8; 13        var angle = Math.atan2(zm-z, xm-x); 14        var realAngle = angle*180/Math.PI; 15        realAngle += angleSpan/2; 16        if (realAngle<0) { 17           realAngle += 360; 18        } 19        var frame = Math.ceil(realAngle/angleSpan); 20        world.char.clip.gotoAndStop("walk"+frame); 21        world.char.frame = frame; 22        world.char.moving = true; 23        var cosAngle = Math.cos(angle); 24        var sinAngle = Math.sin(angle); 25        world.char.xmov = world.char.speed*cosAngle; 26        world.char.zmov = world.char.speed*sinAngle; 27        world.char.feelerx = world.char.feeler*cosAngle; 28        world.char.feelerz = world.char.feeler*sinAngle; 29     } 30  } 

The condition in line 5 checks to see if the clicked area is within the boundaries of the world and if the character is not already moving. If the condition is satisfied, then it is OK to proceed and to prepare the character for movement. What does this preparation involve? For the character to be able to move, we have to determine the angle at which to move, the angled frame (1 of 8) to have the character display, and the speed in each direction for the character to walk. In lines 8 and 9 we store the character's starting position. (This starting position is used later to determine if the character has reached the destination.) In the next two lines we store the character's end position. Then, in line 12, we create a variable called angleSpan that stores the amount of degrees for each of the eight possible angles that the character can show. We will then use this value (along with the angle made with the mouse and the character found in line 14) to determine which of the eight frames to display in line 19. When using Math.atan2() to determine an angle, we'll sometimes get negative angles. Negative angles are perfectly valid, but I prefer to work with positive angles. Since angles are cyclic (that is, 350 is the same as -10), we can just add 360 to any negative angle to get its positive representation. This switch is performed in line 16. As mentioned above, line 19 calculates which character frame number to display in the character movie clip. We apply this in line 20 and store the value in line 21. We then set the property moving to true on the char object. Next we store the values of the sine and cosine of the angle, since they are used more than once (lines 23 and 24). We then use trigonometry to calculate the speed at which to move the character (lines 25 and 26), in the same way as we have done many times throughout this book. And finally, we set the feelerx and feelerz values. These are the values we will add to the temporary positions when checking for collisions.

Next we have the moveCharacter() function. This function is called in every frame.

 1   function moveCharacter() { 2      if (world.char.moving) { 3         world.char.tempx = world.char.x+world.char.xmov; 4         world.char.tempz = world.char.z+world.char.zmov; 5         world.char.tempy = world.char.y+world.char.ymov; 6         var sx = world.char.startx; 7         var sz = world.char.startz; 8         var ex = world.char.endx; 9         var ez = world.char.endz; 10        var tempx = world.char.tempx; 11        var tempz = world.char.tempz; 12        if ((ex-sx)/Math.abs(ex-sx)            != (ex-tempx)/Math.abs(ex-tempx)            || (ez-sz)/Math.abs(ez-sz)            != (ez-tempz)/Math.abs(ez-tempz)) { 13            world.char.moving = false; 14            world.char.xmov = 0; 15            world.char.zmov = 0; 16            world.char.tempx = ex; 17            world.char.tempz = ez; 18            world.char.clip.gotoAndStop                ("stand"+world.char.frame); 19        } 20     } 21  } 

The first task of this function is to check to see if the character is moving. If so (that is, moving has the property of true), then we move on. Lines 3 5 set the temporary position of the character based on its current position and its speed in each direction. Then we create references to its starting and ending positions so that the already-long if statement in line 12 looks a little more reasonable. The condition we are looking for in line 12 is pretty simple, even though it looks complicated. We are trying to determine if the character has reached its destination. If it has, then the sign (+ or -) of the difference between A) its current position and the destination and B) the starting position and the destination will be different (in either the x or z direction).

graphics/hand_icon.gif

Let's take an example of the character moving only in the x direction. The starting position is 10, and the end position is 100. The sign of the difference between the ending position and the starting position is + (positive). You find this by dividing the difference by the absolute value of the difference:

 (endx-startx)/Math.abs(endx-startx)  

We compare this value with the value of the sign of the difference between the end position and the current position. So if the current position is 30, then (100-30)/Math.abs(100-30) is positive. Since this is the same as the sign from the starting and ending positions, the character has not yet reached the destination (whew!).

At some point the character's current position will be greater than the destination say, 105. The value of (100-105)/Math.abs(100-105) is negative. Since this value no longer matches the positive value found with the starting and ending positions, we know the character has reached the destination. We perform this check for both the x and z directions. Once one of these two conditions is met, the character needs to stop walking.

In line 13 we set the moving property to false and then the velocities to 0. In lines 16 and 17 we set the character's temporary position to be the destination. Then, in line 18, we change the frame the character is displaying to the standing frame.

The last function we need to look at is detectObjects(). This function is called in every frame to determine if the character is about to step on a frame that contains an object (like a plant or a block).

 1   function detectObjects() { 2      //Extend a little in the direction of motion 3      var x = world.char.tempx+world.char.feelerx; 4      var z = Math.abs(world.char.tempz+world.char.feelerz); 5      var x_tile = Math.ceil(x/world.cellWidth); 6      var z_tile = Math.ceil(z/world.cellWidth); 7      if (world.tiles[x_tile][z_tile].isObject != true) { 8         var x = world.char.tempx; 9         var z = Math.abs(world.char.tempz); 10        var x_tile = Math.ceil(x/world.cellWidth); 11        var z_tile = Math.ceil(z/world.cellWidth); 12        var depth = world.tiles[x_tile][z_tile].depth+1; 13        world.char.clip.swapDepths(depth); 14     } else { 15        world.char.tempx = world.char.x; 16        world.char.tempz = world.char.z; 17        world.char.xmov = 0; 18        world.char.ymov = 0; 19        world.char.moving = false; 20        var frame = world.char.frame; 21        world.char.clip.gotoAndStop("stand"+frame); 22     } 23  } 

In lines 3 6 we add the feelerx and feelerz values to the temporary x and z values to determine which tile the feeler is touching. Then, in line 7, a conditional statement checks to see if there is an object in the tile that feelers are in. If there is, then we skip to the else leg of the ActionScript, which stops the character from walking, using the same code we used in the moveCharacter() function. If there is no object in that tile, then we enter the first leg of the if statement. We determine the depth of the tile that the character is currently on, and then add 1 to that depth. Then we move the character to that depth using swapDepths().

We have now discussed all of the functions used in this file. Let's look at when these functions are called. Here are the last 17 lines of ActionScript in this frame:

 1   maxx = 10;  2   maxz = 10; 3   iso = new isometricAS(maxx, maxz); 4   buildWorld(maxx, maxz); 5   _root.onEnterFrame = function() { 6      moveCharacter(); 7      detectObjects(); 8      positionCharacter(); 9   }; 10  makeObject(2, 8, "plant"); 11  makeObject(5, 4, "plant"); 12  makeObject(6, 9, "block"); 13  makeObject(5, 9, "block"); 14  makeObject(5, 8, "block"); 15  changeManyGroundTiles(2, 5, 3, 1, "concrete"); 16  changeManyGroundTiles(6, 1, 3, 5, "concrete"); 17  changeManyGroundTiles(6, 5, 8, 1, "concrete"); 

In lines 1 and 2 we set the number of tiles that are to be used in the world in both the x and z directions. We then create an instance of the isometricAS object, passing in these x and z boundaries. They are used by the isometricAS object when calculating depth. Next, in line 4, we call the buildWorld() function, passing in the x and z boundaries. The buildWorld() function stores this information on the world object and in turn calls the buildFloor() function, which uses these values. Next we set up an onEnterFrame event. This calls the functions moveCharacter(), detectObjects(), and positionCharacter() in every frame. The final eight lines of ActionScript place the objects on the screen and create the concrete tiles.

Generate a SWF from this file. Click different tiles around the world. Notice how the depth of the character changes as the character moves around a plant or a block.

graphics/08fig20.gif

With this basic introduction to isometric worlds, you should be able to start making some very interesting and fun environments that can be used for games or for chats.



Macromedia Flash MX Game Design Demystified(c) The Official Guide to Creating Games with Flash
Macromedia Flash MX Game Design Demystified: The Official Guide to Creating Games with Flash -- First 1st Printing -- CD Included
ISBN: B003HP4RW2
EAN: N/A
Year: 2005
Pages: 163
Authors: Jobe Makar

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net