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.
Onwards to the
next page!
 |
page 6
of 7 |
 |
|