うにてぃブログ

主にUnityとC#に関する記事を書いていきます

【Unity】uGUI での Raycast 時に親子関係と GraphicRaycaster の影響によるイベント挙動の解説

同じ階層にある子供オブジェクトに対して、親にGraphicRaycasterが存在しない場合、Rayが下のオブジェクトではなく上のオブジェクトに当たる問題が発生します。

例えば、以下のヒエラルキーが考えられます。ここで、GraphicRaycasterが「Node Up」と「Node Down」にのみ付いている場合、イベントは「Node Up」側のオブジェクトで発生します。しかし、「Root」にもGraphicRaycasterが存在する場合、イベントは「Node Down」のオブジェクトで発生します。なぜ親の有無で異なるオブジェクトがイベントを受け取るのでしょうか?

この問題を解決するため、uGUIでRaycastを行った後に取得されるデータを確認します。

var hits = new List<RaycastResult>();
UnityEngine.EventSystems.EventSystem.current.RaycastAll(eventData, hits);
foreach (var hit in hits)
{
    Debug.Log(hit.ToString());
}

以下は取得されるデータの一部です。

上にあるオブジェクトの場合:

Name: Image (UnityEngine.GameObject)
module: Name: Node Up (UnityEngine.GameObject)
eventCamera: 
sortOrderPriority: 100
renderOrderPriority: 0
distance: 0
index: 0
depth: 0

下にあるオブジェクトの場合:

Name: Image (UnityEngine.GameObject)
module: Name: Node Down (UnityEngine.GameObject)
eventCamera: 
sortOrderPriority: 100
renderOrderPriority: 0
distance: 0
index: 1
depth: 1

実際にRayを飛ばしてソートする処理を見てみましょう。

uGUIのRaycastを行った後にソートされる処理は次のようになっています。

private static int RaycastComparer(RaycastResult lhs, RaycastResult rhs)
{
    if (lhs.module != rhs.module)
    {
        var lhsEventCamera = lhs.module.eventCamera;
        var rhsEventCamera = rhs.module.eventCamera;
        if (lhsEventCamera != null && rhsEventCamera != null && lhsEventCamera.depth != rhsEventCamera.depth)
        {
            // 標準的なcompareToを逆転させる必要があります
            if (lhsEventCamera.depth < rhsEventCamera.depth)
                return 1;
            if (lhsEventCamera.depth == rhsEventCamera.depth)
                return 0;

            return -1;
        }

        if (lhs.module.sortOrderPriority != rhs.module.sortOrderPriority)
            return rhs.module.sortOrderPriority.CompareTo(lhs.module.sortOrderPriority);

        if (lhs.module.renderOrderPriority != rhs.module.renderOrderPriority)
            return rhs.module.renderOrderPriority.CompareTo(lhs.module.renderOrderPriority);
    }

    // Renderer sorting
    if (lhs.sortingLayer != rhs.sortingLayer)
    {
        // レイヤー値を使用して、レイヤーの相対順序を適切に比較します。
        var rid = SortingLayer.GetLayerValueFromID(rhs.sortingLayer);
        var lid = SortingLayer.GetLayerValueFromID(lhs.sortingLayer);
        return rid.CompareTo(lid);
    }

    if (lhs.sortingOrder != rhs.sortingOrder)
        return rhs.sortingOrder.CompareTo(lhs.sortingOrder);

    // 2つのRaycast結果が同じルートキャンバスでなければ、深度を比較することは意味があります。
    if (lhs.depth != rhs.depth && lhs.module.rootRaycaster == rhs.module.rootRaycaster)
        return rhs.depth.CompareTo(lhs.depth);

    if (lhs.distance != rhs.distance)
        return lhs.distance.CompareTo(rhs.distance);

    return lhs.index.CompareTo(rhs.index);
}

親の GraphicRaycaster が同じ場合以下のifのソートになります、そのため depth が大きいほど優先順位が高くなります

if (lhs.depth != rhs.depth && lhs.module.rootRaycaster == rhs.module.rootRaycaster)


しかし、親のGraphicRaycasterが異なる場合、最後まで条件判定を通過せず、indexが小さいほど優先順位が高くなります。

この例では、上のオブジェクトのindexとdepthが0であり、下のオブジェクトのindexとdepthが1であるため、親のGraphicRaycasterの有無によってソート順が異なり、この結果が生じたことがわかります。