Unity 箭头指示屏幕外物体

随手记录一下,以防下次忘记

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),将上面的脚本挂载到场景中,拖拽上箭头节点和需要指向的目标节点,运行后拖拽目标即可看到箭头指示的变化。

如果对你有帮助,麻烦点赞收藏,一起进步

Unity中创建一个箭头指引物体到达目的地的功能,通常涉及到以下几个步骤: 1. **制作箭头模型**: - 可以直接在3D建模软件里设计并导入到Unity项目中。 - 或者利用Unity自带的Mesh工具简单构建。 2. **编写导航脚本**: - 使用Nav Mesh(导航网格)组件让目标物体能够自动寻路至指定地点。这需要先烘焙场景中的可行走区域,并为目标物添加`NavMeshAgent`组件; - 编写自定义脚本来控制箭头的方向更新频率以及它所指向的位置。例如,在每次帧刷新时计算从当前位置到下一个节点之间的向量角度,然后旋转箭头使其朝向该方向; 3. **设置触发条件及交互逻辑**: - 定义何时显示或隐藏引导箭头(比如当玩家接近特定位置时),并且考虑如何处理特殊情况如障碍物阻挡路径等; 4. **优化用户体验**: - 加入平滑过渡效果使得转向看起来更自然流畅; - 根据实际需求调整透明度、颜色变化等方式增强视觉反馈; 这里给出一段简单的示例代码用于说明上述第二点的一部分内容——动态改变箭头指示方向: ```csharp using UnityEngine; using UnityEngine.AI; public class ArrowGuide : MonoBehaviour { public Transform targetPoint; // 目标点 private NavMeshAgent agent; void Start(){ if (!targetPoint){ Debug.LogError("未分配目标点!"); return; } this.agent = GetComponent<NavMeshAgent>(); SetDestination(targetPoint.position); } void Update(){ RotateArrowToTarget(); } /// <summary> /// 设置代理的目的地为新的坐标. /// </summary> /// <param name="newPos">新坐标的Vector3值</param> private void SetDestination(Vector3 newPos){ if (this.agent != null && !float.IsNaN(newPos.x)){ this.agent.SetDestination(newPos); } else{ Debug.LogWarning($"无法将{transform.name}移动到({newPos})"); } } /// <summary> /// 让当前物体(假设是一个箭头)始终朝着给定的目标位置. /// </summary> private void RotateArrowToTarget(){ Vector3 dir = (targetPoint.position - transform.position).normalized; Quaternion lookRotation = Quaternion.LookRotation(dir); transform.rotation = Quaternion.Slerp(transform.rotation, lookRotation, Time.deltaTime * 5f); } } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值