Show Characters inside cards (UI parallax)

Show Characters inside cards (UI parallax)

Here's how to create a really simple parallax effect in Unity, in this case in the game's UI. The technique uses a mask component and a short script (around 40 lines of C#) to rotate a panel based on mouse position, giving the illusion of depth.

0:00
/0:09

How it works

The effect has 2 main parts: the panel rotation and the parallax offset. The script tracks the mouse position relative to the panel's center and rotates the RectTransform by up to the max angle you set (2 axes independently). To make a character "peek" from behind the card, you place it as a child element and set a Z offset (like -100) so it lags visually behind the card rotation. A Mask component keeps everything clipped to the panel boundary.

Steps to set it up

  1. Check if the mouse is inside the Rect, then rotate the panel based on its distance from the centre.
  2. Set up the character's frame: add a Mask (so the character doesn't go outside the panel when rotating), a Text, and an Image.
  3. To create the parallax effect while the panel rotates, move the character's image (or whichever element you want the depth on) backwards. In my example the Z value of the Slime's transform is set to -100.
  4. Reset the target rotation when the mouse is outside the rect and rotate it smoothly back. I'm using AngleLerp here because Vector3.Lerp messes up the angle transitions when crossing 0/360 degrees.

Code

The full script is around 40 lines. The speed field controls how smoothly the panel follows the cursor. Set y_maxRot or x_maxRot to 0 if you only want rotation on one axis. If you have multiple panels in the same screen, consider using a single Update that loops through a list rather than one MonoBehaviour per panel - it's better for performance.

using UnityEngine;

[RequireComponent(typeof(RectTransform))]
public class ParallaxPanel : MonoBehaviour
{

    //Set to 0 if you want don't want it to rotate along this axis
    public float y_maxRot;

    //Set to 0 if you want don't want it to rotate along this axis
    public float x_maxRot;

    //Speed for the rotation
    public float speed;

    RectTransform rect;
    //The rect we want to rotate
    public RectTransform rectToRotate;

    private void Awake()
    {
        rect = GetComponent<RectTransform>();
    }

    //Our target eulerangles rotation
    Vector2 targetEulerAngles = Vector3.zero;

    /// <summary>
    /// Check each frame where the mouse is and rotates the panel
    /// P.S. if you have multiple panels, use a list of "Panels classes" and use only one update, it's better for performance
    /// You could also look for UI methods from the EventTrigger component
    /// </summary>
    private void Update()
    {
        //Difference between the mouse pos and the panel's position
        Vector2 diff = (Vector2)transform.position - (Vector2)Input.mousePosition;

        //If the mouse is inside the rect/panel [...]
        if (Mathf.Abs(diff.x) <= (rect.sizeDelta.x / 2f) &&
            Mathf.Abs(diff.y) <= (rect.sizeDelta.y / 2f))
        {
            targetEulerAngles = new Vector3(
                //Rotates along the X axis, based on the Y distance from the centre
                x_maxRot * -Mathf.Clamp(diff.y / (rect.sizeDelta.y/2f), -1, 1),
                //Same thing, but along the Y axis (so, depends on the X distance)
                y_maxRot * Mathf.Clamp(diff.x / (rect.sizeDelta.x/2f), -1, 1),
                //No rotation along the Z axis
                0);
        }
        else  //Mouse is outside the rect, target euler is zero
        {
            targetEulerAngles = Vector3.zero;
        }

        //Lerps the rotation
        rectToRotate.eulerAngles = AngleLerp(rectToRotate.eulerAngles, targetEulerAngles, speed * Time.deltaTime);
    }

    //Lerps two angles (using Mathf.LerpAngle for each axis)
    public static Vector3 AngleLerp(Vector3 StartAngle, Vector3 FinishAngle, float t)
    {
        return new Vector3(
            Mathf.LerpAngle(StartAngle.x, FinishAngle.x, t),
            Mathf.LerpAngle(StartAngle.y, FinishAngle.y, t),
            Mathf.LerpAngle(StartAngle.z, FinishAngle.z, t)
            );
    }

}

Frequently asked questions

Can I use this with multiple cards on the same screen?

Yes, but instead of one MonoBehaviour per card, put all your panels in a list and loop through them in a single Update. Fewer Update calls means better performance.

Why use AngleLerp instead of Vector3.Lerp?

Vector3.Lerp doesn't handle angle wrapping, so when a value crosses 0 or 360 degrees it snaps instead of interpolating smoothly. Mathf.LerpAngle handles the wrap correctly.

Does the Mask component affect performance?

A Mask adds 2 draw calls per masked element in Unity's UI, so keep it to a minimum. For a card system with a handful of panels it's negligible.

Can I set different max rotation per axis?

Yes, set x_maxRot and y_maxRot independently. Setting either to 0 disables rotation on that axis entirely.

Comments

Want game-ready tools or need specific solutions?

Work with us