Josh-CO Dev

Solving the worlds problems one line of code at a time.

Basic Tile Engine Tutorial – Creating our player class

Leave a comment


If you haven’t completed the previous tutorial yet, be sure you do so. You can find it here. Also, if you want to download the code for all the way through this tutorial, you can find it here.

In this tutorial, we finally get around to the fun stuff and get the base for an actual working game. This tutorial will cover adding our player class, animating the player, etc.

First, create a new folder in your content project and name it peeps. You can really name it whatever you want, but the code I have written points to “peeps”. Download the below image and place it in the peeps folder.

Now that we have our sprite sheet, let’s start the tutorial.

-Create a new class in your main project and name it player.cs.
-Add our normal using statements.

using System;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Content;
using System.Collections.Generic;
using TestGame.Animation;
using TestGame.TileEngine;

-Depending on how you namespaced your code, you may not have or need TestGame.animation and TestGame.TileEngine. If you reference the solution attached above, you’ll see that I have the animation class and the main tile engine separated out into separate namespaces for re-usability.

-One difference here from our other classes is that when we declare our player class, we want to inherit from our animated sprite class. To do this, just add : AnimatedSprite after our class name. This will assign all of the properties and methods of the AnimatedSprite class to our player, so we won’t need properties for things like SpriteWidth. We can declare them in one class and then share them out.

class Player : AnimatedSprite

In our global variables section, we are going to add our first enum. Think of enum as a custom variable that can only be assigned the values you allow. In this case, we are holding what the player can do. Right now, the player can only walk. Later, we’ll add other states; such as running, jumping, dead, etc.

public enum State
{
Walking
}
public State currentState = State.Walking;

-Now we add all of our properties and global variables

float walkSpeed = 3.0f;
private Vector2 screenBounds;

KeyboardState currentKBState;
KeyboardState previousKBState;

//gravity to pull the character back down
const float gravityForce = 0.5f;

//distance to keep the sprite within on the screen
const int borderDistance = 0;

//basic initialized move speeds
float xVelocity;
float yVelocity;

//y coordinate of the ground
float groundLevel;

//this variable tells you if you are on the ground or not
public bool isOnGroud = false;

//hold our level
public Level Level { get; set; }

-Most of these are documented with comments, but we’ll still do some explaining. Walkspeed is how fast the player will move when you hold the left or right arrow keys. ScreenBounds is the width and height of our viewable screen (important later when we have a bigger tilemap). The two keyboard states will hold what the player is doing with the keyboard on the current update and also what they did on the last update. GravityForce is a variable that is basically a gravity that is constantly applied to the player to push them down. This is what calls your player to fall if they are not on a solid object. BorderDistance will be used later to help control the screen scrolling as our maps get bigger. xVelocity is how fast the player moves horizontally and yVelocity is how fast you move vertically. GroundLevel is the yCoordinate of the ground currently. isOnGround is a boolean variable to determine if you are on the ground. Finally, we have a property to hold our current level.

-Let’s move on to our constructor

public Player(Texture2D texture, int currentFrame, int spriteWidth, int spriteHeight, Vector2 startingPosition, Vector2     
screenBounds, ContentManager content, Level level)
{
Texture = texture;
CurrentFrame = currentFrame;
SpriteWidth = spriteWidth;
SpriteHeight = spriteHeight;
Position = startingPosition;
this.Level = level;

this.screenBounds = screenBounds;

//base level is the bottom of the screen. You are dead if you go below this
groundLevel = screenBounds.Y;
}

-Comparatively speaking, this is a pretty big constructor. First we are passing in the texture of our player, a frame of animation, the width and height of the player sprite, the player’s starting position, the size of the screen, a content manager, and a level. Then, we are instantiating all of our local properties with these values and then setting the ground level to the bottom of the screen.

-Next, we add a method to determine if the player is on the ground or not. This is used primarily in our jumping methods that we’ll add later but is also used for gravity.

//determine if the player is on the ground level
//if true, the player is off the ground
public bool isFalling()
{
if (Position.Y < groundLevel)
return true;
else
return false;
}

-Now we have the meat of the player class. I’ll throw it out here first and then we’ll go through it at the end.

public void HandleSpriteMovement(GameTime gt, Level level)
{
this.Level = level;

//by default, we are not moving, so stop moving if no key is pressed
xVelocity = 0;

previousKBState = currentKBState;
currentKBState = Keyboard.GetState();

//create our rectangle for animation
SourceRect = new Rectangle(CurrentFrame * SpriteWidth, YFrame * SpriteHeight, SpriteWidth, SpriteHeight);

//set idle frame
if (currentKBState.GetPressedKeys().Length == 0)
{
CurrentFrame = 0;
YFrame = 0;
}

//move to the right
if (currentKBState.IsKeyDown(Keys.Right))
{
AnimateRight(gt);

//keep 150 pixels from the end of the screen
if (Position.X  borderDistance)
{
xVelocity = -walkSpeed;
}
}

-Ok, so what’s happening so far in this method? First we just assign our level object. This is not used heavily in this method but gets passed to our collision method next. Then, we default our xVelocity to 0 so that our player is not moving at all by default. Next we take the current keyboard state object and assign it to the previous keyboardstate variable and then we get the current keystate. This is how we determine what keys the player is hitting. Next, we determine what position in our sprite sheet we are currently animating. This is done by capturing the x and y position of the sprite sheet and and multiplying them by the spritewidth and spriteheight to determine the x,y coords of the sprite image. Then we just set the rectangle size to the spritewidth and spriteheight. The first if statement checks for no key press and sets our sprite image to the top left image in the sprite sheet. Now we check for the right arrow key down. If true, we set our xvelocity to our walkspeed that we defined earlier. Later on, we’ll see how this moves the character. We also call a method that we haven’t written yet to set the “walking right” animation. Now we do the exact same thing for the left movement.

-Now let’s move on and finish up this method


//make sure the player is on the ground. If so, set the ground level and also flag the isonground boolean
checkForCollision(level);
if (isFalling())
yVelocity += gravityForce;

Position = new Vector2(Position.X + xVelocity, Position.Y + yVelocity);

if (isOnGroud)
{
if (Position.Y > groundLevel)
{

Position = new Vector2(Position.X, groundLevel);
}

yVelocity = 0;

}
else
{
yVelocity += gravityForce;
}

Origin = new Vector2(SourceRect.Width / 2, SourceRect.Height);
}

-Ok, so some more confusing logic here. If any of this doesn’t make sense, just write the code or run my sample and play around with the variables. First we are calling a collision checking method. For now, ignore this. We will write this method and explain it shortly. Same thing with isFalling. In short, this method determines if you are on the ground or not. If you are not on the ground, we want to apply the gravity and push the player downwards, maybe to their doom. Finally, the one line of code that matters, the one where we set our position. All we do is set the players position to the x position plus the x velocity and do the same thing for the y axis. That’s it! That’s all it takes to really move our character. The next section doesn’t make much sense, but it worked for me. We are checking the is on ground variable and checking the y position of the player against the ground level. For some reason, my logic would sometimes push the player a pixel or two into the ground. This method checks for that and sets them to walk on top of the ground rather than a few pixels in. Now we artificially set our yVelocity to zero and hardcode our origin. I like to keep my origin on the bottom as it seems to make it better for collision checking against the ground. Again, this is all really confusing at first, but just play around with it.

-Now let’s start our level checking collision

private void checkForCollision(Level level)
{
isOnGroud = false;

foreach (Tile ts in level.Map)
{
if (ts.Landable)
{
if (this.BoundingBox.Intersects(ts.BoundingBox))
{
groundLevel = ts.Position.Y;
isOnGroud = true;
}
}
else
{
//don't do anything currently, we just want to pass through it
}
}
}

-Finally, we have a short, fairly simple method. We call this method every update and automatically set the isOnGround to false. So we are assuming the player is off the ground. We then loop use our level object that we passed in to loop through all of our tiles in our list and filter out any that are not landable. Then we perform a collision check between the player bounding box and the tile bounding box. If there is a collision, we set the ground level to the tiles Y position and set the isOnGround to true. There is a small glitch here that you should be able to see once we add our jumping logic. If we’re not careful, this means that you can jump and touch a tile with your head and you will teleport up to the tile. We’ll fix this when the time comes but it will do for now.

-Now let’s handle our animation. The first section basically sets our idle frame to the first frame of our animation for facing right. This way if we stop moving the player, he won’t look a different direction. Then we increment our timer and move to the next frame if the timer is greater than our allowed interval. Then we make another check to set our animation back to our first frame if we go past the last, and reset our timer.

private void AnimateRight(GameTime gt)
{
if (currentKBState != previousKBState)
{
CurrentFrame = 0;
YFrame = 2;
}

Timer += (float)gt.ElapsedGameTime.TotalMilliseconds;

if (Timer > Interval)
{
CurrentFrame++;

if (CurrentFrame > 2)
CurrentFrame = 0;

Timer = 0f;
}
}

-Now, let’s do the same for our left animation. The code is identical.

private void AnimateLeft(GameTime gt)
{
if (currentKBState != previousKBState)
{
CurrentFrame = 0;
YFrame = 1;
}

Timer += (float)gt.ElapsedGameTime.TotalMilliseconds;

if (Timer > Interval)
{
CurrentFrame++;

if (CurrentFrame > 2)
CurrentFrame = 0;

Timer = 0f;
}
}

-To draw our sprite, we just call the draw method from our animated sprite class.

public override void Draw(SpriteBatch sb)
{
base.Draw(sb);
}
}
}

Finally, that’s it for our player class, though we do still need to make a couple of other minor changes to our project.
If you haven’t already done so, add a folder to your project and name it Levels. Add a text file to the folder and name it Level1.txt. Paste the below information for the text:
000000000
000000000
P00000000
000000000
111111111
000000000
This is how we make our tilemap. Before we continue, make sure you have a tile to use for the 1’s, which you can see in my solution above, feel free to steal my simple tile. Then go back to the code that we wrote earlier and find find your map or level class and locate the section where we are looping through the content of our text file and make sure you have this code:

                        case '1':
                            t = new Tile();
                            t.Texture = cm.Load("Tiles/grass");
                            t.Landable = true;
                            t.Position = new Vector2(xPos * t.SpriteWidth, yPos * t.SpriteHeight);
                            this.addNewSprite(t);
                            break;
                        case 'P': //set our starting position
                            this.StartingPosition = new Vector2(xPos * 32, yPos * 32);
                            break;

The P is where your player will start, feel free to play around with this. To make your own tile, just edit the t.texture field. You can make this whatever you want!

Go back to your main class and add the player. In your global declarations, you should have something like this:

        Level level;
        Player player;

Instantiate your player in your initialization method:

            player = new Player(content.Load("peeps/businessNerd"), 1, 32, 32, level.StartingPosition,
                new Vector2(graphics.PreferredBackBufferWidth, graphics.PreferredBackBufferHeight), content, level);

In your update section, handle the sprite movement


            player.HandleSpriteMovement(gameTime, level);
            base.Update(gameTime);

Then handle the draw in your draw method:

            level.Draw(sb);
            player.Draw(sb);

That’s it! I know this is a lot of code and information, but play with it a little bit and it should all make sense. If you run your solution, you should have a player that will fall and land on top of your tiles. You can move left and right and the player will move and animate in the direction you move. He will stand on the tiles and if you walk off the edge he will fall.

If you run into any issues, check the solution posted above and feel free to leave a comment.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s