5
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【Axmol Engine】 バイブコーディングで脱出ゲームを作る[ 4 ]:ノードを使用してポップアップを実装する

5
Last updated at Posted at 2026-06-23

はじめに

こちらの記事は、Axmol Engine なるものを使ってバイブコーディングでどこまでできるかを、検証と勉学を兼ねた試験的な目論見となります!

前回の記事では bg_title.pngbg_stage_001.png のフェード切替を実装しました。

今回は続きとして、ギミックへの移動と、ギミック操作をポップアップ上で行いたいので、以下を実装します。

建物扉前

ポストをタップしてポストへ近接

更にポストをタップするとギミック用のポップアップを表示(今回は表示のみ実装)
ポップアップには黒50%透過背景・コンテンツ・戻るボタンを配置

スクリーンショット 2026-06-23 18.55.44.png

ポップアップ表示でそれ以外の機能を抑制したい時に便利です
 ・ギミックを表示
 ・設定を表示
 ・メッセージを表示
など

背景画像は ChatGPT(DALL·E)で生成し、Content/ 以下に配置しました。


使用アセット

ファイル 説明
bg_stage_002.png (1870×841) ポストのアップ(ChatGPT で生成)
frame_popup.png (900×600) ポップアップ枠(セーフゾーン、戻るボタンの高さを考慮)
dial/base.png (640×480) ダイヤル背景(ポップアップ枠内に収まるように)
btn_back.png (150×100) 戻るボタン(ポップアップを閉じる)

1. ステート管理を bool から int に変更

前回は bool _bgTransitioned で遷移済みフラグを管理していましたが、
ステージが増えたので int _stage に置き換えます。

// MainScene.h
private:
    int  _stage = 0;  // 0:bg_title  1:bg_stage_001  2:bg_stage_002
    void showPopup();

2. init() に bg_stage_002 を追加

プロンプト:「bg_stage_001が表示されている状態のとき、添付のポスト領域をタップすると、bg_stage_002.pngを表示」

添付した画像
スクリーンショット 2026-06-23 13.13.38.png

bg_stage_001 と同じ要領で、opacity=0z=2tag=102 で配置します。

auto bgStage2 = Sprite::create("bg_stage_002.png");
if (bgStage2)
{
    bgStage2->setAnchorPoint(Vec2::ANCHOR_MIDDLE);
    float scale = visibleSize.height / bgStage2->getContentSize().height;
    bgStage2->setScale(scale);
    bgStage2->setPosition(Vec2(origin.x + visibleSize.width / 2,
                               origin.y + visibleSize.height / 2));
    bgStage2->setOpacity(0);
    bgStage2->setTag(102);
    this->addChild(bgStage2, 2);
}

        // bg_stage_001: ポスト領域タップで bg_stage_002 へ
        auto bgStage = this->getChildByTag(101);
        if (!bgStage)
            return;

        auto local = bgStage->convertToNodeSpace(touches[0]->getLocation());
        if (!ax::Rect(250, 41, 350, 510).containsPoint(local))
            return;

想定したタップ領域と異なりましたので、ここは手動で修正しました。


3. タップ有効領域のデバッグ枠

プロンプト:「タップ領域に3pxの赤いボーダーを表示」

DrawNode をスプライトの 子ノード として追加することで、スプライトのスケールやフェードを自動継承させます。

// bgStage2 の addChild 直後に追加
auto debugPost2 = DrawNode::create();
debugPost2->drawRect(Vec2(550, 51), Vec2(1120, 811), Color::RED);
bgStage2->addChild(debugPost2, 1);

4. onTouchesBegan の多段階対応

_stage の値でタップ処理を分岐させます。
ポップアップ表示中は getChildByTag(200) で早期リターンしてタップを無視します。

void MainScene::onTouchesBegan(const std::vector<ax::Touch*>& touches, ax::Event* event)
{
    // ポップアップ表示中はタップを無視
    if (touches.empty() || this->getChildByTag(200))
        return;

    constexpr float duration = 0.6f;

    if (_stage == 0)
    {
        // bg_title → bg_stage_001
        auto bgTitle = this->getChildByTag(100);
        if (!bgTitle) return;

        auto local = bgTitle->convertToNodeSpace(touches[0]->getLocation());
        auto cs    = bgTitle->getContentSize();
        if (!ax::Rect(cs.width * 0.35f, 0, cs.width * 0.50f, cs.height).containsPoint(local))
            return;

        _stage = 1;
        bgTitle->runAction(FadeOut::create(duration));
        if (auto s = this->getChildByTag(101))
            s->runAction(FadeIn::create(duration));
    }
    else if (_stage == 1)
    {
        // bg_stage_001 → bg_stage_002(ポスト領域)
        auto bgStage = this->getChildByTag(101);
        if (!bgStage) return;

        auto local = bgStage->convertToNodeSpace(touches[0]->getLocation());
        if (!ax::Rect(250, 41, 350, 510).containsPoint(local))
            return;

        _stage = 2;
        bgStage->runAction(FadeOut::create(duration));
        if (auto s = this->getChildByTag(102))
            s->runAction(FadeIn::create(duration));
    }
    else if (_stage == 2)
    {
        // bg_stage_002 → ポップアップ(ポスト領域)
        auto bgStage2 = this->getChildByTag(102);
        if (!bgStage2) return;

        auto local = bgStage2->convertToNodeSpace(touches[0]->getLocation());
        if (!ax::Rect(550, 51, 570, 760).containsPoint(local))
            return;

        showPopup();
    }
}

ここは実装を進めるほど同じような処理が続きそうですので、追々リファクタリング予定です


5. showPopup() の実装

Node(Axmolのオブジェクトです)を利用してポップアップを実装するために下記プロンプトを送信

スクリーンショット 2026-06-23 13.52.25.png

プロンプト:「bg_stage_002.pngが表示されているとき、添付の画像にマッチする領域がタップされたら、ノードを作成して、frame_popup.pngを中央に表示 ノードより下の要素はタップできないように」

5-1. 全画面オーバーレイノードを作成

tag=200 を付与することで、getChildByTag(200) でポップアップの表示状態を判定できます。

void MainScene::showPopup()
{
    auto visibleSize = _director->getVisibleSize();
    auto origin      = _director->getVisibleOrigin();

    auto overlay = Node::create();
    overlay->setContentSize(visibleSize);
    overlay->setAnchorPoint(Vec2::ANCHOR_BOTTOM_LEFT);
    overlay->setPosition(origin);
    overlay->setTag(200);
    this->addChild(overlay, 10);

5-2. 黒50%透過の背景

プロンプト:「ノードの背景は黒の50%透過にしてください」

DrawNode::drawSolidRect では alpha が正しく反映されないケースがあったため、
LayerColorSprite のサブクラス)を使うと確実です。

    auto bgLayer = LayerColor::create(Color32(0, 0, 0, 128),
                                      visibleSize.width, visibleSize.height);
    overlay->addChild(bgLayer, -2);

5-3. コンテンツ(dial/base・frame_popup)を中央に配置

    auto base = Sprite::create("dial/base.png");
    if (base)
    {
        base->setAnchorPoint(Vec2::ANCHOR_MIDDLE);
        base->setPosition(Vec2(visibleSize.width / 2, visibleSize.height / 2 + 50));
        overlay->addChild(base, -1);
    }

    auto popup = Sprite::create("frame_popup.png");
    if (popup)
    {
        popup->setAnchorPoint(Vec2::ANCHOR_MIDDLE);
        popup->setPosition(Vec2(visibleSize.width / 2, visibleSize.height / 2 + 50));
        overlay->addChild(popup, 0);
    }

5-4. 戻るボタンを下部中央に配置

プロンプト:「ノードの中央下にbtn_back.pngをボタンとして配置して、タップされたらノードを閉じてください」

removeChildByTag(200) でオーバーレイを削除するとリスナーも自動的に解除されます。

    auto backItem = MenuItemImage::create(
        "btn_back.png", "btn_back.png",
        [this](Object*) { this->removeChildByTag(200); });
    if (backItem)
    {
        float margin = 20.0f;
        backItem->setPosition(Vec2(visibleSize.width / 2,
                                   margin + backItem->getContentSize().height / 2));
        auto backMenu = Menu::create(backItem, nullptr);
        backMenu->setPosition(Vec2::ZERO);
        overlay->addChild(backMenu, 1);
    }

5-5. タッチをスワローして背後をタップ不可にする

EventListenerTouchOneByOnesetSwallowTouches(true) を設定します。
overlay(z=10)の子として登録することで、より上位の描画レイヤーが優先してタッチを処理します。

    auto listener = EventListenerTouchOneByOne::create();
    listener->setSwallowTouches(true);
    listener->onTouchBegan = [](Touch*, Event*) -> bool { return true; };
    _eventDispatcher->addEventListenerWithSceneGraphPriority(listener, overlay);
}

成果物

廃墟の扉の前
スクリーンショット 2026-06-23 19.02.48.png

ポスト付近をタップしてポストに近接
スクリーンショット 2026-06-23 19.02.56.png

ポストをタップしてダイヤル錠ギミックをポップアップで表示(下の三角のボタンで閉じる)
スクリーンショット 2026-06-23 19.03.07.png


まとめ

やったこと 使った API / 手法
多段階ステート管理 int _stage
背景の多段階フェード FadeOut / FadeIn + getChildByTag
ローカル座標でのヒットテスト convertToNodeSpace / Rect::containsPoint
デバッグ枠(子ノード継承) DrawNode::drawRect(スプライトの子に追加)
半透明オーバーレイ LayerColor::create(Color32(r,g,b,a), w, h)
モーダルタッチブロック EventListenerTouchOneByOne + setSwallowTouches(true)
ポップアップを閉じる removeChildByTag でノードごと削除(リスナーも自動解除)

次は dial/base.png 上でダイヤル操作を実装する予定です。

作成した画像

ChatGPT(DALL·E)でそれぞれ作成しました。

bg_stage_002.png (1870×841) | ポストのアップ(ChatGPT で生成)
bg_stage_002.png

frame_popup.png (900×600) | ポップアップ枠
frame_popup.png

dial/base.png (640×480) | ダイヤル背景(ポップアップ枠内に収まるように)
base.png

btn_back.png (150×100) | 戻るボタン(ポップアップを閉じる)
btn_back.png

5
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?