using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.UI;
///
/// 全局分辨率自适应器:场景加载时自动扫描全屏背景类 Image(居中锚定 + 尺寸接近设计分辨率),
/// 为其附加 UIBackgroundCover,使背景在任意宽高比(含超宽 32:9)下等比放大铺满、不留黑边、不失真。
///
/// 设计原则(对应三要求):
/// 1) 零业务逻辑改动:不改脚本行为、不改对象引用,只在表现层调整背景 sizeDelta。
/// 2) 不改锚点/pivot/位置:位移动画与居中布局完全不受影响;16:9 屏计算结果等于原设计尺寸。
/// 3) 背景总是铺满屏幕。
///
/// 仅针对“全屏背景/遮罩”生效,普通按钮/面板/菜单等居中 UI 保持原样(它们本就该居中,不该拉伸)。
///
public sealed class UIResolutionAutoFitter : MonoBehaviour
{
private const string BootstrapObjectName = "[UIResolutionAutoFitter]";
private static UIResolutionAutoFitter _instance;
// 设计基准分辨率(与全工程 CanvasScaler referenceResolution 一致)。
private const float DesignWidth = 1920f;
private const float DesignHeight = 1080f;
// 判定为“全屏背景”的尺寸容差:宽高需分别接近设计分辨率或其 2 倍(部分背景做了 3840x2160)。
private const float SizeTolerance = 96f;
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
private static void Bootstrap()
{
if (_instance != null) return;
GameObject go = new GameObject(BootstrapObjectName);
_instance = go.AddComponent();
DontDestroyOnLoad(go);
}
private void OnEnable()
{
SceneManager.sceneLoaded += OnSceneLoaded;
}
private void OnDisable()
{
SceneManager.sceneLoaded -= OnSceneLoaded;
}
private void Start()
{
// 处理启动时已经加载好的首个场景。
StartCoroutine(FitAfterFrame());
// 低频常驻补扫:覆盖“任意时刻才实例化/打开”的 prefab 全屏底图
//(结算、歌曲详情、角色/设置面板等可能在会话中随时弹出)。
// 每次只给尚未挂 UIBackgroundCover 的背景加组件,已处理的直接跳过,单次开销很低。
StartCoroutine(PeriodicRescan());
}
private void OnSceneLoaded(Scene scene, LoadSceneMode mode)
{
// 新场景的 Canvas 是新实例,旧记录已失效,清空以便重新 setup。
_setupDoneCanvases.Clear();
_enemyFollowAttached = false;
_trackStabilizerAttached = false;
_cameraTunerAttached = false;
_trackScalerAttached = false;
StartCoroutine(FitAfterFrame());
}
private IEnumerator FitAfterFrame()
{
// 等一帧让 Canvas/布局初始化完成,再扫描(避免 sizeDelta 尚未就绪)。
yield return null;
ScanAndFit();
}
// 当前是否为 gameplay 场景(名字含 gameplay/gamePlay)。仅该场景启用 HUD 自动贴边。
private bool _gameplayActive;
// 已完成一次性 setup(match 适配 + 边缘跟随)的 Canvas 集合,避免每次扫描重复全树遍历。
// 切场景时清空(Canvas 会重建为新实例)。背景 cover 扫描不受此影响,仍每次执行。
private readonly HashSet