Introduction

Welcome to Part 4b of our Tile Set Engine Tutorial. I know this tutorial is very late in coming, but things have been kind of hectic.

Also, this isn't a full-fledged tutorial the size of our previous ones because I decided to make a change of course with the Tile Engine for what will be out in the next day or so as Part 5. In part 5 we will change gears a bit and look at creating a component for drag-and-drop tile mapping.

That said, one of the most frequent requests I got was how to let the Avatar leave the center of the screen. To that end, I wanted to put up a quick lesson on how to do that for those that were interested.

I Can't Spell Avatar

Apparently I have a problem when it comes to typing the word avatar. About half the time I end up with avater instead. Hence, I've discovered the one downside to automatic code completion... I don't notice because once I declare the variable it gets filled in automatically!.

I've updated the Part 4 download to include the proper spelling in all areas, but if you are working from your own project, just do a Edit, Find & Replace, Quick Replace and replace all instances of Avater with Avatar and that should clean things up. It will be important because if you are cutting and pasting the code below it *should* be spelled right in these code snippets!

Leaving the Center

Currently our avatar is stuck in the center of our display window. As you approach the edge of the map, you just end up stopping 3 to 6 squares away (depending on what direction you are travelling). In order for our player to interact with all of the cool, fancy things that might eventually be in the edges and corners of our map, we'll need to change things a bit so that the avatar will leave the center of the screen if you are at an edge, and move back properly towards the center before scrolling again if you go back.

In order to do this, we'll need to a couple of new declarations. Replace your current avatar declarations with these:

   
      // Player Avatar Information
      int iPlayerAvatarXOffset = 6;
      int iPlayerAvatarYOffset = 3;
      int iPlayerAvatarSubTileX = 0;
      int iPlayerAvatarSubTileY = 0;
      int iPlayerAvatarCenterX = 6;
      int iPlayerAvatarCenterY = 3;

This gives us a place to store the avatar's distance from the upper right corner of the screen (iPlayerAvatarXOffset and iPlayerAvatarYOffset) as well as s definition of where the center of the display is (iPlayerAvatarCenterX and iPlayerAvatarCenterY). Additionally, to allow us to move smoothly between tiles we need to track the sub-tile position of our avatar (iPlayerAvatarSubTileX and iPlayerAvatarSubTileY).

Next, we'll need to change our CheckMovementKeys method to determine if we are scrolling the map or moving the avatar, based on the current position. I'm going to paste the whole method here and explain the code afterwards:

        int CheckMapMovementKeys(KeyboardState ksKeyboardState)
        {
            // Check to see if an arrow key is pressed.  If so, set the
            // iMoveDirection to indicate the direction we will be moving in,
            // and the iMoveCount to how many times we need to execute.
            if (ksKeyboardState.IsKeyDown(Keys.Up))
            {
                if ((iMapY + iPlayerAvatarYOffset -1) >=0 )
                {
                    if ((iMapWalkable[(iMapY + iPlayerAvatarYOffset) - 1, (iMapX + iPlayerAvatarXOffset)] == 0) ||
                        (iProgramMode == 1))
                    {
                        if ((iMapY > 0) & (iPlayerAvatarYOffset <= iPlayerAvatarCenterY))
                        {
                            iMoveDirection = 0;
                            iMoveCount = iTileHeight + iMapYScrollRate;
                            return 1;
                        }
                        else
                        {
                            if (iProgramMode == 0)
                            {
                                if (iPlayerAvatarYOffset > 0)
                                {
                                    iMoveDirection = 4;
                                    iMoveCount = iTileHeight + iMapYScrollRate;
                                }
                            }
                        }
                    }
                }
            }
            if (ksKeyboardState.IsKeyDown(Keys.Down))
            {
                if ((iMapY + iPlayerAvatarYOffset + 1) <= iMapHeight)
                {
                    if ((iMapWalkable[(iMapY + iPlayerAvatarYOffset) + 1, (iMapX + iPlayerAvatarXOffset)] == 0) ||
                        (iProgramMode == 1))
                    {
                        if ((iMapY < (iMapHeight - iMapDisplayHeight)) & (iPlayerAvatarYOffset >= iPlayerAvatarCenterY))
                        {
                            iMoveDirection = 1;
                            iMoveCount = iTileHeight + iMapYScrollRate;
                            return 1;
                        }
                        else
                        {
                            if (iProgramMode == 0)
                            {
                                if ((iPlayerAvatarYOffset + iMapY + 2) < iMapHeight)
                                {
                                    iMoveDirection = 5;
                                    iMoveCount = iTileHeight + iMapYScrollRate;
                                }
                            }
                        }
                    }
                }
            }
            if (ksKeyboardState.IsKeyDown(Keys.Left))
            {
                if ((iMapX + iPlayerAvatarXOffset -1 ) >= 0)
                {
                    if ((iMapWalkable[(iMapY + iPlayerAvatarYOffset), (iMapX + iPlayerAvatarXOffset) - 1] == 0) ||
                        (iProgramMode == 1))
                    {
                        if ((iMapX > 0) & (iPlayerAvatarXOffset <= iPlayerAvatarCenterX))
                        {
                            iMoveDirection = 2;
                            iMoveCount = iTileWidth + iMapXScrollRate;
                            return 1;
                        }
                        else
                        {
                            if (iProgramMode == 0)
                            {
                                if (iPlayerAvatarXOffset > 0)
                                {
                                    iMoveDirection = 6;
                                    iMoveCount = iTileWidth + iMapXScrollRate;
                                }
                            }
                        }
                    }
                }
            }
            if (ksKeyboardState.IsKeyDown(Keys.Right))
            {
                if ((iMapX + iPlayerAvatarXOffset + 1) <= iMapWidth)
                {
                    if ((iMapWalkable[(iMapY + iPlayerAvatarYOffset), (iMapX + iPlayerAvatarXOffset) + 1] == 0) ||
                        (iProgramMode == 1))
                    {
                        if ((iMapX < (iMapWidth - iMapDisplayWidth)) & (iPlayerAvatarXOffset >= iPlayerAvatarCenterX))
                        {
                            iMoveDirection = 3;
                            iMoveCount = iTileWidth + iMapXScrollRate;
                            return 1;
                        }
                        else
                        {
                            if (iProgramMode == 0)
                            {
                                if ((iPlayerAvatarXOffset + iMapX + 2) < iMapWidth)
                                {
                                    iMoveDirection = 7;
                                    iMoveCount = iTileWidth + iMapXScrollRate;
                                }
                            }
                        }
                    }
                }
            }
            if (ksKeyboardState.IsKeyDown(Keys.E)) {
                if (iProgramMode==0) {
                    iProgramMode=1;
                } else {
                    iProgramMode=0;
                }
                return 1;
            }
            return 0;
        }

Lets pull out a single directional key and see what is going on here:

            if (ksKeyboardState.IsKeyDown(Keys.Up))
            {
                if ((iMapY + iPlayerAvatarYOffset -1) >=0 )
                {
                    if ((iMapWalkable[(iMapY + iPlayerAvatarYOffset) - 1, (iMapX + iPlayerAvatarXOffset)] == 0) ||
                        (iProgramMode == 1))
                    {
                        if ((iMapY > 0) & (iPlayerAvatarYOffset <= iPlayerAvatarCenterY))
                        {
                            iMoveDirection = 0;
                            iMoveCount = iTileHeight + iMapYScrollRate;
                            return 1;
                        }
                        else
                        {
                            if (iProgramMode == 0)
                            {
                                if (iPlayerAvatarYOffset > 0)
                                {
                                    iMoveDirection = 4;
                                    iMoveCount = iTileHeight + iMapYScrollRate;
                                }
                            }
                        }
                    }
                }
            }

After checking to see if the up key is pressed, we check to see that we aren't trying to move off of the top of the map (iMapY + iPlayerAvatarYOffset -1) >=0), so iMapY (the current Y position of the map, plus the player Y offset minus 1 (the one we would be moving) must be 0 or greater).

If that part is Ok, we check to see that we are either in Editor Mode or that the tile is Walkable. Next, we check to determine if we are going to be moving the player or the whole map. As long as the we aren't at the top of the map visually (iMap > 0) and the avatar is not below the center of the screen (because if he is, we just want to move him up toward the center instead of scrolling) we go ahead and set up a tile move just like we did before.

Otherwise, we need to move the avatar instead of scrolling the map. We only do this if we are in "play" mode, as in editor mode we don't display the avatar. Assuming we are in play mode and the avatar isn't against the top edge of the screen (iPlayerAvatarYOffset > 0) we set up the avatar move. We will be using the same type of routine as a normal move, but we add 4 to the direction so we can tell the difference in our update code later on. So directions 0-3 are map moves while directions 4-7 are avatar moves.


Next, we'll need to edit our Update routine to account for the four new move directions we've set up. Look in your Update method and find the comment that say "// If we are in the middle of a smooth-scroll move"... We'll be updating this area to add the four new directions. You can add these right after the existing "if" statements for directions 0-3:

                if (iMoveDirection == 4)
                {
                    iPlayerAvatarSubTileY -= iMapYScrollRate;
                    iMoveCount -= iMapYScrollRate;
                }
                if (iMoveDirection == 5)
                {
                    iPlayerAvatarSubTileY += iMapYScrollRate;
                    iMoveCount -= iMapYScrollRate;
                }
                if (iMoveDirection == 6)
                {
                    iPlayerAvatarSubTileX -= iMapXScrollRate;
                    iMoveCount -= iMapXScrollRate;
                }
                if (iMoveDirection == 7)
                {
                    iPlayerAvatarSubTileX += iMapXScrollRate;
                    iMoveCount -= iMapXScrollRate;
                }
To finish up, we need to accound for what happens when the avatar move is finished. Just below the directional lines are four "if" statements to compensate for moving off the edge of a tile. Right below those, add these four "if" statements to handle the avatar completing a move:
                if (iPlayerAvatarSubTileX < 0) { iPlayerAvatarSubTileX = iTileWidth; iPlayerAvatarXOffset--; }
                if (iPlayerAvatarSubTileX > iTileWidth) { iPlayerAvatarSubTileX = 0; iPlayerAvatarXOffset++; }
                if (iPlayerAvatarSubTileY < 0) { iPlayerAvatarSubTileY = iTileHeight; iPlayerAvatarYOffset--; }
                if (iPlayerAvatarSubTileY > iTileHeight) { iPlayerAvatarSubTileY = 0; iPlayerAvatarYOffset++; }

Finally we need to alter our Draw routine slightly. We need to include the iPlayerAvatarSubTileX and iPlayerAvatarSubTileY variables in our draw routine for drawing the avatar.

Near the bottom of our Draw method is a spriteBatch.Draw command to draw the player avatar. Replace the call with the following:

                spriteBatch.Draw(t2dPlayerAvatar, 
                                 new Rectangle(((iPlayerAvatarXOffset * iTileWidth) 
                                     + iMapDisplayOffsetX + iPlayerAvatarSubTileX),
                                 ((iPlayerAvatarYOffset * iTileHeight) + iMapDisplayOffsetY 
                                     + iPlayerAvatarSubTileY),
                                 iTileWidth, iTileHeight), new Rectangle(0, 0, 48, 48), 
                                 Color.White);

The only difference between this version and the last version is that we add the two SubTile offsets to the destination rectangle in both cases.

That's it! That's all that is needed to allow your avatar to get to the edges of the screen.

Download Source

Down the Road...

As I said above, my original intention for this tutorial was quite a bit different and more extensive, but as I was working on it I decided that things needed to be taken in a different direction. Leaving the center of the screen was one of the most requested features I got from readers of this tutorial series, so I wanted to get that part out there.

I've got a working Tile Engine component just about ready to go and just need to type up the tutorial. It is pretty close to drag-and-drop tile maps, not limited to RPG games. One thing I would like to do that I haven't completed yet is the ability to use "Mappy32" maps but I won't hold up posting the component tutorial for that if I can't get it in in time. I'll release an updated component if necessary.

































 
 
 
Site Contents Copyright © 2006 Full Revolution, Inc. All rights reserved.
This site is in no way affiliated with Microsoft or any other company.
All logos and trademarks are copyright their respective companies.
RSS FEED