Files
ihob/Assets/ProCamera2D/Runtime/Code/Extensions/ProCamera2DContentFitter.cs

419 lines
15 KiB
C#
Raw Normal View History

2026-02-21 17:04:05 -08:00
using System;
using System.Collections;
using UnityEngine;
namespace Com.LuisPedroFonseca.ProCamera2D
{
public enum ContentFitterMode
{
AspectRatio,
FixedWidth,
FixedHeight
}
[HelpURLAttribute("http://www.procamera2d.com/user-guide/extension-content-fitter/")]
public class ProCamera2DContentFitter : BasePC2D, ISizeOverrider
{
public static string ExtensionName = "Content Fitter";
public ContentFitterMode ContentFitterMode
{
set
{
_contentFitterMode = value;
ProCamera2D.GameCamera.ResetProjectionMatrix();
if(_contentFitterMode == ContentFitterMode.AspectRatio)
TargetWidth = TargetHeight * TargetAspectRatio;
}
get { return _contentFitterMode; }
}
[SerializeField] private ContentFitterMode _contentFitterMode;
public bool UseLetterOrPillarboxing
{
set
{
_useLetterOrPillarboxing = value;
ToggleLetterPillarboxing(value);
}
get { return _useLetterOrPillarboxing; }
}
[SerializeField] private bool _useLetterOrPillarboxing;
private static float ScreenAspectRatio
{
get { return Screen.width / (float) Screen.height; }
}
public float TargetHeight
{
get { return _targetHeight; }
set
{
_targetHeight = value;
_targetWidth = ContentFitterMode == ContentFitterMode.AspectRatio ? value * TargetAspectRatio : value * ScreenAspectRatio;
}
}
[SerializeField]
private float _targetHeight = 5.625f;
public float TargetWidth
{
get { return _targetWidth; }
set
{
_targetWidth = value;
_targetHeight = ContentFitterMode == ContentFitterMode.AspectRatio ? value / TargetAspectRatio : value / ScreenAspectRatio;
}
}
[SerializeField]
private float _targetWidth = 10;
public float TargetAspectRatio
{
get { return _targetAspectRatio; }
set
{
_targetAspectRatio = value;
_targetWidth = _targetHeight * value;
}
}
[Range(.1f, 3f)]
[SerializeField]
private float _targetAspectRatio = 16 / 9f;
[Range(-1, 1)] public float VerticalAlignment;
[Range(-1, 1)] public float HorizontalAlignment;
private float
_prevTargetHeight,
_prevTargetWidth,
_prevTargetAspectRatio,
_prevAspectRatio,
_prevVerticalAlignment,
_prevHorizontalAlignment;
private bool _prevUseLetterOrPillarboxing;
private Camera _letterPillarboxingCamera;
protected override void Awake()
{
base.Awake();
ProCamera2D.AddSizeOverrider(this);
}
private IEnumerator Start()
{
if (UseLetterOrPillarboxing)
CreateLetterPillarboxingCamera();
yield return null;
if (ContentFitterMode == ContentFitterMode.AspectRatio)
UpdateCameraAlignment(
ProCamera2D.GameCamera,
TargetHeight * .5f > TargetWidth * .5f / ProCamera2D.GameCamera.aspect,
TargetAspectRatio,
HorizontalAlignment,
VerticalAlignment);
}
protected override void OnDestroy()
{
base.OnDestroy();
if(ProCamera2D)
ProCamera2D.RemoveSizeOverrider(this);
}
protected override void OnDisable()
{
base.OnDisable();
ProCamera2D.GameCamera.ResetProjectionMatrix();
}
#if UNITY_EDITOR
protected override void DrawGizmosSelected()
{
base.DrawGizmosSelected();
var fillColor = EditorPrefsX.GetColor(PrefsData.FitterFillColorKey, PrefsData.FitterFillColorValue);
var lineColor = EditorPrefsX.GetColor(PrefsData.FitterLineColorKey, PrefsData.FitterFillColorValue);
Gizmos.color = lineColor;
var camSize =
Utils.GetScreenSizeInWorldCoords(ProCamera2D.GameCamera, Mathf.Abs(Vector3D(transform.localPosition)));
var camPos = VectorHVD(Vector3H(ProCamera2D.transform.position), Vector3V(ProCamera2D.transform.position),
0);
var rectCorners = DrawGizmoRectangle(
Vector3H(ProCamera2D.transform.position),
Vector3V(ProCamera2D.transform.position),
ContentFitterMode != ContentFitterMode.FixedHeight ? TargetWidth : camSize.x,
ContentFitterMode != ContentFitterMode.FixedWidth ? TargetHeight : camSize.y,
fillColor,
lineColor);
if (_contentFitterMode == ContentFitterMode.AspectRatio)
{
if (TargetHeight * .5f > TargetWidth * .5f / ProCamera2D.GameCamera.aspect)
{
DrawGizmoRectangle(
Vector3H(ProCamera2D.transform.position) - HorizontalAlignment * (TargetHeight * ProCamera2D.GameCamera.aspect - TargetWidth) / 2f,
Vector3V(ProCamera2D.transform.position),
TargetHeight * ProCamera2D.GameCamera.aspect,
TargetHeight,
fillColor,
lineColor);
}
else
{
DrawGizmoRectangle(
Vector3H(ProCamera2D.transform.position),
Vector3V(ProCamera2D.transform.position) - VerticalAlignment * (TargetWidth / ProCamera2D.GameCamera.aspect - TargetHeight) / 2f,
TargetWidth,
TargetWidth / ProCamera2D.GameCamera.aspect,
fillColor,
lineColor);
}
Utils.DrawArrowForGizmo(camPos, camPos - rectCorners[0], camSize.y * .03f);
Utils.DrawArrowForGizmo(camPos, camPos - rectCorners[2], camSize.y * .03f);
}
else if (_contentFitterMode == ContentFitterMode.FixedWidth)
{
DrawGizmoRectangle(
Vector3H(ProCamera2D.transform.position),
Vector3V(ProCamera2D.transform.position),
TargetWidth,
TargetWidth / ProCamera2D.GameCamera.aspect,
fillColor,
lineColor);
Utils.DrawArrowForGizmo(camPos, ProCamera2D.transform.right * TargetWidth * .5f, camSize.y * .03f);
Utils.DrawArrowForGizmo(camPos, -ProCamera2D.transform.right * TargetWidth * .5f, camSize.y * .03f);
}
else
{
DrawGizmoRectangle(
Vector3H(ProCamera2D.transform.position),
Vector3V(ProCamera2D.transform.position),
TargetHeight * ProCamera2D.GameCamera.aspect,
TargetHeight,
fillColor,
lineColor);
Utils.DrawArrowForGizmo(camPos, ProCamera2D.transform.up * TargetHeight * .5f, camSize.y * .03f);
Utils.DrawArrowForGizmo(camPos, -ProCamera2D.transform.up * TargetHeight * .5f, camSize.y * .03f);
}
}
#endif
#region ISizeOverrider implementation
public float OverrideSize(float deltaTime, float originalSize)
{
return !enabled ? originalSize : GetSize(ContentFitterMode);
}
public int SOOrder
{
get { return _soOrder; }
set { _soOrder = value; }
}
private int _soOrder = 5000;
#endregion
private float GetSize(ContentFitterMode mode)
{
switch (mode)
{
case ContentFitterMode.FixedHeight:
return TargetHeight * .5f;
case ContentFitterMode.FixedWidth:
return TargetWidth * .5f / ProCamera2D.GameCamera.aspect;
case ContentFitterMode.AspectRatio:
if (_prevTargetWidth != TargetWidth ||
_prevTargetHeight != TargetHeight ||
_prevTargetAspectRatio != TargetAspectRatio ||
_prevAspectRatio != ProCamera2D.GameCamera.aspect ||
_prevVerticalAlignment != VerticalAlignment ||
_prevHorizontalAlignment != HorizontalAlignment ||
_prevUseLetterOrPillarboxing != _useLetterOrPillarboxing)
{
StartCoroutine(UpdateFixedAspectRatio());
}
_prevTargetWidth = TargetWidth;
_prevTargetHeight = TargetHeight;
_prevTargetAspectRatio = TargetAspectRatio;
_prevAspectRatio = ProCamera2D.GameCamera.aspect;
_prevVerticalAlignment = VerticalAlignment;
_prevHorizontalAlignment = HorizontalAlignment;
_prevUseLetterOrPillarboxing = _useLetterOrPillarboxing;
return Mathf.Max(TargetHeight * .5f, TargetWidth * .5f / ProCamera2D.GameCamera.aspect);
default:
throw new ArgumentOutOfRangeException();
}
}
IEnumerator UpdateFixedAspectRatio()
{
var isPillarbox = TargetHeight * .5f > TargetWidth * .5f / ScreenAspectRatio;
if(_prevUseLetterOrPillarboxing != _useLetterOrPillarboxing)
ToggleLetterPillarboxing(_useLetterOrPillarboxing);
if (UseLetterOrPillarboxing)
{
UpdateLetterPillarbox(
ProCamera2D.GameCamera,
isPillarbox,
TargetAspectRatio,
HorizontalAlignment,
VerticalAlignment);
}
yield return new WaitForEndOfFrame();
UpdateCameraAlignment(
ProCamera2D.GameCamera,
isPillarbox,
TargetAspectRatio,
HorizontalAlignment,
VerticalAlignment);
}
private static void UpdateCameraAlignment(
Camera cam,
bool isPillarbox,
float targetAspectRatio,
float horizontalAlignment,
float verticalAlignment)
{
cam.ResetProjectionMatrix();
var horizontalOffset = isPillarbox
? (-.5f + (targetAspectRatio / cam.aspect) * 0.5f) * horizontalAlignment
: 0;
var verticalOffset = !isPillarbox
? (-.5f + (cam.aspect / targetAspectRatio) * 0.5f) * verticalAlignment
: 0;
cam.projectionMatrix = GetScissorRect(
new Rect(
horizontalOffset,
verticalOffset,
1, 1), cam.projectionMatrix);
}
private static Matrix4x4 GetScissorRect(Rect targetScissor, Matrix4x4 camProjectionMatrix)
{
var m2 = Matrix4x4.TRS(
new Vector3((1 / targetScissor.width - 1), (1 / targetScissor.height - 1), 0),
Quaternion.identity,
new Vector3(1 / targetScissor.width, 1 / targetScissor.height, 1));
var m3 = Matrix4x4.TRS(
new Vector3(-targetScissor.x * 2 / targetScissor.width, -targetScissor.y * 2 / targetScissor.height, 0),
Quaternion.identity,
Vector3.one);
return m3 * m2 * camProjectionMatrix;
}
private static void UpdateLetterPillarbox(
Camera cam,
bool isPillarbox,
float targetAspectRatio,
float horizontalAlignment,
float verticalAlignment)
{
if (isPillarbox)
{
var inset = 1.0f - targetAspectRatio / (Screen.width / (float) Screen.height);
cam.rect = new Rect((inset / 2f) + (inset / 2f) * horizontalAlignment, 0.0f, 1.0f - inset, 1.0f);
}
else
{
var inset = 1.0f - (Screen.width / (float) Screen.height) / targetAspectRatio;
cam.rect = new Rect(0.0f, (inset / 2f) + (inset / 2f) * verticalAlignment, 1.0f, 1.0f - inset);
}
}
private void ToggleLetterPillarboxing(bool value)
{
if (value && _letterPillarboxingCamera == null)
CreateLetterPillarboxingCamera();
if (value)
{
_letterPillarboxingCamera.gameObject.SetActive(true);
UpdateLetterPillarbox(
ProCamera2D.GameCamera,
TargetHeight * .5f > TargetWidth * .5f / ScreenAspectRatio,
TargetAspectRatio,
HorizontalAlignment,
VerticalAlignment);
}
else
{
if (_letterPillarboxingCamera != null)
_letterPillarboxingCamera.gameObject.SetActive(false);
ProCamera2D.GameCamera.rect = new Rect(0, 0, 1, 1);
UpdateCameraAlignment(
ProCamera2D.GameCamera,
TargetHeight * .5f > TargetWidth * .5f / ScreenAspectRatio,
TargetAspectRatio,
HorizontalAlignment,
VerticalAlignment);
}
}
private void CreateLetterPillarboxingCamera()
{
_letterPillarboxingCamera = new GameObject("PC2DBackgroundCamera", typeof(Camera)).GetComponent<Camera>();
_letterPillarboxingCamera.depth = int.MinValue;
_letterPillarboxingCamera.clearFlags = CameraClearFlags.SolidColor;
_letterPillarboxingCamera.backgroundColor = Color.black;
_letterPillarboxingCamera.cullingMask = 0;
_letterPillarboxingCamera.transform.position = new Vector3(10000, 10000, 10000);
_letterPillarboxingCamera.gameObject.hideFlags = HideFlags.HideInHierarchy;
}
private Vector3[] DrawGizmoRectangle(float x, float y, float width, float height, Color fillColor,
Color borderColor)
{
var rect = new Rect(x, y, width, height);
rect.x -= rect.width / 2f;
rect.y -= rect.height / 2f;
Vector3[] rectangleCorners =
{
VectorHVD(rect.position.x, rect.position.y, 0), // Bottom Left
VectorHVD(rect.position.x + rect.width, rect.position.y, 0), // Bottom Right
VectorHVD(rect.position.x + rect.width, rect.position.y + rect.height, 0), // Top Right
VectorHVD(rect.position.x, rect.position.y + rect.height, 0) // Top Left
};
#if UNITY_EDITOR
UnityEditor.Handles.DrawSolidRectangleWithOutline(rectangleCorners, fillColor, borderColor);
#endif
return rectangleCorners;
}
}
}