はじめに
こちらの記事は、Axmol Engine なるものを使ってバイブコーディングでどこまでできるかを、検証と勉学を兼ねた試験的な目論見となります!
前回の記事では bg_title.png → bg_stage_001.png のフェード切替を実装しました。
- 第1回【Axmol Engine】 バイブコーディングで脱出ゲームを作る[ 1 ]:スプラッシュ画面シーンを実装する
- 第2回【Axmol Engine】 バイブコーディングで脱出ゲームを作る[ 2 ]:タイトルシーンを作って画像を表示する(iPhone/Android対応)
- 第3回【Axmol Engine】 バイブコーディングで脱出ゲームを作る[ 3 ]:タップした領域への移動
- 第4回【Axmol Engine】 バイブコーディングで脱出ゲームを作る[ 4 ]:ノードを使用してポップアップを実装する (今回)
今回は続きとして、ギミックへの移動と、ギミック操作をポップアップ上で行いたいので、以下を実装します。
建物扉前
↓
ポストをタップしてポストへ近接
↓
更にポストをタップするとギミック用のポップアップを表示(今回は表示のみ実装)
ポップアップには黒50%透過背景・コンテンツ・戻るボタンを配置
ポップアップ表示でそれ以外の機能を抑制したい時に便利です
・ギミックを表示
・設定を表示
・メッセージを表示
など
背景画像は 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を表示」
bg_stage_001 と同じ要領で、opacity=0・z=2・tag=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のオブジェクトです)を利用してポップアップを実装するために下記プロンプトを送信
プロンプト:「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 が正しく反映されないケースがあったため、
LayerColor(Sprite のサブクラス)を使うと確実です。
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. タッチをスワローして背後をタップ不可にする
EventListenerTouchOneByOne に setSwallowTouches(true) を設定します。
overlay(z=10)の子として登録することで、より上位の描画レイヤーが優先してタッチを処理します。
auto listener = EventListenerTouchOneByOne::create();
listener->setSwallowTouches(true);
listener->onTouchBegan = [](Touch*, Event*) -> bool { return true; };
_eventDispatcher->addEventListenerWithSceneGraphPriority(listener, overlay);
}
成果物
ポストをタップしてダイヤル錠ギミックをポップアップで表示(下の三角のボタンで閉じる)

まとめ
| やったこと | 使った 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 で生成)

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







