不管是开发游戏还是做虚拟现实,我想您都不会对NGUI感到陌生,但由于其不是科技自己原生的产品,所以其必定不会做得很完善,至少有很多东西需要自己研究其源代码之后,才能按功能写出自己想要的东西。这次就让我们尝试一下解决一个必须解决的,而且还有点棘手的功能吧!
打开NGUI的第6个例子,它为我们提供了一个很炫的功能,窗口拖拽,而且还带有加速度和摇摆功能。可是,这里面有一个非常严重的Bug:窗口很容易会跑出屏幕,而且你无法再将其拖到屏幕中。不知道NGUI官方为什么不提供此功能,还是有什么其他原因,但绝不是他们做不到,因为我都做到了,或许是他们想给我们一点点成就感,故意做成这样的吧!好了,我们入正题吧!
我们可以先尝试一下阅读官方的UIDragObject这个脚本,至少要将拖拽窗口的基本部分给弄清楚。在此基础上,我们就可以利用一些方法来限制窗体拖拽的区域了。下面我将我的代码列出:
using UnityEngine;
using System.Collections;
public class TestDrag : MonoBehaviour {
public Transform target;//拖拽的窗体的根节点
public Vector3 scale = Vector3.one;//希望拖拽时窗体移动随着拖拽的方向移动的幅度的一个决定因素
//public float padding = 0.1f;
Plane mPlane;//参考平面
bool mPressed = false;//是否按下
Vector3 mLastPos = Vector3.zero;//上一次NGUI摄像机沿着屏幕上的鼠标点方向发射射线与参考平面的交点
void OnPress(bool pressed)
{
mPressed = pressed;
if(pressed)//如果鼠标被按下
{
mLastPos = UICamera.lastHit.point;
Transform trans = UICamera.currentCamera.transform;
mPlane = new Plane(trans.rotation * Vector3.back,mLastPos);//注意,此平面的方向与NGUI正交摄像机裁剪面平行
}
}
void OnDrag(Vector2 delta)
{
Ray ray = UICamera.currentCamera.ScreenPointToRay(UICamera.currentTouch.pos);//发射射线
float dist = 0f;
if(mPlane.Raycast(ray,out dist))//与参考平面相交
{
Vector3 currentPos = ray.GetPoint(dist);//取得相交点
Vector3 offset = currentPos - mLastPos;//计算鼠标单次拖拽(OnDrag是由UICamera脚本中的Update函数的某个子程序在恰当的时候利用SendMessage发送过来的)移动的步长分量
mLastPos = currentPos;//更新mLastPos为当前currentPos,一边下一次的计算
if (offset.x != 0f || offset.y != 0f)
{
offset = target.InverseTransformDirection(offset);//将计算出的鼠标移动步长转为根节点的坐标系下的分量
offset.Scale(scale);//我们可以在检视面板中修改此scale的值,可以看到不同的效果
offset = target.TransformDirection(offset);//再转回世界坐标系
}
Vector3 pos = target.position;
pos += offset;//先将target的预期位置给计算出来。下面才是最重要的限制窗体的部分:
Camera curcam = UICamera.currentCamera;
Bounds bs = NGUIMath.CalculateAbsoluteWidgetBounds(target);//根据根节点计算窗体在世界坐标系中的大小
/*
首先解释一下我的代码的功能:限制窗体在屏幕里面。又由于此窗体的坐标在其中心,所以我只需让其中心点在一个矩形区域里面就行了,但是:此中心点必须遵循以下规则,否则就会有部分或全部飞出屏幕。下面看图:


这两张图显示的两个位置正是此窗口的两个临界位置,即左下角的最远坐标与右上角的最远坐标(好像有些绕口,就暂且先这样叫着吧!)。于是我们得出一个结论:
1.窗口在屏幕上的横坐标必须大于或等于窗体的宽度的二分之一,小于或等于屏幕长度减去窗体宽度的二分之一。
2.窗口在屏幕上的纵坐标坐标必须大于或等于窗体的高度的二分之一,小于或等于屏幕高度减去窗体高度的二分之一。
所以以下代码就顺理成章了!
*/
Vector3 _lb =new Vector3(target.position.x - bs.size.x/2,target.position.y-bs.size.y/2,0f);//求出窗体的左下角的世界坐标
Vector3 lb = curcam.WorldToScreenPoint(_lb);//转屏幕坐标
Vector3 _rt = new Vector3(target.position.x+bs.size.x/2,target.position.y+bs.size.y/2,0f);//求出窗体的右上角世界坐标
Vector3 rt = curcam.WorldToScreenPoint(_rt);//转屏幕坐标
float width = rt.x - lb.x;//求出窗体的屏幕坐标系宽度
float height = rt.y - lb.y;//求出窗体的屏幕坐标系高度
Vector3 ClampVector1 = new Vector3(width / 2, height / 2, 0f);//窗体的左下角的最远点(相对于屏幕)
Vector3 ClampVector2 = new Vector3(Screen.width - width / 2, Screen.height - height / 2, 0f);//窗体的右上角的最远点(相对于屏幕)
Vector3 scrPos = curcam.WorldToScreenPoint(pos);//将pos转为屏幕坐标
//将pos的屏幕坐标限制在屏幕的两个最远点范围之内
scrPos.x = Mathf.Clamp(scrPos.x, ClampVector1.x, ClampVector2.x);
scrPos.y = Mathf.Clamp(scrPos.y, ClampVector1.y, ClampVector2.y);
然后将限制后的pos屏幕坐标再转为世界坐标,赋值给target。
target.position = curcam.ScreenToWorldPoint(scrPos);
}
}
}
首先我先声明一下,如果您对世界坐标与摄像机坐标还有局部坐标系与世界坐标系的概念不清楚的话,先补补这方面的知识吧,以免打击自己的信心。其实这里面没用到很高深的数学知识,那些引擎都封装好了,我们直接调用就行了。我的代码注释已经写得很详细了,如果大家有什么疑问的话,请给我留言。运行一下,功能以大功告成,我就不附上截图了。不早了,下次见!
(2016.10.08)