はじめに
この記事は、Axmol Engineを使ってバイブコーディングでどこまでできるかを、検証と勉学を兼ねて試している連載です。
- 第1回【Axmol Engine】 バイブコーディングで脱出ゲームを作る[ 1 ]:スプラッシュ画面シーンを実装する
- 第2回【Axmol Engine】 バイブコーディングで脱出ゲームを作る[ 2 ]:タイトルシーンを作って画像を表示する(iPhone/Android対応)
- 第3回【Axmol Engine】 バイブコーディングで脱出ゲームを作る[ 3 ]:タップした領域への移動
- 第4回【Axmol Engine】 バイブコーディングで脱出ゲームを作る[ 4 ]:ノードを使用してポップアップを実装する
- 第5回【Axmol Engine】 バイブコーディングで脱出ゲームを作る[ 5 ]:テクスチャアトラスでダイヤル錠UIを実装する
- 第6回【Axmol Engine】 バイブコーディングで脱出ゲームを作る[ 6 ]:リファクタリング(メモリ消費対応、ステージをJSON管理、処理修正)+戻るボタン設置
- 第7回【Axmol Engine】 バイブコーディングで脱出ゲームを作る[ 7 ]:ローカルストレージでステージ進捗管理・ギミックをJSONで管理
- 第8回【Axmol Engine】 バイブコーディングで脱出ゲームを作る[ 8 ]:アイテムシステムの実装1 - アイテムの取得 -
- 第9回【Axmol Engine】 バイブコーディングで脱出ゲームを作る[ 9 ]:アイテムシステムの実装2 - アイテムの選択・表示 -(今回)
前回の記事ではアイテムの定義・配置・取得・所持パネル表示までを実装しました。今回はアイテムの選択トグル・長押しによるポップアップ表示・詳細ビューの実装を行います。
用意した素材(ChatGPTが作成)
アイテムポップアップのフレーム枠(frame_item_popup.png)です。
アイテムのポップアップ用画像です。
1. アイテム選択トグルの実装
プロンプト:
右側のアイテムスロットのアイテムが選択されたら
- アイテム画像を白い3pxのボーダーで囲む
- ローカルストレージの"currentItem" にアイテム番号をセット
選択中のアイテムが再度タップされたら
- アイテム画像を囲む白いボーダーを削除
- ローカルストレージの"currentItem"に"0"をセット
refreshItemPanel() の末尾でタッチリスナーを登録します。タップでアイテムを選択・解除し、"currentItem" をローカルストレージに保存します。
選択中のアイテムには DrawNode で白枠を描画します。refreshItemPanel() のスプライト追加部分に追記します。
if (key == currentItem)
{
auto border = DrawNode::create();
border->drawRect(Vec2(0, 0), Vec2(cs.width, cs.height),
Color(1.0f, 1.0f, 1.0f, 1.0f), 3.0f);
sp->addChild(border);
}
タッチリスナーは refreshItemPanel() の末尾に追加します。
auto touchedKey = std::make_shared<std::string>();
auto longPressed = std::make_shared<bool>(false);
_eventDispatcher->removeEventListenersForTarget(panel);
auto panelListener = EventListenerTouchOneByOne::create();
panelListener->setSwallowTouches(true);
panelListener->onTouchBegan = [this, panel, keys, touchedKey, longPressed](Touch* touch, Event*) -> bool {
*touchedKey = "";
*longPressed = false;
for (const auto& key : keys)
{
auto* sp = panel->getChildByTag(std::stoi(key));
if (!sp) continue;
auto local = sp->convertToNodeSpace(touch->getLocation());
auto cs = sp->getContentSize();
if (Rect(0, 0, cs.width, cs.height).containsPoint(local))
{
*touchedKey = key;
return true;
}
}
return false;
};
panelListener->onTouchEnded = [this, touchedKey, longPressed](Touch*, Event*) {
if (touchedKey->empty() || *longPressed)
return;
this->unschedule("item_longpress");
auto* ud = ax::UserDefault::getInstance();
std::string current{ud->getStringForKey("currentItem", "0")};
if (current == *touchedKey)
ud->setStringForKey("currentItem", "0");
else
ud->setStringForKey("currentItem", touchedKey->c_str());
ud->flush();
refreshItemPanel();
*touchedKey = "";
};
panelListener->onTouchCancelled = [this, touchedKey, longPressed](Touch*, Event*) {
this->unschedule("item_longpress");
*touchedKey = "";
*longPressed = false;
};
_eventDispatcher->addEventListenerWithSceneGraphPriority(panelListener, panel);
2. 長押しでアイテムポップアップを表示
プロンプト:
画面右のアイテムスロットゾーンのアイテムを1秒長押ししたら、アイテムポップアップを表示
プロンプト:
経過時間が1000ms経ったら強制的にポップアップを表示
onTouchEnded を待たずに scheduleOnce で1秒後に自動発火させます。onTouchBegan に追記します。
*touchedKey = key;
this->scheduleOnce([this, key, longPressed](float) {
*longPressed = true;
this->removeChildByTag(200);
showItemPopup(key);
}, 1.0f, "item_longpress");
return true;
longPressed フラグが true のとき onTouchEnded の選択トグル処理をスキップすることで、長押し後に指を離しても選択状態が変わりません。
プロンプト:
アイテムポップアップが表示中でも、アイテム選択トグルができるように。また、長押しの場合は一度アイテムポップアップを破棄してから再度表示
当初はポップアップ表示中のタップを getChildByTag(200) で弾いていましたが、このガードを撤廃しました。代わりに長押し時のコールバックで removeChildByTag(200) してから再表示することで、ポップアップ表示中の長押し再表示にも対応しています。
ポップアップ表示中のアイテムに対して、アイテムを使う、を想定してこのような形を取りました
3. アイテムポップアップの実装
プロンプト:
Content/items/item_01_1_large.png を配置しました。
items.json に "image_large" フィールドを追加しました。
各アイテムをタップしたら
- frame_item_popup.png を使用したポップアップを表示。
- image_large の画像を表示。
- btn_back.png も配置してタップでポップアップを閉じるように。
アイテムのポップアップの枠は frame_item_popup.png にしてください。
アイテム画像の下にフォントサイズ24px、白、中央寄せでアイテム名を表示。
右側のアイテムスロットゾーンはポップアップのz軸より上に表示。
items.json に "image_large" フィールドを追加します。
{
"1": {
"name": "金の鍵",
"image": "items/item_01_1.png",
"image_large": "items/item_01_1_large.png"
}
}
ポップアップは tag=200 の Node として z=10 に追加します。パネル(z=11)はポップアップより上に留まります。
void MainScene::showItemPopup(const std::string& itemNo)
{
auto it = s_itemDefs.find(itemNo);
if (it == s_itemDefs.end())
return;
const auto& def = it->second;
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);
auto bgLayer = LayerColor::create(Color32(0, 0, 0, 128),
visibleSize.width, visibleSize.height);
overlay->addChild(bgLayer, -2);
// ポップアップ枠
auto popup = Sprite::create("frame_item_popup.png");
if (popup)
{
popup->setAnchorPoint(Vec2::ANCHOR_MIDDLE);
popup->setPosition(Vec2(visibleSize.width / 2, visibleSize.height / 2 + 50));
overlay->addChild(popup, 0);
}
const float centerY = visibleSize.height / 2 + 50;
// アイテム大画像
if (!def.imageLarge.empty())
{
auto largeSp = Sprite::create(def.imageLarge);
if (largeSp)
{
largeSp->setAnchorPoint(Vec2::ANCHOR_MIDDLE_BOTTOM);
largeSp->setPosition(Vec2(visibleSize.width / 2, centerY - 100));
overlay->addChild(largeSp, 1);
}
}
// アイテム名ラベル
auto label = Label::createWithSystemFont(def.name, "Arial", 24);
if (label)
{
label->setTextColor(Color32(255, 255, 255, 255));
label->setAlignment(TextHAlignment::CENTER);
label->setAnchorPoint(Vec2::ANCHOR_MIDDLE_TOP);
label->setPosition(Vec2(visibleSize.width / 2, centerY - 100 - 4));
overlay->addChild(label, 1);
}
// 閉じるボタン
auto backItem = MenuItemImage::create(
"btn_back.png", "btn_back.png",
[this](Object*) { this->removeChildByTag(200); });
if (backItem)
{
constexpr 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);
}
// タッチをスワロー(背景への透過タップを防止)
auto listener = EventListenerTouchOneByOne::create();
listener->setSwallowTouches(true);
listener->onTouchBegan = [](Touch*, Event*) -> bool { return true; };
_eventDispatcher->addEventListenerWithSceneGraphPriority(listener, overlay);
}
成果物
アイテム取得後、画面右のアイテム枠に「金の鍵」が表示されます。
「金の鍵」をタップすると選択状態になります。この状態で扉の鍵穴をタップすると、開錠されて扉を開けれるようになる、という想定です。
「金の鍵」を1秒長押しするとポップアップで表示されます。
まとめ
| 機能 | 実装 |
|---|---|
| アイテム選択・解除 |
UserDefault["currentItem"] + DrawNode で白枠 |
| 長押し判定 |
scheduleOnce で1秒後に自動発火 |
| ポップアップ重複防止 | 長押しコールバックで removeChildByTag(200) してから再表示 |
| パネルの前面固定 | パネル z=11、ポップアップ z=10 |






