Monday, April 6, 2020

Tutorial: How to Make a 2d Platformer in Unity - Custom Physics Shapes on Tiles and Slopes

Creating Custom Physics Shapes:

The source code used for this post is available here.
Open the sprite Editor and click on the ground_46 sprite.  It looks like this:
At the top left of the Window click on the dropdown that says Sprite Editor.  Select Custom Physics Shape.  Then, click in drag inside that sprite to begin creating a shape or you can click the generate button to have Unity generate a shape.  Next, click on the white squares and drag them to create the shape that best fits the sprite.  Also make sure to toggle on snap at the top of the window to make the process easier.  The most important thing is to make sure that neighboring sprites have shapes that line up well.  Once you have the shape you want hit apply to save the shape.  The Tile Palette will automatically be updated.  
 Create Custom Physics Shapes for the following Tiles (ground_1, ground_33, ground_47, ground_48,) and add them all to the Tile Palette to paint with. Below is a screen shot of the Tile Palette I created.

If the Tilemap Collider doesn't update correctly try disabling and reenabling it.

Another error I encountered while making this Tilemap is in the screenshot below.  If you encounter this problem you can solve it by making a sprite material Pixel Snap enabled and apply it to your TilemapRenderer.


Your Tilemap should look like the screenshot below.  Some shortcut keys will help you achieve this result.  
Press [ : Rotate Tile Counter Clockwise
Press ] : Rotate Tile Clockwise
Press Shift+[ : Flip Tile on X axis
Press Shift+] : Flip Tile on Y axis

Our character will have a few issues if we hit play right, now.  The character will launch into the air when reaching the top of a hill and if you move against a wall while in the air character will get stuck.  I'll illustrate this below. 

To fix the stuck to wall problem the character needs a physics material with no friction.  Right click in the Project window and click Create>Physics Material 2D.  Set the material's friction to 0.  This creates another problem which is illustrated below.  This character while lacking friction will simply slide down hills.

To fix this we need to add these we need to freeze the Rigidbody2D's X position when the character isn't moving.  We can do this by adding these lines that are highlighted in red:
     if (x < 0)  
     {  
       playerRig.constraints =  
         RigidbodyConstraints2D.FreezeRotation;  
       playerAnimator.SetFloat(movingFloatName, 1f);  
       playerSpriteRenderer.flipX = true;  
       playerAnimator.speed = 1f;  
     }  
     else if (x > 0)  
     {  
       playerRig.constraints =  
         RigidbodyConstraints2D.FreezeRotation;  
       playerAnimator.SetFloat(movingFloatName, 1f);  
       playerSpriteRenderer.flipX = false;  
       playerAnimator.speed = 1f;  
     }  
     else  
     {  
       playerRig.constraints =  
         RigidbodyConstraints2D.FreezeRotation |  
         RigidbodyConstraints2D.FreezePositionX;  
       playerAnimator.SetFloat(movingFloatName, 0f);  
       playerAnimator.speed = .5f;  
     }  

Next, we need to fix this problem the causes the character to fly up in the air when hitting the top of a wall:
This is caused by the bottom of the capsule collider pressing against another collider and sliding by the collider.  The character gains upward momentum while the sliding against the other collider.  To fix this we will just keep the character from gaining this momentum.  We need to create a new variable to determine when the character is jumping off the ground.
bool leftGround; 

Then we need to add code to use this variable:

In the Idle code block, add this line:
 if (IsGrounded())  
       {  
         playerRig.gravityScale = groundedGravityScale;  
         leftGround = false;  
         if (Input.GetKeyDown(KeyCode.Space))  
         {  
           Jump();  
         }  
       }  


In the Jump state code block add this code:
       if (IsGrounded())  
       {  
         if (playerRig.velocity.y <= 0)  
         {  
           playerAnimator.SetBool(jumpBoolName, false);  
           currentPlayerPhase = PlayerPhase.IDLE;  
           playerRig.gravityScale = groundedGravityScale;  
         }  
         else if (leftGround)  
         {  
           playerRig.gravityScale = groundedGravityScale;  
         }  
       }  
       else if (playerRig.velocity.y <= 0)  
       {  
         playerRig.gravityScale = fallingGravityScale;  
       }  
       else  
       {  
         leftGround = true;  
       }  
What this does is apply higher gravity when the character returns to the ground after jumping.  The result is this:




That finishes up navigating slopes.  The next post will cover parallax backgrounds.


The source code used for this post is available here.

Sunday, April 5, 2020

Tutorial: How to Make a 2d Platformer in Unity - Platformer Basics

Setting Up the Player Character:

The source code for this post can be found here or at the bottom of the page.

First, create a script named Player and attach it to your character's GameObject.  Next we need to change the character's layer.  In the Inspector, at the top right is the layer selector.  Click it and select add new layer.  Then, in the layer 8 field type Player.  Go back to your character's Inspector window and change the layer to Player.

To finish, the character needs a CapsuleCollider2D (it is important that we use a CapsuleCollider2D because the rounded edges are necessary to navigate slopes) and a Rigidbody2D.  The Rigidbody2D must have the following settings: In the Inspector set BodyType to Dynamic, click the checkbox next to Simulated, and click the arrow next to Constraints to expand it and click the checkbox next to FreezeRotation>Z.  If you don't freeze the Z rotation you will end up with the problem in the gif below.
Adjust the CapsuleCollider2D to encompass the character.  Below is a screen shot of the settings I used and all the required components.  Ensure that your character has the Player script, an Animator with attached AnimatorController, a CapsuleCollider2D, a RigidBody2D and a SpriteRenderer.  If you need help setting up an AnimatorController check out my Creating Animations Tutorial.

Let's start coding.  

Open the Player script.  First we need a list of Components.  Add the following code inside the Player class declaration.

   //components  
   Rigidbody2D playerRig;  
   Animator playerAnimator;  
   SpriteRenderer playerSpriteRenderer;  
   CapsuleCollider2D playerCapsuleCollider2D;  

Then, add these lines to the Start function.

   void Start()  
   {  
     playerRig=gameObject.GetComponent<Rigidbody2D>();  
     playerAnimator = gameObject.GetComponent<Animator>();  
     playerSpriteRenderer = gameObject.GetComponent<SpriteRenderer>();  
     playerCapsuleCollider2D = gameObject.GetComponent<CapsuleCollider2D>();  
   }  

These lines will grab the components we need or you can also set them to public and fill in the reference in the scene.

Next, we need to create new variables and constants.  Add the following to the class declaration.
   const string movingFloatName = "Moving";  
   const float movementSpeed = 5f, jumpPower=7f,  
     jumpGravityScale = 1f, fallingGravityScale = 5f;  
   public LayerMask groundedLayerMask;  
   Vector2 groundOverlapArea;  
   float groundOffset; 
movingFloatName is used so that we can use code auto completion when interacting with the Animator.  movementSpeed is used to control the speed that the character moves.  jumpPower is used to control jump height.  groundedGravityScale will be used to set the Rigidbody2D's gravity scale value when initially jumping.  fallingGravityScale is used to increase the fall speed of the character.  The groundedLayerMask will specify to Unity which layers are considered to be ground and setting it to public makes it much easier to adjust.  Make sure to set it include only Default.  groundOverlapArea is used to define an area that will detect when the player is on the ground.  By default Unity centers a sprite.  Because we want to use the feet of the sprite to determine when the character is standing on ground, we need to use an offset to determine the position of the feet.  groundOffset is a variable where we can store that offset value.  We need to add two lines of code to the Start function to set the last two values.
     groundOverlapArea = new Vector2((  
       playerCapsuleCollider2D.size.x / 2f) * .9f, .1f);  
     groundOffset = playerCapsuleCollider2D.offset.y -  
       playerCapsuleCollider2D.size.y / 2f; 
The X component of groundOverlapArea is set to half the width of the CapsuleCollider2D but we need to shrink the value down a little so that player won't be able to jump off of vertical walls.  The Y component is just a small value to determine how close the player needs to be to the floor to jump.  For the groundOffset we take the Y offset of the CapsuleCollider2D and subtract half the CapsuleCollider2D's height to get the position where it contacts the ground.

Now we need add some code to the Update function that will move the character around.
     //float x = Input.GetAxis("Horizontal");  
     float x = Input.GetAxisRaw("Horizontal");  
     playerRig.velocity = new Vector2(movementSpeed * x,  
     playerRig.velocity.y);  

We can use GetAxis or GetAxisRaw for horizontal movement.  The difference is that by default Unity adds smoothing to axes.  GetAxisRaw allows more precise movement because it doesn't have smoothing.  You can try both and select your preference.  To move the character we simply set the x velocity to horizontal axis multiplied by the character's movement speed and the Y velocity is just the current velocity.  If you hit play, your character will move but won't be facing the correct direction when moving left and won't play the run animation.

Next we need to set up the player state machine and fix the the character's facing error.  First we add an enum to the bottom (outside of the class declaration) of the script.
 public enum PlayerPhase  
 {  
   IDLE,JUMP  
 } 

Next, we need a state declaration that is under the class declaration.
 public PlayerPhase currentPlayerPhase; 

Finally, we need to add these lines to the update function.  To establish our player state and fix the character's facing error.
     if (currentPlayerPhase == PlayerPhase.IDLE)  
     {  
       if (x < 0)  
       {  
         playerAnimator.SetFloat(movingFloatName, 1f);  
         playerSpriteRenderer.flipX = true;  
         playerAnimator.speed = 1f;  
       }  
       else if (x > 0)  
       {  
         playerAnimator.SetFloat(movingFloatName, 1f);  
         playerSpriteRenderer.flipX = false;  
         playerAnimator.speed = 1f;  
       }  
       else  
       {  
         playerAnimator.SetFloat(movingFloatName, 0f);  
         playerAnimator.speed = .5f;  
       }  
     }  

We simply use the flipX value to determine where the player is facing and leave the value alone when the character is not moving to continue facing in the direction he was last moving.  We set the Animator float to 0 or 1 depending if the player is moving (if the Animator was set up the way I showed you in the second tutorial this should work and below I have included screenshot of the blend tree values I have used).  

So let's hit play and see how it works.
You may notice that this character's idle animation seems a little fast compared to the run animation.  This is because the idle animation has fewer frames but you can fix that easily if you wish to.  Just add this line of code any time you change the animation.
 playerAnimator.speed = .5f; 
Because we use a blend tree we can't just set speed value in the Animator.

Now we need to add jumping.  First we need a function that will determine if the player is on the ground.
   bool IsGrounded()  
   {   
     Vector2 a = new Vector2(  
       transform.position.x - groundOverlapArea.x,  
       transform.position.y + groundOffset);  
     Vector2 b = new Vector2(  
       transform.position.x + groundOverlapArea.x,  
       transform.position.y + groundOffset -  
       groundOverlapArea.y);  
   
     return (Physics2D.OverlapArea(a, b, groundedLayerMask));  
   }  
Point B is set to the bottom and left of the character's collider.  Point B extends below the character's feet and is on the right side of the collider.   We use the physics system to check if that logical box overlaps with anything included in our LayerMask.  Next, we need to add a call this function in the Update function and we need a Jump function.

Here is the Jump function:
   void Jump()  
   {  
     playerRig.gravityScale = jumpGravityScale;  
     playerRig.velocity = new Vector2(playerRig.velocity.x,  
       jumpPower);  
     currentPlayerPhase = PlayerPhase.JUMP;  

   }  
We set the gravityScale equal to jumpGravityScale (this blows the player to have a slower acceleration to his jump peak), Y velocity equal to jumpPower, and we set the Jump state.

Now we add calls to the IsGrounded and Jump functions in the Update function.  Below is the code added to the Update function in the Idle state code block.
       if (IsGrounded())  
       {  
         playerRig.gravityScale = fallingGravityScale;  
         
         if (Input.GetKeyDown(KeyCode.Space))  
         {  
           Jump();  
         }  
       }  

First, we check if the player is grounded.  Then, we check if the the spacebar was pressed. If so we make a call to the Jump function.  We also want to set the gravity scale back to 5f while the player is on the ground.

Then, we need to add the Jump state code block.
     else if (currentPlayerPhase == PlayerPhase.JUMP)  
     {  
       if (x < 0)  
       {  
         playerSpriteRenderer.flipX = true;  
       }  
       else if (x > 0)  
       {  
         playerSpriteRenderer.flipX = false;  
       }  
       if (IsGrounded())  
       {  
         if (playerRig.velocity.y <= 0)  
         {  
           currentPlayerPhase = PlayerPhase.IDLE;  
           playerRig.gravityScale = groundedGravityScale;  
         }  
       }  
       else if (playerRig.velocity.y <= 0)  
       {  
         playerRig.gravityScale = fallingGravityScale;  
       }  
     }  

Here is the result:
The character is affected by stronger gravity as soon as he reaches the arch of the jump.

The Next thing we should do is add a jump animation.  Create an animation using the jump sprites (adventurer-jump-00, adventurer-jump-01, adventurer-jump-03, adventurer-jump-03).  Then, add the animation to your Animator and add a bool parameter called Jump.  Create two transitions with the parameter Jump set to true and false.
Below I have included my transition settings:


Now we just need to add two lines of code.  First, to the Jump function:
 playerAnimator.SetBool(jumpBoolName, true); 

Then, add this line of code to the Update function after the grounded check in the Jump state code block:
       if (IsGrounded())  
       {  
         if (playerRig.velocity.y <= 0)  
         {  
           playerAnimator.SetBool(jumpBoolName, false);  
           currentPlayerPhase = PlayerPhase.IDLE;  
           playerRig.gravityScale = groundedGravityScale;  
         }  
       }  

And here is the result:

In the next post you will learn how to navigate slopes using physics materials.

The source code for this post can be found here.

Tuesday, March 31, 2020

Tutorial: How to Make a 2d Platformer in Unity - Tilemap Basics

Setting up the Sprite Sheet:

In the Project window, select ground.png from the pixel2dcastle1.1.zip.  Then, in the inspector change the Texture Type to Sprite (2D and UI), change Sprite Mode to Multiple, change the Pixels Per Unit to 16, and change the Filter Mode to Point.  Next, open the Sprite Editor window, click on Slice, change Type to Grid By Cell Size, change Pixel Size to 16x16, click the Slice button, and before you close the Sprite Editor window click Apply in upper right corner to save the changes.

Creating the Tile Palette:

First, create a folder where you would like to store your Tile Palette and click on Window>2D>Tile Palette.  In the Tile Palette Window, click on Create New Palette and save the palette in the folder you created.  Find the ground.png file in the project view and click the arrow next to it expand the sprite list.  Drag ground_1 and ground_17 into the Tile Palette Window and save the tiles to your folder.  

Creating the Tilemap:

Click on the menu option GameObject>2D Object>Tilemap to create a Tilemap in your scene.  Next, click on the arrow next to the Grid object in the Hierarchy view to expand it and click on the Tilemap object.  Open the Tile Palette window and click on the paint brush icon, select the tile you want to paint with.  Then, paint a row of tiles that we can move the player around on.  Finally, in the Inspector view with the tile map object selected click Add Component and add Tilemap Collider to the Tilemap object.

We're finally ready to start writing code.  In the next part we'll be animating our character implementing running and jumping.

Follow this link to the next post.

Monday, March 30, 2020

Tutorial: How to Make a 2d Platformer in Unity - Creating Animations

Creating Animations:

Before we get started programming movement, we need to create our character and our animations.  To create a sprite animation in Unity simply select the sprites in the project window and drag them into the scene.  Highlight the idle sprites (adventurer-idle-00, adventurer-idle-01, adventurer-idle-02) from the adventurer character we downloaded in the last post and drag them into the scene to create an Idle animation.

This will create a GameObject with a SpriteRenderer and an Animator.  The Animator an Idle animation that is set as the default state.  If you hit the play button you will see the character looping through the idle animation.  Do the same with running animation sprites (adventurer-run-00, adventurer-run-01, adventurer-run-02, adventurer-run-03, adventurer-run-04, adventurer-run-05) and the jumping animation sprites (adventurer-jump-00, adventurer-jump-01, adventurer-jump-02, adventurer-jump-03) and save the animations with a name you'll remember.

Setting Up The Animator:

Delete all but one of the SpriteRenderers.  Click on the SpriteRenderer in the scene view and make sure it has an Animator and AnimatorController(if not add an Animator component and right in project view>create>Animator Controller).  Then click on Window>Animation>Animator to bring up the Animator window.  You should see the Animator that is attached to the SpriteRenderer.  Delete all the Animator states.  On the left side of the window click on the parameters tab and click on the plus sign to create a new float.  Name the float Moving.  Right click in the Animator window and select Create State>From New Blend Tree.  Click on the Blend Tree and rename it Idle.  Double click on the blend tree to edit it.  In the inspector, make sure that the blend type is set to 1D and that the parameter is set to Moving.  Click on the plus sign and add 2 motion fields.  Then, click on the circle to show the available animations and choose the idle and running animation for the fields.  The idle animation field should have its threshold set to 0 and the running animation field should have its threshold set to 1.


With the animation set up we're ready to move on the next tutorial!  Next we need to create an environment that our the player can move around in.  We will need to slice a sprite sheet and create a tilemap.
Follow this link to the next post.

Sunday, March 29, 2020

Tutorial: How to Make a 2d Platformer in Unity - Overview

This is the first in a series of tutorials showing You how to create a 2d platformer in Unity using the Unity physics system.  Before I show you the code I want to point you to the assists I will be using and I want to provide an overview of the series.  Below you will find links to the other posts and links to the assets I will be using.

Overview:

1.  Getting Started.
6.  Parallax Background.
7.  Climbing Ladders and Double Jump.
8.  Grabbing and Climbing Ledges.
9.  Enemy AI.


Getting started:

First make sure you have Unity installed.  You can grab it here.  I will be using Unity 2019.2 but any version after 5 should work.

We also need to download the assets I will be using.  Download Adventurer-1.5.zip, pixel2dcastle1.1.zip, and Grassy_Mountains_Parallax_Background-vnitti.zip.

Create a new project and move the files into the asset folder and let's get started programming!
Follow this link to the next post.