最近在Unity上要写一个东东,功能差不多就是在Unity编辑器上的旋转,移动这些,在手机上也能比较容易操作最好,原来用Axiom3D写过一个类似的,有许多位置并不好用,刚好在研究UE4的源码,在模型操作上,很多位置都解决了,其实大家可以对比下,在UE4与Unity中,UE4的如移动和旋转都要正确和好用。
如下是根据UE4中简单移植过来的效果图,差不多已经够用,UE4相关源码主要在EditorViewportClient与UnrealWidget。
介绍一下这个组件主要功能。
1. 模型本地空间与世界空间二种模式。
2. 根据情况动态生成操作模型,如在移动模型时,选择的轴变色,旋转时,视角与模型的方向产生不同模型。
3. 移动根据鼠标平面映射到对应移动平面,点保存上轴上距离不变(作为对比,可以看到Unity上长距离移动,鼠标位置在轴上的位置位移会不断拉大)。
4. 不管模型与摄像机的距离,旋转与移动操作都是合适的大小。
5. 旋转方向的确定,简单说,就是在旋转时,如果用鼠标移动来确定旋转的方向,这个问题看似简单,我以前就没搞出来。
6. 在移动本台,我们需要更方便的操作,所以在移动平台会有些操作,如更容易选中,生成的模型会更大等。
最后,有一些,如箭头模型,选择旋转与移动轴的算法以前考虑过,就没用UE4本身的,如果感觉有问题,自己去移植UE4的。其中旋转因为移动平台易用性,就设定了一个值,如在我这设定的是10,就是每次只旋转10度。
简单分析一下,UE4里相关思路。
其中移动的算法思路非常赞,比如我们要移动X轴,那么我们对应在法线为Y或是法线为Z轴上的平面都可以,通过摄像机的方向与这二个平面的夹角,在这如果摄像机的方向与法线Y平面的角大于与法线Z平面的角,那么我们选择法线Y平面的面做映射面,而Z向量作偏向轴方向,什么意思了,我们鼠标是在二维面上移动的,但是对应的只在X轴上移动,那么我们在法线Y平面上的映射向量需要去掉在Z向量上偏向量的影响。如下是移动的主要代码,每步我加了注释,其中一些比较常用如投影,向量减向量在某向量上的投影的意义要记清,当初我也是看到这,就一下想通这个算法的思路了。
/// <summary> /// FWidget::GetAbsoluteTranslationDelta /// 算法思想,如果移动X轴,选取以Y轴或是Z轴为法线并过模型上的面,鼠标移动映射在这个面上。 /// 其中,如果选择Y轴面,要去掉Z轴上运动值,参看NormalToRemove /// </summary> /// <param name="inParams"></param> /// <returns></returns> public Vector3 GetAbsoluteTranslationDelta(AbsoluteMovementParams inParams) { //鼠标移动的位置 对应的面,请看GetAxisPlaneNormalAndMask方法 Plane movementPlane = new Plane(inParams.PlaneNormal, inParams.Position); //估算鼠标点击在模型上的位置(点击射线方向) Vector3 eyeVector = inParams.EyePos + inParams.PixelDir * (inParams.Position - inParams.EyePos).magnitude; //模型的世界位置 Vector3 requestedPositon = inParams.Position; //点击方向与面的夹角 float dotPlaneNormal = Vector3.Dot(inParams.PixelDir, inParams.PlaneNormal); //摄像机方向与面的夹角不为90度 if (Mathf.Abs(dotPlaneNormal) > 0.00001) { //摄像机到点击位置 与 面的交点 ,把requestedPositon映射到面上位置 requestedPositon = LinePlaneIntersection(inParams.EyePos, eyeVector, movementPlane); } //拖动的增量(都在movementPlane上,二点相差) var deltaPosition = requestedPositon - inParams.Position; //保存最开始点击下去得到的偏移 Vector3 offset = GetAbsoluteTranslationInitialOffset(requestedPositon, inParams.Position); //去掉最开始本身的偏移 deltaPosition -= initialOffset; //.Log("delta:" + deltaPosition); //去掉deltaPosition到NormalToRemove上投影 outDrag与NormalToRemove 互相垂直,outDrag+NormalToRemove = deltaPosition float movementAxis = Vector3.Dot(deltaPosition, inParams.NormalToRemove); Vector3 outDrag = deltaPosition - inParams.NormalToRemove * movementAxis; //Debug.Log("outDrag:" + outDrag); //get the distance from the original position to the new proposed position //Vector3 deltaFromStart = inParams.Position + outDrag - initialPosition; //模型到摄像机方向 Vector3 eyeToNewPosition = inParams.Position + outDrag - inParams.EyePos; //模型到摄像机方向与摄像机方向 夹角大于90度 float behindDot = Vector3.Dot(eyeToNewPosition, inParams.CameraDir); if (behindDot <= 0) { outDrag = Vector3.zero; } return outDrag; }