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

2023-11-05

最终效果

请添加图片描述

实现

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

首先我们需要确定实现思路。我想到的方案是将玩家和敌人的世界坐标转换为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<Vector3> points = new List<Vector3>(); //canvas边界点

    void Start()
    {
        //存储canvas边界点
        points.Add(new Vector3(-canvas.GetComponent<RectTransform>().rect.width / 2f, -canvas.GetComponent<RectTransform>().rect.height / 2f, 0));
        points.Add(new Vector3(canvas.GetComponent<RectTransform>().rect.width / 2f, -canvas.GetComponent<RectTransform>().rect.height / 2f, 0));
        points.Add(new Vector3(canvas.GetComponent<RectTransform>().rect.width / 2f, canvas.GetComponent<RectTransform>().rect.height / 2f, 0));
        points.Add(new Vector3(-canvas.GetComponent<RectTransform>().rect.width / 2f, canvas.GetComponent<RectTransform>().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.敌人进入视野内箭头消失
最开始打算使用OnBecameVisibleOnBecameInVisible来判断目标是否在视野内,但是这种方法不够灵活。于是考虑用另一种方法,就是通过判断玩家和目标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<Vector3> points = new List<Vector3>(); //canvas边界点
    bool isHaveIntersection = false; //判断有没有交点来显隐箭头
    public float targetOffset; //设置目标点偏移,用来控制箭头提前或者延后显隐


    void Start()
    {
        //存储canvas边界点
        points.Add(new Vector3(-canvas.GetComponent<RectTransform>().rect.width / 2f, -canvas.GetComponent<RectTransform>().rect.height / 2f, 0));
        points.Add(new Vector3(canvas.GetComponent<RectTransform>().rect.width / 2f, -canvas.GetComponent<RectTransform>().rect.height / 2f, 0));
        points.Add(new Vector3(canvas.GetComponent<RectTransform>().rect.width / 2f, canvas.GetComponent<RectTransform>().rect.height / 2f, 0));
        points.Add(new Vector3(-canvas.GetComponent<RectTransform>().rect.width / 2f, canvas.GetComponent<RectTransform>().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;
                    }
                }
            }
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Unity实现UI在屏幕边缘跟随并指向视野外敌人 的相关文章

随机推荐

  • 开发微信公众号支付代码

    一 url传入当前页面url地址或者微信公众平台配置的域名根目录 使用window location href方法获取 二 下面代码请结合微信公众号开发文档 微信公众号开发文档 function test url uni request u
  • 如何在Linux下安装vim编辑器

    目前的Ubuntu版本都安装了vi编辑器 vim编辑器可以看做vi编辑器的升级版 可以识别特殊字符 显示不同颜色 目录 第一步 第二步 第三步 第四步 第一步 在terminal里面输入vi命令后按下tab键可以看到当前vi可以执行的命令
  • 算法的时间及空间复杂度

    简介 java系列技术分享 持续更新中 初衷 一起学习 一起进步 坚持不懈 如果文章内容有误与您的想法不一致 欢迎大家在评论区指正 希望这篇文章对你有所帮助 欢迎点赞 收藏 留言 更多文章请点击 文章目录 一 什么是算法 二 算法初体验 案
  • C++ String替换&分割指定字符串

    C String替换 分割指定字符串 1 C String替换指定字符串 C 的string对象提供了replace方法来实现字符串的替换 本文实现对于将字符串中某个字符串全部替换的功能 string replace all string
  • MOOC清华《程序设计基础》第5章:求n的阶乘(用递推法做)

    使用递推思想 求解正整数的阶乘 本算法的数学模型为 n n 1 n include
  • CCF CSP 202206-3角色授权【70分】

    include
  • ts文件服务器端加密,加密ts文件解密

    EXTM3U EXT X VERSION 3 EXT X MEDIA SEQUENCE 0 EXT X ALLOW CACHE YES EXT X TARGETDURATION 13 EXT X KEY METHOD AES 128 URI
  • Linux动态库(.so)搜索路径

    Linux动态库 so 搜索路径 众所周知 Linux动态库的默认搜索路径是 lib和 usr lib 动态库被创建后 一般都复制到这两个目录中 当程序执行时需要某动态库 并且该 动 态库还未加载到内存中 则系统会自动到这两个默认搜索路径中
  • 网站无法访问的一些问题与解决

    最近阿里云的服务器上跑的博客出现了一些问题 上阿里云官网看了一下 原来是忘记续费了 也没给我发邮件 悄摸的给我停了 续费之后 问题依旧 可以使用xshell5进行远程连接 但是在浏览器上不能进行访问 会显示这个 很尴尬 在本地ping主机是
  • cuda异步并行执行

    异步函数使得主机端与设备端并行执行 控制在设备还没有完成前就被返回给主机线程 包括 kernel启动 以Async为后缀的内存拷贝函数 device到device内存拷贝函数 存储器初始化函数 比如cudaMemset cudaMemset
  • simulink中积分环节、惯性环节、比例环节

    第一步 第二步 第三步 注 适当修改参数即可变成所需环节 如下所示 修改为 或者为 抑或
  • hadoop搭建好,启动服务后,无法从web界面访问50070

    在hadoop完全分布式搭建好以后 从主节点启动正常 使用jps查看启动的进程 正常 在几个从节点上使用jps查看 显示正常 但从web上输入下面网址 http 主节点IP 50070 无法正常连接显示 试了若干网上查到的方法 是通过下面方
  • 怎样选择合适的循环体(do&while、while和for)

    我们都知道 循环体可以有五种 while do while for goto和递归 虽然理论上任何循环都可以用其他四种转换 但是因为goto在安全性以及在功能上能够被取代的特点 所以一般不会用到 而递归的特殊性和编写的困难性使递归的登场次数
  • 今天带你体验79毫秒启动一个SpringBoot项目

    大家好 我是雷小帅 今天来个项目实战 先抛一个问题 大家在自己电脑上启动一个 spring boot 项目需要花费多久 根据项目大小和机器环境 花费几秒到几十秒的人应该都有 最近 spring 官方推出了一项技术可以将项目的启动时间缩短到
  • 荒野行动服务器维护啥时好,荒野行动服务器真的极差

    说起 荒野行动 这个游戏 我想大家都不陌生 毕竟这个是在端游吃鸡出现不到三个月的时间 就由网易出品的一款吃鸡手游 可以说这个是第一款吃鸡手游 我玩了快半年的 荒野行动 了 先不说这个游戏咋样 咱们先说一说官网的态度 当真是不想让我们在继续玩
  • Vue2.0与Vue3.0的区别

    Vue2 0 Vue3 0 双向绑定 利用ES5的ApiObject defineProperty 对数据进行劫持 并结合发布订阅模式的方式实现 利用Es6的Proxy 对数据进行代理的方式实现 根节点 根节点只能是一个 根节点可以是多个
  • nginx之反向代理服务器

    本文摘抄自 深入理解Nginx 模块开发与架构解析 反向代理 reverse proxy 方式是指用代理服务器来接受Internet上的连接请求 然后将请求转发给内部网络中的上游服务器 并将从上游服务器上得到的结果返回给Internet上请
  • QGuiApplication底层鼠标处理(一)使用QSocketNotifier建立侦听连接

    QGuiApplication底层鼠标处理 一 使用QSocketNotifier建立侦听连接 读取外设信息 建立外设连接 init plugins QEvdevMousePlugin QEvdevMouseManager QEvdevMo
  • vmware 最近 win10 更新补丁后,需要更新后才能使用

    preface 最近准备换工作 在搭建自己的项目 一直未更新博文 等到 稳定后就把 项目流程 相关 发布出来 项目都是 搭建在 vmware 的 centos 中 尴尬的是 昨天 vmware 不能打开了 不能打开并且提示需要更新 vmwa
  • Unity实现UI在屏幕边缘跟随并指向视野外敌人

    最终效果 实现 心急的小伙伴可以直接跳到文章末尾查看最终代码 如果有问题再来看下思路 首先我们需要确定实现思路 我想到的方案是将玩家和敌人的世界坐标转换为UI坐标 然后求玩家和敌人坐标的线段与Canvas边界的交点即为箭头坐标 下面是求出交