虚無をクリックできるようにする

お世話になっております。クラスター株式会社でUnityエンジニアをしているRDAGでございます。
本記事はクラスター Advent Calendar 2023(2ページ目)の22日目の記事です!

前回は @MIRINPR さんの「メタバース空間で「におわせ写真は撮れるのか」を検証しました②」です。2022年の前編と合わせてお楽しみください。


右クリックに思いを馳せる

今回はUnityで役に立つのか立たないのか分からない自由研究をしていきます。
突然ですが皆さんは右クリックを活用されていますか?
いろんなところで右クリックすると場所に応じたコンテキストメニューが出てきて、色々出来てなんか便利ですよね。

ところで、右クリックしてみて「いややっぱり用事ないな」という時、どうやってコンテキストメニューを閉じていますか?
何もない所をクリックしますよね。

そうです。
虚無をクリックしていますよね。

という事で作っていきましょう。

本題の前にシンプルに作ってみる

凝ったことをせずシンプルに作るなら、

  1. 右クリック時にコンテキストメニューを出しつつ、同時に透明な全画面ボタンを出す

  2. 全画面ボタンが押されたらコンテキストメニューと全画面ボタンを非表示にする

という感じになると思います。
ただこのアプローチの場合、全画面ボタンの後ろにある要素は一時的に操作できない状態になります。
操作をブロックしていることを伝えるために全画面ボタンにちょっと色を付けておくなど、ユーザに伝える工夫をした方がよさそうです。

これでも十分な気はしますが、chromeブラウザやwindowsのエクスプローラーを見ているとコンテキストメニューの裏でも操作を完全にブロックしているわけではなさそうです。
虚無をクリックできるようにして、この操作感に近づけていきます。

クリックできる虚無

違う目的でクリックイベントの制御をしているコードを見かけたので参考にしていきます。

(kameffeeさんはUnity1週間ゲームジャムで書いたコードを公開されていたりして日頃からめちゃくちゃ参考にさせていただいています。感謝……)

考え方としては、自分がクリックされたら同じクリックイベントでraycastをもう一度飛ばして、自分を貫通した先に誰かいればそれのイベントを呼ぶ……という感じです。

public class SimpleClickable : MonoBehaviour, IPointerClickHandler
{
    readonly List<RaycastResult> raycastResults = new();
    public UnityEvent onClick;

    void IPointerClickHandler.OnPointerClick(PointerEventData eventData)
    {
        var child = ExecuteChildEvent(eventData);
        if (child != null)
        {
            ExecuteEvents.Execute(child, eventData, ExecuteEvents.pointerClickHandler);
        }
        onClick?.Invoke();
    }

    GameObject ExecuteChildEvent(PointerEventData eventData)
    {
        // 自分のクリックイベントの情報でもう一回raycast
        EventSystem.current.RaycastAll(eventData, raycastResults);

        // 1つは自分自身なので2つ目を取得(EventSystem側でsortされているので[1]を取ればよい)
        if (raycastResults.Count <= 1)
        {
            return null;
        }
        var raycastResult = raycastResults[1].gameObject;

        // 今回はOnPointerClickを伝搬させたいのでitnerfaceを探してみる
        // InParentをしないと、Buttonの子要素のTextをクリックしてもButtonのイベントが発火しない
        var selectable = raycastResult.GetComponentInParent<IPointerClickHandler>();
        if (selectable != null)
        {
            return (selectable as Component)?.gameObject;
        }

        return raycastResult;
    }
}

シンプル版と微妙に動きが違うのが分かるでしょうか。コンテキストメニューを消すために左クリックした時、下層にイベントが伝搬するので「ボタンおした数」が加算されるようになっています。

もうちょっと本格的な虚無にする

上の実装で雰囲気は伝わったかな~と思いますが、Unityらしい挙動を再現していくにはもう少し手間が必要になったりします。
Unityらしい動作というのは以下のような奴です。

  • ボタンを押しながらマウスカーソルを外してボタン外で話すとクリックイベントは発火しない

  • スクロールビューの中にあるボタンをドラッグするとスクロール側の判定になる

これらを再現するときには、虚無がIPointerEnterやIPointerExitを塞いでしまう点を考慮する必要があります。
虚無のOnPointerMoveを起点にraycastしたり、PointerDownしたものを覚えておいてPointerUpを個別に渡してあげるなど、ちまちました実装をしていく形になると思います。

// 虚無越しにボタンホバー時のハイライトを発火できるようにする追加コード
// (イメージ)
void IPointerMoveHandler.OnPointerMove(PointerEventData eventData)
{
    var child = ExecuteChildEvent<IPointerEnterHandler>(eventData);
    if (hoverChild != child && hoverChild != null)
    {
        // 直前のhoverとちがっていればイベント発火
        ExecuteEvents.Execute(hoverChild, eventData, ExecuteEvents.pointerExitHandler);
    }
    if (child == null) {
        return;
    }
    ExecuteEvents.Execute(child, eventData, ExecuteEvents.pointerEnterHandler);
    hoverChild = child;
}

gifでホバー時に色が変わってないタイミングがありますが、これはボタンがSelectedステートに入ったためにホバー時に見た目が変わらなくなった……という感じです。NavigationをNoneにすれば見た目は直ります(脳筋解決)。

いかがでしたか欄

という事で虚無の作り方でした。はたしてUnityで虚無をクリックできることで本当に嬉しいのか……?みたいな所はありますが、そこは自由研究なので目をつむっていただけたらと思います。
活用方法を思いついた方がいたらこっそり教えてください。

明日はSoraさんの「米農家による稲作解説」です。たくさん食べて年越しに備えましょう!


この記事が気に入ったらサポートをしてみませんか?