最近在搞有关Unity的交互和线路绘制的问题,在这里总结一下。
比如说:我们需要在屏幕上用鼠标拖动,然后屏幕上面会根据我们的动作动态绘制出线路,可以是直线,点,或者抛物线等等。这里主要的问题有两个,第一:是采用什么方法来绘制,第二,采用什么坐标来绘制。
首先我们绘制一根线(或者点集)的话,线的绘制可以采用的方式有:
1.LineRender,
2.NGUI绘制
3.直接使用Unity内置的基本几何图形(cube ,sphere等)做点集合,
4.使用GL这个底层库等等方式。
根据不同的绘制方式,我们需要搞清楚它们所使用的坐标,这里是个很重要的问题,涉及多种坐标的转换问题,Unity本身有这么几种坐标:
1.世界坐标:普通三维世界使用,原点在屏幕中间,比如说:XXobject.transform.position
2.屏幕坐标:鼠标使用(移动平台的手指触控也是),和屏幕分辨率有关,原点在屏幕左下角,如:Input.mousePosition
3.投影坐标:和摄像机投影变换有关,用户使用比较少
4.GUI坐标,Unity内置GUI使用,单位是像素和屏幕坐标一样,但是原点在左上角
下面说说具体使用的方式:
使用LineRenderer :
如果说我们选择绘制线路的方式是LineRender,由于其使用的是世界坐标,假如说我们要实现在屏幕上面点一下,然后就在那个点放置我们的小球。那么我们的步骤大致是:1.捕获鼠标点击的时候的屏幕坐标,2.把屏幕坐标转换为世界坐标。3.把世界坐标赋值给小球。
1. 获取鼠标的屏幕坐标的方法是Vector pos = Input.mousePosition,
2.坐标转换的方法是Camera.main.ScreenToWorldPoint(pos), 但是!,这里有一个非常重要的点要注意,就是由于鼠标的坐标是2d(屏幕坐标),z轴是有问题的,因为屏幕上面的一点对应的的是空间上面的一条射线(如下图,透视投影和正交投影)

1.jpg

无论是透视投影还是正交投影。所以直接转换过去的世界坐标是很有问题的,z轴不对。经过实践,Unity里面的Camera.main.ScreenToWorld方法,并不会直接能把获取到的屏幕坐标(如:Vector3 A)转成正确的世界坐标。这里我们要先在转成世界坐标之前给它的z轴赋一个有意义的值。如果转换之后再赋值会出错的。你们可以简单的把pos.z = 1;这样赋予正确的值。这里介绍我的做法:由于我知道了我的点的z轴坐标需要和起点一致,所以我先取得起点的世界坐标(如Vector3 B =_vecStart ),转成屏幕坐标Vector C = Camera.main.ScreenToWorldPoint(B),再拿出z轴赋给我们要处理的坐标(A.z= C.z),这样A转换过后的z轴和起点_vecStart一致了。此时A的三个坐标的是有意义的,这时对A进行转换才能达到我们的预期。

坐标的转换才是关键,做完了转换的话,直接赋值给绘制的方法就好了,LineRender的具体使用方法是:

另外,在inspector面板,还可以可视化的指定材质,点的个数,等等。材质可以设成颜色,这样不太美观(如左下图),设贴图的话在比较段的线还不错,如果设置的点数比较多的话,采用贴图很有问题,贴图被拉伸。如果要绘制长线的话只能采取多个LineRenderer,首尾连接,这样又会变得非常的麻烦。

QQ截图20151103095846.jpg

使用NGUI :

采取NGUI这个比较成熟的插件的话,绘制点线的话,图元的制作会变得非常的简单,主要问题是坐标的问题。由于NGUI采用的是却别于Unity的几大坐标,使用自己独有的一套坐标,因此需要在进行一次转换,至于Unity和NGUI的对接点在屏幕坐标,因为NGUI的几个转换坐标的方法中ScreenToWorldPoint(vecTempScreen)这个方法很像Unity 的那个,这里转换后的世界坐标是NGUI的世界坐标,转换前的屏幕坐标就是Unity的屏幕坐标,所以可以用屏幕坐标变中介。
好了,具体的做法是这样子的,如果是Unity的场景中一个物体Cube,要把它的世界坐标转成NGUI坐标,我们可以这么做:

左下图为上面代码中的摄像机参数的可视化显示,中下图为运行效果,右下图为NGUI的坐标和世界坐标的对比(白色为世界坐标,红框里面为NGUI内容)

QQ截图20151103095954.jpg

这里要提一点,有时候Unity会提示 UICamera.currentCamera取不到实例,这个极有可能是场景中有好几台摄像机(最基本的世界场景一台,NGUI一台,背景和背包各用一台摄像机是很正常的,所以场景中的摄像机比较多的情况下,UICamera.currentCamera取不到我们想要的那台摄像机,所以自己直接找到那台拖拽赋值是最保险的),我们可以自己定义一个变量public Camera NguiCurrentCamera,直接在Inspector界面中把UIRoot中的摄像机拖给它,这样不会为空了。这么使用就可以NguiCurrentCamera.ScreenToWorldPoint(posScreen);接下来,就可以直接把posNgui赋值给NGUI的图元的坐标了,比如说:NGUIGameObject.transform.position = posNgui.

注意:
如果是直接获取屏幕坐标(比如说鼠标点击),转为NGUI世界坐标,我们很容易想到直接把上面的步骤拿来用,忽略第一步即可,思路是这样的。但是不要忘记,还是那个问题,屏幕坐标的Z轴是有问题的,所以应该先把Z轴赋予正确的值,比如说1,再进行转换:

这样posNgui就可以使用了。
另外提一点,由于世界坐标相对来说比较小,不像屏幕那样1360*768这种,可能一个场景中的边界的距离就是30-60左右,所以计算的时候要注意误差问题。
使用GL:
GL是Unity里面的一个底层的图形库,其实是GL立即绘制函数 只用当前material的设置。因此除非你显示指定mat,否则mat可以是任何材质。并且GL可能会改变材质。
GL是立即执行的,如果你在Update()里调用,它们将在相机渲染前执行,相机渲染将会清空屏幕,GL效果将无法看到。通常GL用法是,在camera上贴脚本,并在OnPostRender()里执行。
注意:
1.GL的线等基本图元并没有uv. 所有是没有贴图纹理影射的,shader里仅仅做的是单色计算或者对之前的影像加以处理。所以GL的图形不怎么美观,如果需要炫丽效果不推荐
2.GL所使用的shader里必须有Cull off指令,否则显示会变成如下
使用GL的一般步骤是:
这是官方的一个例子,小作修改

这其实是官方文档的一个例子,我进行了一些小小的修改,上面是部分摘录,脚本挂在摄像机中,在OnPostRender中的代码有些可能看起来很熟悉,没错,在window编程中我们绘制的时候也是类似于这种写法,底层使用的是状态机,设置各种绘制属性(java,windows,or mfc甚至是directx,设置画刷颜色,线框等),在下一次改变之前都是使用前一次设置的属性。其实使用的方法不难,但是这里有一个关键点:
GL.LoadOrtho();
这个方法的意思是使用设置ortho perspective,即水平视角。范围(0,0,-1) to (1,1,100). (还是坐标问题)主要用于在纯2D里绘制图元。GL.Vertex3()的取值范围从左下角的(0,0,0) 至右上角的(1,1,0),所以我们如果设置了这个东西,那么我们的代码要这么写:
假如我们要使用vecTemp这个世界坐标赋值给
坐标转换就不多说了。世界坐标转屏幕坐标,在缩放到0-1的范围

我们要把坐标限制在范围(0,0,-1) to (1,1,100)内就需要进行相应的缩放,很明显,vecTemp是屏幕坐标了
如果没有调用 GL.LoadOrtho();那就是使用普通世界坐标了
_GLPos = new Vector3(vecTemp.x, vecTemp.y, 0);
下面来看看,是否使用GL.LoadOrtho();的不同效果,可以看出感觉是一样的,但是坐标其实不一样,图1 数值x,y轴的范围始终在0-1之间,图2,会比1大
图一

1.jpg

图二

2.jpg

使用Unity的内置几何体

在绘制点线的时候,如果可以的话,其实可以直接使用Unity的内置几何体,如Cube,Sphere这些直接拿来用,坐标使用的就是普通的世界坐标,比LineRenderer有时候更加简单方便。可以使用如Sphere制作一个自己满意的点,做成预制体,动态加载进场景中,数量我们根据自己的需求定。然后根据我们的具体情况设置世界坐标即可。

QQ截图20151103100324.jpg