Tz2/Assets/Scripts/GuideMask.cs

868 lines
31 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 System;
using System.Threading.Tasks;
using Cysharp.Threading.Tasks;
using TMPro;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.UI;
using DG.Tweening;
/// <summary>
/// 引导遮罩类,用于创建和管理引导过程中的遮罩效果
/// </summary>
public class GuideMask : MonoBehaviour
{
// 遮罩的 RectTransform 组件,可在 Inspector 面板中赋值
public RectTransform rectTransform;
public GameObject CanvasParent;
public RectTransform buttonPrefab;
// 遮罩的 RawImage 组件,用于显示遮罩图片
private RawImage _rawImage;
// 遮罩的 RectTransform 组件
private RectTransform _rectTrans;
// 遮罩使用的材质
private Material _materia;
// 当前遮罩的原点位置,初始化为一个很大的值
private Vector4 _currentOrigin = new Vector4(-9999, -9999, 9999, 9999);
// 当前正在进行的 DOTween 动画
private Tween currentTween;
// 遮罩所在的 Canvas 组件
private Canvas _canvas;
// 事件穿透组件
private EventPenetrate ev;
// 引导遮罩的游戏对象
public GameObject guide;
// 是否正在显示动画
private bool ShowTween = false;
// 当前遮罩的半径数值
private float CurRadNum;
// 动画完成后的回调函数
private Action Tweencallback;
// 回调函数是否被触发的标志
private bool callback = false;
// 单例模式,确保全局只有一个 GuideMask 实例
public static GuideMask Instance { private set; get; }
// 遮罩的偏移量,用于调整遮罩的大小
private float offset = 3;
// 目标位置跟踪相关字段
private GameObject _trackingTarget; // 当前跟踪的目标对象
private Vector3 _lastTargetPosition; // 上次记录的目标位置
private Vector3 _lastTargetScale; // 上次记录的目标缩放
private bool _isTrackingTarget = false; // 是否正在跟踪目标
/// <summary>
/// 脚本实例被创建时调用,进行初始化操作
/// </summary>
void Awake()
{
// 将当前实例赋值给单例
Instance = this;
// // 获取渲染器的材质
// _materia = GetComponent<MeshRenderer>().material;
// // 设置材质的 _Origin 属性为当前原点位置
// _materia.SetVector("_Origin", _currentOrigin);
}
/// <summary>
/// 创建一个带有点击事件的遮罩
/// </summary>
/// <param name="Clickcallback">点击遮罩时执行的回调函数</param>
/// <param name="callback">遮罩显示完成后执行的回调函数</param>
/// <param name="black">是否使用黑色遮罩,默认为 false</param>
public void CreateMaskClick(UnityAction Clickcallback, Action callback, bool black = false)
{
// 显示引导遮罩,并在显示完成后执行回调
ShowGuideMask(() =>
{
// 如果引导遮罩对象存在
if (guide != null)
{
// 如果需要黑色遮罩
if (black)
{
// 设置遮罩图片的颜色为黑色且不透明
_rawImage.color = new Color(0, 0, 0, 1);
}
else
{
// 设置遮罩图片的颜色为白色且透明
_rawImage.color = new Color(1, 1, 1, 0);
}
// 移除按钮上的所有点击事件监听器
guide.GetComponent<Button>().onClick.RemoveAllListeners();
// 添加新的点击事件监听器
guide.GetComponent<Button>().onClick.AddListener(Clickcallback);
// 执行回调函数
callback();
}
});
}
/// 创建一个矩形遮罩区域
/// </summary>
/// <param name="target">目标对象,遮罩将围绕该对象创建</param>
/// <param name="tipePlane">提示面板对象,可选参数</param>
public void CreateRectangleMask(GameObject target, GameObject tipePlane = null)
{
if (!rectTransform.gameObject.activeInHierarchy)
{
rectTransform.gameObject.SetActive(true);
}
// 如果目标对象存在
if (target != null)
{
try
{
// 获取目标对象的 RectTransform 组件
RectTransform rec = target.GetComponent<RectTransform>();
// 用于存储目标对象的四个角的世界坐标
Vector3[] _corners = new Vector3[4];
// 获取目标对象的四个角的世界坐标
rec.GetWorldCorners(_corners);
// 将目标对象左下角的世界坐标转换为画布坐标
Vector2 pos1 = WorldToCanvasPos(_corners[0]);
// 将目标对象右上角的世界坐标转换为画布坐标
Vector2 pos2 = WorldToCanvasPos(_corners[2]);
// 获取目标对象的全局缩放比例
Vector3 targetScale = target.transform.lossyScale;
// 计算目标对象的实际大小,考虑缩放因素
Vector2 actualSize = new Vector2(
rec.sizeDelta.x * targetScale.x,
rec.sizeDelta.y * targetScale.y
);
// 检查目标对象是否在ScrollRect中
ScrollRect scrollRect = target.GetComponentInParent<ScrollRect>();
// // 打印左下角的画布坐标
// Debug.Log(pos1);
// // 打印右上角的画布坐标
// Debug.Log(pos2);
// 获取目标对象的四个角的世界坐标
Vector3[] corners = new Vector3[4];
rec.GetWorldCorners(corners);
// 将目标对象左下角的世界坐标转换为画布坐标
Vector2 worldPos = WorldToCanvasPos(corners[0]);
// 如果遮罩的 RectTransform 组件存在
if (rectTransform != null)
{
if (scrollRect != null)
{
// 获取UI对象的RectTransform
RectTransform targetRectTrans = target.GetComponent<RectTransform>();
// 打印目标对象的基本信息
// Debug.Log($"目标对象: {target.name}, 位置: {target.transform.position}, localPosition: {target.transform.localPosition}");
// Debug.Log($"目标对象: pivot: {targetRectTrans.pivot}, anchor: {targetRectTrans.anchorMin}/{targetRectTrans.anchorMax}");
// 获取UI对象在世界空间中的四个角坐标
Vector3[] corners1 = new Vector3[4];
targetRectTrans.GetWorldCorners(corners1);
// 打印四个角的坐标
// Debug.Log($"目标四角: 左下:{corners1[0]}, 左上:{corners1[1]}, 右上:{corners1[2]}, 右下:{corners1[3]}");
// 计算世界空间中的中心点
Vector3 centerWorld = (corners1[0] + corners1[2]) / 2;
// 计算UI对象的尺寸
float width = Vector3.Distance(corners1[0], corners1[3]);
float height = Vector3.Distance(corners1[0], corners1[1]);
// 打印遮罩对象信息
//Debug.Log($"遮罩对象: pivot: {rectTransform.pivot}, anchor: {rectTransform.anchorMin}/{rectTransform.anchorMax}");
// Debug.Log($"遮罩位置前: position: {rectTransform.position}, anchoredPosition: {rectTransform.anchoredPosition}");
// 设置锚点和轴心点都在中心
rectTransform.pivot = new Vector2(0.5f, 0.5f);
rectTransform.anchorMin = new Vector2(0.5f, 0.5f);
rectTransform.anchorMax = new Vector2(0.5f, 0.5f);
// 设置遮罩的世界位置和大小
rectTransform.transform.localScale = Vector3.one;
//rectTransform.position = centerWorld; // 直接设置世界坐标位置
rectTransform.sizeDelta = new Vector2(width + 10, height + 10);
// 打印设置后的信息
// Debug.Log($"遮罩位置后: position: {rectTransform.position}, anchoredPosition: {rectTransform.anchoredPosition}");
// Debug.Log($"ScrollRect中的UI对象 - 中心点:{centerWorld}, 宽度:{width}, 高度:{height}");
rectTransform.SetParent(target.transform);
rectTransform.anchoredPosition = Vector2.zero;
// rectTransform.parent=null;
rectTransform.SetParent(CanvasParent.transform);
}
}
else
{
// 创建一个默认的矩形遮罩区域
// CreateRectangleMaskRect(new Vector2(0, 0), new Vector2(0, 0), target);
// 清空动画完成后的回调函数
Tweencallback = null;
}
if (scrollRect != null)
{
// 如果在ScrollRect中调整位置计算
//Debug.Log($"目标对象在ScrollRect中原始位置: pos1={pos1}, pos2={pos2}");
// 调整位置考虑UI对象的中心点
Vector2 adjustedPos1 = pos1;
Vector2 adjustedPos2 = pos2;
buttonPrefab.anchoredPosition = new Vector2(0, rectTransform.sizeDelta.y / 2);
//Debug.Log($"调整后位置: pos1={adjustedPos1}, pos2={adjustedPos2}");
// 创建矩形遮罩区域
//CreateRectangleMaskRect(adjustedPos1, adjustedPos2, target, tipePlane);
}
else
{
rectTransform.SetParent(target.transform);
rectTransform.anchoredPosition = Vector2.zero;
rectTransform.sizeDelta = new Vector2(actualSize.x + 10, actualSize.y + 10);
// rectTransform.parent=null;
rectTransform.SetParent(CanvasParent.transform);
// Debug.Log(rectTransform.sizeDelta.y/2);
buttonPrefab.anchoredPosition = new Vector2(0, rectTransform.sizeDelta.y / 2);
// Debug.Log(pos1);
// Debug.Log(pos2);
// 如果不在ScrollRect中使用常规方法
// CreateRectangleMaskRect(pos1, pos2, target, tipePlane);
}
}
catch (Exception e)
{
Debug.Log(e.Message);
}
// 清空动画完成后的回调函数
Tweencallback = null;
// 自动开始跟踪目标对象位置变化
StartTrackingTarget(target);
}
}
/// <summary>
/// 根据给定的坐标创建矩形遮罩区域
/// </summary>
/// <param name="pos1">矩形的左下角坐标</param>
/// <param name="pos2">矩形的右上角坐标</param>
/// <param name="target">目标对象</param>
/// <param name="tipePlane">提示面板对象,可选参数</param>
public void CreateRectangleMaskRect(Vector3 pos1, Vector3 pos2, GameObject target, GameObject tipePlane = null)
{
// 显示引导遮罩,并在显示完成后执行回调
ShowGuideMask(() =>
{
if (!_rectTrans.gameObject.activeInHierarchy)
{
_rectTrans.gameObject.SetActive(true);
}
// 标记回调函数需要被触发
callback = true;
// 设置事件穿透组件的目标对象
ev.SetTargetImage(target);
// 设置遮罩的大小为零
_rectTrans.sizeDelta = Vector2.zero;
// 设置材质的 _MaskType 属性为 1表示矩形遮罩
_materia.SetFloat("_MaskType", 1.0f);
// 设置允许点击穿透属性为1.0,表示开启点击穿透
_materia.SetFloat("_AllowClickThrough", 1.0f);
// 提示面板的宽度
float tipeWidth = 0;
// 提示面板的高度
float tipeHeight = 0;
// 如果提示面板对象存在
if (tipePlane != null)
{
// 获取提示面板的 RectTransform 组件
RectTransform tipeRect = tipePlane.GetComponent<RectTransform>();
// 用于存储提示面板的四个角的世界坐标
Vector3[] tipeCorners = new Vector3[4];
// 获取提示面板的四个角的世界坐标
tipeRect.GetWorldCorners(tipeCorners);
// 将提示面板左下角的世界坐标转换为画布坐标
Vector2 tipePos1 = WorldToCanvasPos(tipeCorners[0]);
// 打印提示面板左下角的画布坐标
Debug.Log(tipePos1.y);
// 打印目标对象左下角的画布坐标
Debug.Log(pos1.y);
// 获取提示面板的大小
Vector2 ve = tipeRect.sizeDelta;
// 设置提示面板的宽度
tipeWidth = ve.x;
// 如果提示面板的左下角比目标对象的左下角低
if (tipePos1.y < pos1.y)
{
// 设置提示面板的高度
tipeHeight = ve.y;
}
}
// 设置遮罩的位置
SetMaskPosition(new Vector2(pos1.x, pos1.y), new Vector2(pos2.x, pos2.y), tipeWidth, tipeHeight);
});
}
/// <summary>
/// 获取目标对象的半径
/// </summary>
/// <param name="rect">目标对象的 RectTransform 组件</param>
/// <returns>目标对象的半径</returns>
public float GetTargetRad(RectTransform rect)
{
// 用于存储目标对象的四个角的世界坐标
Vector3[] _corners = new Vector3[4];
// 获取目标对象的四个角的世界坐标
rect.GetWorldCorners(_corners);
// 计算目标对象的半径,通过计算左下角和右上角的距离并除以 2
float _radius = Vector2.Distance(WorldToCanvasPos(_corners[0]),
WorldToCanvasPos(_corners[2])) / 2f;
// 返回计算得到的半径
return _radius;
}
/// <summary>
/// 获取目标对象的中心点坐标
/// </summary>
/// <param name="rect">目标对象的 RectTransform 组件</param>
/// <returns>目标对象的中心点坐标</returns>
public Vector2 GetTargetCenter(RectTransform rect)
{
// 用于存储目标对象的四个角的世界坐标
Vector3[] _corners = new Vector3[4];
// 获取目标对象的四个角的世界坐标
rect.GetWorldCorners(_corners);
// 计算目标对象中心点的 x 坐标
float x = _corners[0].x + ((_corners[3].x - _corners[0].x) / 2f);
// 计算目标对象中心点的 y 坐标
float y = _corners[0].y + ((_corners[1].y - _corners[0].y) / 2f);
// 创建目标对象中心点的世界坐标
Vector3 centerWorld = new Vector3(x, y, 0);
// 将目标对象中心点的世界坐标转换为画布坐标
Vector2 center = WorldToCanvasPos(centerWorld);
// 返回目标对象中心点的画布坐标
return center;
}
/// <summary>
/// 初始化材质设置_Origin属性
/// </summary>
public void InitializeMaterial()
{
if (_materia != null)
{
_materia.SetVector("_Origin", new Vector4(-9999, -9999, 9999, 9999));
_currentOrigin = new Vector4(-9999, -9999, 9999, 9999);
}
}
/// <summary>
/// 获取目标对象的中心点坐标,并进行偏移
/// </summary>
/// <param name="rect">目标对象的 RectTransform 组件</param>
/// <param name="_x">x 轴的偏移量</param>
/// <param name="_y">y 轴的偏移量</param>
/// <returns>目标对象偏移后的中心点坐标</returns>
public Vector2 GetTargetCenter(RectTransform rect, float _x, float _y)
{
// 用于存储目标对象的四个角的世界坐标
Vector3[] _corners = new Vector3[4];
// 获取目标对象的四个角的世界坐标
rect.GetWorldCorners(_corners);
// 计算目标对象中心点的 x 坐标
float x = _corners[0].x + ((_corners[3].x - _corners[0].x) / 2f);
// 计算目标对象中心点的 y 坐标
float y = _corners[0].y + ((_corners[1].y - _corners[0].y) / 2f);
// 创建目标对象偏移后的中心点的世界坐标
Vector3 centerWorld = new Vector3(x + _x, y + _y, 0);
// 将目标对象偏移后的中心点的世界坐标转换为画布坐标
Vector2 center = WorldToCanvasPos(centerWorld);
// 返回目标对象偏移后的中心点的画布坐标
return center;
}
/// <summary>
/// 将世界坐标转换为画布坐标
/// </summary>
/// <param name="world">世界坐标</param>
/// <returns>画布坐标</returns>
private Vector2 WorldToCanvasPos(Vector3 world)
{
// 如果画布组件为空
if (null == _canvas)
// 获取当前游戏对象的画布组件
_canvas = gameObject.GetComponent<Canvas>();
// 用于存储转换后的画布坐标
Vector2 position;
// 将世界坐标转换为画布坐标
RectTransformUtility.ScreenPointToLocalPointInRectangle(_canvas.transform as RectTransform,
world, _canvas.GetComponent<Camera>(), out position);
// 返回转换后的画布坐标
return position;
}
/// <summary>
/// 显示引导遮罩
/// </summary>
/// <param name="callback">遮罩显示完成后执行的回调函数</param>
public void ShowGuideMask(Action callback)
{
// 标记不显示动画
ShowTween = false;
// 如果遮罩的 RectTransform 组件为空
if (_rectTrans == null)
{
// 从资源中加载引导遮罩的预制体
GameObject obj = Resources.Load<GameObject>("GuideMode/GuideSystem");
// 实例化引导遮罩预制体
guide = Instantiate((GameObject)obj, transform);
guide.transform.SetSiblingIndex(0);
// 获取引导遮罩的 RectTransform 组件
_rectTrans = guide.GetComponent<RectTransform>();
// 获取引导遮罩的 RawImage 组件
_rawImage = guide.GetComponent<RawImage>();
// 设置引导遮罩图片的颜色为白色且不透明
_rawImage.color = new Color(1, 1, 1, 1);
// 获取引导遮罩的材质
_materia = _rawImage.material;
// 获取事件穿透组件
ev = guide.GetComponent<EventPenetrate>();
// 执行回调函数
callback();
}
else
{
// 执行回调函数
callback();
}
}
/// <summary>
/// 将可点击区域设置为空
/// </summary>
public void SetTargetNil()
{
// 如果事件穿透组件存在
if (ev != null)
{
// 设置事件穿透组件的目标对象为空
ev.SetTargetImage(null);
}
}
/// <summary>
/// 隐藏引导遮罩(不销毁,只是隐藏)
/// </summary>
public void HideGuideMask()
{
// 只停止跟踪状态,但保留目标对象引用
_isTrackingTarget = false;
// 如果引导遮罩对象存在
if (guide != null)
{
// 隐藏引导遮罩对象
guide.SetActive(false);
Debug.Log("引导遮罩已隐藏");
}
// 如果遮罩的 RectTransform 组件存在
if (rectTransform != null)
{
// 隐藏遮罩
rectTransform.gameObject.SetActive(false);
}
}
/// <summary>
/// 显示引导遮罩(重新显示之前隐藏的遮罩)
/// </summary>
public void ShowGuideMask()
{
// 如果引导遮罩对象存在
if (guide != null)
{
// 显示引导遮罩对象
guide.SetActive(true);
Debug.Log("引导遮罩已显示");
}
// 如果遮罩的 RectTransform 组件存在
if (rectTransform != null)
{
// 显示遮罩
rectTransform.gameObject.SetActive(true);
}
}
/// <summary>
/// 关闭引导遮罩
/// </summary>
public void CloseGuideMask()
{
// 停止跟踪目标对象
StopTrackingTarget();
// 如果引导遮罩对象存在
if (guide != null)
{
// 清空 RawImage 组件引用
_rawImage = null;
// 清空 RectTransform 组件引用
_rectTrans = null;
// 如果材质存在
if (_materia != null)
{
// 设置材质的 _MaskType 属性为 1
_materia.SetFloat("_MaskType", 1.0f);
// 设置材质的 _Origin 属性为零向量
_materia.SetVector("_Origin", new Vector4(0, 0, 0, 0));
}
// 如果遮罩的 RectTransform 组件存在
if (rectTransform != null)
// 设置遮罩的缩放比例为零
rectTransform.transform.localScale = Vector3.zero;
// 销毁引导遮罩对象
Destroy(guide);
}
}
// 动画时间
float TweenTime = 0;
/// <summary>
/// 每帧更新时调用
/// </summary>
public void Update()
{
// 如果正在显示动画
if (ShowTween)
{
// 如果材质存在
if (_materia != null)
{
// 获取材质的 _Origin 属性
Vector4 mateV4 = _materia.GetVector("_Origin");
// 如果当前半径小于等于目标半径加 25
if (mateV4.z <= CurRadNum + 25)
{
// 如果动画完成回调函数存在
if (Tweencallback != null)
{
// 执行动画完成回调函数
Tweencallback();
}
// 标记不显示动画
ShowTween = false;
// 退出方法
return;
}
// 线性插值更新当前半径
mateV4.z = Mathf.Lerp(mateV4.z, CurRadNum, 15f * Time.deltaTime);
// 设置材质的 _Origin 属性
_materia.SetVector("_Origin", mateV4);
}
}
// 如果回调函数需要被触发
if (callback)
{
// 如果动画完成回调函数存在
if (Tweencallback != null)
{
// 执行动画完成回调函数
Tweencallback();
}
// 标记回调函数已触发
callback = false;
}
// 目标位置跟踪更新
if (_trackingTarget != null)
{
// 检查目标UI对象是否激活
if (!_trackingTarget.activeInHierarchy)
{
// 如果目标UI对象未激活隐藏引导遮罩
if (_isTrackingTarget)
{
if (_trackingTarget.name != "电子表格页面")
HideGuideMask();
}
return;
}
else
{
// 如果目标UI对象激活但当前没有跟踪重新开始跟踪
if (!_isTrackingTarget)
{
ShowGuideMask();
StartTrackingTarget(_trackingTarget);
}
// 如果正在跟踪且位置发生变化,更新位置
if (_isTrackingTarget && HasTargetPositionChanged())
{
UpdateTargetPosition();
}
}
}
}
/// <summary>
/// 脚本实例被销毁时调用
/// </summary>
public void OnDestroy()
{
// 关闭引导遮罩
CloseGuideMask();
// 如果当前有动画正在播放
if (currentTween != null)
{
// 杀死当前动画
currentTween.Kill();
}
}
/// <summary>
/// 设置遮罩的位置
/// </summary>
/// <param name="pos1">矩形的左下角坐标</param>
/// <param name="pos2">矩形的右上角坐标</param>
/// <param name="tipeWidth">提示面板的宽度,默认为 0</param>
/// <param name="tipeHeight">提示面板的高度,默认为 0</param>
/// <param name="duration">动画持续时间,默认为 0.3 秒</param>
/// <param name="onComplete">动画完成后的回调函数,可选参数</param>
public void SetMaskPosition(Vector2 pos1, Vector2 pos2, float tipeWidth = 0, float tipeHeight = 0, float duration = 0.3f, Action onComplete = null)
{
// 如果当前有动画正在播放
if (currentTween != null && currentTween.IsPlaying())
{
// 杀死当前动画
currentTween.Kill();
}
// 创建目标原点位置
Vector4 targetOrigin = new Vector4(pos1.x, pos1.y, pos2.x, pos2.y);
// 使用 DOTween 创建动画
currentTween = DOTween.To(
() => _currentOrigin,
(value) =>
{
// 更新当前原点位置
_currentOrigin = value;
// 设置材质的 _Origin 属性
_materia.SetVector("_Origin", value);
},
targetOrigin,
duration
).SetEase(Ease.OutQuad);
// 如果动画完成回调函数存在
if (onComplete != null)
{
// 添加动画完成回调函数
currentTween.OnComplete(() => onComplete());
}
}
/// <summary>
/// 专门处理ScrollRect中UI元素的遮罩定位
/// </summary>
/// <param name="target">目标UI对象</param>
private void HandleScrollRectMask(GameObject target)
{
if (target == null || rectTransform == null) return;
RectTransform targetRectTrans = target.GetComponent<RectTransform>();
if (targetRectTrans == null) return;
// 获取目标在世界空间的位置和大小
Vector3[] corners = new Vector3[4];
targetRectTrans.GetWorldCorners(corners);
// 计算中心点和大小
Vector3 centerWorld = (corners[0] + corners[2]) / 2;
float width = Vector3.Distance(corners[0], corners[3]);
float height = Vector3.Distance(corners[0], corners[1]);
//Debug.Log($"ScrollRect UI对象: 中心点世界坐标={centerWorld}, 宽度={width}, 高度={height}");
// 设置遮罩属性
rectTransform.pivot = new Vector2(0.5f, 0.5f);
rectTransform.anchorMin = new Vector2(0.5f, 0.5f);
rectTransform.anchorMax = new Vector2(0.5f, 0.5f);
// 直接设置世界位置和大小
rectTransform.position = centerWorld;
rectTransform.sizeDelta = new Vector2(width + 10, height + 10);
//Debug.Log($"遮罩设置后: 位置={rectTransform.position}, 大小={rectTransform.sizeDelta}");
}
/// <summary>
/// 开始跟踪目标对象的位置变化
/// </summary>
/// <param name="target">要跟踪的目标对象</param>
public void StartTrackingTarget(GameObject target)
{
if (target == null) return;
_trackingTarget = target;
_lastTargetPosition = target.transform.position;
_lastTargetScale = target.transform.lossyScale;
_isTrackingTarget = true;
Debug.Log($"开始跟踪目标对象: {target.name}");
}
/// <summary>
/// 停止跟踪目标对象
/// </summary>
public void StopTrackingTarget()
{
_isTrackingTarget = false;
_trackingTarget = null;
Debug.Log("停止跟踪目标对象");
}
/// <summary>
/// 检查目标对象是否重新激活,如果是则重新显示引导
/// </summary>
public void CheckTargetActiveStatus()
{
if (_trackingTarget != null && _trackingTarget.activeInHierarchy)
{
// 如果目标对象重新激活,显示引导遮罩
ShowGuideMask();
// 重新开始跟踪
StartTrackingTarget(_trackingTarget);
}
}
/// <summary>
/// 检查目标对象位置是否发生变化
/// </summary>
/// <returns>如果位置发生变化返回true否则返回false</returns>
private bool HasTargetPositionChanged()
{
if (_trackingTarget == null) return false;
Vector3 currentPosition = _trackingTarget.transform.position;
Vector3 currentScale = _trackingTarget.transform.lossyScale;
// 检查位置或缩放是否发生变化(使用小的阈值避免浮点精度问题)
bool positionChanged = Vector3.Distance(currentPosition, _lastTargetPosition) > 0.01f;
bool scaleChanged = Vector3.Distance(currentScale, _lastTargetScale) > 0.01f;
return positionChanged || scaleChanged;
}
/// <summary>
/// 更新目标位置并重新计算遮罩位置
/// </summary>
private void UpdateTargetPosition()
{
if (_trackingTarget == null || rectTransform == null) return;
try
{
// 更新记录的位置和缩放
_lastTargetPosition = _trackingTarget.transform.position;
_lastTargetScale = _trackingTarget.transform.lossyScale;
// 获取目标对象的RectTransform组件
RectTransform targetRect = _trackingTarget.GetComponent<RectTransform>();
if (targetRect == null) return;
// 检查目标对象是否在ScrollRect中
ScrollRect scrollRect = _trackingTarget.GetComponentInParent<ScrollRect>();
if (scrollRect != null)
{
// 处理ScrollRect中的UI对象
HandleScrollRectMask(_trackingTarget);
}
else
{
// 处理普通UI对象
Vector3[] corners = new Vector3[4];
targetRect.GetWorldCorners(corners);
// 计算目标对象的实际大小,考虑缩放因素
Vector2 actualSize = new Vector2(
targetRect.sizeDelta.x * _lastTargetScale.x,
targetRect.sizeDelta.y * _lastTargetScale.y
);
// 设置遮罩位置和大小
rectTransform.SetParent(_trackingTarget.transform);
rectTransform.anchoredPosition = Vector2.zero;
rectTransform.sizeDelta = new Vector2(actualSize.x + 10, actualSize.y + 10);
rectTransform.SetParent(CanvasParent.transform);
// 更新按钮位置
buttonPrefab.anchoredPosition = new Vector2(0, rectTransform.sizeDelta.y / 2);
}
Debug.Log($"目标位置已更新: {_trackingTarget.name}");
}
catch (Exception e)
{
Debug.LogError($"更新目标位置时发生错误: {e.Message}");
}
}
}