ことれいのもり

OpenSiv3Dのメニューバーでクリックイベントが貫通する?よくある誤解と対処法【高さ判定編】

はじめに

OpenSiv3Dでメニューバーを使っていて、「UIをクリックしたのに、なぜか背後のゲーム画面までクリックしたことになってしまう」という現象に遭遇したことはありませんか?

これは、クリックイベントがUIで遮られずに背後にまで届いてしまうために見えますが、実は単純なミスの場合が多いです。

この記事では、この現象の真相と正しいクリック判定の制御方法を紹介します。

よくある誤解

私にも経験があるのですが、「メニューバーを表示しているのに、クリックが背後まで貫通して処理される」と勘違いしてしまいがちです。

しかし、OpenSiv3DのSimpleMenuBar自体はクリックイベントを適切に処理しているので、UIの背後にクリックが通り抜けることはありません。

ではなぜ、クリックが貫通したように見えるのでしょうか?

原因は、自作したゲーム側のクリック処理がメニューバーの領域を考慮しない設計になっているからです。

例えば、メニューバーが表示されている領域にマウスカーソルがあっても、MouseL.down()を使ったクリック判定を実行している場合、UIをクリックしても背後のゲーム処理が走ってしまいます!

これにより、「貫通している」と錯覚してしまうのです。

正しいクリック制御方法:メニューバーの高さ範囲を無視する

これを防ぐには、メニューバーの高さ範囲内にマウスがある場合は、ゲーム側のクリック処理をスキップする判定を入れるだけで解決できます。

以下はその対応を施したサンプルコードです。

# include <Siv3D.hpp>

// メニューバーの高さ(SimpleMenuBarの高さは取得できないため手動指定)
double menuBarHeight;

void Main()
{
    // メニューバーのアクティブ状態(メニューが開いているとき)
    bool isMenuBarActive = false;

    // デバッグカメラ(簡易的な3Dカメラ)
    const MSRenderTexture renderTexture{ Scene::Size(), TextureFormat::R8G8B8A8_Unorm_SRGB, HasDepth::Yes };
    DebugCamera3D camera{ renderTexture.size(), 30_deg, Vec3{ 10, 16, -32 } };

    // メニューバーの項目
    Array<std::pair<String, Array<String>>> items = {
            { U"ファイル" },
            { U"編集"}
    };
    SimpleMenuBar menuBar{ items };
    Optional<MenuBarItemIndex> item;

    // メニューバーの高さを取得
    menuBarHeight = menuBar.MenuBarHeight;

    while (System::Update())
    {
        camera.update();
        menuBar.draw();
        item = menuBar.update();

        // メニューバーを表示
        if (item)
        {
            // 「ファイル」
            if (item->menuIndex == 0)
            {

            }
            // 「編集」
            else if (item->menuIndex == 1)
            {

            }
        }

        // -----------------------------
        // クリック貫通防止処理ここから
        // -----------------------------

        // マウスがメニューバーの高さより上(=メニューの上)にある場合は無視
        if (Cursor::PosF().y < menuBarHeight)
        {
            // メニュー操作中なので、後ろの処理はスキップ
            continue;
        }

        // -----------------------------
        // メニュー以外のクリック処理
        // -----------------------------

        // 例:左クリックで何かを配置したい処理
        if (MouseL.down())
        {
            Print << U"クリック処理実行";
        }

        // デバッグカメラビューを描画
        camera.update(2.0);
    }
}

ポイント

ポイントはメニューバーの高さより上にマウス座標が存在するときは、スキップする判定部分です。

if (Cursor::PosF().y < menuBarHeight)
        {
            // メニュー操作中なので、後ろの処理はスキップ
            continue;
        }

マウスクリック処理はこの処理を入れた後に行ないましょう!

おわりに

実は私自身、この記事を作成するまで「SimpleGUIのMenuBarってクリックイベントを貫通するんだ~」と思っていました!

普通に考えたらそんな設計な訳がないのですが、自分のコードのせい、という考えがなかなか思いつかないものです。

この記事をきっかけに同じような人が気づいてくれたら嬉しいです。

これでメニューバーのクリック処理は解決したかと思いきや、「その下にアイテムリストが来たときはどうするの?」という問題も残っています。

この問題については別の記事で解説しようと思っています。

参考リンク

OpenSiv3D GUI 38.12メニューバー