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 属性 /// /// 背景参考 /// public RectTransform Background { get => _background; set { _background = value; UpdatePosition(); } } /// /// 设计时位置(在设计分辨率下相对于父节点中心的位置) /// public Vector2 DesignPosition { get => _designPosition; set { _designPosition = value; UpdatePosition(); } } /// /// 设计分辨率 /// public Vector2 DesignResolution { get => _designResolution; set { _designResolution = value; UpdatePosition(); } } /// /// 获取当前相对位置(UV坐标) /// 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(); _lastScreenSize = new Vector2(Screen.width, Screen.height); // 如果没有设置背景,尝试从父对象获取 if (_background == null) { Transform parent = transform.parent; if (parent != null) { _background = parent.GetComponent(); } } } 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(); if (Application.isPlaying || UnityEditor.EditorApplication.isPlayingOrWillChangePlaymode) { return; } // 编辑器模式下,参数改变时更新 UnityEditor.EditorApplication.delayCall += () => { if (this != null && _autoUpdate) { UpdatePosition(); } }; } #endif #endregion #region 核心方法 /// /// 更新Item位置 /// 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; } } /// /// 计算当前位置 /// private Vector2 CalculatePosition() { switch (_relativeMode) { case RelativeMode.UV: return CalculateUVPosition(); case RelativeMode.PixelPerfect: return CalculatePixelPerfectPosition(); case RelativeMode.AnchorBased: return CalculateAnchorBasedPosition(); default: return CalculateUVPosition(); } } /// /// 使用UV模式计算位置 /// 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; } /// /// 使用像素完美模式计算位置 /// 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; } /// /// 使用锚点模式计算位置 /// 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; } /// /// 检查分辨率变化 /// 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(); } } /// /// 从当前位置反推设计位置 /// 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}"); } /// /// 强制立即更新位置 /// 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(); go.AddComponent(); // 注册撤销操作 UnityEditor.Undo.RegisterCreatedObjectUndo(go, "Create " + go.name); // 选中新创建的对象 UnityEditor.Selection.activeObject = go; } #endif #endregion }