Creating the game characters
- Thorough understanding of RPG Map tutorial
- Knowledge of your preferred language
- Ability to draw images with your chosen language/API
- Ability to get keyboard input from your language/API
We're going to look into how we interact with our newly created map by means of our playing character. We'll explore the easiest type of collision detection and movement, both of which being tile based. We'll also get into how to set up image files to hold frames of animation in an efficient pattern. The game is updated on a frame basis, meaning you're going to have to cap the FPS at a maximum in order to keep things working at the same speed on all machines.
Tile based movement doesn't look the greatest, I'll admit. However it is the most efficient way to go about it. Everything will be dealt with in tile coordinates, not screen coordinates. The screen coordinates are the actual coordinates you'll be using when displaying your image, i.e. coordinates in pixels. Tile coordinates are the coordinates you'd use to access a tile in your map array, i.e. coordinates in tiles. To get tile coordinates from screen coordinates we divide the screen x and y coordinate by the tile width and height respectively. Likewise to get the screen coordinates from tile coordinates we multiply the tile x and y coordinate by the width and height (respectively) of a tile. Maybe you already figured this out from the previous tutorials.
We're going to create an object class to respresent our playing character because that's just what it is, an object. Inside the object we store some data we'll need for drawing the object: screen coordinates and the object's width and height. Next we store some data to tell us how the object (sprite) moves:
Direction - This is the direction our sprite is facing. Since we're using four-way directional movement, this can only be values 0, 1, 2, and 3. State - Lets us know what our sprite is currently in the process of doing. In this tutorial 0 means it's doing nothing and 1 means we're moving, but you can modify it as you want and even add more states like attacking, blocking, swimming, and so on... Speed - This is the number of pixels our sprite moves per frame. Keep it as a multiple of your tile width and height so that our sprite won't move more than we want it to.
Here's how we can interpret our various states graphically:
Your code may look something like the following:
PLAYER structure x : integer y : integer width : integer height : integer state : integer direction : integer speed : integer PLAYER MyChar
Now we get down to it. We check for input if and only if our state is at 0, meaning we're not doing anything. If we change our state in the middle of moving (state 1) then our sprite's coordinates will get out of sync with the map, meaning the coordinates wont be easy multiples of the tile dimensions. So if the state is in fact 0 then we can go ahead and check which arrow (cursor) key has been pressed. Check each of the four cursor keys now, and if a cursor key is down then we set the sprite's direction in accordance with which key is down. If any of the keys are down and a direction has been set, we set our sprite's state to 1 (moving). Of course all this code would go into your game loop where you would make these checks on a per-frame basis:
If MyChar.State = 0 then If Keyboard.UpArrowIsDown = True then // Up arrow pressed. MyChar.direction = 0 MyChar.state = 1 Elseif Keyboard.RightArrowIsDown = True then // Right arrow pressed. MyChar.direction = 1 MyChar.state = 1 Elseif Keyboard.DownArrowIsDown = True then // Down arrow pressed. MyChar.direction = 2 MyChar.state = 1 Elseif Keyboard.LeftArrowIsDown = True then // Left arrow pressed. MyChar.direction = 3 MyChar.state = 1 End If End If
If one of the arrow keys are pressed, the direction is set accordingly and the state is set to 1. When the State is set to 1, we're moving in the direction of direction. As we move we need to check if we're on a new tile or not, which determines whether we stop or not. We do this by using the modulus operator, making our sprite stop once it has moved a complete tile. Let's add a function to our character class that will manage everything:
Function Manage() // If our state is equal to 1, then that means we're in motion If MyChar.state = 1 then Selection MyChar.direction Case 0 MyChar.y = MyChar.y - MyChar.speed // Move up. If MyChar.y mod TileHeight = 0 Then // Have we move a complete tile yet? MyChar.state = 0 // If so, stop moving. End If Case 1 MyChar.x = MyChar.x + MyChar.speed // Move right. If MyChar.x mod TileWidth = 0 Then // Have we move a complete tile yet? MyChar.state = 0 // If so, stop moving. End If Case 2 MyChar.y = MyChar.y + MyChar.speed // Move down. If MyChar.y mod TileHeight = 0 Then // Have we move a complete tile yet? MyChar.state = 0 // If so, stop moving. End If Case 3 MyChar.x = MyChar.x - MyChar.speed // Move left. If MyChar.x mod TileWidth = 0 Then // Have we move a complete tile yet? MyChar.state = 0 // If so, stop moving. End If End Selection End If End Function
After we check the input we would call this funtion, Manage(). Try it out. Pretty cool, right? Whats that? Your sprite is walking all over walls and such that it shouldn't be? Well, let's do some checks to determine whether it's legal to move onto certain tiles, or what is referred to as performing collision detection.
With the way we've set up our map and character movements, it's a no-brainer to get collision detection working. Collision detection with tiles that is. Basically, we just test whether a tile that we want to move onto is Blocked or not. If it's False, we move onto the tile, if it's True then we obviously stay where we are and wait for the user to go in a different direction. We want to perform our collision detection before we actually move the sprite, therefore we can just modify our Manage() function:
Function Manage() // If our MyChar.state is equal to 1, Then that means we// re in motion If MyChar.state = 1 Then Selection MyChar.direction Case 0 // Is the tile above blocked? If Map[MyChar.yChar.x / TileWidth, MyChar.y / TileHeight - 1].blocked = False Then // If not, Move up MyChar.y = MyChar.y - MyChar.speed // Have we moved a complete tile MyChar.yet? If MyChar.y mod TileHeight = 0 Then // If so, stop moving MyChar.state = 0 End if End If Case 1 If Map[MyChar.yChar.x / TileWidth + 1, MyChar.y / TileHeight].blocked = False Then // Move right MyChar.yChar.x = MyChar.yChar.x + MyChar.speed If MyChar.yChar.x mod TileWidth = 0 Then MyChar.state = 0 End if End If Case 2 If Map[MyChar.yChar.x / TileWidth, MyChar.y / TileHeight + 1].blocked = False Then // Move down MyChar.y = MyChar.y + MyChar.speed If MyChar.y mod TileHeight = 0 Then MyChar.state = 0 End if End If Case 3 If Map[MyChar.yChar.x / TileWidth - 1, MyChar.y / TileHeight].blocked = False Then // Move left MyChar.yChar.x = MyChar.yChar.x - MyChar.speed If MyChar.yChar.x mod TileWidth = 0 Then MyChar.state = 0 End if End If End Selection End If End Function
Of course in order for you to see the effects you need some tiles in your map that are Blocked.
So now you're moving about in your map and running into walls. But wouldn't it be better if your character looked like he was actually moving instead of just sliding everywhere? Lets jump into how animation is achieved. There are two things to base your animations on: time or frames. We increment our animation frames one after another everytime a certain interval is met. This can be a time interval or frame interval. Lets recap: Every loop cycle of the game (or frame of the game, trying not to confuse it with animation frames) we call a function to manage our animation. Every time an interval is met, we increment the current animation frame. If the current animation frame is past the maximum amount of frames specified, loop it (set frame = 0 and start again).
But before we can peform animation we need to design sprite image layout. In this tutorial we're going to use a layout resembling the following:
Now that we know why this code will work, lets see how it works. This code is called when the sprite is drawn:
PLAYER structure ... frame : integer maxFrame : integer interval : integer // In frames. tick : integer // Is the number of cycles passed the specified maximum frames? If tick >= MyChar.interval Then // If so, increment our animation frame MyChar.frame = MyChar.frame + 1 // If we've reached the end of our animation frames If MyChar.frame >= MyChar.maxFrame Then // Then loop it (MyChar.frame = 0) MyChar.frame = 0 End if // Reset our cycle counter to start again. tick = 0 Else // If it was never time to update the frames, just increment our cycle counter to count this cycle. tick = tick + 1 End if
PLAYER structure ... frame : integer frames : integer interval : integer // in milliseconds lastTick : integer // Is it time to update our animation? If time() - lastTick >= MyChar.interval Then // If so, increment the frame. MyChar.frame = MyChar.frame + 1 // If we've reached the end of the maximum specified frames, If MyChar.frame >= MaxMyChar.frames Then // Then loop it (frame = 0) MyChar.frame = 0 End if // Reset our counter to start again. Since we're using time lastTick = time() End if
And so wraps up animation. Now you've got a moving character running around into walls. Any problems raised or not answered by this tutorial deserve light in the forum.