在制作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"));
}
}
这样人物鼠标转一圈,大概球会围绕屏幕转一圈,感觉不错呢
上面的脚本是图片是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") );
}
}