栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 游戏开发 > Unity3D

Unity实现UI在屏幕边缘跟随并指向视野外敌人

Unity3D 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

Unity实现UI在屏幕边缘跟随并指向视野外敌人

最终效果

实现

心急的小伙伴可以直接跳到文章末尾查看最终代码,如果有问题再来看下思路。

首先我们需要确定实现思路。我想到的方案是将玩家和敌人的世界坐标转换为UI坐标,然后求玩家和敌人坐标的线段与Canvas边界的交点即为箭头坐标。
下面是求出交点的代码。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

public class FindTest : MonoBehaviour
{
    public Canvas canvas;
    public Camera cam;
    public Image find; //箭头UI
    public GameObject player; //玩家
    public GameObject target; //跟踪目标
    List points = new List(); //canvas边界点

    void Start()
    {
        //存储canvas边界点
        points.Add(new Vector3(-canvas.GetComponent().rect.width / 2f, -canvas.GetComponent().rect.height / 2f, 0));
        points.Add(new Vector3(canvas.GetComponent().rect.width / 2f, -canvas.GetComponent().rect.height / 2f, 0));
        points.Add(new Vector3(canvas.GetComponent().rect.width / 2f, canvas.GetComponent().rect.height / 2f, 0));
        points.Add(new Vector3(-canvas.GetComponent().rect.width / 2f, canvas.GetComponent().rect.height / 2f, 0));
    }

    void Update()
    {
        Vector3 pos = Vector3.zero;
        Vector3 pos1 = Camera.main.WorldToScreenPoint(target.transform.position); //目标屏幕坐标
        Vector3 pos2 = Camera.main.WorldToScreenPoint(player.transform.position); //玩家屏幕坐标
        Vector2 worldPoint1;
        Vector2 worldPoint2;

        //求出目标与玩家的UI坐标
        RectTransformUtility.ScreenPointToLocalPointInRectangle(canvas.transform as RectTransform, pos1, cam, out worldPoint1);
        RectTransformUtility.ScreenPointToLocalPointInRectangle(canvas.transform as RectTransform, pos2, cam, out worldPoint2);

        //玩家与目标UI坐标连线与Canvas边界连写交点即为箭头位置
        for (int i = 0; i < points.Count; i++)
        {
            //这里的if是为了让最后一条边界为 ps[Count - 1]与ps[0]连线
            if (i < points.Count - 1)
            {
                if (SegmentsInterPoint(worldPoint1, worldPoint2, points[i + 1], points[i], ref pos))
                {
                    find.rectTransform.anchoredPosition = pos;
                    break;
                }
            }
            else
            {
                if (SegmentsInterPoint(worldPoint1, worldPoint2, points[i], points[0], ref pos))
                {
                    find.rectTransform.anchoredPosition = pos;
                    break;
                }
            }
        }
    }
    
    public static float Cross(Vector3 a, Vector3 b)
    {
        return a.x * b.y - b.x * a.y;
    }

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

        //v1×v2=x1y2-y1x2 
        //以线段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;
    }
}

效果

核心的部分写完了,但是效果差强人意。还缺少箭头朝向敌人的功能,箭头也没有完整的显示出来,而且如果敌人进入视野内箭头应该消失。

1.箭头朝向敌人
首先是箭头朝向敌人,我们可以加段转向的代码。
因为箭头默认朝上,所以第三个参数传Vector3.up。

	//箭头朝向目标
    UILookAt(find.transform, worldPoint1 - worldPoint2, Vector3.up);

    //参数分别为:1.UI的Transform 2.朝向向量 3.起始向量
    public void UILookAt(Transform transform, Vector3 dir, Vector3 lookAxis)
    {
        Quaternion q = Quaternion.identity;
        q.SetFromToRotation(lookAxis, dir);
        transform.rotation = q;
    }

2.箭头完整显示
可以在代码中加一个箭头位置偏移。还有种更简单的方法就是修改UI的轴心点。如下图。

3.敌人进入视野内箭头消失
最开始打算使用OnBecameVisible和OnBecameInVisible来判断目标是否在视野内,但是这种方法不够灵活。于是考虑用另一种方法,就是通过判断玩家和目标UI坐标的线段与Canvas边界有没有交点来判断物体是否在视野内,并且给目标点设置偏移量让箭头可以提前或者延迟显隐。

最终代码
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

public class FindTest : MonoBehaviour
{
    public Canvas canvas;
    public Camera cam;
    public Image find; //箭头UI
    public GameObject player; //玩家
    public GameObject target; //跟踪目标
    List points = new List(); //canvas边界点
    bool isHaveIntersection = false; //判断有没有交点来显隐箭头
    public float targetOffset; //设置目标点偏移,用来控制箭头提前或者延后显隐


    void Start()
    {
        //存储canvas边界点
        points.Add(new Vector3(-canvas.GetComponent().rect.width / 2f, -canvas.GetComponent().rect.height / 2f, 0));
        points.Add(new Vector3(canvas.GetComponent().rect.width / 2f, -canvas.GetComponent().rect.height / 2f, 0));
        points.Add(new Vector3(canvas.GetComponent().rect.width / 2f, canvas.GetComponent().rect.height / 2f, 0));
        points.Add(new Vector3(-canvas.GetComponent().rect.width / 2f, canvas.GetComponent().rect.height / 2f, 0));
    }

    void Update()
    {
        Vector3 pos = Vector3.zero;
        Vector3 pos1 = Camera.main.WorldToScreenPoint(target.transform.position - targetOffset * (target.transform.position - player.transform.position).normalized); //目标屏幕坐标
        Vector3 pos2 = Camera.main.WorldToScreenPoint(player.transform.position); //玩家屏幕坐标
        Vector2 worldPoint1;
        Vector2 worldPoint2;

        //求出目标与玩家的UI坐标
        RectTransformUtility.ScreenPointToLocalPointInRectangle(canvas.transform as RectTransform, pos1, cam, out worldPoint1);
        RectTransformUtility.ScreenPointToLocalPointInRectangle(canvas.transform as RectTransform, pos2, cam, out worldPoint2);

        isHaveIntersection = false; //默认没交点

        //玩家与目标UI坐标连线与Canvas边界连写交点即为箭头位置
        for (int i = 0; i < points.Count; i++)
        {
            //这里的if是为了让最后一条边界为 ps[Count - 1]与ps[0]连线
            if (i < points.Count - 1)
            {
                if (SegmentsInterPoint(worldPoint1, worldPoint2, points[i + 1], points[i], ref pos))
                {
                    find.rectTransform.anchoredPosition = pos;
                    isHaveIntersection = true;
                    break;
                }
            }
            else
            {
                if (SegmentsInterPoint(worldPoint1, worldPoint2, points[i], points[0], ref pos))
                {
                    find.rectTransform.anchoredPosition = pos;
                    isHaveIntersection = true;
                    break;
                }
            }
        }

        //判断有没有交点来显隐箭头
        if (isHaveIntersection)
        {
            find.gameObject.SetActive(true);

            //箭头朝向目标
            UILookAt(find.transform, worldPoint1 - worldPoint2, Vector3.up);
        }
        else
        {
            find.gameObject.SetActive(false);
        }
    }

    //参数分别为:1.UI的Transform 2.朝向向量 3.起始向量
    public void UILookAt(Transform transform, Vector3 dir, Vector3 lookAxis)
    {
        Quaternion q = Quaternion.identity;
        q.SetFromToRotation(lookAxis, dir);
        transform.rotation = q;
    }
    
    public static float Cross(Vector3 a, Vector3 b)
    {
        return a.x * b.y - b.x * a.y;
    }

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

        //v1×v2=x1y2-y1x2 
        //以线段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;
    }
}

PS:如果觉得目标移动的快箭头抖动可以加个过度效果,下面是我用DOTWEEN实现的,可以参考下。

            for (int i = 0; i < points.Count; i++)
            {
                if (i < points.Count - 1)
                {
                    if (SegmentsInterPoint(worldPoint1, worldPoint2, points[i + 1], points[i], ref pos))
                    {
                    	//判断距离是为了让箭头移动距离过大的时候瞬移过去
                        if (Vector3.Distance(arrow.transform.localPosition, new Vector3(pos.x, pos.y, 0)) < 100)
                        {
                        	//过渡效果
                            arrow.transform.DOLocalMove(new Vector3(pos.x, pos.y, 0),0.1f).SetEase(Ease.Linear);
                        }
                        else
                        {
                            arrow.transform.localPosition = new Vector3(pos.x, pos.y, 0);
                        }
                        isHaveIntersection = true;
                        break;
                    }
                }
                else
                {
                    if (SegmentsInterPoint(worldPoint1, worldPoint2, points[i], points[0], ref pos))
                    {
                        if (Vector3.Distance(arrow.transform.localPosition, new Vector3(pos.x, pos.y, 0)) < 100)
                        {
                            arrow.transform.DOLocalMove(new Vector3(pos.x, pos.y, 0), 0.1f).SetEase(Ease.Linear);
                        }
                        else
                        {
                            arrow.transform.localPosition = new Vector3(pos.x, pos.y, 0);
                        }
                        isHaveIntersection = true;
                        break;
                    }
                }
            }
转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/900583.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号