关于屏幕外物体在屏幕边缘的指示功能实现

效果

思路:

1、描出屏幕边缘框,这个可以计算得出,也可以直接弄几个点,拉成一个框;

2、计算规则,用屏幕中心到目标物体的屏幕坐标这条线,和上诉各个边框线计算,得出交点;

3、设置气泡位置,并根据方向计算箭头位置和旋转角度;

4、小优化,不要将刷新直接放进LateUpdate,而是在相机移动时才刷新;

代码:

//==========================
// - 作者:Xiao
// - 时间:2022/07/05 09:49:05
// - 描述:屏幕外物体提示hud
//==========================

using UnityEngine;
using System.Collections.Generic;

public class OutViewTip : MonoBehaviour
{
    public RectTransform iconRoot;
    public RectTransform arrow;

    //偏移
    public float offset;
    //箭头旋转的半径
    public float arrowRadius;

    private RectTransform self;
    [HideInInspector]
    public Transform target;

    private bool intersect;

    private Canvas canvas;
    private Camera uiCamera;

    //屏幕边框的各个点
    private List<Vector3> points;

    protected void SetTarget(Transform target)
    {
        this.target= target;
        Init();
    }

    private void Init()
    {
        self = GetComponent<RectTransform>();
        canvas = UIUtil.MainUI.transform.parent.GetComponent<Canvas>();
        uiCamera = MgrUtil.GUI.GetUICamera();
        points = UIUtil.MainUI.GetBorderPoses();

        Follow();
    }

    private void OnEnable()
    {
           //将刷新放进相机或者视野移动事件
        Notifications.CameraViewChange += Follow;
    }

    private void OnDisable()
    {
        //移除
        Notifications.CameraViewChange -= Follow;
    }

    private void Follow()
    {
        if (target== null)
        {
            return;
        }

        Vector3 pos = Vector3.zero;
        //目标屏幕坐标
        Vector3 pos1 = Camera.main.WorldToScreenPoint(target.position);
        Vector2 worldPoint1;
        Vector2 worldPoint2;

        RectTransformUtility.ScreenPointToLocalPointInRectangle(canvas.transform as RectTransform, pos1, uiCamera, out worldPoint1);
        worldPoint2 = pos;

        intersect = false;

        Vector3 border1;
        Vector3 border2;
        for (int i = 0; i < points.Count; i++)
        {
            //这里的if是为了让最后一条边界为 ps[Count - 1]与ps[0]连线
            if (i < points.Count - 1)
            {
                border1 = points[i + 1];
                border2 = points[i];
            }
            else
            {
                border1 = points[i];
                border2 = points[0];
            }

            if (SegmentsInterPoint(worldPoint1, worldPoint2, border1, border2, ref pos))
            {
                Vector2 normalized = (worldPoint1 - worldPoint2).normalized;
                Vector3 offsetVec = normalized * (offset *  (1 + Mathf.Abs(normalized.x)));
                pos -= offsetVec;
                //Debug.LogError($"交点位置:{pos}");
                self.anchoredPosition = pos;
                intersect = true;
                break;
            }
        }

        //判断有没有交点来旋转箭头
        if (intersect)
        {
            //箭头朝向目标
            ArrowLookAt(arrow, worldPoint1 - worldPoint2, Vector3.up);
        }
    }

    public void ArrowLookAt(RectTransform arrow, Vector2 dir, Vector3 lookAxis)
    {
        dir = dir.normalized;
        arrow.anchoredPosition = iconRoot.anchoredPosition + dir * arrowRadius;

        Quaternion q = Quaternion.identity;
        q.SetFromToRotation(lookAxis, dir);
        arrow.rotation = q;
    }

    //求交点
    public static bool SegmentsInterPoint(Vector3 a, Vector3 b, Vector3 c, Vector3 d, ref Vector3 IntrPos)
    {

        //以线段ab为准,是否c,d在同一侧
        Vector3 ab = b - a;
        Vector3 ac = c - a;
        float abXac = Cross(ab, ac);

        Vector3 ad = d - a;
        float abXad = Cross(ab, ad);

        if (abXac * abXad >= 0)
        {
            return false;
        }

        //以线段cd为准,是否ab在同一侧
        Vector3 cd = d - c;
        Vector3 ca = a - c;
        Vector3 cb = b - c;

        float cdXca = Cross(cd, ca);
        float cdXcb = Cross(cd, cb);
        if (cdXca * cdXcb >= 0)
        {
            return false;
        }
        //计算交点坐标  
        float t = Cross(a - c, d - c) / Cross(d - c, b - a);
        float dx = t * (b.x - a.x);
        float dy = t * (b.y - a.y);

        IntrPos = new Vector3() { x = a.x + dx, y = a.y + dy };
        return true;
    }

    public static float Cross(Vector3 a, Vector3 b)
    {
        return a.x * b.y - b.x * a.y;
    }

}

附判定某世界位置是否在屏幕内:

    private bool CheckInScreen(Vector3 worldPos)
    {
        Vector2 screenPos = Camera.main.WorldToScreenPoint(worldPos);

        return screenPos.x >= 0 && screenPos.x <= Screen.width && screenPos.y >= 0 &&             
            screenPos.y <= Screen.height;
    }

 

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值