存在重叠的WordSpace Canvas时,UIPointer射线会穿透Canvas,导致触发错误。

问题探究

解决方法直接跳到最后“解决方法”部分。

测试场景如下图,有两个重叠的Canvas,分别叫做Canvas3与Canvas4,它们下面各有一个按钮。

场景层级

Canvas上挂一脚本如下:

    void Start(){
        if(this.GetComponentInChildren<Button>()){
            this.GetComponentInChildren<Button>().onClick.AddListener(this.onBtnClick);
        }
    }
    void onBtnClick(){
        Debug.Log("it's" + this.name);
    }

运行后射线指向Canvas3,射线仿佛穿透了Canvas3,打印出的信息是Canvas4。

Log信息

VR与UGUI交互肯定通过EventSystem进行,通过VRTK_UIPointer脚本向底层找,找到了检测碰撞的代码。

代码把射线出发点、方向传递给EventSystem,通过EventSystem.RaycastAll获取射线指到的UI:

//VRTK_VRInputModule.cs
protected virtual List<RaycastResult> CheckRaycasts(VRTK_UIPointer pointer)
{       
    RaycastResult raycastResult = new RaycastResult();
    raycastResult.worldPosition = pointer.GetOriginPosition();
    raycastResult.worldNormal = pointer.GetOriginForward();
    pointer.pointerEventData.pointerCurrentRaycast = raycastResult;

    List<RaycastResult> raycasts = new List<RaycastResult>();
    eventSystem.RaycastAll(pointer.pointerEventData, raycasts);
    return raycasts;
}

既然如此,打印下碰撞结果:

Debug.Log("碰撞到UI了!,共碰到了" + raycasts.Count);
for (int i = 0; i < raycasts.Count; i++)
{
    Debug.Log(raycasts[i].gameObject.name,raycasts[i].gameObject);
}

运行结果

发现返回结果只有Canvas4下的两个组件(截图省略了VRTK自动创建的drag组件)。

关于EventSystem.RaycastAll,官方文档就一句话:使用所有设置过的BaseRaycaster进行碰撞检测。

翻了翻Unity的开源代码,没有找到EventSystem.RaycastAll的具体实现。后来又试着修改了参数属性值,调整了Layer等,也没有作用。

总结:EventSystem.RaycastAll并没有直接返回All碰撞,(也许)按照内部的排序逻辑,把它认为正确的物体吐了出来。

解决方法

  1. 概率性解决
    将这些重叠Canvas上挂载的Graphic Raycaster组件的Blocking Objects修改为All
    Graphic Raycaster这是概率性解决穿透的方法,有的面板管用,有的面板不好用。
    改为All也无效时,运行后将后面的面板关闭再打开,基本可以解决问题,原因未知,也许和内部排序逻辑有关。

  2. 斩草除根
    在设计阶段避免重叠Canvas同时出现的情况。如果必须出现,那就自求多福吧。

  3. 坐以待毙
    等待着Unity出来给个解释。

我来吐槽

*

*