2D Pokemon-style Movement with working collision

Hey everyone,

I recently picked up Unity again with the idea of making a game with 2D Tilebased movement similar to what the old Pokemon games offered.

So my basic constraints were that the player can only move from tile to tile in horizontal OR vertical direction allowing no diagonal movement. The second constraint is that similar to Pokemon when the player only shortly presses a move key they can change the direction there character is facing without actually moving the player. Incase they are already facing the direction they are looking then the player would just move normally.

My approach is based on this tutorial which has great tile-movement but sadly no tile collision:

The current version allows the player to collide with tiles one a given tilemap which the PlayerMovement scripts receives as a reference.

So here is quick demonstration video as well as the source code for the PlayerMovement c# script:

using UnityEngine;
using UnityEngine.Tilemaps;

// require SpriteRenderer for changing the player's sprite
[RequireComponent(typeof(SpriteRenderer))]
public class PlayerMovement : MonoBehaviour
{
    // walkspeed in tiles per second
    public float walkSpeed = 3f;

    // the tilemap which has the tiles we want to collide with
    public Tilemap tilemap;
 
    // the amount of time we can press an input without moving in seconds
    public float moveDelay = 0.2f;

    // our player's direction
    Direction currentDir = Direction.South;

    // a vector storing the input of our input-axis
    Vector2 input;

    // states if the player is moving or waiting for movement input
    bool isMoving = false;

    // position before a move is executed
    Vector3 startPos;

    // target-position after the move is executed
    Vector3 endPos;

    // stores the progress of the current move in a range from 0f to 1f
    float progress;

    // stores the time remaining before the player can move again
    float remainingMoveDelay = 0f;

    // since we currently do not use any animation components we just use four
    // different sprites for our four directions
    public Sprite northSprite;
    public Sprite eastSprite;
    public Sprite southSprite;
    public Sprite westSprite;

    public void Update()
    {
        // check if the player is moving
        if (!isMoving)
        {
            // The player is currently not moving so check if there is keyinput
            input = new Vector2(Input.GetAxisRaw("Horizontal"),Input.GetAxisRaw("Vertical"));

            // if there is input in x direction disable input in y direction to
            // disable diagonal movement
            if (input.x != 0f)
                input.y = 0;

            // check if there is infact movement or if the input axis are in idle
            // position
            if (input != Vector2.zero)
            {
                // save the old direction for later use
                Direction oldDirection = currentDir;

                // update the players direction according to the input
                #region update Direction
                if (input.x == -1f)
                    currentDir = Direction.West;
                if (input.x == 1f)
                    currentDir = Direction.East;
                if (input.y == 1f)
                    currentDir = Direction.North;
                if (input.y == -1f)
                    currentDir = Direction.South;
                #endregion

                // since there is currently no further animation components we
                // just set the sprite according to the direction
                switch (currentDir)
                {
                    case Direction.North:
                        gameObject.GetComponent<SpriteRenderer>().sprite = northSprite;
                        break;
                    case Direction.East:
                        gameObject.GetComponent<SpriteRenderer>().sprite = eastSprite;
                        break;
                    case Direction.South:
                        gameObject.GetComponent<SpriteRenderer>().sprite = southSprite;
                        break;
                    case Direction.West:
                        gameObject.GetComponent<SpriteRenderer>().sprite = westSprite;
                        break;
                }

                // if the currentDirection is different from the old direction
                // we want to add a delay so the player can just change direction
                // without having to move
                if (currentDir != oldDirection)
                {
                    remainingMoveDelay = moveDelay;
                }

                // if the direction of the input does not change then the move-
                // delay ticks down
                if (remainingMoveDelay > 0f)
                {
                    remainingMoveDelay -= Time.deltaTime;
                    return;
                }

                // for the collision detection and movement we need the current
                // position as well as the target position where our player
                // is going to move to
                startPos = transform.position;
                endPos = new Vector3(startPos.x + input.x, startPos.y + input.y, startPos.z);

                // we subtract 0.5 both in x and y direction to get the coordinates
                // of the upper left corner of our player sprite and convert
                // the floating point vector into an int vector for tile search
                Vector3Int tilePosition = new Vector3Int((int)(endPos.x - 0.5f),
                                                         (int)(endPos.y - 0.5f), 0);

                // with our freshly calculated tile position of the tile where our
                // player want to move to we can now check if there is in fact
                // a tile at that position which we would collide with
                // if there is no tile so the GetTile-function return null then
                // we can go ahead and move towards our target
                if (tilemap.GetTile(tilePosition) == null)
                {
                    // we set our moving variable to true and our progress
                    // towards the target position to 0
                    isMoving = true;
                    progress = 0f;
                }
            }
        }

        // check if the player is currently in the moving state
        if (isMoving)
        {
            // check if the progress is still below 1f so the movement is still
            // going on
            if (progress < 1f)
            {
                // increase our movement progress by our deltaTime times our
                // above specified walkspeed
                progress += Time.deltaTime * walkSpeed;

                // linearly interpolate between our start- and end-positions
                // with the value of our progress which is in range of [0, 1]
                transform.position = Vector3.Lerp(startPos, endPos, progress);
            }
            else
            {
                // if we are moving and our progress is above one that means we
                // either landed exactly on our desired position or we overshot
                // by some tiny amount so in ordered to not accumulate errors
                // we clamp our final position to our desired end-position
                isMoving = false;
                transform.position = endPos;
            }
        }
    }
}

// small Enumeration to help us keep track of the player's direction more easyly
enum Direction
{
    North, East, South, West
}

Since this is my very first appoach at this issue, if you have any suggestion regarding the code I am always open to feedback. Otherwise feel free to use this script in a Pokemon-style 2D game. :slight_smile:

5027714–492710–PlayerMovement.cs (6.71 KB)

1 Like

where is the animation controller?

Well as I mentioned in the comments in the code there is currently no animation controller being used, since I am still relatively new to unity and do not quite know how to use them yet. If you can suggest a good tutorial for animation controllers feel free to comment it below.

Hi, dude, I think i’m kinda late haha.

You can use this code as is follow, it uses raycast to determine where the Player is Colliding.

Here’s the code:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Tilemaps;

public enum Direction
{
    up, down, left, right
}

public class PlayerController : MonoBehaviour
{

    public float Speed = 0f;
    public LayerMask TileCollision;

    Animator Anim;
    Vector3 TargetPosition;
    Direction Direction;

    bool GetCollision
    {
        get
        {
            RaycastHit2D rh;

            Vector2 dir = Vector2.zero;

            if (Direction == Direction.down)
                dir = Vector2.down;

            if (Direction == Direction.left)
                dir = Vector2.left;

            if (Direction == Direction.right)
                dir = Vector2.right;

            if (Direction == Direction.up)
                dir = Vector2.up;

            rh = Physics2D.Raycast(transform.position, dir, 1, TileCollision);

            return rh.collider != null;
        }
    }


    private void Start()
    {
        Anim = GetComponent<Animator>();
        TargetPosition = new Vector2(transform.position.x, transform.position.y);
        Direction = Direction.down;

    }

    private void Update()
    {

        Vector2 AxisInput = new Vector2(Input.GetAxisRaw("Horizontal"), Input.GetAxisRaw("Vertical"));
        Anim.SetInteger("Direccion", (int)Direction); //you can use this to manage the Animation on the Animator Window.

        if(AxisInput != Vector2.zero && TargetPosition == transform.position)
        {
            if(Mathf.Abs(AxisInput.x) > Mathf.Abs(AxisInput.y))
            {

                if(AxisInput.x > 0)
                {
                    Direction = Direction.right;

                    if(!GetCollision)
                        TargetPosition += Vector3.right;
                }
                else
                {
                    Direction = Direction.left;

                    if (!GetCollision)
                        TargetPosition += Vector3.left;
                }

              

            }
            else
            {
                if (AxisInput.y > 0)
                {
                    Direction = Direction.up;

                    if (!GetCollision)
                        TargetPosition += Vector3.up;
                }
                else
                {
                    Direction = Direction.down;

                    if (!GetCollision)
                        TargetPosition += Vector3.down;
                }
            }
        }
        transform.position = Vector3.MoveTowards(transform.position, TargetPosition, Speed * Time.deltaTime);
    }
}

I hope this code can help you.

4 Likes

But there is still no tutorial for the Animator