4
3

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】 バイブコーディングで脱出ゲームを作る[ 10 ]:アイテムシステムの実装3 - アイテムの使用 -

4
Last updated at Posted at 2026-07-05

はじめに

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



前回の記事ではアイテムの選択トグル・長押しポップアップを実装しました。今回は「アイテムを使って扉を開ける」システムを実装します。

用意した画像(ChatGPT作成)

扉の鍵穴の近接。

bg_stage_003.png

鍵を開ける演出用。

attraction_003.png

扉の開放後。

bg_stage_001_2.png


1. 扉ステージの追加(stages.json)

玄関前から扉へのタップ領域をまず追加します。

プロンプト:
stages.jsonの"infront"の"touches"に、
bg_stage_001.pngの扉へのタップ領域の"rect"を追加して、
"stage"は"door"に設定

rect のフォーマットは [x, y, width, height](ax::Rect に対応)です。

"infront": [
  {
    "bg": "bg_stage_001.png",
    "touches": [
      { "rect": [250, 41, 350, 510], "stage": "post" },
      { "rect": [450, 0, 830, 841], "stage": "door" }
    ],
    "back": "start"
  }
]

続けて door ステージを追加します。

プロンプト:
stages.jsonに"door"オブジェクトを追加してください
"bg": "bg_stage_003.png"
"back": "infront"

"door": [
  {
    "bg": "bg_stage_003.png",
    "touches": [],
    "back": "infront"
  }
]

最後に鍵穴タップ領域に useitem を設定します。

プロンプト:
stages.jsonの"door"[0]の"touches"に bg_stage_003.png の鍵穴のタップ領域の"rect"を設定
さらに "useitem": "goldkey" を設定

useitem はタップ時に使用するアイテムを示す独自フィールドです。

"door": [
  {
    "bg": "bg_stage_003.png",
    "touches": [
      {
        "rect": [1000, 270, 150, 220],
        "useitem": "goldkey"
      }
    ],
    "back": "infront"
  }
]

2. useitems.json の設計

プロンプト:
Content/useitems.jsonを作成してください
"goldkey": {
"itemNo": "1",
"itemStatus": "1",
"itemResultStatus": "9",
"stage": "infront",
"stageStatus": 1,
"goStage": "infront"
}
を追加

アイテム使用の条件・結果・遷移先をまとめたJSONファイルを新規作成します。

フィールド 意味
itemNo 使用するアイテム番号
itemStatus 使用可能なアイテムステータス
itemResultStatus 使用後のアイテムステータス
stage ステージデータを書き換えるステージ名
stageStatus 書き換え後のステージステータス
goStage 遷移先ステージ
{
  "goldkey": {
    "itemNo": "1",
    "itemStatus": "1",
    "itemResultStatus": "9",
    "stage": "infront",
    "stageStatus": 1,
    "attractions": [
      {
        "bg": "attractions/attraction_003.png",
        "duration": 1.0
      }
    ],
    "goStage": "infront"
  }
}

attractions 配列はアイテム使用後の演出定義です(詳細は後述)。


3. C++ への初期化処理追加

プロンプト:
useitems.jsonをgimmicks.jsonと同様にメインシーンに初期化処理を入れてください

まず構造体を定義します。

struct UseItemAttractionDef {
    std::string bg;
    float       duration = 1.0f;
};

struct UseItemDef {
    std::string itemNo;
    std::string itemStatus;
    std::string itemResultStatus;
    std::string stage;
    int         stageStatus = 0;
    std::string goStage;
    std::vector<UseItemAttractionDef> attractions;
};

static std::unordered_map<std::string, UseItemDef> s_useItemDefs;

JSONを読み込む loadUseItemDefs() はこちらです。

static void loadUseItemDefs()
{
    auto json = ax::FileUtils::getInstance()->getStringFromFile("useitems.json");
    rapidjson::Document doc;
    doc.Parse(json.c_str());
    if (doc.HasParseError() || !doc.IsObject())
        return;

    s_useItemDefs.clear();
    for (auto it = doc.MemberBegin(); it != doc.MemberEnd(); ++it)
    {
        std::string key   = it->name.GetString();
        const auto& entry = it->value;
        UseItemDef def{
            .itemNo           = entry["itemNo"].GetString(),
            .itemStatus       = entry["itemStatus"].GetString(),
            .itemResultStatus = entry.HasMember("itemResultStatus") ? entry["itemResultStatus"].GetString() : "",
            .stage            = entry.HasMember("stage")            ? entry["stage"].GetString()            : "",
            .stageStatus      = entry.HasMember("stageStatus")      ? entry["stageStatus"].GetInt()         : 0,
            .goStage          = entry.HasMember("goStage")          ? entry["goStage"].GetString()          : "",
        };
        if (entry.HasMember("attractions"))
            for (const auto& a : entry["attractions"].GetArray())
                def.attractions.push_back({
                    .bg       = a["bg"].GetString(),
                    .duration = a.HasMember("duration") ? a["duration"].GetFloat() : 1.0f,
                });
        s_useItemDefs.emplace(key, std::move(def));
    }
}

TouchZone 構造体に useitem フィールドを追加し、loadStageDefs() でパースします。

struct TouchZone {
    ax::Rect    rect;
    std::string stage;
    std::string gimmick;
    std::string useitem;  // 追加
};

// loadStageDefs() 内
def.touches.push_back({
    .rect    = ax::Rect(r[0].GetFloat(), r[1].GetFloat(),
                        r[2].GetFloat(), r[3].GetFloat()),
    .stage   = t.HasMember("stage")   ? t["stage"].GetString()   : "",
    .gimmick = t.HasMember("gimmick") ? t["gimmick"].GetString() : "",
    .useitem = t.HasMember("useitem") ? t["useitem"].GetString() : "",
});

init() にも呼び出しを追加します。

loadStageDefs();
loadGimmickDefs();
loadItemDefs();
loadUseItemDefs();  // 追加

4. タップ処理の実装

プロンプト:
stages.jsonの"useitem"が設定されているとき、
タップ領域がタップされたら、下記を実行

  • 現在の選択アイテムとそのアイテムステータスを参照
  • useitems.jsonの合致するキーのオブジェクトの
    • アイテム番号とアイテムステータスが選択アイテムとそのアイテムステータスが同じなら
      • そのアイテムのアイテムステータスを"itemResultStatus"に設定
        • 画面右側のアイテムスロットを再描画
      • "stage" のローカルストレージのステージステータスを"stageStatus"に設定
      • "goStage"で指定されたステージに遷移

onTouchesBegan のタッチゾーン分岐に useitem の処理を追加します。

else if (!zone.useitem.empty())
{
    auto uit = s_useItemDefs.find(zone.useitem);
    if (uit == s_useItemDefs.end())
        return;
    const auto& udef = uit->second;

    auto* ud = ax::UserDefault::getInstance();
    std::string currentItem{ud->getStringForKey("currentItem", "0")};
    std::string currentStatus{ud->getStringForKey(("item_" + currentItem).c_str(), "0")};

    if (currentItem != udef.itemNo || currentStatus != udef.itemStatus)
        return;

    ud->setStringForKey(("item_" + currentItem).c_str(), udef.itemResultStatus.c_str());
    if (!udef.stage.empty())
        ud->setIntegerForKey(("stageData_" + udef.stage).c_str(), udef.stageStatus);
    ud->flush();
    refreshItemPanel();

    showUseItemPopup(zone.useitem);
}

5. 演出ポップアップの実装

プロンプト:
一致した場合、goStageに遷移する前に、
画面右側アイテムスロットノードよりもz軸の上に、
frame_popup.pngを使ったポップアップを作成

useitems.jsonのattractions配列の
"bg"の画像をframe_popup.pngよりz軸の下に表示して、
"duration"(秒)の時間を経過したら次の配列オブジェクト設定で表示
配列がなくなれば"goStage"に遷移

attractions 配列を Sequence アクションで順番に再生し、終端で goStage に遷移する処理です。

ここだけは一発で上手くいかずに、演出ポップアップが表示されず、3回ほど「attractionsが再生されません」のプロンプトを投げて対応してもらいました

void MainScene::showUseItemPopup(const std::string& useitemKey)
{
    auto uit = s_useItemDefs.find(useitemKey);
    if (uit == s_useItemDefs.end())
        return;
    const UseItemDef udef = uit->second;

    if (udef.attractions.empty())
    {
        _stage = udef.goStage;
        ax::UserDefault::getInstance()->setStringForKey("stage", _stage);
        ax::UserDefault::getInstance()->flush();
        _stageStatus = 0;
        showStageBg();
        return;
    }

    auto visibleSize = _director->getVisibleSize();
    auto origin      = _director->getVisibleOrigin();

    // オーバーレイ(z=12: アイテムスロット z=11 より上)
    auto overlay = Node::create();
    overlay->setContentSize(visibleSize);
    overlay->setAnchorPoint(Vec2::ANCHOR_BOTTOM_LEFT);
    overlay->setPosition(origin);
    overlay->setTag(200);
    this->addChild(overlay, 12);

    auto bgLayer = LayerColor::create(Color32(0, 0, 0, 192), visibleSize.width, visibleSize.height);
    overlay->addChild(bgLayer, -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, 1);
    }

    Vector<FiniteTimeAction*> actions;
    for (size_t i = 0; i < udef.attractions.size(); ++i)
    {
        const std::string bg       = udef.attractions[i].bg;
        const float       duration = udef.attractions[i].duration;

        actions.pushBack(CallFunc::create([overlay, bg, visibleSize]() {
            overlay->removeChildByTag(251);
            auto bgSp = Sprite::create(bg);
            if (bgSp)
            {
                bgSp->setAnchorPoint(Vec2::ANCHOR_MIDDLE);
                bgSp->setPosition(Vec2(visibleSize.width / 2, visibleSize.height / 2 + 50));
                bgSp->setTag(251);
                overlay->addChild(bgSp, 0);
            }
        }));
        actions.pushBack(DelayTime::create(duration));
    }

    actions.pushBack(CallFunc::create([this, udef]() {
        this->removeChildByTag(200);
        _stage = udef.goStage;
        ax::UserDefault::getInstance()->setStringForKey("stage", _stage);
        ax::UserDefault::getInstance()->flush();
        _stageStatus = 0;
        showStageBg();
    }));

    overlay->runAction(Sequence::create(actions));
}

描画レイヤーの構成は以下のとおりです。

ノード z 役割
overlay 12 アイテムスロット(z=11)より上
bgLayer -1 半透明暗幕
bgSp(演出画像) 0 attractions の bg
frame_popup 1 ポップアップフレーム

成果物

「金の鍵」を取得済みです。

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

扉領域をタップして鍵穴に近接します。「金の鍵」を選択していないので、鍵穴をタップしても無反応です。

スクリーンショット 2026-07-05 17.32.04.png

「金の鍵」をタップして選択状態にします。

スクリーンショット 2026-07-05 17.32.12.png

鍵穴をタップして選択アイテムの「金の鍵」を使用します。

スクリーンショット 2026-07-05 17.33.52.png

扉が開きました。

スクリーンショット 2026-07-05 17.34.06.png


まとめ

ファイル 追加内容
stages.json touchesuseitem フィールドを追加
useitems.json アイテム使用条件・結果・演出を定義する新規JSON
MainScene.cpp UseItemDef 構造体、loadUseItemDefs()showUseItemPopup()

useitems.json に新しいキーと attractions 配列を追加するだけで、演出付きのアイテム使用シナリオをコード変更なしで拡張できます。

4
3
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
4
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?