37083
Assets/FR2_Cache.asset
37083
Assets/FR2_Cache.asset
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
264
Assets/Scripts/PixelPerfectBorderBackground.cs
Normal file
264
Assets/Scripts/PixelPerfectBorderBackground.cs
Normal file
@@ -0,0 +1,264 @@
|
||||
using Ichni;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
/// <summary>
|
||||
/// 固定宽高比 + 自动填充黑边脚本
|
||||
/// 根据目标宽高比(targetWidth : targetHeight)自动设置 Camera 的 Viewport,
|
||||
/// 并用四张 RawImage 填充视口外部的黑边(Pillarbox / Letterbox)。
|
||||
/// 支持在编辑器模式(Editor)下实时预览。
|
||||
/// </summary>
|
||||
[ExecuteAlways]
|
||||
[RequireComponent(typeof(RectTransform))]
|
||||
public class PixelPerfectBorderBackground : MonoBehaviour
|
||||
{
|
||||
[Header("Source")]
|
||||
public Camera mainCamera;
|
||||
|
||||
[Header("Fixed Aspect Ratio")]
|
||||
[Tooltip("启用固定宽高比,自动设置 Camera 的 Viewport")]
|
||||
public bool enableFixedAspect = true;
|
||||
[Tooltip("目标宽度(像素,仅用于计算比例)")]
|
||||
public int targetWidth = 1920;
|
||||
[Tooltip("目标高度(像素,仅用于计算比例)")]
|
||||
public int targetHeight = 1080;
|
||||
|
||||
[Header("Border Images")]
|
||||
public RawImage left;
|
||||
public RawImage right;
|
||||
public RawImage top;
|
||||
public RawImage bottom;
|
||||
|
||||
[Header("Behaviour")]
|
||||
public bool updateEveryFrame = true;
|
||||
public bool disableRaycastTargets = true;
|
||||
|
||||
[Header("Debug")]
|
||||
[SerializeField] private Rect lastGamePixelRect;
|
||||
[SerializeField] private Vector2Int lastScreenSize;
|
||||
|
||||
private int _lastScreenWidth = -1;
|
||||
private int _lastScreenHeight = -1;
|
||||
private Rect _lastPixelRect;
|
||||
private bool _forceRefresh = true;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
ApplyRaycastState();
|
||||
}
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
ApplyRaycastState();
|
||||
_forceRefresh = true;
|
||||
|
||||
// 在编辑器下,注册屏幕缩放或重绘的事件,确保能够实时刷新
|
||||
#if UNITY_EDITOR
|
||||
UnityEditor.EditorApplication.update += EditorUpdate;
|
||||
#endif
|
||||
}
|
||||
|
||||
private void OnDisable()
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
UnityEditor.EditorApplication.update -= EditorUpdate;
|
||||
#endif
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
private void EditorUpdate()
|
||||
{
|
||||
if (!Application.isPlaying && updateEveryFrame)
|
||||
{
|
||||
_forceRefresh = true;
|
||||
LateUpdate();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
private void OnValidate()
|
||||
{
|
||||
_forceRefresh = true;
|
||||
}
|
||||
|
||||
private void LateUpdate()
|
||||
{
|
||||
if (updateEveryFrame || _forceRefresh)
|
||||
{
|
||||
ApplyRaycastState();
|
||||
UpdateBars(_forceRefresh);
|
||||
_forceRefresh = false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 根据目标宽高比计算并应用 Camera 的 Viewport Rect。
|
||||
/// 开启 enableFixedAspect 时自动计算 Letterbox / Pillarbox 布局,
|
||||
/// 关闭时恢复 Camera 全屏。
|
||||
/// </summary>
|
||||
private void ApplyFixedAspect(Camera cam)
|
||||
{
|
||||
if (!enableFixedAspect || targetWidth <= 0 || targetHeight <= 0)
|
||||
{
|
||||
// 未启用固定比例 → 恢复全屏 Viewport
|
||||
cam.rect = new Rect(0f, 0f, 1f, 1f);
|
||||
return;
|
||||
}
|
||||
|
||||
float screenW = Mathf.Max(1f, Screen.width);
|
||||
float screenH = Mathf.Max(1f, Screen.height);
|
||||
float screenAspect = screenW / screenH;
|
||||
float targetAspect = (float)targetWidth / targetHeight;
|
||||
|
||||
Rect viewport;
|
||||
|
||||
if (screenAspect > targetAspect)
|
||||
{
|
||||
// 屏幕更宽 → 左右黑边(Pillarbox)
|
||||
// 让 Viewport 高度占满,宽度按比例缩小居中
|
||||
float viewWidth = targetAspect / screenAspect;
|
||||
float viewX = (1f - viewWidth) * 0.5f;
|
||||
viewport = new Rect(viewX, 0f, viewWidth, 1f);
|
||||
}
|
||||
else
|
||||
{
|
||||
// 屏幕更高 → 上下黑边(Letterbox)
|
||||
// 让 Viewport 宽度占满,高度按比例缩小居中
|
||||
float viewHeight = screenAspect / targetAspect;
|
||||
float viewY = (1f - viewHeight) * 0.5f;
|
||||
viewport = new Rect(0f, viewY, 1f, viewHeight);
|
||||
}
|
||||
|
||||
cam.rect = viewport;
|
||||
}
|
||||
|
||||
public void UpdateBars(bool force)
|
||||
{
|
||||
Camera cam = ResolveCamera();
|
||||
if (cam == null)
|
||||
{
|
||||
SetEnabled(left, false);
|
||||
SetEnabled(right, false);
|
||||
SetEnabled(top, false);
|
||||
SetEnabled(bottom, false);
|
||||
return;
|
||||
}
|
||||
|
||||
// ---- 1. 应用固定宽高比(设置 Camera Viewport) ----
|
||||
ApplyFixedAspect(cam);
|
||||
|
||||
// ---- 2. 原有的黑边填充逻辑 ----
|
||||
// 获取当前分辨率(在编辑器下代表 Game 窗口分辨率)
|
||||
int screenWidth = Mathf.Max(1, Screen.width);
|
||||
int screenHeight = Mathf.Max(1, Screen.height);
|
||||
|
||||
// cam.pixelRect 已经根据相机的 Viewport Rect 自动换算成了实际像素区域
|
||||
Rect pixelRect = ClampPixelRect(cam.pixelRect, screenWidth, screenHeight);
|
||||
|
||||
if (!force &&
|
||||
screenWidth == _lastScreenWidth &&
|
||||
screenHeight == _lastScreenHeight &&
|
||||
Approximately(pixelRect, _lastPixelRect))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_lastScreenWidth = screenWidth;
|
||||
_lastScreenHeight = screenHeight;
|
||||
_lastPixelRect = pixelRect;
|
||||
lastScreenSize = new Vector2Int(screenWidth, screenHeight);
|
||||
lastGamePixelRect = pixelRect;
|
||||
|
||||
// 计算并设置四个方向的遮罩大小与 UV
|
||||
SetSlice(left, 0f, 0f, pixelRect.xMin, screenHeight, screenWidth, screenHeight);
|
||||
SetSlice(right, pixelRect.xMax, 0f, screenWidth, screenHeight, screenWidth, screenHeight);
|
||||
SetSlice(bottom, pixelRect.xMin, 0f, pixelRect.xMax, pixelRect.yMin, screenWidth, screenHeight);
|
||||
SetSlice(top, pixelRect.xMin, pixelRect.yMax, pixelRect.xMax, screenHeight, screenWidth, screenHeight);
|
||||
}
|
||||
|
||||
private Camera ResolveCamera()
|
||||
{
|
||||
if (mainCamera != null)
|
||||
return mainCamera;
|
||||
if (EditorManager.instance != null) return EditorManager.instance.cameraManager.currentCamera;
|
||||
return Camera.main;
|
||||
}
|
||||
|
||||
private void SetSlice(RawImage image, float xMin, float yMin, float xMax, float yMax, int screenWidth, int screenHeight)
|
||||
{
|
||||
if (image == null) return;
|
||||
|
||||
float width = Mathf.Max(0f, xMax - xMin);
|
||||
float height = Mathf.Max(0f, yMax - yMin);
|
||||
|
||||
// 允许一点点像素误差
|
||||
bool visible = width > 0.1f && height > 0.1f;
|
||||
SetEnabled(image, visible);
|
||||
if (!visible) return;
|
||||
|
||||
RectTransform rect = image.rectTransform;
|
||||
Vector2 parentSize = GetParentSize(rect, screenWidth, screenHeight);
|
||||
Vector2 min = new Vector2(xMin / screenWidth, yMin / screenHeight);
|
||||
Vector2 max = new Vector2(xMax / screenWidth, yMax / screenHeight);
|
||||
|
||||
rect.anchorMin = Vector2.zero;
|
||||
rect.anchorMax = Vector2.zero;
|
||||
rect.offsetMin = new Vector2(min.x * parentSize.x, min.y * parentSize.y);
|
||||
rect.offsetMax = new Vector2(max.x * parentSize.x, max.y * parentSize.y);
|
||||
rect.pivot = new Vector2(0.5f, 0.5f);
|
||||
|
||||
// 如果你用的是纯黑图片,UV 没影响;如果是背景图,这行代码能保证四张图拼在一起是无缝完整的
|
||||
image.uvRect = new Rect(min.x, min.y, max.x - min.x, max.y - min.y);
|
||||
}
|
||||
|
||||
private Vector2 GetParentSize(RectTransform rect, int screenWidth, int screenHeight)
|
||||
{
|
||||
if (rect != null && rect.parent is RectTransform parent)
|
||||
{
|
||||
Rect parentRect = parent.rect;
|
||||
if (parentRect.width > 0.001f && parentRect.height > 0.001f)
|
||||
return parentRect.size;
|
||||
}
|
||||
|
||||
return new Vector2(screenWidth, screenHeight);
|
||||
}
|
||||
|
||||
private void SetEnabled(RawImage image, bool enabled)
|
||||
{
|
||||
if (image != null && image.enabled != enabled)
|
||||
image.enabled = enabled;
|
||||
}
|
||||
|
||||
private Rect ClampPixelRect(Rect rect, int screenWidth, int screenHeight)
|
||||
{
|
||||
float xMin = Mathf.Clamp(rect.xMin, 0f, screenWidth);
|
||||
float xMax = Mathf.Clamp(rect.xMax, xMin, screenWidth);
|
||||
float yMin = Mathf.Clamp(rect.yMin, 0f, screenHeight);
|
||||
float yMax = Mathf.Clamp(rect.yMax, yMin, screenHeight);
|
||||
return Rect.MinMaxRect(xMin, yMin, xMax, yMax);
|
||||
}
|
||||
|
||||
private bool Approximately(Rect a, Rect b)
|
||||
{
|
||||
return Mathf.Approximately(a.x, b.x) &&
|
||||
Mathf.Approximately(a.y, b.y) &&
|
||||
Mathf.Approximately(a.width, b.width) &&
|
||||
Mathf.Approximately(a.height, b.height);
|
||||
}
|
||||
|
||||
private void ApplyRaycastState()
|
||||
{
|
||||
if (!disableRaycastTargets) return;
|
||||
|
||||
SetRaycastTarget(left, false);
|
||||
SetRaycastTarget(right, false);
|
||||
SetRaycastTarget(top, false);
|
||||
SetRaycastTarget(bottom, false);
|
||||
}
|
||||
|
||||
private void SetRaycastTarget(RawImage image, bool value)
|
||||
{
|
||||
if (image != null && image.raycastTarget != value)
|
||||
image.raycastTarget = value;
|
||||
}
|
||||
}
|
||||
2
Assets/Scripts/PixelPerfectBorderBackground.cs.meta
Normal file
2
Assets/Scripts/PixelPerfectBorderBackground.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 275bf303286a3594e872f552a4f450ed
|
||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user