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

3
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"の画像を表示

まず StageDef に items フィールドを追加します。

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.json の items に rect を追加します。

"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()(再起動後の復元)とアイテムタップ取得後の2箇所です。

// 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.json の items ステージ上の演出画像・タップ取得エリア
UserDefault["item_<no>"] アイテム取得状態("0" 未取得 / "1" 取得済)
refreshItemPanel() 所持アイテムを右パネルに反映

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

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