RPG Programming:
Tiles
      
by SeiferTim : 24 November 2004

While HitTesting is a wonderful thing, it can be a little finicky in practice sometimes, as you'll see with practice. Not only that, it tends to be difficult to use when making maps, as we're going to do with this RPG. Almost all classic-style RPGs use tiles, at the very least for background use. As you'll see, using tiles can be very useful. Start out by opening up your favorite painting program, Photoshop will work just as good as MS Paint. First up, you want to determine the base size for all your tiles. I made them 64 X 64, but you can pick whatever you like. Then, draw away! I simply made 3 tiles: One for basic grass, one for Water, and one for a rock wall. Save them, and then open up Flash. Make a new MovieClip, named "TileSet1", and on frame one import your first tile, frame 2 the second, and frame 3 the third. Align them all to the top-left of the Stage, like this:

[ placement of the tiles ]

I would make the tile you plan to use the most as the first frame, but its up to you. Now, once you've done this, put "stop();" into the ActionScript of each frame. This is just a precaution. Make a new layer on the main stage, below everything else. Next, right-click on the MovieClip in the library, and choose "Linkage". Check the first check-box, where it says: "Export for ActionScript", and hit "Okay"..

Next, set the height and width of your movie to something divisible by the height and width of your tiles. In this case, 576 is my height, and width.

Now, get ready for some more advanced ActionScripting...Fun. Basically, what we need to do is tell Flash which Tile is which, and what those tiles are allowed to do. We do this by using some prototypes, and OOP, and other fun things which I really don't feel like trying to explain in great detail. For those who care, you can find some great tutorials on those subjects right here on Kirupa - written by people who know a whole lot more than I do on the subject. Anyway - this is pretty simple though.

Make a new Movie Clip, name it "blank", and click on "Advanced". Check where it says "Export for ActionScript". Keep this MovieClip blank, and then go back to the Main Timeline. Copy the following code to frame 1 of the main timeline:

mvWdth = 576;
mvHght = 576;
tileWdth = 64;
tileHght = 64;
 
tile0 = function () {};
tile0.prototype.pos = 1;
tile0.prototype.barrier = false;
 
tile1 = function () {};
tile1.prototype.pos = 2;
tile1.prototype.barrier = true;
 
tile2 = function () {};
tile2.prototype.pos = 3;
tile2.prototype.barrier = true;
 
map1 = [[2,2,2,2,2,2,2,2,2],
[2,2,2,0,0,0,0,0,2],
[2,2,1,0,0,0,0,0,2],
[2,1,1,1,0,0,0,0,1],
[1,1,1,1,0,0,0,0,1],
[1,1,1,1,0,0,0,0,2],
[1,1,1,0,0,0,1,1,2],
[1,1,0,0,0,0,0,1,2],
[1,1,1,1,2,2,2,2,2]];

Now, let me try to explain what this all means... First, I simply told Flash how big our stuff is:

mvWdth = 576;
mvHght = 576;
tileWdth = 64;
tileHght = 64;

Then, I defined our 3 tile prototypes:

tile0 = function () {}; // Tell Flash that there will be a Tile1
tile0.prototype.pos = 1; // this is the number of the frame that the image is in
tile0.prototype.barrier = false; //since the first tile is grass, its not a barrier
 
tile1 = function () {};
tile1.prototype.pos = 2;
tile1.prototype.barrier = true; // the water is a barrier, since you can't walk through it.
 
tile2 = function () {};
tile2.prototype.pos = 3;
tile2.prototype.barrier = true;

Next, I defined our first Map. Now, this map is essentially a 2-Level Array, also known as a Matrix. So, when someone asks you: "What is the Matrix?" you can say: "Its a 2-Level Array". Moving right along, Neo, if you can tell, our Map Matrix is made up of numbers from 0 to 2, and in seemingly random order. What we're going to do, is go through the numbers, one at a time, and place a copy of the "currentTileSet" clip on the stage, in the order that the map shows. This is how we do it:

function MapMaker (map) {
_root.attachMovie("blank", "tiler", d++);
var mapWdth = map[0].length;
var mapHght = map.length;
for (var i = 0; i < mapHght; i++) {
for (var j = 0; j < mapWdth; j++) {
var name = "tile_"+j+"_"+i;
_root[name] = new _root["tile"+map[i][j]];
_root.tiler.attachMovie("TileSet1", name, i*100+j*2);
 
_root.tiler[name]._x = (j*_root.tileWdth);
_root.tiler[name]._y = (i*_root.tileHght);
_root.tiler[name].gotoAndStop(_root[name].pos);
 
}
}
}
 
MapMaker(map1);

What this does, is create a new instance of the "blank" mc on the stage, which will hold all our tiles. Then, it goes one-by-one through our Map Matrix, and checks the number to see which tile it wants, and it makes a new tile on the stage, changing the frame to match what our tile prototype says. And finally, it moves the tile to the point of the stage that it should go. Simple, huh? The last line of code: "MapMaker(map1);", is how we call the function to do all the work. The reason this is in a function, is so that you can change maps, which we will do later.

When you run the movie, you'll see a map, based on what your map matrix looked like But what happened to our guy? Well, he was buried under the tiles. Sucks to be him. We can easily fix this by making our Guy get placed on the screen after the tiles. So, go ahead and delete our Guy MC from the main stage, as well as the coins and whatever else you may have dragged there. (I recommend saving the file under a new name at this point, to keep from having losing anything...)

Next, we want to go to our library, and right-click on the Mc the contains the Guy graphic. In this case, I named it: "moving_mc". Choose "Linkage", and set it to "Export for ActionScript", I named mine "guy_mc", but you can name it whatever you like as long as you know what part of the next few lines of code to change.... Next, make a new Matrix in the first Frame of the main timeline, which will look like this:

itemMap1 = [[0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,1,0,0,0],
[0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0]]

Essentially, we're going to have 2 layers on the screen. The first one will be the map, and all the background stuff, and the top one will be the Player, and all interactable items. Simple enough, right? This new matrix should be the same size as the map matrix - there's no reason why this should be otherwise. Next, create the function to place all the items on the screen:

function placeItems (map) {
_root.attachMovie("blank", "items", this.getNextHighestDepth());
var mapWdth = map[0].length;
var mapHght = map.length;
for (var i = 0; i < mapHght; i++) {
for (var j = 0; j < mapWdth; j++) {
switch (map[i][j]){
case 1 :
_root.items.attachMovie("Guy","guy_mc", d++);
_root.items.guy_mc._x = (_root.tileWdth * j);
_root.items.guy_mc._y = (_root.tileHght * i);
}
}
}
}

And call it with:

placeItems(itemMap1);

Now, if you test the movie, Guy should appear wherever the "1" is in the matrix. Make sure the 1 is always on a space which corresponds to a non-barrier tile. You can move him around, but he is in no way hindered by the tiles - yet.

The next phase is fairly complicated. In order to make this work the best, we need to determine where Guy is going to be before he moves. And then check his X, Y coordinates to see if he will hit a wall or not. This means we almost have to completely re-write our movement code. First, lets start with a new function, which will go just above all the movement code we've already made, and inside the "onClipEvent(EnterFrame)" block:

onClipEvent (enterFrame) {
 
function checkCoords(x,y,direct){
guyTL = _root["tile_"+Math.floor((x-(_parent._width/2)+1)/_root.tileWdth)+"_"+Math.floor((y-(_parent._height/2)+1)/_root.tileHght)].barrier;
guyTR = _root["tile_"+Math.floor((x+(_parent._width/2)-1)/_root.tileWdth)+"_"+Math.floor((y-(_parent._height/2)+1)/_root.tileHght)].barrier;
guyBR = _root["tile_"+Math.floor((x+(_parent._width/2)-1)/_root.tileWdth)+"_"+Math.floor((y+(_parent._height/2)-1)/_root.tileHght)].barrier;
guyBL = _root["tile_"+Math.floor((x-(_parent._width/2)+1)/_root.tileWdth)+"_"+Math.floor((y+(_parent._height/2)-1)/_root.tileHght)].barrier;
 
switch(direct) {
case 0:
if (guyTL || guyTR) {
return false;
}
case 1:
if (guyBR || guyBL) {
return false;
}
case 2:
if (guyTR || guyBR) {
return false;
}
case 3:
if (guyTL || guyBL) {
return false;
}
}
return true;
}

Okay, let me try to explain what this function is going to do when we use it. First, it takes 3 variables: x, y, and direct. X and Y will be the theoretical coordinates of Guy, if he were allowed to move where he wants. direct is the direction he is moving. Next, we find Guy's corners: guyTL, Guy's Top Left Corner, will be either true or false depending on what tile that corner is on, and so on. So if Guy is standing on grass, and is moving to grass all four of those should be false, meaning none of his corners are on top of a Barrier space.

The next section checks to see if the pertinent corners are going to be on a barrier or not, depending on the direction Guy is going to move. If direct is 0 Guy is trying to move UP, so we check his 2 top points to see if they will be on a barrier or not. If one of them is, then the function returns a false value. If they are BOTH false, or, if both points are not going to be on a barrier, then it returns true. Whew. If you don't understand at this point, feel free to e-mail me and let me know.... its a bit hard to explain this perfectly...

Okay... next, we adjust our movement code - instead of simply moving if a key is down, we should only move after checking our coordinates with our new function, and if the function says its okay by returning true. Here's the entire modified movement code:

if (Key.isDown(Key.UP)) {
play();
_rotation = 0;
 
if (checkCoords(_parent._x, _parent._y - walkSpeed,0)){
 
_parent._y-= walkSpeed;
}}
 
if (Key.isDown(key.RIGHT)) {
play();
_rotation = 90;
if (checkCoords(_parent._x + walkSpeed, _parent._y,2)){
 
 
_parent._x+= walkSpeed;
}
 
}
if (Key.isDown(key.LEFT)) {
play();
_rotation = 270;
if (checkCoords(_parent._x - walkSpeed, _parent._y,3)){
 
 
_parent._x-= walkSpeed;
}}
if (Key.isDown(key.DOWN)) {
play();
_rotation = 180;
if (checkCoords(_parent._x, _parent._y + walkSpeed,1)){
 
 
_parent._y+= walkSpeed;
}
}
if (!Key.isDown(key.UP) & !Key.isDown(key.DOWN) & !Key.isDown(key.LEFT) & !Key.isDown(key.RIGHT)) {
stop();
}
 
 
}

Well, this should be pretty self-explanatory. If you notice, we're not sending the current x, y coordinates of Guy, we're sending what they would be if he moved. Also, in case you were wondering, when we send the direct value, 0 = UP, 1 = DOWN, 2 = RIGHT, and 3 = LEFT. Test out the Movie, and you have a walking Guy, who cannot walk through walls, or over water. Yay! Play with this a little bit, and experiment. See if you can make bigger maps, or change the maps around. If its not working, compare my source code to yours... but try to get at least some understanding before the next section, where I will show you how to make a simple Map editor, so that you can make maps much easier.

 

Download FLA

Onwards to the next page!


page 6 of 7


 




SUPPORTERS:

kirupa.com's fast and reliable hosting provided by Media Temple.