Difference between revisions of "Simple Beat Em Up"

From TRCCompSci - AQA Computer Science
Jump to: navigation, search
(Draw Your Level)
 
(24 intermediate revisions by the same user not shown)
Line 1: Line 1:
This will show you how to create a Beat Em Up game using a tiled map, and tile based collision detection. This will be a fighting game but you will walk around the scene fighting the enemies you encounter.
+
This will show you how to create a Beat Em Up game using a tiled map, and tile based collision detection. This will be a fighting game but you will walk around the scene fighting the enemies you encounter. You could make this into a street fighter style game by removing the code to walk around, maybe they should just be able to go left and right.
  
 
=Create Map=
 
=Create Map=
Line 21: Line 21:
  
 
===New Tiled Map===
 
===New Tiled Map===
You will need a new tiled map, the tile size in the screen shot is 128 pixels, in the end i actually changed this to 32 x 32 and resized the tiles accordingly:
+
You will need a new tiled map, the tile size in the screen shot is 128 pixels, in the end i actually changed this to 16 x 16 and resized the tiles accordingly:
  
 
[[File:Plat new map.gif]]
 
[[File:Plat new map.gif]]
 
It is important to change the format of the map to Base64 gzip compressed:
 
 
[[File:Plat tile layer format.gif]]
 
  
 
===Add Tile Set===
 
===Add Tile Set===
Line 37: Line 33:
  
 
===Draw Your Level===
 
===Draw Your Level===
Now you have a tile set build a simple map, I have created an area for my floor and above this is the wall area. These are in my brackground layer, and my Bounds layer is made up of the wooden style tiles to identify the bounds of the level. You should name your layers to something appropriate:
+
Now you have a tile set, it is time to build a simple map, I have created an area for my floor and above this is the wall area. These are in my background layer, and my Bounds layer is made up of the wooden style tiles to identify the bounds of the level. You should name your layers to something appropriate:
  
 
[[File:Beat Em Map.gif]]
 
[[File:Beat Em Map.gif]]
  
 
===Add Player Object===
 
===Add Player Object===
Now you have a section of platforms, we can now set the position of the player. This will also be the object moved by the code and player input. So insert an object layer, and then use the rectangle tool to create the object. My final player was 60 pixels high and 40 pixels wide:
+
Now you have bounds and the background, we can now set the position of the player. This will also be the object moved by the code and player input. So insert an object layer, and then use the rectangle tool to create the object. My final player was 60 pixels high and 40 pixels wide:
  
 
[[File:Beat Em Player Object.gif]]
 
[[File:Beat Em Player Object.gif]]
Line 76: Line 72:
 
===LoadContent for map===
 
===LoadContent for map===
  
In the LoadContent method add the following lines to load the map, the collision layer and to set the texture of the player. The variable tilepixel assumes your tiles are square, the number of pixels is taken from the map:
+
In the LoadContent method add the following lines to load the map, the bounds layer and to set the texture of the player. My map is called SimpleBeatEmUp.tmx you may have given yours a different name. My layer is called Bounds, again you may have given it a different name:
  
 
<syntaxhighlight lang=csharp>
 
<syntaxhighlight lang=csharp>
 
map = Map.Load(Path.Combine(Content.RootDirectory, "SimpleBeatEmUp.tmx"), Content);
 
map = Map.Load(Path.Combine(Content.RootDirectory, "SimpleBeatEmUp.tmx"), Content);
 
bounds = map.Layers["Bounds"];
 
bounds = map.Layers["Bounds"];
tilePixel = map.TileWidth;
 
 
map.ObjectGroups["Objects"].Objects["Player"].Texture = Content.Load<Texture2D>("hero");
 
map.ObjectGroups["Objects"].Objects["Player"].Texture = Content.Load<Texture2D>("hero");
 +
viewportPosition = new Vector2(0,0);
 +
</syntaxhighlight>
 +
 +
Remember to also add the png files for the hero, and the tileset for your map into the content pipeline.
 +
 +
===The Draw Method===
 +
Add the following to the draw method to draw the map and hero to the screen.
 +
 +
If you already have spriteBatch.Begin() or spriteBatch.End() then just place the middle line inbetween your lines.
 +
 +
<syntaxhighlight lang=csharp>
 +
spriteBatch.Begin();
 +
map.Draw(spriteBatch, new Rectangle(0, 0, GraphicsDevice.Viewport.Width, GraphicsDevice.Viewport.Height), viewportPosition);
 +
spriteBatch.End();
 
</syntaxhighlight>
 
</syntaxhighlight>
  
===The Update Method===
+
At this point your project should run and display your map and the player.
 +
 
 +
==Moving The Map==
  
 
You will also need to update the viewportPosition. I want this to change in stages, so that you can walk around the screen and switch the viewportPosition when the player gets close to the end or start of the screen:
 
You will also need to update the viewportPosition. I want this to change in stages, so that you can walk around the screen and switch the viewportPosition when the player gets close to the end or start of the screen:
Line 105: Line 116:
 
I have chosen multiples of 700 because my screen width is 800 pixels.
 
I have chosen multiples of 700 because my screen width is 800 pixels.
  
===The Draw Method===
+
==Moving The Player==
Add the following to the draw method to draw the map and hero to the screen.
+
We can add the code below into the update method for Game1, it should probably go before the code above to move the map:
 +
 
 +
<syntaxhighlight lang=csharp>
 +
KeyboardState keyState = Keyboard.GetState();
 +
 
 +
if (keyState.IsKeyDown(Keys.Left))
 +
  map.ObjectGroups["Objects"].Objects["Player"].X -= 1;
 +
if (keyState.IsKeyDown(Keys.Right))
 +
  map.ObjectGroups["Objects"].Objects["Player"].X += 1;
 +
if (keyState.IsKeyDown(Keys.Up))
 +
  map.ObjectGroups["Objects"].Objects["Player"].Y -= 1;
 +
if (keyState.IsKeyDown(Keys.Down))
 +
  map.ObjectGroups["Objects"].Objects["Player"].Y += 1;
 +
</syntaxhighlight>
 +
 
 +
At this stage your map should display with the character also visible. The character should be able to move, try moving to the right it should change the position of your map accordingly.
 +
 
 +
==Checking Bounds==
 +
We need to check if the player has moved out of bounds. We are therefore going to record the players position before any movement, then allow them to move, then check the bounds, and finally if they have moved out of bounds use the pre movement position to reset the current position.
 +
 
 +
This will require a new procedure:
 +
<syntaxhighlight lang=csharp>
 +
public bool CheckBounds()
 +
{
 +
 
 +
}
 +
</syntaxhighlight>
 +
 
 +
Inside the new procedure we need to declare a boolean, this will be used to return back from this method. We also need to create a rectangle for the player, this only covers the bottom 10 pixels of the texture:
 +
 
 +
<syntaxhighlight lang=csharp>
 +
bool check = false;
 +
 
 +
Rectangle playrec = new Rectangle(
 +
  map.ObjectGroups["Objects"].Objects["Player"].X,
 +
  map.ObjectGroups["Objects"].Objects["Player"].Y + (map.ObjectGroups["Objects"].Objects["Player"].Height - 10),
 +
  map.ObjectGroups["Objects"].Objects["Player"].Width,
 +
  10
 +
);
 +
</syntaxhighlight>
 +
 
 +
Now we need 2 for loops to cycle through every tile on the map:
 +
<syntaxhighlight lang=csharp>
 +
for (int x = 0; x < map.Width; x++)
 +
{
 +
  for (int y = 0; y < map.Height; y++)
 +
  {
 +
 
 +
  }
 +
}         
 +
</syntaxhighlight>
 +
 
 +
 
 +
Now inside the inner for loop we need to add the code to check the current tile. bounds.GetTile(x,y) will tell us the tile number from the tile set for the tile at postion x,y. If the tile number is 0 then no tile is present at x,y , if the tile number is not 0 then a tile is present at x,y. If a tile exists at x,y we then check to see if it intersects the player rectangle we have already created:
 +
 
 +
<syntaxhighlight lang=csharp>
 +
for (int x = 0; x < map.Width; x++)
 +
{
 +
  for (int y = 0; y < map.Height; y++)
 +
  {
 +
        if (bounds.GetTile(x,y) != 0)
 +
        {
 +
          Rectangle tile = new Rectangle(
 +
            (int)x * map.TileWidth(),
 +
            (int)y * map.TileHeight(),
 +
            map.TileWidth(),
 +
            map.TileHeight()
 +
          );
 +
 
 +
          if (playrec.Intersects(tile))
 +
              check = true;
 +
          }
 +
  }
 +
}         
 +
</syntaxhighlight>
 +
   
 +
 
 +
Finally we need to add the code to return the value of check, you can see this in rhe completed CheckBounds procedure below:
 +
 
 +
<syntaxhighlight lang=csharp>
 +
        public bool CheckBounds()
 +
        {
 +
            bool check = false;
 +
 
 +
            Rectangle playrec = new Rectangle(
 +
                map.ObjectGroups["Objects"].Objects["Player"].X,
 +
                map.ObjectGroups["Objects"].Objects["Player"].Y + (map.ObjectGroups["Objects"].Objects["Player"].Height - 10),
 +
                map.ObjectGroups["Objects"].Objects["Player"].Width,
 +
                10
 +
                );
 +
 
 +
            for (int x = 0; x < map.Width; x++)
 +
            {
 +
                for (int y = 0; y < map.Height; y++)
 +
                {
 +
                    if (bounds.GetTile(x,y) != 0)
 +
                    {
 +
                        Rectangle tile = new Rectangle(
 +
                            (int)x * map.TileWidth(),
 +
                            (int)y * map.TileHeight(),
 +
                            map.TileWidth(),
 +
                            map.TileHeight()
 +
                            );
 +
 
 +
                        if (playrec.Intersects(tile))
 +
                            check = true;
 +
                    }
 +
                }             
 +
            }       
 +
 
 +
            return check;
 +
        }
 +
</syntaxhighlight>
 +
 
 +
==Using Check Bounds==
 +
Firstly add these lines to record the position before any movement:
 +
<syntaxhighlight lang=csharp>
 +
int tempx = map.ObjectGroups["Objects"].Objects["Player"].X;
 +
int tempy = map.ObjectGroups["Objects"].Objects["Player"].Y;
 +
</syntaxhighlight>
 +
 
 +
Now after we check for movement add the following to use CheckBounds:
 +
<syntaxhighlight lang=csharp>
 +
if (CheckBounds())
 +
{
 +
    map.ObjectGroups["Objects"].Objects["Player"].X = tempx;
 +
    map.ObjectGroups["Objects"].Objects["Player"].Y = tempy;
 +
}
 +
</syntaxhighlight>
 +
 
 +
Now the player should be able to move but still be contained by the bounds set. For example the screenshot below shows the character's feet should always stay within bounds:
 +
 
 +
[[File:Beat Em Bounds Test.gif]]
 +
 
 +
=Player Moves & Animation=
 +
Now obviously in a Beat'm Up game the playing character needs to perform moves such has a kick or punch. This will need to be animations because otherwise it will seem very erratic. This will therefore require a spritesheet, and may require a spritesheet for each move. The spritesheet I used is '''[https://drive.google.com/open?id=1EyZ7ax2LhRn-rSw-unXp1rupJDC7Za9E THIS]''', it has several different animations on the same sheet.
 +
 
 +
==Animation base class==
 +
Create a new class within your project (Project tab, then Add Class) and call it a suitable name, mine is called Animation. Now within the class we will need to create the following variables:
 +
 
 +
<syntaxhighlight lang=c#>
 +
    class Animation
 +
    {
 +
        // The current animation
 +
        int animation;
 +
 
 +
        // The time since we last updated the frame
 +
        int elapsedTime;
 +
 
 +
        // The number of frames that the animation contains
 +
        int frameCount;
 +
 
 +
        // The index of the current frame we are displaying
 +
        int currentFrame;
 +
 
 +
        // The color of the frame we will be displaying
 +
        Color color;
 +
 
 +
        // The area of the image strip we want to display
 +
        Rectangle sourceRect = new Rectangle();
 +
 
 +
        // The area where we want to display the image strip in the game
 +
        Rectangle destinationRect = new Rectangle();
 +
 
 +
        // This is the spritesheet for the animations
 +
        Texture2D PlayerAnimation;
 +
 
 +
        // The current position of the animation
 +
        Vector2 Position;
 +
 
 +
        // Is the animation active, if so it will be updated
 +
        bool Active;
 +
 
 +
        // Flip will be used to allow one set of animations work facing left or right
 +
        bool flip;
 +
 
 +
        // Width of a given frame
 +
        public int FrameWidth;
 +
 
 +
        // Height of a given frame
 +
        public int FrameHeight;
 +
    }
 +
</syntaxhighlight>
 +
 
 +
Now, within your class and after the variable declarations we need to create an initialise method for the animation. We need to do this to setup the animation with a spritesheet, a position, the correct dimensions and information about our spritesheet:
 +
 
 +
<syntaxhighlight lang=c#>
 +
        public void Initialize(Texture2D texture, Vector2 position, Bool facing)
 +
        {
 +
            // Set starting direction
 +
            flip = facing;
 +
 
 +
            // Set the spritesheet texture
 +
            PlayerAnimation = texture;
 +
 
 +
            // Set position of the animation
 +
            Position = position;
 +
 
 +
            // Set the player to be active
 +
            Active = false;
 +
 
 +
            // Set starting animation
 +
            animation = 1;
 +
 
 +
            // Set the color
 +
            this.color = Color.White;
 +
           
 +
            // Set the width of each frame
 +
            this.FrameWidth = 70;
 +
 
 +
            // Set the height of each frame
 +
            this.FrameHeight = 80;
 +
 
 +
            // Set the number of frames in the animation
 +
            this.frameCount = 4;
 +
 
 +
            // Set the time to zero
 +
            elapsedTime = 0;
 +
 
 +
            // Set current frame to frame 0
 +
            currentFrame = 0;
 +
        }
 +
</syntaxhighlight>
 +
 
 +
Now we need to create an update method, this will be used to move from frame to frame, elapsedTime is used to store the amount of time since the last frame change. So when this is greater than 100 (100 milliseconds, and thats 10 frames a second) we move to the next frame. When the current frame is equal to framecount(the number of frames in this animation) we reset the current frame to 0:
 +
 
 +
<syntaxhighlight lang=c#>
 +
        public void Update(GameTime gameTime)
 +
        {
 +
            elapsedTime += (int)gameTime.ElapsedGameTime.TotalMilliseconds;
 +
 
 +
            // If the elapsed time is larger than the frame time
 +
            // we need to switch frames
 +
            if (elapsedTime > 100)
 +
            {
 +
                // Move to the next frame
 +
                currentFrame++;
 +
 
 +
                // If the currentFrame is equal to frameCount reset currentFrame to zero
 +
                if (currentFrame == frameCount)
 +
                {
 +
                    currentFrame = 0;
 +
 
 +
                    // the current animation has finished
 +
                    Active = false;
 +
                }
 +
 
 +
                // Reset the elapsed time to zero
 +
                elapsedTime = 0;
 +
            }
 +
 
 +
            // Grab the correct frame in the image strip by multiplying the currentFrame index by the Frame width
 +
            // Each animation is in a different row of the spritesheet, so multiplying the frame height by animation will
 +
            // choose the animation to show
 +
            sourceRect = new Rectangle(currentFrame * FrameWidth, animation*FrameHeight, FrameWidth, FrameHeight);
 +
 
 +
            // Grab the correct frame in the image strip by multiplying the currentFrame index by the frame width
 +
            destinationRect = new Rectangle((int)Position.X,
 +
                (int)Position.Y , (int)(FrameWidth), (int)(FrameHeight));
 +
        }
 +
</syntaxhighlight>
 +
 
 +
Now we need to create a draw method, this will be used to draw the animation to the screen at the current position, is flip is set to true we need to draw using a different set of arguments:
 +
 
 +
<syntaxhighlight lang=csharp>
 +
        public void Draw(SpriteBatch spriteBatch)
 +
        {
 +
            if (flip)
 +
                spriteBatch.Draw(PlayerAnimation, destinationRect, sourceRect, color, 0f, Vector2.Zero, SpriteEffects.FlipHorizontally, 0f);
 +
            else
 +
                spriteBatch.Draw(PlayerAnimation, destinationRect, sourceRect, color);
 +
        }
 +
</syntaxhighlight>
 +
 
 +
Now we need a way to change the position of the animation and also the direction it is facing, so add these methods within your class:
 +
 
 +
<syntaxhighlight lang=c#>
 +
        public Vector2 SetPosition
 +
        {
 +
            get { return Position; }
 +
            set { Position = value; }
 +
        }
 +
 
 +
        public bool Flip
 +
        {
 +
            get { return flip; }
 +
            set { flip = value; }
 +
        }
 +
 
 +
</syntaxhighlight>
 +
 
 +
Now the complex bit, we need to be able to switch animations for when the player kicks, punches and so on. We need to create a new method within your animation class to achieve this. The get property will return the current animation, but the set property will allow you to change the animation but only if the current animation is finished and it must not be the same as the current one. Because some animations might have a different number of frames, it will be important to set the framecount for each one. The current frame is always set back to the start of the new animation:
 +
 
 +
<syntaxhighlight lang=c#>
 +
public int SwitchAnimation
 +
        {
 +
            get { return animation; }
 +
            set
 +
            {
 +
                // if the animation set is not the current one, and we aren't in the middle of an animation
 +
                if (value != animation && !Active)
 +
                {
 +
                    // Active means we are in the middle of an animation
 +
                    Active = true;
 +
 
 +
                    // Change the current animation
 +
                    animation = value;
 +
 
 +
                    // If the animation changes we need to start at frame 0
 +
                    currentFrame = 0;
 +
 
 +
                    // Different animations may have a different number of frames
 +
                    switch (animation)
 +
                    {
 +
                        case 0:
 +
                            frameCount = 4;
 +
                            break;
 +
                        case 6:
 +
                            frameCount = 5;
 +
                            break;
 +
                    }
 +
                }
 +
            }
 +
        }
 +
</syntaxhighlight>
 +
 
 +
==Completed Animation Class==
 +
 
 +
<syntaxhighlight lang=c#>
 +
    class Animation
 +
    {
 +
        int animation;
 +
 
 +
        // The time since we last updated the frame
 +
        int elapsedTime;
 +
 
 +
        // The number of frames that the animation contains
 +
        int frameCount;
 +
 
 +
        // The index of the current frame we are displaying
 +
        int currentFrame;
 +
 
 +
        // The color of the frame we will be displaying
 +
        Color color;
 +
 
 +
        // The area of the image strip we want to display
 +
        Rectangle sourceRect = new Rectangle();
 +
 
 +
        // The area where we want to display the image strip in the game
 +
        Rectangle destinationRect = new Rectangle();
 +
 
 +
        Texture2D PlayerAnimation;
 +
 
 +
        Vector2 Position;
 +
 
 +
        bool Active;
 +
 
 +
        bool flip;
 +
 
 +
        // Width of a given frame
 +
        public int FrameWidth;
 +
 
 +
        // Height of a given frame
 +
        public int FrameHeight;
 +
 
 +
        public Vector2 SetPosition
 +
        {
 +
            get { return Position; }
 +
            set { Position = value; }
 +
        }
 +
 
 +
        public bool Flip
 +
        {
 +
            get { return flip; }
 +
            set { flip = value; }
 +
        }
 +
 
 +
        public int SwitchAnimation
 +
        {
 +
            get { return animation; }
 +
            set
 +
            {
 +
                if (value != animation && !Active)
 +
                {
 +
                    Active = true;
 +
                    animation = value;
 +
                    currentFrame = 0;
 +
                    switch (animation)
 +
                    {
 +
                        case 0:
 +
                            frameCount = 4;
 +
                            break;
 +
                        case 1:
 +
                            frameCount = 4;
 +
                            break;
 +
                        case 2:
 +
                            frameCount = 4;
 +
                            break;
 +
                        case 3:
 +
                            frameCount = 5;
 +
                            break;
 +
                        case 6:
 +
                            frameCount = 4;
 +
                            break;
 +
                        default:
 +
                            frameCount = 4;
 +
                            break;
 +
                    }
 +
                }
 +
            }
 +
        }
 +
 
 +
        public void Initialize(Texture2D texture, Vector2 position)
 +
        {
 +
            PlayerAnimation = texture;
 +
            Position = position;
 +
 
 +
            // Set the player to be active
 +
 
 +
            animation = 1;
 +
 
 +
            this.color = Color.White;
 +
            this.FrameWidth = 70;
 +
            this.FrameHeight = 80;
 +
            this.frameCount = 4;
 +
 
 +
            // Set the time to zero
 +
            elapsedTime = 0;
 +
            currentFrame = 0;
 +
        }
 +
 
 +
        public void Update(GameTime gameTime)
 +
        {
 +
            elapsedTime += (int)gameTime.ElapsedGameTime.TotalMilliseconds;
 +
 
 +
            // If the elapsed time is larger than the frame time
 +
            // we need to switch frames
 +
            if (elapsedTime > 100)
 +
            {
 +
                // Move to the next frame
 +
                currentFrame++;
 +
 
 +
                // If the currentFrame is equal to frameCount reset currentFrame to zero
 +
                if (currentFrame == frameCount)
 +
                {
 +
                    currentFrame = 0;
 +
                    Active = false;
 +
                }
 +
 
 +
                // Reset the elapsed time to zero
 +
                elapsedTime = 0;
 +
            }
 +
 
 +
            // Grab the correct frame in the image strip by multiplying the currentFrame index by the Frame width
 +
 
 +
            sourceRect = new Rectangle(currentFrame * FrameWidth, animation*FrameHeight, FrameWidth, FrameHeight);
 +
 
 +
            // Grab the correct frame in the image strip by multiplying the currentFrame index by the frame width
 +
 
 +
            destinationRect = new Rectangle((int)Position.X,
 +
                (int)Position.Y , (int)(FrameWidth), (int)(FrameHeight));
 +
        }
 +
 
 +
        public void Draw(SpriteBatch spriteBatch)
 +
        {
 +
            if (flip)
 +
                spriteBatch.Draw(PlayerAnimation, destinationRect, sourceRect, color, 0f, Vector2.Zero, SpriteEffects.FlipHorizontally, 0f);
 +
            else
 +
                spriteBatch.Draw(PlayerAnimation, destinationRect, sourceRect, color);
 +
        }
 +
 
 +
 
 +
    }
 +
</syntaxhighlight>
 +
 
 +
==Using the Animation Class==
 +
Now, using the Game1.cs, we need to create an object of Animation called player. So in the declaration section of Game1.cs add the following:
 +
 
 +
<syntaxhighlight lang=c#>
 +
        Animation player;
 +
</syntaxhighlight>
 +
 
 +
Now, in the LoadContent method we need to get the starting position, texture and then initialise the player. We have set the proper height and width of the map object to make sure its the same size as the player:
 +
 
 +
<syntaxhighlight lang=c#>
 +
            player = new Animation();
 +
            Vector2 temp = new Vector2(
 +
                map.ObjectGroups["Objects"].Objects["Player"].X,
 +
                map.ObjectGroups["Objects"].Objects["Player"].Y
 +
                );
 +
            player.Initialize(Content.Load<Texture2D>("Fight"), temp, false);
 +
            map.ObjectGroups["Objects"].Objects["Player"].Height = 80;
 +
            map.ObjectGroups["Objects"].Objects["Player"].Width = 70;
 +
</syntaxhighlight>
 +
 
 +
Now, in the update method you should have something similar to this:
 +
 
 +
<syntaxhighlight lang=csharp>
 +
KeyboardState keyState = Keyboard.GetState();
 +
 
 +
if (keyState.IsKeyDown(Keys.Left))
 +
  map.ObjectGroups["Objects"].Objects["Player"].X -= 1;
 +
if (keyState.IsKeyDown(Keys.Right))
 +
  map.ObjectGroups["Objects"].Objects["Player"].X += 1;
 +
if (keyState.IsKeyDown(Keys.Up))
 +
  map.ObjectGroups["Objects"].Objects["Player"].Y -= 1;
 +
if (keyState.IsKeyDown(Keys.Down))
 +
  map.ObjectGroups["Objects"].Objects["Player"].Y += 1;
 +
</syntaxhighlight>
  
If you already have spriteBatch.Begin() or spriteBatch.End() then just place the middle line inbetween your lines.
+
We need to edit the left and right movement to use our code to face a certain direction, also animation 3 is my moving animation:
  
 
<syntaxhighlight lang=csharp>
 
<syntaxhighlight lang=csharp>
spriteBatch.Begin();
+
KeyboardState keyState = Keyboard.GetState();
map.Draw(spriteBatch, new Rectangle(0, 0, GraphicsDevice.Viewport.Width, GraphicsDevice.Viewport.Height), viewportPosition);
+
 
spriteBatch.End();
+
if (keyState.IsKeyDown(Keys.Left))
 +
{
 +
  map.ObjectGroups["Objects"].Objects["Player"].X -= 1;
 +
  player.Flip = true;
 +
  player.SwitchAnimation = 3;
 +
}
 +
if (keyState.IsKeyDown(Keys.Right))
 +
{
 +
  map.ObjectGroups["Objects"].Objects["Player"].X += 1;
 +
  player.Flip = false;
 +
  player.SwitchAnimation = 3;
 +
}
 +
if (keyState.IsKeyDown(Keys.Up))
 +
{
 +
  map.ObjectGroups["Objects"].Objects["Player"].Y -= 1;
 +
  player.SwitchAnimation = 3;
 +
}
 +
if (keyState.IsKeyDown(Keys.Down))
 +
{
 +
  map.ObjectGroups["Objects"].Objects["Player"].Y += 1;
 +
  player.SwitchAnimation = 3;
 +
}
 +
</syntaxhighlight>
 +
 
 +
Now for the combat moves:
 +
 
 +
<syntaxhighlight lang=c#>
 +
            //check for player combat moves
 +
            if (keys.IsKeyDown(Keys.B))
 +
            {
 +
                player.SwitchAnimation = 0;
 +
            }
 +
            else if (keys.IsKeyDown(Keys.P))
 +
            {
 +
                player.SwitchAnimation = 2;
 +
            }
 +
            else if (keys.IsKeyDown(Keys.K))
 +
            {
 +
                player.SwitchAnimation = 6;
 +
            }
 +
            else
 +
            {
 +
                player.SwitchAnimation = 1;
 +
            }
 +
</syntaxhighlight>
 +
 
 +
Finally in the update method we need to add the code to update the player, so add this after the move code:
 +
 
 +
<syntaxhighlight lang=c#>
 +
            // set the player position after movement
 +
            // sets position based on player object in map
 +
            // - viewPort will match the position in the map and screen
 +
            player.SetPosition = new Vector2(
 +
                map.ObjectGroups["Objects"].Objects["Player"].X,
 +
                map.ObjectGroups["Objects"].Objects["Player"].Y
 +
                ) - viewPort;
 +
 
 +
            // update the player
 +
            player.Update(gameTime);
 
</syntaxhighlight>
 
</syntaxhighlight>
  
At this point your project should run an display your map centered onto the player.
+
Finally in the Draw method of Game1.cs we need to add the code to draw the player, this will need to go after the map.draw but before spriteBatch.End():
 +
 
 +
<syntaxhighlight lang=c#>
 +
            player.Draw(spriteBatch);
 +
</syntaxhighlight>

Latest revision as of 14:05, 13 June 2018

This will show you how to create a Beat Em Up game using a tiled map, and tile based collision detection. This will be a fighting game but you will walk around the scene fighting the enemies you encounter. You could make this into a street fighter style game by removing the code to walk around, maybe they should just be able to go left and right.

Create Map

Tiled

You will firstly need to install the Tiled program from the website and link below. In college the Tiled executeables are on moodle, under project, technical skill, monogame, and tiled. I have also added links to other tutorials for using Tiled.

Tiled Website and Download

Tiled Map Editor

Tutorials for using Tiled

Offical Tiled Tutorials

Tiled Basics

Tiled Youtube Playlist Series

Written Version of Above Tutorials


New Tiled Map

You will need a new tiled map, the tile size in the screen shot is 128 pixels, in the end i actually changed this to 16 x 16 and resized the tiles accordingly:

Plat new map.gif

Add Tile Set

Now your map is created we need to add a tile set:

New tileset.gif

In creating this tutorial i first tried a tileset based upon a collection of images, this seemed fine in Tiled but failed to draw using Square.Tiled. So make sure you choose embed and based on tileset image:

Draw Your Level

Now you have a tile set, it is time to build a simple map, I have created an area for my floor and above this is the wall area. These are in my background layer, and my Bounds layer is made up of the wooden style tiles to identify the bounds of the level. You should name your layers to something appropriate:

Beat Em Map.gif

Add Player Object

Now you have bounds and the background, we can now set the position of the player. This will also be the object moved by the code and player input. So insert an object layer, and then use the rectangle tool to create the object. My final player was 60 pixels high and 40 pixels wide:

Beat Em Player Object.gif

MonoGame Project

Create a new MonoGame project, mine is a Windows project.

Setup Square.Tiled

If you have a project ready, create a new class in your project. Click project and new class and call it Tiled.cs, then copy the code from this document over the code in your new class: Square.Tiled Class

Or get it from GitHub: GitHub TRCCompSci tiled-xna

Remember to set the name space to Squared.Tiled.

You will need to add references in the using section for the following:

using System.IO;
using Squared.Tiled;

Code to Display Map

Map Variables

At the top of your Game1 class add these additional variables:

Map map;
Layer bounds;
Vector2 viewportPosition;
int tilePixel;

LoadContent for map

In the LoadContent method add the following lines to load the map, the bounds layer and to set the texture of the player. My map is called SimpleBeatEmUp.tmx you may have given yours a different name. My layer is called Bounds, again you may have given it a different name:

map = Map.Load(Path.Combine(Content.RootDirectory, "SimpleBeatEmUp.tmx"), Content);
bounds = map.Layers["Bounds"];
map.ObjectGroups["Objects"].Objects["Player"].Texture = Content.Load<Texture2D>("hero");
viewportPosition = new Vector2(0,0);

Remember to also add the png files for the hero, and the tileset for your map into the content pipeline.

The Draw Method

Add the following to the draw method to draw the map and hero to the screen.

If you already have spriteBatch.Begin() or spriteBatch.End() then just place the middle line inbetween your lines.

spriteBatch.Begin();
map.Draw(spriteBatch, new Rectangle(0, 0, GraphicsDevice.Viewport.Width, GraphicsDevice.Viewport.Height), viewportPosition);
spriteBatch.End();

At this point your project should run and display your map and the player.

Moving The Map

You will also need to update the viewportPosition. I want this to change in stages, so that you can walk around the screen and switch the viewportPosition when the player gets close to the end or start of the screen:

int xpos = map.ObjectGroups["Objects"].Objects["Player"].X;

int stage = 0;
if (xpos < 700)
    stage = 0;
else if (xpos < 1400)
    stage = 700;
else if (xpos < 2100)
    stage = 1400;

viewportPosition= new Vector2(stage,0);

I have chosen multiples of 700 because my screen width is 800 pixels.

Moving The Player

We can add the code below into the update method for Game1, it should probably go before the code above to move the map:

KeyboardState keyState = Keyboard.GetState();

if (keyState.IsKeyDown(Keys.Left))
   map.ObjectGroups["Objects"].Objects["Player"].X -= 1;
if (keyState.IsKeyDown(Keys.Right))
   map.ObjectGroups["Objects"].Objects["Player"].X += 1;
if (keyState.IsKeyDown(Keys.Up))
   map.ObjectGroups["Objects"].Objects["Player"].Y -= 1;
if (keyState.IsKeyDown(Keys.Down))
   map.ObjectGroups["Objects"].Objects["Player"].Y += 1;

At this stage your map should display with the character also visible. The character should be able to move, try moving to the right it should change the position of your map accordingly.

Checking Bounds

We need to check if the player has moved out of bounds. We are therefore going to record the players position before any movement, then allow them to move, then check the bounds, and finally if they have moved out of bounds use the pre movement position to reset the current position.

This will require a new procedure:

public bool CheckBounds()
{

}

Inside the new procedure we need to declare a boolean, this will be used to return back from this method. We also need to create a rectangle for the player, this only covers the bottom 10 pixels of the texture:

bool check = false;

Rectangle playrec = new Rectangle(
   map.ObjectGroups["Objects"].Objects["Player"].X,
   map.ObjectGroups["Objects"].Objects["Player"].Y + (map.ObjectGroups["Objects"].Objects["Player"].Height - 10),
   map.ObjectGroups["Objects"].Objects["Player"].Width,
   10
);

Now we need 2 for loops to cycle through every tile on the map:

for (int x = 0; x < map.Width; x++)
{
   for (int y = 0; y < map.Height; y++)
   {

   }
}


Now inside the inner for loop we need to add the code to check the current tile. bounds.GetTile(x,y) will tell us the tile number from the tile set for the tile at postion x,y. If the tile number is 0 then no tile is present at x,y , if the tile number is not 0 then a tile is present at x,y. If a tile exists at x,y we then check to see if it intersects the player rectangle we have already created:

for (int x = 0; x < map.Width; x++)
{
   for (int y = 0; y < map.Height; y++)
   {
        if (bounds.GetTile(x,y) != 0)
        {
           Rectangle tile = new Rectangle(
             (int)x * map.TileWidth(),
             (int)y * map.TileHeight(),
             map.TileWidth(),
             map.TileHeight()
           );

           if (playrec.Intersects(tile))
              check = true;
           }
   }
}


Finally we need to add the code to return the value of check, you can see this in rhe completed CheckBounds procedure below:

        public bool CheckBounds()
        {
            bool check = false;

            Rectangle playrec = new Rectangle(
                map.ObjectGroups["Objects"].Objects["Player"].X,
                map.ObjectGroups["Objects"].Objects["Player"].Y + (map.ObjectGroups["Objects"].Objects["Player"].Height - 10),
                map.ObjectGroups["Objects"].Objects["Player"].Width,
                10
                );

            for (int x = 0; x < map.Width; x++)
            {
                for (int y = 0; y < map.Height; y++)
                {
                    if (bounds.GetTile(x,y) != 0)
                    {
                        Rectangle tile = new Rectangle(
                            (int)x * map.TileWidth(),
                            (int)y * map.TileHeight(),
                            map.TileWidth(),
                            map.TileHeight()
                            );

                        if (playrec.Intersects(tile))
                            check = true;
                    }
                }               
            }         

            return check;
        }

Using Check Bounds

Firstly add these lines to record the position before any movement:

int tempx = map.ObjectGroups["Objects"].Objects["Player"].X;
int tempy = map.ObjectGroups["Objects"].Objects["Player"].Y;

Now after we check for movement add the following to use CheckBounds:

if (CheckBounds())
{
    map.ObjectGroups["Objects"].Objects["Player"].X = tempx;
    map.ObjectGroups["Objects"].Objects["Player"].Y = tempy;
}

Now the player should be able to move but still be contained by the bounds set. For example the screenshot below shows the character's feet should always stay within bounds:

Beat Em Bounds Test.gif

Player Moves & Animation

Now obviously in a Beat'm Up game the playing character needs to perform moves such has a kick or punch. This will need to be animations because otherwise it will seem very erratic. This will therefore require a spritesheet, and may require a spritesheet for each move. The spritesheet I used is THIS, it has several different animations on the same sheet.

Animation base class

Create a new class within your project (Project tab, then Add Class) and call it a suitable name, mine is called Animation. Now within the class we will need to create the following variables:

    class Animation
    {
        // The current animation
        int animation;

        // The time since we last updated the frame
        int elapsedTime;

        // The number of frames that the animation contains
        int frameCount;

        // The index of the current frame we are displaying
        int currentFrame;

        // The color of the frame we will be displaying
        Color color;

        // The area of the image strip we want to display
        Rectangle sourceRect = new Rectangle();

        // The area where we want to display the image strip in the game
        Rectangle destinationRect = new Rectangle();

        // This is the spritesheet for the animations
        Texture2D PlayerAnimation;

        // The current position of the animation
        Vector2 Position;

        // Is the animation active, if so it will be updated
        bool Active;

        // Flip will be used to allow one set of animations work facing left or right
        bool flip;

        // Width of a given frame
        public int FrameWidth;

        // Height of a given frame
        public int FrameHeight;
    }

Now, within your class and after the variable declarations we need to create an initialise method for the animation. We need to do this to setup the animation with a spritesheet, a position, the correct dimensions and information about our spritesheet:

        public void Initialize(Texture2D texture, Vector2 position, Bool facing)
        {
            // Set starting direction
            flip = facing;

            // Set the spritesheet texture
            PlayerAnimation = texture;

            // Set position of the animation
            Position = position;

            // Set the player to be active
            Active = false;

            // Set starting animation
            animation = 1;

            // Set the color
            this.color = Color.White;
            
            // Set the width of each frame
            this.FrameWidth = 70;

            // Set the height of each frame
            this.FrameHeight = 80;

            // Set the number of frames in the animation
            this.frameCount = 4;

            // Set the time to zero
            elapsedTime = 0;

            // Set current frame to frame 0
            currentFrame = 0;
        }

Now we need to create an update method, this will be used to move from frame to frame, elapsedTime is used to store the amount of time since the last frame change. So when this is greater than 100 (100 milliseconds, and thats 10 frames a second) we move to the next frame. When the current frame is equal to framecount(the number of frames in this animation) we reset the current frame to 0:

        public void Update(GameTime gameTime)
        {
            elapsedTime += (int)gameTime.ElapsedGameTime.TotalMilliseconds;

            // If the elapsed time is larger than the frame time
            // we need to switch frames
            if (elapsedTime > 100)
            {
                // Move to the next frame
                currentFrame++;

                // If the currentFrame is equal to frameCount reset currentFrame to zero
                if (currentFrame == frameCount)
                {
                    currentFrame = 0;

                    // the current animation has finished
                    Active = false;
                }

                // Reset the elapsed time to zero
                elapsedTime = 0;
            }

            // Grab the correct frame in the image strip by multiplying the currentFrame index by the Frame width
            // Each animation is in a different row of the spritesheet, so multiplying the frame height by animation will
            // choose the animation to show
            sourceRect = new Rectangle(currentFrame * FrameWidth, animation*FrameHeight, FrameWidth, FrameHeight);

            // Grab the correct frame in the image strip by multiplying the currentFrame index by the frame width
            destinationRect = new Rectangle((int)Position.X,
                (int)Position.Y , (int)(FrameWidth), (int)(FrameHeight));
        }

Now we need to create a draw method, this will be used to draw the animation to the screen at the current position, is flip is set to true we need to draw using a different set of arguments:

        public void Draw(SpriteBatch spriteBatch)
        {
            if (flip)
                spriteBatch.Draw(PlayerAnimation, destinationRect, sourceRect, color, 0f, Vector2.Zero, SpriteEffects.FlipHorizontally, 0f);
            else
                spriteBatch.Draw(PlayerAnimation, destinationRect, sourceRect, color);
        }

Now we need a way to change the position of the animation and also the direction it is facing, so add these methods within your class:

        public Vector2 SetPosition
        {
            get { return Position; }
            set { Position = value; }
        }

        public bool Flip
        {
            get { return flip; }
            set { flip = value; }
        }

Now the complex bit, we need to be able to switch animations for when the player kicks, punches and so on. We need to create a new method within your animation class to achieve this. The get property will return the current animation, but the set property will allow you to change the animation but only if the current animation is finished and it must not be the same as the current one. Because some animations might have a different number of frames, it will be important to set the framecount for each one. The current frame is always set back to the start of the new animation:

public int SwitchAnimation
        {
            get { return animation; }
            set
            {
                // if the animation set is not the current one, and we aren't in the middle of an animation
                if (value != animation && !Active)
                {
                    // Active means we are in the middle of an animation
                    Active = true;

                    // Change the current animation
                    animation = value;

                    // If the animation changes we need to start at frame 0
                    currentFrame = 0;

                    // Different animations may have a different number of frames
                    switch (animation)
                    {
                        case 0:
                            frameCount = 4;
                            break;
                        case 6:
                            frameCount = 5;
                            break;
                    }
                }
            }
        }

Completed Animation Class

    class Animation
    {
        int animation;

        // The time since we last updated the frame
        int elapsedTime;

        // The number of frames that the animation contains
        int frameCount;

        // The index of the current frame we are displaying
        int currentFrame;

        // The color of the frame we will be displaying
        Color color;

        // The area of the image strip we want to display
        Rectangle sourceRect = new Rectangle();

        // The area where we want to display the image strip in the game
        Rectangle destinationRect = new Rectangle();

        Texture2D PlayerAnimation;

        Vector2 Position;

        bool Active;

        bool flip;

        // Width of a given frame
        public int FrameWidth;

        // Height of a given frame
        public int FrameHeight;

        public Vector2 SetPosition
        {
            get { return Position; }
            set { Position = value; }
        }

        public bool Flip
        {
            get { return flip; }
            set { flip = value; }
        }

        public int SwitchAnimation
        {
            get { return animation; }
            set
            {
                if (value != animation && !Active)
                {
                    Active = true;
                    animation = value;
                    currentFrame = 0;
                    switch (animation)
                    {
                        case 0:
                            frameCount = 4;
                            break;
                        case 1:
                            frameCount = 4;
                            break;
                        case 2:
                            frameCount = 4;
                            break;
                        case 3:
                            frameCount = 5;
                            break;
                        case 6:
                            frameCount = 4;
                            break;
                        default:
                            frameCount = 4;
                            break;
                    }
                }
            }
        }

        public void Initialize(Texture2D texture, Vector2 position)
        {
            PlayerAnimation = texture;
            Position = position;

            // Set the player to be active

            animation = 1;

            this.color = Color.White;
            this.FrameWidth = 70;
            this.FrameHeight = 80;
            this.frameCount = 4;

            // Set the time to zero
            elapsedTime = 0;
            currentFrame = 0;
        }

        public void Update(GameTime gameTime)
        {
            elapsedTime += (int)gameTime.ElapsedGameTime.TotalMilliseconds;

            // If the elapsed time is larger than the frame time
            // we need to switch frames
            if (elapsedTime > 100)
            {
                // Move to the next frame
                currentFrame++;

                // If the currentFrame is equal to frameCount reset currentFrame to zero
                if (currentFrame == frameCount)
                {
                    currentFrame = 0;
                    Active = false;
                }

                // Reset the elapsed time to zero
                elapsedTime = 0;
            }

            // Grab the correct frame in the image strip by multiplying the currentFrame index by the Frame width

            sourceRect = new Rectangle(currentFrame * FrameWidth, animation*FrameHeight, FrameWidth, FrameHeight);

            // Grab the correct frame in the image strip by multiplying the currentFrame index by the frame width

            destinationRect = new Rectangle((int)Position.X,
                (int)Position.Y , (int)(FrameWidth), (int)(FrameHeight));
        }

        public void Draw(SpriteBatch spriteBatch)
        {
            if (flip)
                spriteBatch.Draw(PlayerAnimation, destinationRect, sourceRect, color, 0f, Vector2.Zero, SpriteEffects.FlipHorizontally, 0f);
            else
                spriteBatch.Draw(PlayerAnimation, destinationRect, sourceRect, color);
        }


    }

Using the Animation Class

Now, using the Game1.cs, we need to create an object of Animation called player. So in the declaration section of Game1.cs add the following:

        Animation player;

Now, in the LoadContent method we need to get the starting position, texture and then initialise the player. We have set the proper height and width of the map object to make sure its the same size as the player:

            player = new Animation();
            Vector2 temp = new Vector2(
                map.ObjectGroups["Objects"].Objects["Player"].X,
                map.ObjectGroups["Objects"].Objects["Player"].Y
                );
            player.Initialize(Content.Load<Texture2D>("Fight"), temp, false);
            map.ObjectGroups["Objects"].Objects["Player"].Height = 80;
            map.ObjectGroups["Objects"].Objects["Player"].Width = 70;

Now, in the update method you should have something similar to this:

KeyboardState keyState = Keyboard.GetState();

if (keyState.IsKeyDown(Keys.Left))
   map.ObjectGroups["Objects"].Objects["Player"].X -= 1;
if (keyState.IsKeyDown(Keys.Right))
   map.ObjectGroups["Objects"].Objects["Player"].X += 1;
if (keyState.IsKeyDown(Keys.Up))
   map.ObjectGroups["Objects"].Objects["Player"].Y -= 1;
if (keyState.IsKeyDown(Keys.Down))
   map.ObjectGroups["Objects"].Objects["Player"].Y += 1;

We need to edit the left and right movement to use our code to face a certain direction, also animation 3 is my moving animation:

KeyboardState keyState = Keyboard.GetState();

if (keyState.IsKeyDown(Keys.Left))
{
   map.ObjectGroups["Objects"].Objects["Player"].X -= 1;
   player.Flip = true;
   player.SwitchAnimation = 3;
}
if (keyState.IsKeyDown(Keys.Right))
{
   map.ObjectGroups["Objects"].Objects["Player"].X += 1;
   player.Flip = false;
   player.SwitchAnimation = 3;
}
if (keyState.IsKeyDown(Keys.Up))
{
   map.ObjectGroups["Objects"].Objects["Player"].Y -= 1;
   player.SwitchAnimation = 3;
}
if (keyState.IsKeyDown(Keys.Down))
{
   map.ObjectGroups["Objects"].Objects["Player"].Y += 1;
   player.SwitchAnimation = 3;
}

Now for the combat moves:

            //check for player combat moves
            if (keys.IsKeyDown(Keys.B))
            {
                player.SwitchAnimation = 0;
            }
            else if (keys.IsKeyDown(Keys.P))
            {
                player.SwitchAnimation = 2;
            }
            else if (keys.IsKeyDown(Keys.K))
            {
                player.SwitchAnimation = 6;
            }
            else
            {
                player.SwitchAnimation = 1;
            }

Finally in the update method we need to add the code to update the player, so add this after the move code:

            // set the player position after movement
            // sets position based on player object in map
            // - viewPort will match the position in the map and screen
            player.SetPosition = new Vector2(
                map.ObjectGroups["Objects"].Objects["Player"].X,
                map.ObjectGroups["Objects"].Objects["Player"].Y
                ) - viewPort;

            // update the player
            player.Update(gameTime);

Finally in the Draw method of Game1.cs we need to add the code to draw the player, this will need to go after the map.draw but before spriteBatch.End():

            player.Draw(spriteBatch);