默认页面
<h2>移动组件-简介</h2>
<p>为了增强元器件移动能力,方便实验操作,新移动组件能够支持近距离靠近、爬升和层叠。</p>
<p>近距离靠近:被拖拽元器件在靠近(视觉靠近)其它元器件时,自动去爬升当前元器件。</p>
<p>爬升:被拖拽元器件能够沿着鼠标路径,爬升到其它元器件表面,如当前鼠标点无可爬升元器件则沿着桌面移动。</p>
<p>层叠:被拖拽元器件在释放时,会做下落检测,判断是否能够叠在其它元器件之上。如果能层叠,则它就与这个元器件建立了层叠关系。</p>
<p>层叠状态下,可以随意拖动层叠链中的元器件,但其上元器件不跟随一起拖动。当被拖拽元器件之上的元器件已经脱离它时,其上的元器件都会掉落,依次掉落在其它元器件上或桌面上。</p>
<p>设计案如下:<a href="http://cube.nd.com.cn/svn/prototype/VirtualLab/New_Design/common/index.html#g=1&p=%E7%89%88%E6%9C%AC%E6%9B%B4%E6%96%B0%E8%AE%B0%E5%BD%95">http://cube.nd.com.cn/svn/prototype/VirtualLab/New_Design/common/index.html#g=1&p=%E7%89%88%E6%9C%AC%E6%9B%B4%E6%96%B0%E8%AE%B0%E5%BD%95</a></p>
<h2>移动组件-原理</h2>
<p>移动组件的爬升和层叠判断,依赖于Unity的物理投射能力/接口。投射判断基本思路:从指定位置(摄像机位置/元器件位置),向指定方向(屏幕鼠标方向/垂直向下方向)投射被拖拽元器件的包围盒,检测投射到的元器件和投射点坐标,依据这两项信息来做爬升和层叠判断。</p>
<p>摄像机位置 -> 屏幕鼠标方向 => 爬升信息</p>
<p>元器件位置 -> 垂直向下方向 => 层叠信息</p>
<1> 元器件包围盒
根据元器件各部位的Collider位置和大小,计算得到元器件整体的包围盒,如下所示:
![](https://wiki.doc.101.com/images/4/40/Vlab-newdragger-bounds.png)
1
![](https://wiki.doc.101.com/images/thumb/8/8a/Vlab-newdragger-drop-element.png/400px-Vlab-newdragger-drop-element.png)
<2> 投射原理
下面是爬升投射示意图:
[![](https://wiki.doc.101.com/images/4/40/vlab-newdragger-raycast-element.png|400px]]
注:从摄像机位置向鼠标方向投射包围盒(被拖拽元器件),与其最近的相交对象为元器件A,同时依据投射距离能够计算出被拖拽元器件在元器件A上的爬升坐标。
如无可爬升对象,则被拖拽元器件沿着桌面移动。
下面是掉落投射示意图:
[![](https://wiki.doc.101.com/images/4/40/vlab-newdragger-drop-element.png|400px]]
注:从被拖拽元器件当前位置,向下投射包围盒(被拖拽元器件),与其最近的相交对象为元器件A,同事依据投射距离能够计算出被拖拽元器件的掉落高度,并得到其层叠关系。
如无可层叠对象,则被拖拽元器件掉落在桌面上。
## 移动组件-投射接口 ##
针对元器件投射能力,抽象出来两个投射类,方便在其他情况下使用。
<1> ObjectRaycaster
```c
/// <summary>
/// 投射获取相交的元器件以及投射距离
/// </summary>
/// <param name="castObject">投射对象</param>
/// <param name="castCenter">投射中心</param>
/// <param name="castExtends">投射包围盒尺寸</param>
/// <param name="castRotation">投射包围盒角度</param>
/// <param name="castDirection">投射方向</param>
/// <param name="raycastDistance">命中距离(out)</param>
/// <returns></returns>
public LabBaseObject RaycastElement(LabBaseObject castObject, Vector3 castCenter, Vector3 castExtends, Quaternion castRotation, Vector3 castDirection, out float raycastDistance)
/// <summary>
/// 下落检测,计算得到层叠元器件和投射距离
/// </summary>
/// <param name="castObject">投射对象</param>
/// <param name="dropOffsetY">下落起点偏移(往上抬高)</param>
/// <param name="castExtends">投射包围盒尺寸</param>
/// <param name="castRotation">投射包围盒角度</param>
/// <param name="layerMask">检测层掩码</param>
/// <param name="dropDistance">命中距离(out)</param>
/// <returns></returns>
public LabBaseObject DropElement(LabBaseObject castObject, float dropOffsetY, Vector3 castExtends, Quaternion castRotation, int layerMask, out float dropDistance)
```
<2> TableRaycaster
```c
/// <summary>
/// 桌面移动,与当前位置的offset值
/// </summary>
/// <param name="eventData">鼠标点信息</param>
/// <param name="transform">拖拽对象</param>
/// <param name="rayHitObjPosY">拖拽点偏移高度</param>
/// <param name="rayHitObjOffset">拖拽对象中心点与拖拽点的偏移</param>
/// <returns></returns>
public Vector3 RaycastOnTable(PointerEventData eventData, Transform transform, float rayHitObjPosY, Vector3 rayHitObjOffset)
/// <summary>
/// 判断元器件/部件是否在桌面范围
/// </summary>
/// <param name="labTransform"></param>
/// <returns></returns>
public bool IsOnTable(Transform labTransform)
/// <summary>
/// 检查元器件是不是在桌面上(避免在桌面下)
/// </summary>
/// <param name="dragObject"></param>
public static void CheckObjectOnTable(LabBaseObject dragObject)
```
## 移动组件-元器件规范 ##
移动组件对元器件制作有一些规范要求:
<1> 元器件中心点需要位于底部中心点,注意是所有部件的底部中心。对于可改变部件样式的元器件(如干簧管演示盒),在改变样式之后也需要保证中心点位于底部中心。
<2> 避免元器件部件碰撞器缺失,同时注意尽量少使用Mesh Collider。
<3> 元器件部件的碰撞器尽量反映部件大小,特别是轮廓部件。
## 移动组件-自定义部分 ##
<1> 元器件自定义
元器件移动计算,按照通用规则:将元器件内的所有Collider计算得到包围盒,所有元器件都可被爬升,投射距离以包围盒相接触为准。
同时也提供元器件可自定义部分,以适配不同的元器件。
对于不参与爬升的部件,可将其Tag设置为"IgnoreCascade"。
```c
/// <summary>
/// 检测包围盒,元器件可自定义自身包围盒
/// </summary>
public virtual Bounds DetectBounds
/// <summary>
/// 包围盒与中心点的偏移
/// </summary>
public virtual Vector3 BoundsOffset
/// <summary>
/// 检测包围盒角度
/// </summary>
public virtual Quaternion DetectRotation
/// <summary>
/// 是否支持爬升,如连接中的导线不支持爬升
/// </summary>
/// <param name="dragObject">正在拖拽对象</param>
/// <returns></returns>
public virtual bool CanBeClimbed(LabBaseObject dragObject)
/// <summary>
/// 爬升时表面间隙(负代表嵌入),方便元器件间交互
/// </summary>
/// <returns></returns>
public virtual float EmbedDepth()
```
<2> 移动组件自定义
移动组件LabDragMove(LabObjDragMove & LabObjPartDragMove),提供拖拽事件监听和优先级处理。
LabObjDragMove:用于元器件做整体移动的组件,挂载元器件根节点,支持靠近、爬升、层叠。
LabObjPartDragMove:用于元器件部件做靠近、爬升操作,方便元器件部件进行更好的交互,但不提供层叠和更多的自定义行为。
下面是针对拖拽提供的可监听事件:
```c
/// <summary>
/// 拖拽开始事件
/// </summary>
public DragMoveBeginEvent onDragMoveBegin
/// <summary>
/// 拖拽中事件
/// </summary>
public DragMoveEvent onDragMove
/// <summary>
/// 拖拽结束事件
/// </summary>
public DragMoveEndEvent onDragMoveEnd
```
移动组件同时在拖动中和拖动结束,提供优先级判断方法:
```c
/// <summary>
/// 拖拽优先级判断(元器件/部件是否接管)
/// </summary>
public Func<bool> OnDragMoveFunc;
/// <summary>
/// 拖拽结束优先判断(元器件/部件是否接管)
/// </summary>
public Func<bool> OnEndDragFunc;
```
注:上述两个优先级判断委托,如果返回true则交由元器件/部件处理,移动组件不做爬升和下落判断;如果返回false则由移动组件做爬升和下落判断。
<3> 元器件操作自适应
针对改变元器件位置和方向(元器件旋转、影响其他元器件层叠状态的旋转/平移)的操作,需要元器件进行自适应,因为无法逐个元器件频繁去更新层叠关系(消耗非常大)。
LabBaseObject基类提供两个刷新层叠关系的函数,提供给元器件主动来调用。
```c
/// <summary>
/// 更新层叠关系(通知其上所有元器件)
/// </summary>
/// <param name="offsetY">自身高度变化,需要掉落测试的偏移高度(默认抬高1cm)</param>
public void UpdateCascading(float offsetY = 0.01f)
/// <summary>
/// 更新层叠关系(通知自身刷新层叠关系)
/// </summary>
public void UpdateSelfCascading()
```
注:仅在必要时(需要刷新层叠关系)调用,切不可在帧回调里调用。[[Category:组件]]