屏幕外目标点标记的实现方法

Unity开发:两种屏幕外目标点标记的实现方法 | indienova 独立游戏 转载

以上转载的文章,适用于第一人称视角游戏,但对于mmo类型游戏,相机围绕角色转动,使用该文章代码,如果相机偏顶视角向下看角色和目标点,表现正常,但如果相机偏平视角看角色和目标点,会出现目标点从世界空间转换后的屏幕空间有偏差

为解决该问题,提供以下代码

using System;
using UnityEngine;
using XLua;

public class ShowTargetDistance:MonoBehaviour
{
  
    private float refreshTime = 0.01f;
    private float refreshTimer;
    
    private float offsetLeft = 200;
    private float offsetRight = 200;
    private float offsetUp = 120;
    private float offsetDown = 120;
    
    private bool isInSideView = false;
    
    private bool isShowTarget;
    
    private Unit targetUnit;

    private Vector3 mainPlayerPos;
    private Vector3 targetPos;
    
    private Transform targetTransform;
    private GameObject targetSelectNode;
    
    private GameObject showTargetMoveNode;
    private GameObject showTargetRotateNode;
    private LText showTargetDisText;
    
    private Transform camTransform;
    
    private Vector3 playerScreenPos;
    private Vector3 targetScreenPos;
    private Vector3 targetVector;

    private float targetShowRotate;
    private float targetScreenEuler;

    private float distanceToTarget;
    private float lastDisShow;
    
    private int screenWidth;
    private int screenHeight;
    
    private void Start()
    {
        InitParams();
    }

    private void OnDestroy()
    {
        Clear();
    }
    
    private void Update( )
    {
        if (isShowTarget)
        {
            if (GameUtil.GetPlayerMgr().GetMainPlayer()!=null)
            {
                mainPlayerPos = GameUtil.GetPlayerMgr().GetMainPlayer().GetActorMeterPos();
                targetPos = targetUnit.GetActorMeterPos();
                
                refreshTimer += Time.deltaTime;
                if (refreshTimer>refreshTime)
                {
                    RefreshTargetScreenPos();
                    RefreshTargetScreenEuler();
                    RefreshTargetDistance();
                    
                    RefreshUI();
                    refreshTimer = 0;
                }
            }
        }
    }
    
    [WhiteList]
    public void SetShowTargetUI(GameObject moveNode,GameObject rotateNode,LText showDisText)
    {
        showTargetMoveNode = moveNode;
        showTargetRotateNode = rotateNode;
        showTargetDisText = showDisText;
    }
    
    [WhiteList]
    public void SetShowTargetState(bool _isShow,Unit _target=null)
    {
        refreshTimer = refreshTime;
        if (_isShow)
        {
            targetUnit = _target;
            targetTransform = targetUnit.actor.transform;
            
            // targetSelectNode = targetUnit.actor.GetActorNode("fx_body");
            // if (targetSelectNode == null)
            // {
            //     targetSelectNode = targetTransform.gameObject;
            // }
        }
        else
        {
            Clear();
        }
        isShowTarget = _isShow;
    }
    
    [WhiteList]
    public void RefreshOffset(float _offsetLeft, float _offsetRight, float _offsetUp, float _offsetDown,float _refreshTime)
    {
        offsetLeft = _offsetLeft;
        offsetRight = _offsetRight;
        offsetUp = _offsetUp;
        offsetDown = _offsetDown;
        refreshTime = _refreshTime;
    }

    //可以打开界面的时候再初始化,因为这个时候拿到的屏幕尺寸没问题
    private void InitParams()
    {
        screenWidth = Screen.width;
        screenHeight = Screen.height;
    }

    private void Clear()
    {
        isShowTarget = false;
        targetUnit = null;
    }

    //计算起始点目标点的连线和边界框的交叉点
    private Vector3 GetBoundPos(Vector3 targetScreenPos)
    {
        float x = targetScreenPos.x;
        float y = targetScreenPos.y;
        
        Vector2 center = playerScreenPos;
        
        float k = (targetScreenPos.y - center.y) / (targetScreenPos.x - center.x);
        float b = targetScreenPos.y - k * targetScreenPos.x;

        if (targetScreenPos.x=offsetDown&&y<=screenHeight-offsetUp)
            {
                x = offsetLeft;
            }
            else
            {
                if (yscreenHeight-offsetUp)
                {
                    y = screenHeight-offsetUp;
                }
                x = (y - b) / k;
            }
        }else if (targetScreenPos.x>offsetLeft&&targetScreenPos.xscreenHeight-offsetUp)
            {
                y = screenHeight - offsetUp;
            }else if (targetScreenPos.y=offsetDown&&y<=screenHeight-offsetUp)
            {
                x = screenWidth-offsetRight;
            }
            else
            {
                if (yscreenHeight-offsetUp)
                {
                    y = screenHeight-offsetUp;
                }
                x = (y - b) / k;
            }
        }
        
        targetScreenPos.x = x;
        targetScreenPos.y = y;

        return targetScreenPos;
    }

    private Vector3 KClamp(Vector3 targetScreenPos,Vector3 playerScreenPos)
    {
        
        if (targetScreenPos.y>=offsetDown&&targetScreenPos.y<=screenHeight-offsetUp&&targetScreenPos.x>=offsetLeft&&targetScreenPos.x<=screenWidth-offsetRight)
        {
            //在限制框范围内,不显示
            isInSideView = true;
        }
        else
        {
            //超出限制范围,则在边框处显示
            targetScreenPos = GetBoundPos(targetScreenPos);
            isInSideView = false;
        }
        
        return targetScreenPos;
    }
    
    private void RefreshTargetScreenEuler()
    {
        targetVector = (targetScreenPos-playerScreenPos).normalized;
        // 计算向量和Y轴正方向的夹角
        float angle = Math.Abs(Vector3.Angle(targetVector, Vector3.up));
        
        if (targetVector.x>=0)
        {
            if (targetVector.y>=0)
            {
                //1象限
                angle = 90 - angle;
            }
            else
            {
                //4
                angle = 360 - (angle - 90);
            }
        }
        else
        {
            if (targetVector.y>=0)
            {
                //2
                angle = angle+90;
            }
            else
            {
                //3
                angle = angle+90;
            }
        }
        //图片0度箭头向上
        targetShowRotate = angle-90;
    }

    //这种计算方式是以相机为起始点,适用于第一人称视角的游戏,但现在的情况是第三人称,以角色为起始点,用该方法会出现当相机从偏向顶视角向下看的时候,目标点位置有偏移
    private Vector3 WorldPosToScreenPos(Vector3 worldPos)
    {
        Vector3 screenPos;
        camTransform = Camera.main.transform;
    
        var vFov = Camera.main.fieldOfView;
        var radHFov = 2 * Mathf.Atan(Mathf.Tan(vFov * Mathf.Deg2Rad / 2) * Camera.main.aspect);
        var hFov = Mathf.Rad2Deg * radHFov;
    
        Vector3 deltaUnitVec = (worldPos - camTransform.position).normalized;
        
        float vdegobj = Vector3.Angle(Vector3.up, deltaUnitVec) - 90f;
        float vdegcam = Vector3.SignedAngle(Vector3.up, camTransform.forward, camTransform.right) - 90f;
    
        float vdeg = vdegobj - vdegcam;
    
        float hdeg = Vector3.SignedAngle(Vector3.ProjectOnPlane(camTransform.forward, Vector3.up), Vector3.ProjectOnPlane(deltaUnitVec, Vector3.up), Vector3.up);
    
        vdeg = Mathf.Clamp(vdeg, -89f, 89f);
        hdeg = Mathf.Clamp(hdeg, hFov * -0.5f, hFov * 0.5f);
    
        Vector3 projectedPos = Quaternion.AngleAxis(vdeg, camTransform.right) * Quaternion.AngleAxis(hdeg, camTransform.up) * camTransform.forward;
    
        Vector3 newPos = Camera.main.WorldToScreenPoint(camTransform.position + projectedPos);
        
        //newPos.z = 0;
        screenPos = newPos;
        return screenPos;
    }

    // 用点积判断目标点是否处于相机背后,用 Camera.main.WorldToScreenPoint 会出现当目标点接近相机背后时,出现转换的屏幕空间位置出现在了屏幕顶部
    private Vector3 WorldPosToScreenPosNew(Vector3 worldPos)
    {
        Vector3 newPos;
        camTransform = Camera.main.transform;
        Vector3 delta = worldPos - camTransform.position;
        float dot = Vector3.Dot(camTransform.forward, delta);
    
        if (dot < 0)
        {
            Vector3 projectedPos = camTransform.position + (delta - camTransform.forward * dot * 1.01f);
            newPos = Camera.main.WorldToScreenPoint(projectedPos);
        }
        else
        {
            newPos = Camera.main.WorldToScreenPoint(worldPos);
        }

        return newPos;
    }

    private void RefreshTargetScreenPos()
    {
        
        playerScreenPos = Camera.main.WorldToScreenPoint(mainPlayerPos);
        
        //targetScreenPos = WorldPosToScreenPosNew(targetSelectNode.transform.position);
        
        //策划希望指向脚下
        targetScreenPos = WorldPosToScreenPosNew(targetPos);
        
        targetScreenPos = KClamp(targetScreenPos,playerScreenPos);
    }

    private void RefreshTargetDistance()
    {
        distanceToTarget = Vector3.Distance(mainPlayerPos, targetPos);
       
        if (showTargetMoveNode.activeInHierarchy!=!isInSideView)
        {
            showTargetMoveNode.SetActive(!isInSideView);
        }
    }

    private void RefreshUI()
    {
        if (showTargetMoveNode)
        {
            showTargetMoveNode.transform.position = Vector3.Lerp(showTargetMoveNode.transform.position, targetScreenPos, 20 * Time.deltaTime);
        }
        
        if (showTargetRotateNode)
        {
            showTargetRotateNode.transform.rotation = Quaternion.Euler(0, 0, targetShowRotate);
        }
        
        if (showTargetDisText)
        {
            float roundedNum = (float)Math.Round(distanceToTarget, 1);
         
            if (Mathf.Abs(lastDisShow-roundedNum)>0.2f)
            {
                showTargetDisText.text = roundedNum.ToString()+"m";
            }
            lastDisShow = roundedNum;
        }
    }

}

设置了四个方向的缩进距离形成的视野限制框,目标点进入限制框后,目标在边框上显示。

你可能感兴趣的:(unity,游戏引擎)