396 lines
11 KiB
C#
396 lines
11 KiB
C#
|
|
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
|
|||
|
|
}
|