RPG CHAR

From GPWiki
Jump to: navigation, search

Creating the game characters

Prerequisites

  1. Thorough understanding of RPG Map tutorial
  2. Knowledge of your preferred language
  3. Ability to draw images with your chosen language/API
  4. Ability to get keyboard input from your language/API

Introduction

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.

Movement

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:

Rpg animation direction.png

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.

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.

Animation

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:

Rpg animation frames.png (Frame should read "0, 1" rather than "1, 2")

Now that we know why this code will work, lets see how it works. This code is called when the sprite is drawn:

Frame based:

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

Time based:

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.