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】 バイブコーディングで脱出ゲームを作る[ 9 ]:アイテムシステムの実装2 - アイテムの選択・表示 -

5
Last updated at Posted at 2026-07-04

はじめに

この記事は、Axmol Engineを使ってバイブコーディングでどこまでできるかを、検証と勉学を兼ねて試している連載です。



前回の記事ではアイテムの定義・配置・取得・所持パネル表示までを実装しました。今回はアイテムの選択トグル・長押しによるポップアップ表示・詳細ビューの実装を行います。


用意した素材(ChatGPTが作成)

アイテムポップアップのフレーム枠(frame_item_popup.png)です。

frame_item_popup.png

アイテムのポップアップ用画像です。

item_01_1_large.png

1. アイテム選択トグルの実装

スクリーンショット 2026-07-03 17.44.49.png

プロンプト:
右側のアイテムスロットのアイテムが選択されたら

  • アイテム画像を白い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. 長押しでアイテムポップアップを表示

スクリーンショット 2026-07-03 17.44.58.png

プロンプト:
画面右のアイテムスロットゾーンのアイテムを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=200Node として 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);
}

成果物

アイテム取得後、画面右のアイテム枠に「金の鍵」が表示されます。

スクリーンショット 2026-07-03 17.44.42.png

「金の鍵」をタップすると選択状態になります。この状態で扉の鍵穴をタップすると、開錠されて扉を開けれるようになる、という想定です。

スクリーンショット 2026-07-03 17.44.49.png

「金の鍵」を1秒長押しするとポップアップで表示されます。

スクリーンショット 2026-07-03 17.44.58.png


まとめ

機能 実装
アイテム選択・解除 UserDefault["currentItem"] + DrawNode で白枠
長押し判定 scheduleOnce で1秒後に自動発火
ポップアップ重複防止 長押しコールバックで removeChildByTag(200) してから再表示
パネルの前面固定 パネル z=11、ポップアップ z=10
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?