效果
思路:
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;
}