NLDClient-yudde/ProjectNLD/Assets/Code/Scripts/Framework/UI/BackgroundRelativePosition.cs

396 lines
11 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

using UnityEngine;
using UnityEngine.UI;
using System;
[RequireComponent(typeof(RectTransform))]
[ExecuteAlways] // 在编辑模式下也执行
public class BackgroundRelativePosition : MonoBehaviour
{
[Header("背景参考")]
[SerializeField] private RectTransform _background; // 参考背景
[Header("设计分辨率设置")]
[SerializeField] private Vector2 _designResolution = new Vector2(1920, 1080); // 设计分辨率
[Header("设计时位置")]
[SerializeField] private Vector2 _designPosition; // 在设计分辨率下的位置(相对于父节点中心)
[Header("相对位置模式")]
[SerializeField] private RelativeMode _relativeMode = RelativeMode.UV;
[Header("更新设置")]
[SerializeField] private bool _autoUpdate = true; // 自动更新
[SerializeField] private UpdateMode _updateMode = UpdateMode.OnStartAndResolutionChange;
private RectTransform _rectTransform;
private Vector2 _lastScreenSize;
private Vector2 _lastBackgroundSize;
public enum RelativeMode
{
UV, // 使用UV坐标0-1范围
PixelPerfect, // 像素完美缩放
AnchorBased // 基于锚点
}
public enum UpdateMode
{
OnStartAndResolutionChange,
EveryFrame,
Manual
}
#region 属性
/// <summary>
/// 背景参考
/// </summary>
public RectTransform Background
{
get => _background;
set
{
_background = value;
UpdatePosition();
}
}
/// <summary>
/// 设计时位置(在设计分辨率下相对于父节点中心的位置)
/// </summary>
public Vector2 DesignPosition
{
get => _designPosition;
set
{
_designPosition = value;
UpdatePosition();
}
}
/// <summary>
/// 设计分辨率
/// </summary>
public Vector2 DesignResolution
{
get => _designResolution;
set
{
_designResolution = value;
UpdatePosition();
}
}
/// <summary>
/// 获取当前相对位置UV坐标
/// </summary>
public Vector2 RelativeUV
{
get
{
if (_background == null || _rectTransform == null) return Vector2.zero;
// 计算当前相对于背景的UV坐标
Vector2 backgroundSize = _background.rect.size;
Vector2 relativePos = _rectTransform.anchoredPosition - _background.anchoredPosition;
return new Vector2(
(relativePos.x + backgroundSize.x * 0.5f) / backgroundSize.x,
(relativePos.y + backgroundSize.y * 0.5f) / backgroundSize.y
);
}
}
#endregion
#region Unity生命周期
private void Awake()
{
_rectTransform = GetComponent<RectTransform>();
_lastScreenSize = new Vector2(Screen.width, Screen.height);
// 如果没有设置背景,尝试从父对象获取
if (_background == null)
{
Transform parent = transform.parent;
if (parent != null)
{
_background = parent.GetComponent<RectTransform>();
}
}
}
private void Start()
{
if (_updateMode == UpdateMode.OnStartAndResolutionChange)
{
UpdatePosition();
}
}
private void Update()
{
if (!_autoUpdate) return;
switch (_updateMode)
{
case UpdateMode.EveryFrame:
UpdatePosition();
break;
case UpdateMode.OnStartAndResolutionChange:
CheckResolutionChange();
break;
}
}
private void OnRectTransformDimensionsChange()
{
if (_autoUpdate && _updateMode != UpdateMode.Manual)
{
UpdatePosition();
}
}
#if UNITY_EDITOR
private void OnValidate()
{
// 在编辑器模式下验证时更新
if (_rectTransform == null)
_rectTransform = GetComponent<RectTransform>();
if (Application.isPlaying || UnityEditor.EditorApplication.isPlayingOrWillChangePlaymode)
{
return;
}
// 编辑器模式下,参数改变时更新
UnityEditor.EditorApplication.delayCall += () =>
{
if (this != null && _autoUpdate)
{
UpdatePosition();
}
};
}
#endif
#endregion
#region 核心方法
/// <summary>
/// 更新Item位置
/// </summary>
public void UpdatePosition()
{
if (_background == null || _rectTransform == null)
{
Debug.LogWarning("BackgroundRelativePosition: 背景或RectTransform未设置", this);
return;
}
// 保存原始锚点
Vector2 originalAnchorMin = _rectTransform.anchorMin;
Vector2 originalAnchorMax = _rectTransform.anchorMax;
Vector2 originalPivot = _rectTransform.pivot;
try
{
// 根据模式计算位置
Vector2 newPosition = CalculatePosition();
// 设置位置
_rectTransform.anchoredPosition = newPosition;
// 记录背景大小变化
_lastBackgroundSize = _background.rect.size;
}
finally
{
// 恢复原始锚点设置
_rectTransform.anchorMin = originalAnchorMin;
_rectTransform.anchorMax = originalAnchorMax;
_rectTransform.pivot = originalPivot;
}
}
/// <summary>
/// 计算当前位置
/// </summary>
private Vector2 CalculatePosition()
{
switch (_relativeMode)
{
case RelativeMode.UV:
return CalculateUVPosition();
case RelativeMode.PixelPerfect:
return CalculatePixelPerfectPosition();
case RelativeMode.AnchorBased:
return CalculateAnchorBasedPosition();
default:
return CalculateUVPosition();
}
}
/// <summary>
/// 使用UV模式计算位置
/// </summary>
private Vector2 CalculateUVPosition()
{
// 获取背景实际大小
Vector2 backgroundSize = _background.rect.size;
if (backgroundSize.x <= 0 || backgroundSize.y <= 0)
{
Debug.LogWarning("BackgroundRelativePosition: 背景大小为0", this);
return _rectTransform.anchoredPosition;
}
// 计算设计分辨率下item相对于设计背景的相对位置以背景中心为原点
// _designPosition是相对于父节点中心的位置
// 假设在设计分辨率下,背景也是相对于父节点中心居中的
// 那么item相对于背景的偏移 = _designPosition - 背景位置偏移
// 但如果背景居中,背景位置偏移 = (0, 0)
Vector2 relativeToDesignBackground = _designPosition;
// 计算相对位置的比例(相对于设计背景大小)
Vector2 relativeRatio = new Vector2(
relativeToDesignBackground.x / _designResolution.x,
relativeToDesignBackground.y / _designResolution.y
);
// 应用该比例到当前背景,得到相对于当前背景中心的位置
Vector2 relativePos = new Vector2(
relativeRatio.x * backgroundSize.x,
relativeRatio.y * backgroundSize.y
);
// 加上背景的实际位置,得到相对于父节点的最终位置
return _background.anchoredPosition + relativePos;
}
/// <summary>
/// 使用像素完美模式计算位置
/// </summary>
private Vector2 CalculatePixelPerfectPosition()
{
// 获取背景实际大小
Vector2 backgroundSize = _background.rect.size;
Vector2 designBackgroundSize = _designResolution;
// 计算缩放比例
float scaleX = backgroundSize.x / designBackgroundSize.x;
float scaleY = backgroundSize.y / designBackgroundSize.y;
// 应用缩放
Vector2 scaledPosition = new Vector2(
_designPosition.x * scaleX,
_designPosition.y * scaleY
);
// 加上背景的位置偏移
return _background.anchoredPosition + scaledPosition;
}
/// <summary>
/// 使用锚点模式计算位置
/// </summary>
private Vector2 CalculateAnchorBasedPosition()
{
// 将设计位置转换为锚点值
Vector2 designUV = new Vector2(
(_designPosition.x + _designResolution.x * 0.5f) / _designResolution.x,
(_designPosition.y + _designResolution.y * 0.5f) / _designResolution.y
);
// 直接设置锚点这会改变RectTransform的布局方式
_rectTransform.anchorMin = designUV;
_rectTransform.anchorMax = designUV;
_rectTransform.pivot = new Vector2(0.5f, 0.5f);
// 返回背景位置
return _background.anchoredPosition;
}
/// <summary>
/// 检查分辨率变化
/// </summary>
private void CheckResolutionChange()
{
Vector2 currentScreenSize = new Vector2(Screen.width, Screen.height);
Vector2 currentBackgroundSize = _background != null ? _background.rect.size : Vector2.zero;
if (currentScreenSize != _lastScreenSize ||
(_background != null && currentBackgroundSize != _lastBackgroundSize))
{
_lastScreenSize = currentScreenSize;
_lastBackgroundSize = currentBackgroundSize;
UpdatePosition();
}
}
/// <summary>
/// 从当前位置反推设计位置
/// </summary>
public void CalculateDesignPositionFromCurrent()
{
if (_background == null || _rectTransform == null)
return;
// 获取当前相对位置
Vector2 currentPos = _rectTransform.anchoredPosition - _background.anchoredPosition;
Vector2 backgroundSize = _background.rect.size;
// 计算当前UV
Vector2 currentUV = new Vector2(
(currentPos.x + backgroundSize.x * 0.5f) / backgroundSize.x,
(currentPos.y + backgroundSize.y * 0.5f) / backgroundSize.y
);
// 反推设计位置
_designPosition = new Vector2(
currentUV.x * _designResolution.x - _designResolution.x * 0.5f,
currentUV.y * _designResolution.y - _designResolution.y * 0.5f
);
Debug.Log($"设计位置已更新为: {_designPosition}");
}
/// <summary>
/// 强制立即更新位置
/// </summary>
public void ForceUpdate()
{
UpdatePosition();
}
#endregion
#region 编辑器辅助
#if UNITY_EDITOR
[UnityEditor.MenuItem("GameObject/UI/Background Relative Position", false, 10)]
static void CreateBackgroundRelativeItem(UnityEditor.MenuCommand menuCommand)
{
// 创建GameObject
GameObject go = new GameObject("BackgroundRelativeItem");
UnityEditor.GameObjectUtility.SetParentAndAlign(go, menuCommand.context as GameObject);
// 添加必要的组件
go.AddComponent<Image>();
go.AddComponent<BackgroundRelativePosition>();
// 注册撤销操作
UnityEditor.Undo.RegisterCreatedObjectUndo(go, "Create " + go.name);
// 选中新创建的对象
UnityEditor.Selection.activeObject = go;
}
#endif
#endregion
}