Files
bansonic_beta_main/Assets/scripts/gamePlay_gameplay/TrackScreenRatioStabilizer.cs
T
2026-07-24 04:43:13 +08:00

101 lines
4.8 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using UnityEngine;
/// <summary>
/// 让 gameplay 画面在任意分辨率/宽高比下都"完整可见、绝不裁剪"(contain 容纳式)
/// 同时尽量维持轨道的可点击尺寸。避免此前 cover 式补偿(缩小FOV=镜头拉近)把上下/背景裁掉的问题。
///
/// 原理(透视相机、垂直 FOV 控制):轨道/背景是世界空间对象(Z=0 平面)。
/// 垂直半可视世界高 ∝ tan(vFov/2);水平半可视世界宽 ∝ tan(vFov/2) · aspect。
/// 要"包含"住 16:9 设计画面(设计横向范围 + 设计纵向范围)且绝不裁剪,取二者所需 FOV 的较大值:
/// tan(vFov/2) = tan(designVFov/2) · max(1, designAspect / aspect)
/// - 16:9max(1,1)=1 → vFov = designVFov(与原设计逐像素一致)。
/// - 更宽(21:9/32:9aspect>designAspect)max=1 → vFov 保持 designVFov。
/// 纵向完整不变、横向自然更宽(显示更多内容/背景),一寸不裁;轨道显得细些——
/// 由已有的点击区兜底(OverlapPointAll+最近轨道匹配,横向无缝)保证仍好点,不误触。
/// - 更窄(4:3aspect<designAspect)max>1 → vFov 增大(镜头拉远),保证设计横向范围完整可见,
/// 纵向显示更多,同样不裁剪。
/// 本式**永不缩小 FOV**(不拉近),因此绝不会裁掉设计画面——这是与之前 cover 式的本质区别。
///
/// 只调相机一个表现参数(fieldOfView),不动任何 Transform;音符/轨道/点击区在同一世界平面,
/// 相对对齐不受 FOV 影响;TrackTouchInput 每帧用当前相机状态做屏幕→世界命中,自动跟随。
///
/// 用一个 bool(enableStabilization)开关。关闭时恢复设计 FOV,行为与未加此脚本一致。
/// </summary>
[RequireComponent(typeof(Camera))]
[DisallowMultipleComponent]
public sealed class TrackScreenRatioStabilizer : MonoBehaviour
{
[Tooltip("是否启用轨道屏占比稳定(按宽高比补偿相机垂直FOV)。关闭则恢复设计FOV。")]
// 关闭:contain 式补偿会在窄屏增大垂直FOV(镜头拉远)=轨道变细+视距变长,二者都不符合需求。
// 改由 CameraResolutionTuner(降Z+增X俯角)在保持垂直FOV恒定(视距恒定)的前提下让轨道铺开。
public bool enableStabilization = false;
[Tooltip("设计基准宽高比(轨道占比在此比例下为设计值)。16:9 = 1.7778。")]
public float designAspect = 16f / 9f;
[Tooltip("设计基准垂直FOV(该相机在16:9下的原始FOV)。留 0 则启动时自动取相机当前FOV。")]
public float designVerticalFov = 0f;
[Tooltip("补偿后垂直FOV的安全范围,避免极端宽高比下过小/过大。")]
public float minFov = 20f;
public float maxFov = 100f;
private Camera _cam;
private float _lastAspect = -1f;
private bool _lastEnabled;
private void OnEnable()
{
_cam = GetComponent<Camera>();
// 自动记录设计 FOV(仅一次):此时相机 FOV 即 16:9 设计值。
if (designVerticalFov <= 0f && _cam != null)
{
designVerticalFov = _cam.fieldOfView;
}
_lastAspect = -1f; // 强制下一次 Apply
Apply();
}
private void Update()
{
float aspect = GetAspect();
// 宽高比或开关变化时才重算。
if (Mathf.Abs(aspect - _lastAspect) > 0.0001f || enableStabilization != _lastEnabled)
{
Apply();
}
}
private float GetAspect()
{
// 优先用相机自身 aspect(可能被 viewport 影响),否则用屏幕。
if (_cam != null && _cam.aspect > 0.0001f) return _cam.aspect;
return (float)Screen.width / Mathf.Max(1, Screen.height);
}
private void Apply()
{
if (_cam == null) _cam = GetComponent<Camera>();
if (_cam == null || _cam.orthographic) return; // 仅透视相机适用
if (designVerticalFov <= 0f) designVerticalFov = _cam.fieldOfView;
float aspect = GetAspect();
_lastAspect = aspect;
_lastEnabled = enableStabilization;
if (!enableStabilization)
{
_cam.fieldOfView = designVerticalFov; // 关闭:恢复设计 FOV
return;
}
// contain(容纳、绝不裁剪)tan(vFov/2) = tan(designVFov/2) · max(1, designAspect/aspect)
// max(1, …) 保证 FOV 永不小于设计值(不拉近、不裁上下);窄屏才放大以容纳设计横向范围。
float designHalfTan = Mathf.Tan(designVerticalFov * 0.5f * Mathf.Deg2Rad);
float widen = (aspect > 0.0001f) ? Mathf.Max(1f, designAspect / aspect) : 1f;
float targetHalfTan = designHalfTan * widen;
float targetFov = 2f * Mathf.Atan(targetHalfTan) * Mathf.Rad2Deg;
_cam.fieldOfView = Mathf.Clamp(targetFov, minFov, maxFov);
}
}