Part of these game systems:
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.
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
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;
}
}
}