实现类似原神指引目标位置效果

实现类似原神指引目标位置效果

之前在网上查了一下关于目标指引类的文章。有一篇 Unity在屏幕边界追踪目标提示 文章,已经将核心代码提供了。主要是通过目标物体世界坐标转换成屏幕坐标,将此坐标和摄像机的世界坐标转成的屏幕坐标进行连线,然后判断是否和屏幕有交点来给指引的图片进行赋值。如果是对于一个镜头不需要360°旋转的游戏来说已经完全够用了。但是如果类似原神一样的3D游戏的话,还是会有两个问题:
第一个就是如果目标物体无限平行于镜头的时候,会出现转成的屏幕坐标会从一个很大的负数跳到一个很大的整数,这样出现的效果就会变成从图标从左下角直接跳到了右上角。
第二个问题就是如果目标物体在镜头后方,指引图片也会出现在屏幕中间位置。而我们想要的是指引图片还是会在屏幕边缘。那么根据这两个问题,我们就需要改变一下计算的方法。
所以只要计算目标物体处于摄像机的方位,转换成屏幕方向,通过该方向的延长线和屏幕相交,计算交点就可以避免问题一,第二个问题的话就判断一下目标物体转换成屏幕坐标后的Z值的正负就可以了。
下面上代码。

using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

public class ScreenEdgeTips : MonoBehaviour
{
    
    
    /// <summary> 图片父物体,最好是全屏的Panel </summary>
    private RectTransform directContainer;
    /// <summary> 玩家自己或者摄像机标志位 </summary>
    private GameObject _cameraTarget;
    /// <summary> 目标物体</summary>
    public GameObject TargetObj;

    /// <summary> 主摄像机 </summary>
    public Camera mainCamera;
    /// <summary> UI摄像机 </summary>
    public Camera uicamera;
    private List<Line2D> screenLines;
    /// <summary> 在画面内的UI </summary>
    public Sprite InImage;
    /// <summary> 画面外的UI </summary>
    public Sprite OutImage;
    public InOrOut ImageType = InOrOut.None;
    RectTransform rect;
    private Image image;
    Vector2 finalPos;
    private Vector2 lookPos;
    public void Init()
    {
    
    
        _cameraTarget = new GameObject("Target");
        _cameraTarget.transform.parent = mainCamera.transform;
        _cameraTarget.transform.localPosition = new Vector3(0, 0, 0.5f);
        directContainer = transform.parent.GetComponent<RectTransform>();
        rect = GetComponent<RectTransform>();
        image = GetComponent<Image>();
        finalPos = new Vector2();
    }

    private void Update()
    {
    
    
        UpdateImp();
        UpdateLookAt();
    }

    private void Start()
    {
    
    
        Init();
        InitWidth(0);
    }
    private void InitWidth(float width)
    {
    
    
        Vector3 point1 = new Vector3(width, width);
        Vector3 point2 = new Vector3(width, Screen.height- width);
        Vector3 point3 = new Vector3(Screen.width- width, Screen.height- width); //     P2------------P3           
        Vector3 point4 = new Vector3(Screen.width- width, width);                //       |           |
        this.screenLines = new List<Line2D>();                                   //       |           |
        this.screenLines.Add(new Line2D(point1, point2));                        //     P1------------ P4
        this.screenLines.Add(new Line2D(point2, point3));
        this.screenLines.Add(new Line2D(point3, point4));
        this.screenLines.Add(new Line2D(point4, point1));
    }

    /// <summary>
    /// 点是否在屏幕内
    /// </summary>
    /// <param name="pos"></param>
    /// <returns></returns>
    private bool PointIsInScreen(Vector3 pos)
    {
    
    
        if (pos.x <= this.screenLines[0].point1.x
            || pos.x >= this.screenLines[1].point2.x
            || pos.y <= this.screenLines[0].point1.y
            || pos.y >= this.screenLines[1].point2.y)
        {
    
    
            return false;
        }
        return true;
    }

    //世界坐标转换为屏幕坐标
    private Vector3 WorldToScreenPoint(Vector3 pos)
    {
    
    
        if (null != this.mainCamera)
        {
    
    
            return mainCamera.WorldToScreenPoint(pos);
        }
        return Vector3.zero;
    }

    public void UpdateImp()
    {
    
    
        if (_cameraTarget != null)
        {
    
    
            Vector3 fromPos = this.WorldToScreenPoint(_cameraTarget.transform.position);
            Vector3 toPos = WorldToScreenPoint(TargetObj.transform.position);

            if (PointIsInScreen(toPos))//如果目标在屏幕内
            {
    
    
                if (toPos.z<0)
                {
    
    
                    ChangeImage(InOrOut.Out);
                    CacleIntersce(fromPos);
                }
                else
                {
    
    
                    ChangeImage(InOrOut.In);
                    RectTransformUtility.ScreenPointToLocalPointInRectangle(directContainer, toPos, uicamera, out finalPos);
                    rect.anchoredPosition = finalPos;//ui的绝对布局
                }
                
            }
            else
            {
    
    
                ChangeImage(InOrOut.Out);
                CacleIntersce(fromPos);
            }

        }

    }

    private void CacleIntersce(Vector3 fromPos)
    {
    
    
        Vector2 intersecPos = Vector2.zero;
        Vector3 localpos = _cameraTarget.transform.InverseTransformPoint(TargetObj.transform.position);
        Vector3 screenpos = new Vector3(localpos.x, localpos.y, 0);//根据场景坐标不同可以调整
        lookPos = fromPos + screenpos.normalized * 10000;
        Line2D line2 = new Line2D(fromPos, lookPos);
        foreach (Line2D l in this.screenLines)
        {
    
    
            if (line2.Intersection(l, out intersecPos) == Line2D.CROSS)
            {
    
    
                break;
            }
        }
        RectTransformUtility.ScreenPointToLocalPointInRectangle(directContainer, intersecPos, uicamera, out finalPos);
        rect.anchoredPosition = finalPos;//ui的绝对布局
    }

    /// <summary>
    /// 改变图片
    /// </summary>
    private void ChangeImage(InOrOut type)
    {
    
    
        if (type == ImageType)
        {
    
    
            return;
        }
        switch (type)
        {
    
    
            case InOrOut.In:
                image.sprite = InImage;
                break;
            case InOrOut.Out:
                image.sprite = OutImage;
                break;
            case InOrOut.None:
                break;
            default:
                break;
        }
        ImageType = type;
    }
    private void UpdateLookAt()
    {
    
    
        if (TargetObj != null)
        {
    
    
            if (ImageType == InOrOut.Out)
            {
    
    
                rect.right = lookPos.normalized;//根据图片轴向改变
            }
        }
    }
}
public enum InOrOut
{
    
    
    In, Out, None
}

Line2D脚本从上面连接处博客自行copy即可。
有些数值需要根据项目不同进行调整。有些东西可能会有些许偏差,自行调整即可。

猜你喜欢

转载自blog.csdn.net/weixin_37836657/article/details/122582657