2
2

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

2
Last updated at Posted at 2026-07-03

はじめに

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



前回の記事ではステージ・ギミックのJSON設定化を行いました。
今回はアイテムの定義・ステージへの配置・取得・所持パネル表示までを実装します。

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


やったこと

  • アイテム関連画像作成
  • アイテム情報定義
  • ステージ定義にアイテム取得情報追加
  • アイテム未所持の場合にステージにアイテムが置いてある画像を表示
  • アイテム所持後はステージにアイテム画像を表示しない
  • アイテム所持後は右側のアイテムスロットゾーンにアイテムアイコンを表示

まずは画像をChatGPTに描いてもらいましたので、Contentに配置してclaudeにメモリしてもらいます

プロンプト:
Content/item_002.png ... 郵便受けに置いてある鍵画像
Content/items/item_01.png ... アイテム:鍵
を配置しましたので覚えておいてください

郵便受けを開けた状態の背景画像上に重ねることで鍵が置いてある状況を演出
item_002.png

所持アイテム表示用
item_01_1.png

Step 1. ステージにアイテムを配置する(stages.json)

プロンプト:
stages.jsonの"openpost"に 配列オブジェクト"items" を追加してください
"items"には
背景上に重ねて表示するアイテム画像:"item_002.png"、
アイテム番号:1
を設定

アイテム画像ファイル名はステージと連動している名前が分かりやすいですです

各ステージ定義に items 配列を追加します。itemNo は後述のローカルストレージキー生成と統一するため、文字列型にします。

"openpost": [
  {
    "bg": "bg_stage_002_2.png",
    "touches": [],
    "back": "infront",
    "items": [
      { "image": "item_002.png", "itemNo": "1" }
    ]
  }
]

Step 2. アイテム定義ファイルを作成する(items.json)

プロンプト:
items.jsonを作成してください
オブジェクトを定義して、文字列のアイテム番号をキーとしてください
アイテム番号:1
アイテム名称:金の鍵
アイテム画像:items/item_01_1.png

アイテム番号(文字列)をキーにしたオブジェクトです。

{
  "1": {
    "name": "金の鍵",
    "image": "items/item_01_1.png"
  }
}

Step 3. items.json を C++ に読み込む

プロンプト:
メインシーンの初期処理で、items.jsonをマップで保持しておいてください

構造体と static 変数

struct ItemDef {
    std::string name;
    std::string image;
};

static std::unordered_map<std::string, ItemDef> s_itemDefs;

loadItemDefs()

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

    s_itemDefs.clear();
    for (auto it = doc.MemberBegin(); it != doc.MemberEnd(); ++it)
    {
        std::string key   = it->name.GetString();
        const auto& entry = it->value;
        s_itemDefs.emplace(key, ItemDef{
            .name  = entry["name"].GetString(),
            .image = entry["image"].GetString(),
        });
    }
}

init() で呼び出します。

loadStageDefs();
loadGimmickDefs();
loadItemDefs();

Step 4. ステージ表示時にアイテム画像を重ねる

プロンプト:
各ステージ表示時に、stages.jsonの"items" > "itemNo" を参照して、
設定されていれば、ローカルストレージの"item_"を参照、その値をアイテムのステータスとして扱い、まだ保存されていなければ"0"として、
"0"であれば"image"の画像を表示

まず StageDefitems フィールドを追加します。

struct StageItem {
    std::string image;
    std::string itemNo;
    ax::Rect    rect;
    bool        hasRect = false;
};

struct StageDef {
    std::string            bg;
    std::vector<TouchZone> touches;
    std::string            back;
    std::vector<StageItem> items;   // 追加
};

loadStageDefs() でパースします。

if (entry.HasMember("items"))
    for (const auto& item : entry["items"].GetArray())
    {
        StageItem si;
        si.image  = item["image"].GetString();
        si.itemNo = item["itemNo"].GetString();
        if (item.HasMember("rect"))
        {
            const auto& r = item["rect"].GetArray();
            si.rect    = ax::Rect(r[0].GetFloat(), r[1].GetFloat(),
                                  r[2].GetFloat(), r[3].GetFloat());
            si.hasRect = true;
        }
        def.items.push_back(std::move(si));
    }

showStageBg()loadAndFadeIn 内、FadeIn 実行直前でアイテム画像を背景スプライトの子として追加します。背景スプライト(_bg)の子にすることで、ステージ遷移時のフェードアウト・削除と同時に消えます。

auto* ud = ax::UserDefault::getInstance();
for (const auto& item : def.items)
{
    std::string status{ud->getStringForKey(("item_" + item.itemNo).c_str(), "0")};
    if (status != "0")
        continue;
    auto itemSp = Sprite::create(item.image);
    if (!itemSp)
        continue;
    auto bgSize = sp->getContentSize();
    itemSp->setAnchorPoint(Vec2::ANCHOR_MIDDLE);
    itemSp->setPosition(Vec2(bgSize.width / 2, bgSize.height / 2));
    sp->addChild(itemSp, 1);
}

sp->runAction(FadeIn::create(duration));

Step 5. アイテムをタップして取得する

プロンプト:
"items"のオブジェクトに"rect" を追加してください
"post" の "touches" 領域と同じで良いです
アイテムのステータスが "0" で、"rect"の領域をタップした場合、

  • アイテムのステータスを"1" に更新
  • 同じステージを再描画

stages.jsonitemsrect を追加します。

"items": [
  { "image": "item_002.png", "itemNo": "1", "rect": [550, 51, 570, 760] }
]

onTouchesBegan()touches ループの後にアイテムタップ判定を追加します。

for (const auto& item : it->second[_stageStatus].items)
{
    if (!item.hasRect)
        continue;
    std::string status{ax::UserDefault::getInstance()
        ->getStringForKey(("item_" + item.itemNo).c_str(), "0")};
    if (status != "0")
        continue;
    if (!item.rect.containsPoint(local))
        continue;

    auto* ud = ax::UserDefault::getInstance();
    ud->setStringForKey(("item_" + item.itemNo).c_str(), "1");
    ud->flush();
    refreshItemPanel();
    showStageBg();
    return;
}

タップで "item_1" = "1" がローカルストレージに保存され、再描画時にアイテム画像が消えます。


Step 6. 所持アイテム表示パネルを作成する

プロンプト:
画面右側に、z軸上、背景画像より上、ポップアップより下に、幅 200px、縦画面高さいっぱいに、黒+透過50%のノードを作成

init() 内で LayerColor を生成します。z=5(背景 z=0 より上、ポップアップ z=10 より下)。タグ 500 で後から参照できるようにします。

constexpr float panelWidth = 200.0f;
auto rightPanel = LayerColor::create(
    Color32(0, 0, 0, 128), panelWidth, visibleSize.height);
rightPanel->setPosition(Vec2(origin.x + visibleSize.width - panelWidth, origin.y));
rightPanel->setTag(500);
this->addChild(rightPanel, 5);

Step 7. refreshItemPanel() を実装する

プロンプト:
このノードにアイテムを表示するメソッドを作成
items.json の内容を参照し、ローカルストレージをチェックして、ステータス"1"のアイテムの"image"を上から順に表示

アイテム番号(数値)順にソートして上から積み下げます。画像はスケールせず実サイズで表示します。

void MainScene::refreshItemPanel()
{
    auto* panel = this->getChildByTag(500);
    if (!panel)
        return;

    panel->removeAllChildren();

    // アイテム番号順にソート
    std::vector<std::string> keys;
    keys.reserve(s_itemDefs.size());
    for (const auto& kv : s_itemDefs)
        keys.push_back(kv.first);
    std::sort(keys.begin(), keys.end(),
              [](const std::string& a, const std::string& b) {
                  return std::stoi(a) < std::stoi(b);
              });

    auto* ud          = ax::UserDefault::getInstance();
    float panelWidth  = panel->getContentSize().width;
    float panelHeight = panel->getContentSize().height;
    constexpr float margin = 10.0f;
    float y           = panelHeight - margin;

    for (const auto& key : keys)
    {
        std::string status{ud->getStringForKey(("item_" + key).c_str(), "0")};
        if (status != "1")
            continue;

        const auto& item = s_itemDefs.at(key);
        auto sp = Sprite::create(item.image);
        if (!sp || sp->getContentSize().width == 0)
            continue;

        float h = sp->getContentSize().height;
        sp->setAnchorPoint(Vec2::ANCHOR_MIDDLE_TOP);
        sp->setPosition(Vec2(panelWidth / 2, y));
        panel->addChild(sp);
        y -= h + margin;
    }
}

呼び出しタイミング

// init() — 再起動後の復元
showStageBg();
refreshItemPanel();

// アイテムタップ取得後
ud->setStringForKey(("item_" + item.itemNo).c_str(), "1");
ud->flush();
refreshItemPanel();
showStageBg();

成果物

郵便受けを開けると鍵が・・・
bg_stage_002_2.pngの上にitem_002.pngを描画して鍵が置いてある演出をしています

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

タップすると鍵を取得、右の所持アイテムスロットゾーンに表示されました!

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

まとめ

ファイル 役割
items.json アイテム番号をキーとした名称・画像の定義
stages.jsonitems ステージ上の演出画像・タップ取得エリア
UserDefault["item_<no>"] アイテム取得状態("0" 未取得 / "1" 取得済)
refreshItemPanel() 所持アイテムを右パネルに反映

アイテムを追加する際は items.json にエントリを追加し、配置したいステージの items に画像とタップ矩形を設定するだけで対応できます。

2
2
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
2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?