Part of these game systems:
intermediate Touch & Mobile

Mobile Responsive UI Scaler

Dynamically adjusts UI scaling based on device aspect ratio, DPI, and orientation. Works with Unity's CanvasScaler to ensure readable fonts and proper layouts across all mobile devices.

Unity 2022.3+ · 4.0 KB · MobileResponsiveScaler.cs

How to Use

1

Attach the MobileResponsiveScaler script to your Canvas GameObject (requires a CanvasScaler)

2

Set the referenceResolution to your design resolution (e.g. 1080x1920)

3

Enable dpiAwareScaling for consistent sizing across different screen densities

4

Optionally assign portraitLayout and landscapeLayout GameObjects for orientation-based switching

5

Connect onOrientationChanged to update any layout-dependent logic

Source Code

MobileResponsiveScaler.cs
C#
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.Events;

[RequireComponent(typeof(CanvasScaler))]
public class MobileResponsiveScaler : MonoBehaviour
{
    [Header("Reference Resolution")]
    [SerializeField] private Vector2 referenceResolution = new Vector2(1080, 1920);
    [SerializeField] private float referenceAspect = 0.5625f; // 9:16

    [Header("Scaling")]
    [SerializeField] private float minMatchWidthHeight = 0f;
    [SerializeField] private float maxMatchWidthHeight = 1f;
    [SerializeField] private float wideScreenThreshold = 0.6f;

    [Header("Font Scaling")]
    [SerializeField] private bool scaleFonts = true;
    [SerializeField] private float baseFontScale = 1f;
    [SerializeField] private float minFontScale = 0.7f;
    [SerializeField] private float maxFontScale = 1.5f;

    [Header("DPI")]
    [SerializeField] private bool dpiAwareScaling = true;
    [SerializeField] private float referenceDPI = 160f;

    [Header("Layout Switching")]
    [SerializeField] private bool enableLayoutSwitching = true;
    [SerializeField] private GameObject portraitLayout;
    [SerializeField] private GameObject landscapeLayout;

    [Header("Events")]
    public UnityEvent<bool> onOrientationChanged; // true = portrait
    public UnityEvent<float> onScaleChanged;

    public float CurrentFontScale { get; private set; } = 1f;
    public bool IsPortrait { get; private set; }
    public float CurrentAspect { get; private set; }
    public float CurrentDPI { get; private set; }

    private CanvasScaler canvasScaler;
    private Vector2Int lastScreenSize;
    private ScreenOrientation lastOrientation;

    private void Awake()
    {
        canvasScaler = GetComponent<CanvasScaler>();
        canvasScaler.uiScaleMode = CanvasScaler.ScaleMode.ScaleWithScreenSize;
        canvasScaler.referenceResolution = referenceResolution;

        UpdateScaling();
    }

    private void Update()
    {
        Vector2Int currentSize = new Vector2Int(Screen.width, Screen.height);
        ScreenOrientation currentOrientation = Screen.orientation;

        if (currentSize != lastScreenSize || currentOrientation != lastOrientation)
        {
            lastScreenSize = currentSize;
            lastOrientation = currentOrientation;
            UpdateScaling();
        }
    }

    private void UpdateScaling()
    {
        if (Screen.width <= 0 || Screen.height <= 0) return;

        CurrentAspect = (float)Screen.width / Screen.height;
        IsPortrait = Screen.height > Screen.width;
        CurrentDPI = Screen.dpi > 0 ? Screen.dpi : referenceDPI;

        UpdateCanvasScaler();
        UpdateFontScale();
        UpdateLayout();
    }

    private void UpdateCanvasScaler()
    {
        float aspectRatio = CurrentAspect;
        float aspectDelta = Mathf.Abs(aspectRatio - referenceAspect);

        // Wider screens should favor width matching, taller screens favor height
        float match;
        if (aspectRatio > wideScreenThreshold)
        {
            match = Mathf.Lerp(maxMatchWidthHeight, minMatchWidthHeight,
                (aspectRatio - wideScreenThreshold) / (1f - wideScreenThreshold));
        }
        else
        {
            // Scale adjustment based on how far we are from reference aspect
            match = Mathf.Lerp(maxMatchWidthHeight, minMatchWidthHeight, aspectDelta);
        }

        match = Mathf.Clamp(match, minMatchWidthHeight, maxMatchWidthHeight);
        canvasScaler.matchWidthOrHeight = match;

        onScaleChanged?.Invoke(match);
    }

    private void UpdateFontScale()
    {
        if (!scaleFonts) return;

        float screenDiagonal = Mathf.Sqrt(Screen.width * Screen.width + Screen.height * Screen.height);
        float referenceDiagonal = Mathf.Sqrt(
            referenceResolution.x * referenceResolution.x +
            referenceResolution.y * referenceResolution.y);

        float sizeRatio = screenDiagonal / referenceDiagonal;

        float dpiScale = 1f;
        if (dpiAwareScaling && CurrentDPI > 0)
        {
            dpiScale = referenceDPI / CurrentDPI;
        }

        CurrentFontScale = baseFontScale * sizeRatio * dpiScale;
        CurrentFontScale = Mathf.Clamp(CurrentFontScale, minFontScale, maxFontScale);
    }

    private void UpdateLayout()
    {
        if (!enableLayoutSwitching) return;

        bool portrait = IsPortrait;

        if (portraitLayout != null)
            portraitLayout.SetActive(portrait);

        if (landscapeLayout != null)
            landscapeLayout.SetActive(!portrait);

        onOrientationChanged?.Invoke(portrait);
    }

    public float GetScaledFontSize(float baseFontSize)
    {
        return baseFontSize * CurrentFontScale;
    }

    public void SetReferenceResolution(Vector2 resolution)
    {
        referenceResolution = resolution;
        canvasScaler.referenceResolution = resolution;
        UpdateScaling();
    }

    public float GetScreenDiagonalInches()
    {
        if (CurrentDPI <= 0) return 0f;
        float diagonalPixels = Mathf.Sqrt(
            Screen.width * Screen.width + Screen.height * Screen.height);
        return diagonalPixels / CurrentDPI;
    }

    public bool IsTablet()
    {
        return GetScreenDiagonalInches() >= 7f;
    }

    public void ForceUpdate()
    {
        UpdateScaling();
    }

    private void OnValidate()
    {
        if (canvasScaler == null)
            canvasScaler = GetComponent<CanvasScaler>();

        if (canvasScaler != null)
        {
            canvasScaler.referenceResolution = referenceResolution;
        }
    }
}