您当前的位置: 首页 >  unity

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

发布时间:2021-11-29 18:09:16 ,浏览量:6

最终效果

请添加图片描述

实现

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

首先我们需要确定实现思路。我想到的方案是将玩家和敌人的世界坐标转换为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.敌人进入视野内箭头消失 最开始打算使用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<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; } } } 
关注
打赏
1688896170
查看更多评论

暂无认证

  • 6浏览

    0关注

    115984博文

    0收益

  • 0浏览

    0点赞

    0打赏

    0留言

私信
关注
热门博文
立即登录/注册

微信扫码登录

0.0876s