游戏场景设计:坐标系和坐标系转换

作者:四五二十 2018-12-26

大家好。今天带来的是一个讲座性质的内容。这里的概念搞清楚且能纯熟运用了,对于游戏开发那是相当的有用。

首先来介绍一下我们经常会用到的几种坐标系:

一.世界坐标系

游戏场景中所有的物体都统一遵守的坐标系统,它标注了每个物体在世界中的唯一位置方向信息。



如上图所示,我们用一个平面世界坐标系来表示红点和蓝点的位置,往后无论增加多少个点,都可以在该坐标系中找到一个数值来表示位置,只要被标注位置的物体不动,位置数值永远不变。

所以世界坐标系又叫绝对坐标系,我们在Unity中使用的世界坐标不过是多了一根Z轴的空间坐标系罢了,原理是一样的。

二.本地坐标系

本地坐标系则是以物体自身位置作为原点,表示物体间相对位置和方向,并且会根据物体自身旋转而旋转:



我们在Inspector看到的坐标系其实就是本地坐标系,包括旋转、缩放都属于本地:



当一个物体有父物体时,无论父物体的位置旋转缩放如何变化,作为子物体它的Transform数值始终保持不变,说明它相对于自己的父物体位置始终没有变化,而当一个物体没有父物体时,可以把“世界” 就看成它的父物体,它在世界中的一切位置变化都可以看做是和世界的相对位置变化,由于世界坐标系和“世界的本地坐标系”是重合的,所以该物体的本地坐标和世界坐标也是一样的。

由于物体各自坐标系的不同参考标准,同一个物体,会在不同本地坐标系中显示出不同的位置信息,产生各种各样的歧义:



熊大将美女在本地坐标系的方向位置告诉熊二,熊二如果也往左手边这个方向看是肯定看不见美女的,所以本地坐标系如果使用不当很容易带来混乱。而如果熊大能将美女的世界坐标告诉熊二,熊二就不会感到迷惑了。

三.屏幕坐标系

在Unity的Game视窗中,以屏幕左下角为原点(0,0),像素为单位,坐标轴往屏幕右上方进行延伸,X轴和Y轴数值不能超过屏幕的最大宽度和最大高度。由于原点位置是唯一的,也可以理解成是一个平面世界坐标系,作用是表示物体在屏幕中的位置。



屏幕越大,屏幕坐标系所能表现的数值就越大。


四.视口坐标系

视口就是当你在Unity 的Scene面板中看到摄像机的那个白色矩形框:



视口坐标系同屏幕坐标系相似,也是以左下角为原点的平面坐标系,不同的是视口坐标系的数值上限不受屏幕大小影响,右上角的坐标永远是(1,1),数值就是物体屏幕坐标与屏幕宽高的比例:( X轴/宽度, Y轴/高度),作用是表示物体在摄像机中的位置。



对这几种坐标系有一个基本的了解后,我们就来看坐标系之间的转换:

一.本地坐标<==>世界坐标:

●本地转世界:transform.TransformPoint

用一个案例来表示:

1.在场景中有物体A和B,且B是A的子物体:



2.在Inspector面板将A的坐标设为(0,0,5),B的坐标设为(0,0,2),那么这时B的世界坐标就应该是(0,0,7)。

3.用代码来验证我们的推断:

public Transform p; //A物体
public Transform s; //B物体
void Update()
{
    if (Input.GetKeyDown(KeyCode.A)) //按下A键
    {           
        print("子物体的本地坐标: " + s.localPosition);
        print("子物体的世界坐标: " + s.position);
        print("本地转世界: " + p.TransformPoint(s.localPosition));
    }
}

4.运行游戏,点击A键:



●本地转世界:transform.InverseTransformPoint

将刚才的代码稍稍改一下:

if (Input.GetKeyDown(KeyCode.A)) //按下A键
{           
    print("子物体的本地坐标: " + s.localPosition);
    print("子物体的世界坐标: " + s.position);
    print("世界转本地: " + p.InverseTransformPoint(s.position));
}




二.世界坐标<==>屏幕坐标:

●世界转屏幕:Camera.main.WorldToScreenPoint

屏幕坐标是一个二维坐标(X,Y),所有被摄像机照的的三维物体都可以在这个二维屏幕上找到自己的点:



在游戏游戏中我们可以看到这种现象:



显示在人物身上的名字信息,会始终跟着人物移动,并且无论人物离摄像机多远多近,字体大小始终一样(通过两幅图主角血条和顶上的时间显示可以看出两个屏幕是一样大小),其实就是将人物的世界坐标转成屏幕坐标,再将这个坐标赋给显示名字的UI,接下来我们就通过一个实例来完成:

1.在场景中创建一个Cube,再用UGUI创建一个Text,内容改为“小兵”:



2.创建脚本:

public Transform cube; //将Cube拖进去
public Text text; //将Text拖进去
void Update()
{
    //获取Cube顶上的位置的世界坐标
    Vector3 pos = cube.position + Vector3.up;
    //将该世界坐标转为屏幕坐标赋给Text
    text.transform.position = Camera.main.WorldToScreenPoint(pos);
}


3.运行游戏:

世界坐标在转屏幕坐标时会忽略Z轴信息,因为屏幕坐标没有Z轴。

●屏幕转世界:Camera.main.ScreenToWoldPoint

和世界转屏幕相反,屏幕转世界则是,将屏幕坐标的XY轴信息转换后再增加一个Z轴信息,赋给三维世界的物体:


Z轴数值可以用该物体到屏幕的垂直距离。

鼠标坐标也是屏幕坐标,我们就来做一个三维物体跟随鼠标移动的案例:

1.创建一个Cube,让摄像机正对着它:



2.创建脚本:

public Transform cube; //方块
public Transform mainCamera; //主摄像机
void Update()
{
    if (Input.GetMouseButton(0)) //按住鼠标左键
    {
        //获取鼠标屏幕坐标(Z轴为0)   
        Vector3 mPos = Input.mousePosition;
        //以摄像机z轴为法线创建摄像机XY轴组成的平面  
        Plane pla = new Plane(mainCamera.forward, mainCamera.position);
        //获取该物体到平面的距离(z轴垂直距离)        
        float dis = pla.GetDistanceToPoint(cube.position);
        //将屏幕坐标转为世界坐标
        cube.position = Camera.main.ScreenToWorldPoint(new Vector3(mPos.x, mPos.y, dis));
    }
}


3.运行程序:


四.屏幕坐标<==>视口坐标:

●屏幕转视口:Camera.main.ScreenToViewportPoint

上面我们说到视口坐标实际就是屏幕坐标与屏幕的宽高比,我们通过一个案例来进行验证:

1.场景中用UGUI新建两个Text,分别用于显示视口坐标与比例:



2.我们还是使用鼠标的屏幕坐标来进行转换,同时将鼠标在摄像机的位置用比例表示出来,创建脚本:

public Text text; //显示视口坐标
    public Text text1; //显示比例
    void Update()
    {
        //获取鼠标坐标的屏幕坐标
        Vector2 mouPos = Input.mousePosition;
        //将屏幕坐标转为视口坐标
        Vector2 viewPos = Camera.main.ScreenToViewportPoint(mouPos);
        //显示视口坐标(保留小数点后两位)
        text.text = "视口: " + viewPos.ToString("0.00");


        //获取屏幕坐标与屏幕的宽高比
        float x = mouPos.x / Screen.width;
        float y = mouPos.y / Screen.height;
        Vector2 viewpos1 = new Vector2(x, y);
        //显示比例
        text1.text = "比值: " + viewpos1.ToString("0.00");
    }


3.运行程序:



●视口转屏幕:Camera.main. ViewportPointToScreenPoint

public Text text; //显示视口坐标
public Text text1; //显示比例
void Update()
{
    //获取鼠标坐标的屏幕坐标
    Vector2 mouPos = Input.mousePosition;
    //直接显示屏幕坐标
    text.text = "直接显示屏幕坐标: " + mouPos.ToString();

    //获取屏幕坐标与屏幕的宽高比
    float x = mouPos.x / Screen.width;
    float y = mouPos.y / Screen.height;
    Vector2 viewpos = new Vector2(x, y);

    //将视口坐标转为屏幕坐标
    Vector2 mouPos1 = Camera.main.ViewportToScreenPoint(viewpos);
    text1.text = "转换后的屏幕坐标: " + mouPos1.ToString();
}





总结:

Unity中的坐标系当然不止上述四种,除此之外如投影坐标系、切线坐标系、GUI坐标系等,坐标转换除了点的转换外,还有方向和向量的转换,如果有兴趣可以查看Unity官方文档了解。

坐标系转换在游戏中用得非常广泛,学习过程中只要能够弄懂坐标系的定义,再去理解它们之间的转换,就相对容易许多。

来,想系统性学习游戏开发、学习Unity开发的,欢迎围观:


在线的教学视频:


以及QQ交流群:869551769

专栏地址:https://zhuanlan.zhihu.com/gdguide


最新评论
暂无评论
参与评论

商务合作 查看更多

编辑推荐 查看更多