2D Top-Down Ice Physics in Unity: Tutorial

2D Top-Down Ice Physics

The goal of this tutorial is to build a 2D Top-Down Ice Physics system similar to The Legend of Zelda: A Link to the Past. There will be a slippery iced area and a regular ground area. We’ll use Rigidbody2D and Collider2D alongside Tilemaps to create the project. At the end we’ll add a Trail Renderer to make the ice physics stand out.

Importing Assets

Download the project files from GitHub. The images are from GFX’s Zelda-like tilesets and sprites.

We are using two assets: a player image and a simple tilemap.

Copy the “player” and “tilemap” images into your Assets folder. Select the “player” image and make these changes in the Inspector:

  • Pixels Per Unit: 16
  • Filter Mode: Point (no filter). This is the preferred filter for pixel art so they don’t blur as they scale up.
Image settings: player

Apply changes then select the “tilemap” image and make these changes:

  • Sprite Mode: Multiple
  • Pixels Per Unit: 16
  • Filter Mode: Point (no filter)
Image settings: tilemap

Apply changes and open the Sprite Editor on the “tilemap” image. Click the Slice dropdown and enter the following settings:

  • Type: Grid By Cell Count
  • Column & Row: C: 4, R: 3
Slicing the tilemap image

Click Slice, Apply and close the Sprite Editor. Our assets are now sized properly and Unity will know how to extract tiles for the tilemap.

Save the project and we’re ready to go!

Ice Physics Player Controller

In this part we’re building a player controller where it feels like you’re on ice. You can use the WASD or arrow keys to control the player.

Drag the “player” image directly onto the scene and it will show up in the Hierarchy. Rename it to “Player” – this is how we’ll refer to the Player Object. In the Player’s Sprite Renderer, change the Order in Layer to 2. This is to keep the Player on top of the tilemap we are making later.

Play Sprite Render

Add a Rigidbody2D Component and change these settings:

  • Linear Drag: 0.5
  • Gravity Scale: 0
  • Constraints: check the Freeze Rotation: Z box to prevent the Player from spinning
Rigidbody2D

Scripting the Player Controller

Create a new C# script called “PlayerController” and drag it onto your Player Object. Open up the script and replace it with the following:

using UnityEngine;

public class PlayerController : MonoBehaviour
{
    private Rigidbody2D player;
    public float speed = 6.0f;

    void Start()
    {
        player = GetComponent<Rigidbody2D>();
    }

    void Update()
    {
        Vector2 direction = new Vector2(Input.GetAxisRaw("Horizontal"), Input.GetAxisRaw("Vertical")) * speed;
        player.AddForce(direction, ForceMode2D.Force);
    }
}

Save the file, hit Play, and you have ice physics!

Halfway there!

If the Player isn’t moving check that the PlayerController script is attached to the Player Object, and that your Rigidbody2D numbers are correct.

Code Explanation

private Rigidbody2D player;
public float speed = 6.0f;

The first line declares that we are using a Rigidbody2D Component, and we are calling it player. The second line sets the default speed of the player. You can change the speed directly in the code or from the Player’s Inspector.

void Start()
{
    player = GetComponent<Rigidbody2D>();
}

In the Start function we get the Rigidbody2D component on the Player Object and assign it to the player variable we made above. We can access the Player Object by using the player. syntax.

void Update()
{
    Vector2 direction = new Vector2(Input.GetAxisRaw("Horizontal"), Input.GetAxisRaw("Vertical")) * speed;
    player.AddForce(direction, ForceMode2D.Force);
}

The Update() function fires once per frame and we’re telling it to monitor the Horizontal and Vertical inputs. If a direction is being pushed we apply force to the Player.

Declare a new Vector2 called direction, which uses Input.GetAxisRaw(“Horizontal”) and Input.GetAxisRaw(“Vertical”) to monitor the Player’s direction from the WASD and arrow keys, then multiplies it by the speed defined above.

We use AddForce() to apply the direction and speed to the player, and tell it to use ForceMode2D.Force mode. This applies force and makes the Player move.

You can adjust the Linear Drag on the Player’s Rigidbody2D Component to make the ice more or less slippery. 0f is maximum slipperiness.

Ice Physics Triggered by a Tilemap Collider

Creating the Tilemap

Create a new 2D Object -> Tilemap in the Hierarchy. Duplicate the default Tilemap, then rename them “Ground” and “Ice,” respectively.

Adding a Tilemap

Select the Ground layer and open the Tile Palette. If it isn’t already open, use Window -> 2D -> Tile Palette. Click the Create New Palette dropdown and add a new palette called Tilemap. Save it in your Assets folder.

Creating a new palette

Drag the “tilemap” image we sliced up earlier directly onto the Tilemap Tile Palette. It will prompt you for a location. Make a new folder called “Tiles” and click “Choose.” The tilemap is done!

Finishing the tilemap

Designing the Scene

We’re going to make a simple scene with ground and ice. Select the Ground tilemap and choose the single grass tile from the Tile Palette. Use the the “Paint a filled box with active brush” and cover the entire scene in green by clicking and dragging.

Drawing tiles

Next select the Ice layer and change the Tilemap Renderer’s Order in Layer to “1.” This will keep it above the Ground layer (default value of “0”) and below the Player Object.

Add a TilemapCollider2D component to the Ice layer and check the “Is Trigger” box. This means that the ice layer can now trigger events, like changing player physics when switching from ground to ice.

Tilemap settings

Now we have to assign a Tag to the new TilemapCollider2D so our code knows which collision is which. Create a new Tag called “Ice” by selecting “Add Tag…” on the Ice layer’s Inspector. Then select the Ice layer in the Inspector again and assign the new “Ice” tag. It doesn’t automatically assign the new Tag.

Adding a new Tag

Done with the tilemap! You can hit Play and everything should work like before, but with a background. If the Player has stopped moving make sure the “Is Trigger” box on the Ice layer is checked.

Player Collider

To make the Player respond to the ice we add a Box Collider 2D to the Player Object and keep the default settings. Click the “Edit Collider” button and a green rectangle should appear around the Player in your Scene View. This represents the area that will interact with the ice. If it doesn’t appear try zooming in and resizing.

Box Collider

That’s it for adding colliders!

Coding the Colliders

Open the PlayerController and replace the contents with:

using UnityEngine;

public class PlayerController : MonoBehaviour
{
    private Rigidbody2D player;

    public float speed = 6.0f;
    public bool isIce;

    void Start()
    {
        player = GetComponent<Rigidbody2D>();
    }

    void Update()
    {
        if (isIce)
        {
            Vector2 direction = new Vector2(Input.GetAxisRaw("Horizontal"), Input.GetAxisRaw("Vertical")) * speed;
            player.AddForce(direction, ForceMode2D.Force);
        }
        else
        {
            player.velocity = new Vector2(Input.GetAxisRaw("Horizontal"), Input.GetAxisRaw("Vertical")) * speed;
        }

    }

    private void OnTriggerEnter2D(Collider2D collision)
    {
        if (collision.tag == "Ice")
        {
            isIce = true;
        }
    }

    private void OnTriggerExit2D(Collider2D collision)
    {
        if (collision.tag == "Ice")
        {
            isIce = false;
        }
    }
}

We’re done! Hit Play and your player should now have ice physics on the ice positions and regular physics on the ground.

If things aren’t working check that Ice layer is tagged as Ice and the Box Collider 2D is sized properly.

Code Explanation

private bool ice;

Declare a new boolean called isIce. We will use this to tell the Player Object whether or not they’re on ice. It sets itself to FALSE by default, meaning the Player is not on ice.

void Update()
{
    if (isIce)
    {
        Vector2 direction = new Vector2(Input.GetAxisRaw("Horizontal"), Input.GetAxisRaw("Vertical")) * speed;
        player.AddForce(direction, ForceMode2D.Force);
    }
    else
    {
        player.velocity = new Vector2(Input.GetAxisRaw("Horizontal"), Input.GetAxisRaw("Vertical")) * speed;
    }
}

In the Update function we use two different ways to move the Player, depending on whether or not it is on ice. We check if isIce is TRUE, and if it is we use the Ice Physics Controller we made earlier.

If isIce is FALSE, we use a different physics controller. It works similar to the Ice Physics Controller but is independent to outside forces like drag and inertia.

private void OnTriggerEnter2D(Collider2D collision)
{
    if (collision.tag == "Ice")
    {
        isIce = true;
    }
}

To determine when the Player comes in contact with ice we use the OnTriggerEnter2D function. Any time the Player enters a Trigger the function will fire. This is why we checked the Is Trigger box on the TilemapCollider2D earlier.

When the Player enters a Trigger with the Tag of Ice, we set isIce to TRUE and activate ice physics.

private void OnTriggerExit2D(Collider2D collision)
{
    if (collision.tag == "Ice")
    {
        isIce = false;
    }
}

This is the reverse of the last function. OnTriggerExit2D fires when the Player leaves a Trigger. In this case, if the collider being exited is tagged “Ice,” we set isIce to FALSE and activate regular physics.

Trail Renderer

To make the ice effect more visible we will add a Trail Renderer. Add it to your Player Object with Effects -> Trail. Reduce the width in a general downward curve, making a thin Trail that gradually fades away. You can double-click on the line to add nodes and use the handles to make curves.

Set the Time to 2 and the Order in Layer to 1. To preview what it looks like, move the Player around the scene.

Trail Renderer Settings

That’s it!

2D Top-Down Ice Physics

If you have a comments or questions please leave them below. Thanks for reading!

Leave a Reply

Your email address will not be published. Required fields are marked *