Last active
June 2, 2025 13:18
-
-
Save Malkyne/d3ea1ae1d0ebd9418c58cc858892242c to your computer and use it in GitHub Desktop.
Unity Canvas Scaler for splash screens
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Copyright (c) 2025 Hidden Achievement, LLC | |
// | |
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated | |
// documentation files (the "Software"), to deal in the Software without restriction, including without limitation the | |
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to | |
// permit persons to whom the Software is furnished to do so, subject to the following conditions: | |
// | |
// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the | |
// Software. | |
// | |
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE | |
// WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS | |
// OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR | |
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | |
using UnityEngine; | |
using UnityEngine.EventSystems; | |
/// <summary> | |
/// This is a Canvas scaler that is designed for screens such as start menus, with large splash art, which should be | |
/// cropped to fit the screen area. These can behave very badly on ultra wide screens, causing important titles and/or | |
/// controls to get crapped. The intent of this scaler is to keep the important area in-frame, no matter what -- even | |
/// if we eventually have to fall back on letter/pillar boxing. | |
/// | |
/// It is intended to behave much like the "Shrink" option on the stock CanvasScaler, where it expands the contents of | |
/// the canvas until they fill up the screen. However, it allows the user to specify a safe area, which should not be | |
/// cropped. If there's a risk of cropping, the scaler will first shift the content, to hold it in the frame, and then, | |
/// if that is insufficient to the task, it will letter/pillar box the content. | |
/// </summary> | |
[RequireComponent(typeof(Canvas))] | |
[ExecuteAlways] | |
[DisallowMultipleComponent] | |
public class SafeFillScaler : UIBehaviour | |
{ | |
[Tooltip("If a sprite has this 'Pixels Per Unit' setting, then one pixel in the sprite will cover one unit in the UI.")] | |
[SerializeField] protected float _referencePixelsPerUnit = 100; | |
/// <summary> | |
/// If a sprite has this 'Pixels Per Unit' setting, then one pixel in the sprite will cover one unit in the UI. | |
/// </summary> | |
public float ReferencePixelsPerUnit { get => _referencePixelsPerUnit; set => _referencePixelsPerUnit = value; } | |
[Tooltip("The resolution the UI layout is designed for. If the screen resolution is larger, the UI will be scaled up, and if it's smaller, the UI will be scaled down.")] | |
[SerializeField] protected Vector2 _referenceResolution = new(800, 600); | |
/// <summary> | |
/// The resolution the UI layout is designed for. | |
/// </summary> | |
/// <remarks> | |
/// If the screen resolution is larger, the UI will be scaled up, and if it's smaller, the UI will be scaled down. | |
/// </remarks> | |
public Vector2 referenceResolution | |
{ | |
get => _referenceResolution; | |
set | |
{ | |
_referenceResolution = value; | |
const float k_MinimumResolution = 0.00001f; | |
if (_referenceResolution.x is > -k_MinimumResolution and < k_MinimumResolution) | |
{ | |
_referenceResolution.x = k_MinimumResolution * Mathf.Sign(_referenceResolution.x); | |
} | |
if (-referenceResolution.y > -k_MinimumResolution && _referenceResolution.y < k_MinimumResolution) | |
{ | |
_referenceResolution.y = k_MinimumResolution * Mathf.Sign(_referenceResolution.y); | |
} | |
_referenceAspectRatio = _referenceResolution.x / _referenceResolution.y; | |
} | |
} | |
[SerializeField] | |
[Tooltip("An area that must be protected from being cropped, when scaling. Should include titling/controls.")] | |
private RectTransform _safeArea; | |
[SerializeField] | |
[Tooltip("This is a parent object to our safe area that will shift its position to keep the safe area within the frame of the screen.")] | |
private RectTransform _shuttle; | |
private Canvas _canvas; | |
private float _prevScaleFactor = 1; | |
private float _prevReferencePixelsPerUnit = 100; | |
private float _referenceAspectRatio = 1; | |
protected override void OnEnable() | |
{ | |
base.OnEnable(); | |
_canvas = GetComponent<Canvas>(); | |
_referenceAspectRatio = _referenceResolution.x / _referenceResolution.y; | |
Handle(); | |
Canvas.preWillRenderCanvases += CanvasPreWillRenderCanvases; | |
} | |
private void CanvasPreWillRenderCanvases() | |
{ | |
Handle(); | |
} | |
protected override void OnDisable() | |
{ | |
SetScaleFactor(1); | |
SetReferencePixelsPerUnit(100); | |
Canvas.preWillRenderCanvases -= CanvasPreWillRenderCanvases; | |
base.OnDisable(); | |
} | |
///<summary> | |
///Method that handles calculations of canvas scaling. | |
///</summary> | |
protected virtual void Handle() | |
{ | |
if (_canvas == null || !_canvas.isRootCanvas) | |
return; | |
Vector2 screenSize = _canvas.renderingDisplaySize; | |
int displayIndex = _canvas.targetDisplay; | |
if (displayIndex > 0 && displayIndex < Display.displays.Length) | |
{ | |
Display disp = Display.displays[displayIndex]; | |
screenSize = new Vector2(disp.renderingWidth, disp.renderingHeight); | |
} | |
float scaleFactor = Mathf.Max(screenSize.x / _referenceResolution.x, screenSize.y / _referenceResolution.y); | |
float screenAspectRatio = screenSize.x / screenSize.y; | |
float canvasHeight = _referenceResolution.x / screenAspectRatio; | |
float canvasWidth = _referenceResolution.y * screenAspectRatio; | |
if (screenAspectRatio > _referenceAspectRatio) | |
{ | |
if (canvasHeight < _safeArea.sizeDelta.y) | |
{ | |
scaleFactor = screenSize.y / _safeArea.sizeDelta.y; | |
canvasHeight = _safeArea.sizeDelta.y; | |
} | |
} | |
else | |
{ | |
if (canvasWidth < _safeArea.sizeDelta.x) | |
{ | |
scaleFactor = screenSize.x / _safeArea.sizeDelta.x; | |
canvasWidth = _safeArea.sizeDelta.x; | |
} | |
} | |
float horizMargin = (canvasWidth - _safeArea.sizeDelta.x) * 0.5f; | |
float vertMargin = (canvasHeight - _safeArea.sizeDelta.y) * 0.5f; | |
Vector2 newPosition = Vector2.zero; | |
if (_safeArea.anchoredPosition.x > horizMargin) // Right is cropped | |
{ | |
newPosition.x = -(_safeArea.anchoredPosition.x - horizMargin); | |
} | |
else if (-_safeArea.anchoredPosition.x > horizMargin) // Left side is cropped | |
{ | |
newPosition.x = _safeArea.anchoredPosition.x + horizMargin; | |
} | |
if (_safeArea.anchoredPosition.y > vertMargin) // Bottom is cropped | |
{ | |
newPosition.y = -(_safeArea.anchoredPosition.y - vertMargin); | |
} | |
else if (-_safeArea.anchoredPosition.y > vertMargin) // Top is cropped | |
{ | |
newPosition.y = _safeArea.anchoredPosition.y + vertMargin; | |
} | |
_shuttle.anchoredPosition = newPosition; | |
SetScaleFactor(scaleFactor); | |
SetReferencePixelsPerUnit(_referencePixelsPerUnit); | |
} | |
/// <summary> | |
/// Sets the scale factor on the canvas. | |
/// </summary> | |
/// <param name="scaleFactor">The scale factor to use.</param> | |
protected void SetScaleFactor(float scaleFactor) | |
{ | |
if (scaleFactor == _prevScaleFactor) | |
return; | |
_canvas.scaleFactor = scaleFactor; | |
_prevScaleFactor = scaleFactor; | |
} | |
/// <summary> | |
/// Sets the referencePixelsPerUnit on the Canvas. | |
/// </summary> | |
/// <param name="referencePixelsPerUnit">The new reference pixels per Unity value</param> | |
protected void SetReferencePixelsPerUnit(float referencePixelsPerUnit) | |
{ | |
if (referencePixelsPerUnit == _prevReferencePixelsPerUnit) | |
return; | |
_canvas.referencePixelsPerUnit = referencePixelsPerUnit; | |
_prevReferencePixelsPerUnit = referencePixelsPerUnit; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment