ことれいのもり

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

はじめに

OpenSiv3Dでメニューバーを使っていると、UIをクリックしたつもりが背後のゲーム処理まで動いてしまうことがあります。

以前の記事では、「メニューバーの高さを使ってクリック処理をスキップする」ことで子の問題を回避しました。

この方法は、メニュー項目が1つだけのときには有効です。

しかし、メニューバーにサブ項目を追加したり、項目数が増えたりすると表示される範囲が動的に変わります。

このとき、単純に高さで判定する方法では対応できません。

今回はそんな場面でも使える、「SimpleMenuBar::update()の戻り値を使う方法」を紹介します。

判定方法の考え方

それでは、update()の戻り値 を使う方法を見ていきましょう。

公式のドキュメントに次のように書かれています。

  • .update() の戻り値は Optional<MenuBarItemIndex> 型です。メニューの項目が選択された場合はその項目インデックス、選択されなかった場合は無効値です

この戻り値を使えば、「メニューバーが操作されているか?」をフラグとして扱うことができ、貫通してクリックされることを防げます!

実装例

# 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"開く", U"保存" } },
        { U"編集", { U"コピー", U"貼り付け" } }
    };
    SimpleMenuBar menuBar{ items };
    Optional<MenuBarItemIndex> item;

    while (System::Update())
    {
        camera.update();

        menuBar.draw();

        // この戻り値でメニューバーをクリックしているかが分かる
        item = menuBar.update();

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

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

            }
        }

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

        // メニューバーがクリックされているかの判定
        if (item)
        {
            // メニュー操作中なので、後ろの処理はしない
            continue;
        }

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

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

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

ポイント:メニューバー操作中の処理をスキップする

ポイントは、meunBar.update()の戻り値を確認する部分です。

        // メニューバーがクリックされているかの判定
        if (item)
        {
            // メニュー操作中なので、後ろの処理はしない
            continue;
        }

以前はここで「マウスの座標がメニューバーの高さより上かどうか」で判定していましたが、今回はメニューバーのクリック発生をチェックしています。

ただし、注意点があります。

それは、このupdate()の戻り値が「メニューバーが開いているかどうか」を直接表しているわけではありません。

あくまで、「そのフレームでメニュー項目がクリックされたかどうか」を返しているだけです。

しかし実際には、ユーザーがメニューを操作しているとき(項目を選んだ瞬間)にのみ値が返るため、メニュー操作中かどうかを判定する目的では十分に機能します。

そのため、今回のような「メニュー操作時に後ろの処理をスキップしたい」というケースでは、この戻り値を使うことで簡単に実現できるのです。

おわりに

OpenSiv3Dには「メニューバーが開かれているか?」を判定する関数がありませんが、update()の戻り値を活用することで、UI部分とゲーム部分の干渉を避けることができます。

もし他にもOpenSiv3Dの開発で困った場面があれば、今回のように既存の挙動を逆算して判定するという考え方が役立つかもしれません。

今後の開発のヒントになれば幸いです!

参考リンク

OpenSiv3D GUI 38.12メニューバー