Unity 目标指示器的实现方法

先决条件

在一切之前,请检查这些东西:

  1. 指示器所在画布的Canvas组件渲染模式为“屏幕空间-覆盖”;
  2. 指示器的锚点必须在左下角;
  3. 指示器的轴心为 ( 0.5 , 0.5 ) (0.5, 0.5) (0.5,0.5)

请注意,以上三点非常重要。尤其是第二、三点,决定了指示器的最终显示位置是否正确(当然,不这么做也是可以通过代码来修正的)。

实现逻辑

因为本文讲的是目标指示器的实现方法,所以我们先把实现它所需要的代码给预备好:

public Transform target;	//追踪目标
public RectTransform indicator;	//指示器

void Update()
{
	UpdateIndicatorState();
}

/// <summary>
/// 更新指示器状态
/// </summary>
void UpdateIndicatorState()
{
	if (target)
	{
		indicator.gameObject.SetActive(true);
		//我们即将实现的内容
	}
	else
	{
		indicator.gameObject.SetActive(false);
	}
}

/// <summary>
/// 检查目标是否在摄像机前面
/// </summary>
/// <returns>若是,则返回true,否则返回false</returns>
public bool IsTargetInCameraFront()
{
    Vector3 directionToTarget = (target.transform.position - transform.position).normalized;
    return Vector3.Dot(directionToTarget, transform.forward) > 0;
}

以上代码在Update()时会调用我们的处理函数UpdateIndicatorState(),我们将在这个函数中实现我们的逻辑。

首先,我们需要获取到目标点的屏幕坐标:

//函数 UpdateIndicatorState() 中
Vector3 targetScreenPoint = Camera.main.WorldToScreenPoint(target.position);

由于屏幕坐标等于锚点必须在左下角的UI元素的anchoredPosition(前提是该UI元素所在画布的Canvas组件渲染模式为“屏幕空间-覆盖”),因此在拿到目标点的屏幕坐标后,我们就可以使指示器指向目标点了:

//函数 UpdateIndicatorState() 中
indicator.anchoredPosition = targetScreenPoint;

但是这样,我们遇到了第一个问题,即:当目标点在消失屏幕外时,指示器也会跟着消失,所以我们需要限制指示器的位置在屏幕范围内:

//函数 UpdateIndicatorState() 中
Vector2 indicatorAnchoredPosition = new Vector2(Mathf.Clamp(targetScreenPoint.x, 0, Screen.width), Mathf.Clamp(targetScreenPoint.y, 0, Screen.height));

这样就解决了指示器消失的问题,值得注意的是:当画布Canvas Scaler的缩放模式不为“恒定像素大小”时,Screen.widthScreen.height并不能表示画布真实的长宽。顺带一提,我们还可以让指示器到屏幕边缘保持一段距离:

//在脚本类中
public float indicatorMargin;

//函数 UpdateIndicatorState() 中,刚才的代码
Vector2 indicatorAnchoredPosition = new Vector2(Mathf.Clamp(targetScreenPoint.x, indicatorMargin, Screen.width - indicatorMargin), Mathf.Clamp(targetScreenPoint.y, indicatorMargin, Screen.height - indicatorMargin));

然而,当目标点在摄像机背面时,指示器也会跑到屏幕中间。因此当目标点在摄像机后面时,我们要给targetScreenPoint乘一个极小的负数,以使指示器能够继续靠边。

//函数 UpdateIndicatorState() 中
if (!IsEnemyInPlayerFront())
{
    targetScreenPoint *= (Screen.width * Screen.height * -1);
}

然后,运行代码,你的指示器已经初具雏形了。接下来请按照你的需求而自由改造它。

完整代码

using UnityEngine;

public class IndicatorManagerSimple : MonoBehaviour
{
    public Transform target;    //追踪目标
    public RectTransform indicator; //指示器
    public float indicatorMargin;   //指示器活动范围到屏幕的距离

    void Update()
    {
        UpdateIndicatorState();
    }

    /// <summary>
    /// 更新指示器状态
    /// </summary>
    void UpdateIndicatorState()
    {
        if (target)
        {
            indicator.gameObject.SetActive(true);

            //核心代码部分

            //获取目标点屏幕坐标
            Vector3 targetScreenPoint = Camera.main.WorldToScreenPoint(target.position);
            if (!IsTargetInCameraFront())
            {
                targetScreenPoint *= (Screen.width * Screen.height * -1);   //当目标点在摄像机后面时,乘上一个极小的负数
            }

            //对 targetScreenPoint 的范围进行限定
            Vector2 indicatorAnchoredPosition = new Vector2(Mathf.Clamp(targetScreenPoint.x, indicatorMargin, Screen.width - indicatorMargin), Mathf.Clamp(targetScreenPoint.y, indicatorMargin, Screen.height - indicatorMargin));

            indicator.anchoredPosition = indicatorAnchoredPosition;
        }
        else
        {
            indicator.gameObject.SetActive(false);
        }
    }

    /// <summary>
    /// 检查目标是否在摄像机前面
    /// </summary>
    /// <returns>若是,则返回true,否则返回false</returns>
    public bool IsTargetInCameraFront()
    {
        Vector3 directionToTarget = (target.transform.position - transform.position).normalized;
        return Vector3.Dot(directionToTarget, transform.forward) > 0;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值