世界坐标物体在屏幕上的方位显示

在制作FPS游戏的时候,经常遇到把镜头背对的队友或者NPC或者其他目标显示在屏幕上。例如守望先锋放置了NPC后,相机离开npc,就出现图标,显示在你身后的某个地方,方便转向找到他。

using UnityEngine;
using System.Collections;

public class CTestCamerNpc : MonoBehaviour {


    public Transform target;  //npc目标
    public Transform pic; //一张canvas图片
	void Start () {
	    
	}

    const float borderUnit = 50f; //距离左右上3边最小边距
    const float bottomBorderUnit = 200f; //距离下方边距(最下方可能有UI,所以距离大一些)
    Vector3 scrPos; //最终显示位置
    Vector3 scrPosTest; //
    Rect rect0 = new Rect(10f, 10f, 300f, 100f);
    Rect rect1 = new Rect(10f, 30f, 300f, 100f);


    Rect rectA = new Rect(10f, 160f, 300f, 100f);
    Rect rectB = new Rect(10f, 180f, 300f, 100f);
    Rect rectC = new Rect(10f, 200f, 300f, 100f);

	void Update () {


        float rightMax = Screen.width - borderUnit ;
        float heightMax = Screen.height - borderUnit ;
        scrPos = Camera.main.WorldToViewportPoint(target.position);
        Vector3 dir = (target.position - Camera.main.transform.position).normalized;
        cross = Vector3.Cross(Camera.main.transform.forward, dir);
        //正向方向
        if (scrPos.z > 0f)
        {
            scrPos.x = scrPos.x * Screen.width;
            scrPos.y = scrPos.y * Screen.height;
        }
        else //北向方向
        {
            scrPos.x = (0.5f + cross.y) * Screen.width; //这里用左右两边正负值来做位移.
            scrPos.y = -scrPos.y * Screen.height; //这里有点问题,改成最底部,现在不是
        }
        if (scrPos.x < borderUnit)
        {
            scrPos.x = borderUnit;
        }
        else if (scrPos.x > rightMax)
        {
            scrPos.x = rightMax;
        }
        if (scrPos.y < bottomBorderUnit)
        {
            scrPos.y = bottomBorderUnit;
        }
        else if (scrPos.y > heightMax)
        {
            scrPos.y = heightMax;
        }
        pic.position = scrPos;

	}
    Vector3 cross;
    void OnGUI()
    {

        GUI.Label(rect1, scrPos.ToString("F2"));


        GUI.Label(rectA, "WorldToViewportPoint : " + Camera.main.WorldToViewportPoint(target.position).ToString("F2"));
        GUI.Label(rectB, "WorldToScreenPoint : " + Camera.main.WorldToScreenPoint(target.position).ToString("F2"));
        GUI.Label(rectC, "Dis : " + Vector3.Distance(Camera.main.transform.position, target.position).ToString("F2") + " ,cross : " + cross.ToString("F2"));


    }
}

附上效果图:
世界坐标物体在屏幕上的方位显示_第1张图片
上图是球在右前方

世界坐标物体在屏幕上的方位显示_第2张图片
上图是球在坐前方

世界坐标物体在屏幕上的方位显示_第3张图片
上图是球在左后

世界坐标物体在屏幕上的方位显示_第4张图片
上图是球在右后方

世界坐标物体在屏幕上的方位显示_第5张图片
上图是球在大概正后方

这样人物鼠标转一圈,大概球会围绕屏幕转一圈,感觉不错呢

上面的脚本是图片是UGUI的Canvas Render Mode为 Screen Space - Overlay的情况,如果是Screen Space - Camera,那么图片Image的坐标系就改为localPostion , 并且坐标是从屏幕中心开始的.

如下:

using UnityEngine;
using System.Collections;

public class CTestCamerNpc : MonoBehaviour {


    public Transform target;
    public Transform pic;
	void Start () {
	    
	}

    const float borderUnit = 50f;
    const float bottomBorderUnit = 200f;
    Vector3 scrPos;
    Vector3 scrPosTest;
    Rect rect0 = new Rect(10f, 10f, 300f, 100f);
    Rect rect1 = new Rect(10f, 30f, 300f, 100f);


    Rect rectA = new Rect(10f, 160f, 300f, 100f);
    Rect rectB = new Rect(10f, 180f, 300f, 100f);
    Rect rectC = new Rect(10f, 200f, 300f, 100f);

	void Update () {


        float rightMax = Screen.width * 0.5f - borderUnit;
        float topMax = Screen.height * 0.5f - borderUnit;
        float leftMax = -Screen.width * 0.5f + borderUnit;
        float bottomMax = -Screen.height * 0.5f + bottomBorderUnit;
        scrPos = Camera.main.WorldToViewportPoint(target.position);
        Vector3 dir = (target.position - Camera.main.transform.position).normalized;
        cross = Vector3.Cross(Camera.main.transform.forward, dir);
        if (scrPos.z > 0f)
        {
            scrPos.x = scrPos.x * Screen.width - Screen.width * 0.5f;
            scrPos.y = scrPos.y * Screen.height - Screen.height * 0.5f;
        }
        else
        {
            scrPos.x = (0.5f + cross.y) * Screen.width - Screen.width * 0.5f; 
            scrPos.y = bottomMax;//-scrPos.y * Screen.height - Screen.height * 0.5f;
        }
        if (scrPos.x < leftMax)
        {
            scrPos.x = leftMax;
        }
        else if (scrPos.x > rightMax)
        {
            scrPos.x = rightMax;
        }
        if (scrPos.y < bottomMax)
        {
            scrPos.y = bottomMax;
        }
        else if (scrPos.y > topMax)
        {
            scrPos.y = topMax;
        }
        scrPos.z = 0f;
        pic.localPosition = scrPos;

	}
    Vector3 cross;
    void OnGUI()
    {

        GUI.Label(rect1, scrPos.ToString("F2"));


        GUI.Label(rectA, "WorldToViewportPoint : " + Camera.main.WorldToViewportPoint(target.position).ToString("F2"));
        GUI.Label(rectB, "WorldToScreenPoint : " + Camera.main.WorldToScreenPoint(target.position).ToString("F2"));
        GUI.Label(rectC, "Dis : " + Vector3.Distance(Camera.main.transform.position, target.position).ToString("F2") + " ,cross : " + cross.ToString("F2"));


    }
}

一般情况使用ScreenSpace - Overalay比价好,性能高.

经过上面的2次处理后 , 遇到一种情况,就是Canvas和比例和实际屏幕比例不同,也就是Canvas Scaler的UI Scale Mode是Scale With Screen Size的时候,是需要输入一个Reference Resolution,那么需要经过换算一下比例关系,如下:

using UnityEngine;
using System.Collections;

public class CTestCamerNpc : MonoBehaviour {


    public Transform target;
    public Transform pic;

    public Canvas cav;
	void Start () {
	    
	}

    const float borderUnit = 50f;
    const float bottomBorderUnit = 200f;
    Vector3 scrPos;
    Vector3 scrPosTest;
    Rect rect0 = new Rect(10f, 10f, 300f, 100f);
    Rect rect1 = new Rect(10f, 30f, 300f, 100f);


    Rect rectA = new Rect(10f, 160f, 300f, 100f);
    Rect rectB = new Rect(10f, 180f, 300f, 100f);
    Rect rectC = new Rect(10f, 200f, 300f, 100f);

	void LateUpdate () {

        Vector3 vSize = cav.GetComponent().sizeDelta;

        Camera camera = GetComponent();
        float rightMax = (Screen.width * vSize.x / Screen.width * 0.5f - borderUnit);
        float topMax = (Screen.height * vSize.y / Screen.height * 0.5f - borderUnit);
        float leftMax = -Screen.width* vSize.x / Screen.width * 0.5f + borderUnit;
        float bottomMax = -Screen.height * vSize.y / Screen.height * 0.5f + bottomBorderUnit;

        Vector3 pos = target.position + Vector3.up;

        scrPos = camera.WorldToViewportPoint(pos);
        Vector3 dir = (pos - camera.transform.position).normalized;

        cross = Vector3.Cross(camera.transform.forward, dir);
        if (scrPos.z > 0f)
        {
            scrPos.x = (scrPos.x * Screen.width - Screen.width * 0.5f) * vSize.x / Screen.width;
            scrPos.y = (scrPos.y * Screen.height - Screen.height * 0.5f) * vSize.y / Screen.height;
        }
        else
        {
            scrPos.x = (0.5f + cross.y) * Screen.width - Screen.width * 0.5f;
            scrPos.y = bottomMax;// -scrPos.y * Screen.height - Screen.height * 0.5f;
        }
        if (scrPos.x < leftMax)
        {
            scrPos.x = leftMax;
        }
        else if (scrPos.x > rightMax)
        {
            scrPos.x = rightMax;
        }
        if (scrPos.y < bottomMax)
        {
            scrPos.y = bottomMax;
        }
        else if (scrPos.y > topMax)
        {
            scrPos.y = topMax;
        }
        scrPos.z = 0f;
        pic.localPosition = scrPos;

	}
    Vector3 cross;
    void OnGUI()
    {

        GUI.Label(rect1, scrPos.ToString("F2"));


        GUI.Label(rectA, "WorldToViewportPoint : " + Camera.main.WorldToViewportPoint(target.position).ToString("F2"));
        GUI.Label(rectB, "WorldToScreenPoint : " + Camera.main.WorldToScreenPoint(target.position).ToString("F2"));
        GUI.Label(rectC, "Dis : " + Vector3.Distance(Camera.main.transform.position, target.position).ToString("F2") + " ,cross : " + cross.ToString("F2"));


    }
}

上面的都是有问题的,在背面的时候,相机的计算坐标函数在背面的上下半球没办法区分(在目标和相机位置高低不同的情况下).在网上找了找别人写的,貌似没有问题,但是有些地方没看懂! 后面有空再研究补充下注释.

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


/// 
/// https://www.youtube.com/watch?v=gAQpR1GN0Os
/// 
public class COffScreen : MonoBehaviour {


    public Transform target;
    public Transform pic;

    public Canvas cav;
	// Use this for initialization
	void Start () {
		
	}
	
	// Update is called once per frame
	void Update () {
        show();
	}
    Vector3 scrPos;
    void show()
    { 
        Camera camera = GetComponent();

        Vector3 pos = target.position;
        Vector3 screenpos = camera.WorldToScreenPoint(pos);

        Vector3 vSize = cav.GetComponent().sizeDelta;
        if (screenpos.z > 0 && screenpos.x > 0 && screenpos.y > 0 && screenpos.x < Screen.width && screenpos.y < Screen.height)
        {
            scrPos = screenpos;
            scrPos.z = 0f;
            scrPos.x = (scrPos.x - Screen.width * 0.5f) * vSize.x / Screen.width;
            scrPos.y = (scrPos.y - Screen.height * 0.5f) * vSize.y / Screen.height;
            pic.localPosition = scrPos;
        }
        else
        {
            //OffScreen
            if (screenpos.z < 0f)    //seems hacky but necessary,
            {
                //screenpos *= -1f; //stuff is flipped when its behind us

                screenpos.x = Screen.width - screenpos.x;   //这个改动要注意可能不对,有问题开启上面的screenpos *= -1f;
                screenpos.y = Screen.height - screenpos.y;
            }
            Vector3 screenCenter = new Vector3(Screen.width, Screen.height, 0f) * 0.5f;
            
            //Note coordinates translated
            //make 00 the center of screen instead of bottom left
            screenpos -= screenCenter;

            //find angle from center of screen to mouse position
            float angle = Mathf.Atan2(screenpos.y, screenpos.x);
            angle -= 90f * Mathf.Deg2Rad;

            float cos = Mathf.Cos(angle);
            float sin = -Mathf.Sin(angle);

            screenpos = screenCenter + new Vector3(sin * 150f, cos * 150f, 0f);

            //y = mx + b format
            float m = cos / sin;

            Vector3 screenBounds = screenCenter * 0.9f;

            //check up and down first
            if (cos > 0f)
            {
                screenpos = new Vector3(screenBounds.y / m, screenBounds.y, 0f);
            }
            else
            {
                //down
                screenpos = new Vector3(-screenBounds.y / m, -screenBounds.y, 0f);
            }

            //if out of bounds ,get point on appropriate side
            if (screenpos.x > screenBounds.x)
            {
                screenpos = new Vector3(screenBounds.x , screenBounds.x*m, 0f);
            }
            else if(screenpos.x < -screenBounds.x) //out of bounds left
            {
                screenpos = new Vector3(-screenBounds.x , -screenBounds.x * m, 0f);
            }//else in bounds

            //remove coordinate translation
            screenpos += screenCenter;

            scrPos = screenpos;
            scrPos.z = 0f;
            scrPos.x = (scrPos.x - Screen.width * 0.5f) * vSize.x / Screen.width;
            scrPos.y = (scrPos.y - Screen.height * 0.5f) * vSize.y / Screen.height;
            pic.localPosition = scrPos;
        }
        
    }

    Rect rect0 = new Rect(10f, 10f, 300f, 100f);
    Rect rect1 = new Rect(10f, 30f, 300f, 100f);


    Rect rectA = new Rect(10f, 160f, 800f, 100f);
    Rect rectB = new Rect(10f, 180f, 300f, 100f);
    Rect rectC = new Rect(10f, 200f, 300f, 100f);
    void OnGUI()
    {

        GUI.Label(rect1, scrPos.ToString("F2"));
        Camera camera = GetComponent();
        Matrix4x4 cameraMatrix = camera.transform.worldToLocalMatrix;
        Vector3 pos = target.position;
        scrPos = cameraMatrix.MultiplyPoint3x4(pos);
        scrPos.z = 0;
        GUI.Label(rectA, "WorldToScreenPoint : " + Camera.main.WorldToScreenPoint(target.position).ToString("F2") );
        GUI.Label(rectB, "MultiplyPoint3x4 : " + scrPos.ToString("F2"));
        GUI.Label(rectC, "Dis : " + Vector3.Distance(Camera.main.transform.position, target.position).ToString("F2") );


    }
}

你可能感兴趣的:(Unity,C#,游戏)