随手记录一下,以防下次忘记
using UnityEngine;
namespace Test {
public class ArrowTest: MonoBehaviour {
public Transform ArrowObject;
public Transform TargetObject;
private void FixedUpdate() {
DrawArrow( ArrowObject, TargetObject.position );
}
/// <summary>
/// 在Canvas设置指向屏幕外部物体的箭头,返回此箭头对象。
/// </summary>
/// <param name="arrowObject">箭头对象</param>
/// <param name="objectPosition">目标物体的位置</param>
public Transform DrawArrow( Transform arrowObject, Vector3 objectPosition ) {
Vector2 objectScreenPoint = Camera.main.WorldToScreenPoint( objectPosition );
if ( !CheckInView( objectScreenPoint.x, objectScreenPoint.y, Screen.width, Screen.height ) ) {
Vector2 arrowPosition = CalculateIntersectionBetter( objectScreenPoint.x, objectScreenPoint.y, Screen.width, Screen.height );
Vector3 direction = ( arrowPosition - new Vector2( Screen.width / 2, Screen.height / 2 ) ).normalized;
arrowObject.gameObject.SetActive( true );
arrowObject.position = arrowPosition;
arrowObject.rotation = Quaternion.AngleAxis( Vector3.SignedAngle( Vector3.up, direction, Vector3.forward ), Vector3.forward );
} else {
arrowObject.gameObject.SetActive( false );
}
return arrowObject;
}
/// <summary>
/// 确认目标是否在视野内
/// </summary>
/// <param name="x">物体X坐标</param>
/// <param name="y">物体Y坐标</param>
/// <param name="width">屏幕宽度</param>
/// <param name="height">屏幕高度</param>
private bool CheckInView( float x, float y, float width, float height ) {
return x > 0 && x < width && y > 0 && y < height;
}
/// <summary>
/// 得到【屏幕外物体位置到屏幕中心的连线】与屏幕边界的交点,无法过渡四角。
/// </summary>
/// <param name="x">物体X坐标</param>
/// <param name="y">物体Y坐标</param>
/// <param name="width">屏幕宽度</param>
/// <param name="height">屏幕高度</param>
/// <returns></returns>
private Vector2 CalculateIntersection( float x, float y, float width, float height ) {
Vector2 position = new Vector2();
if ( CheckInView( x, y, width, height ) ) {
position.x = x;
position.y = y;
}
if ( 0 <= y && y <= height ) {
if ( x < 0 ) {
position.x = 0;
position.y = height / 2 + ( y - ( height / 2 ) ) * ( width / 2 ) / ( width / 2 - x );
} else if ( x > width ) {
position.x = width;
position.y = height / 2 + ( y - ( height / 2 ) ) * ( width / 2 ) / ( x - width / 2 );
}
} else if ( 0 <= x && x <= width ) {
if ( y < 0 ) {
position.y = 0;
position.x = width / 2 + ( x - ( width / 2 ) ) * ( height / 2 ) / ( height / 2 - y );
} else if ( y > height ) {
position.y = height;
position.x = width / 2 + ( x - ( width / 2 ) ) * ( height / 2 ) / ( y - height / 2 );
}
} else//四角当如何?
{
position.x = x < 0 ? 0 : width;
position.y = y < 0 ? 0 : height;
}
return position;
}
/// <summary>
/// 得到【屏幕外物体位置到屏幕中心的连线】与屏幕边界的交点,无死角。
/// </summary>
/// <param name="x">物体X坐标</param>
/// <param name="y">物体Y坐标</param>
/// <param name="width">屏幕宽度</param>
/// <param name="height">屏幕高度</param>
/// <returns></returns>
private Vector2 CalculateIntersectionBetter( float x, float y, float width, float height ) {
Vector2 position = new Vector2();
if ( CheckInView( x, y, width, height ) ) {
position.x = x;
position.y = y;
return position;
}
float aspectRatio = height / width;
float relativeY = y - height / 2;
float relativeX = x - width / 2;
float k = relativeY / GetSafeFloatDivisor( relativeX );
if ( y > height / 2 ) {
if ( x < width / 2 ) {
if ( -aspectRatio < k ) //1
{
position.x = 0;
position.y = height / 2 + ( y - ( height / 2 ) ) * ( width / 2 ) / ( width / 2 - x );
} else //2
{
position.x = width / 2 + ( x - ( width / 2 ) ) * ( height / 2 ) / ( y - height / 2 );
position.y = height;
}
} else {
if ( aspectRatio < k ) //3
{
position.x = width / 2 + ( x - ( width / 2 ) ) * ( height / 2 ) / ( y - height / 2 );
position.y = height;
} else //4
{
position.x = width;
position.y = height / 2 + ( y - ( height / 2 ) ) * ( width / 2 ) / ( x - width / 2 );
}
}
} else {
if ( x > width / 2 ) {
if ( -aspectRatio < k ) //5
{
position.x = width;
position.y = height / 2 + ( y - ( height / 2 ) ) * ( width / 2 ) / ( x - width / 2 );
} else //6
{
position.y = 0;
position.x = width / 2 + ( x - ( width / 2 ) ) * ( height / 2 ) / ( height / 2 - y );
}
} else {
if ( aspectRatio < k ) //7
{
position.y = 0;
position.x = width / 2 + ( x - ( width / 2 ) ) * ( height / 2 ) / ( height / 2 - y );
} else //8
{
position.x = 0;
position.y = height / 2 + ( y - ( height / 2 ) ) * ( width / 2 ) / ( width / 2 - x );
}
}
}
return position;
}
private float GetSafeFloatDivisor( float value ) {
return value = value == 0 ? 0.01f : value;
}
/// <summary>
/// 利用物体位置所在角度和屏幕周长比例,
/// 得到【屏幕外物体位置到屏幕中心的连线】
/// 与屏幕边界的交点
/// </summary>
/// <param name="x">物体X坐标</param>
/// <param name="y">物体Y坐标</param>
/// <param name="width">屏幕宽度</param>
/// <param name="height">屏幕高度</param>
/// 有一些误差
private static Vector2 CalculateIntersectionByPerimeter( float x, float y, float width, float height ) {
Vector2 position = new Vector2();
Vector3 direction = new Vector2( x - width / 2, y - height / 2 ).normalized;
float C = 2 * ( width + height );
//AXIS做大拇指,四指从from转到to,其实就是叉乘。 这是左手系。 正的Z是向里的。
float angle = Vector3.SignedAngle( Vector3.right, direction, Vector3.forward );
float ratio = angle / 360f;
float L = ratio * C;
if ( width + height / 2 <= L && L < C / 2 ) //1
{
position.x = 0;
position.y = C / 2 - L + height / 2;
} else if ( height / 2 <= L && L < width + height / 2 ) //2
{
position.x = width + height / 2 - L;
position.y = height;
} else if ( -height / 2 <= L && L < height / 2 ) //3
{
position.x = width;
position.y = L + height / 2;
} else if ( -height / 2 - width <= L && L < -height / 2 )//4
{
position.x = L + width + height / 2;
position.y = 0;
} else if ( -C / 2 <= L && L < -height / 2 - width ) //5
{
position.x = 0;
position.y = -C / 2 - L + height / 2;
}
return position;
}
}
}
代码中的后三个方法都可以实现,效果有差异
需要的其他设置:
箭头使用的是UGUI中的Image,箭头默认方向朝上,将箭头节点的Pivot设置为(0.5,1),将上面的脚本挂载到场景中,拖拽上箭头节点和需要指向的目标节点,运行后拖拽目标即可看到箭头指示的变化。
如果对你有帮助,麻烦点赞收藏,一起进步