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】 バイブコーディングで脱出ゲームを作る[ 15 ]:アイテム合成の実装

2
Last updated at Posted at 2026-08-06

はじめに

前回の記事では置き時計の針合わせギミックとライターオイルの取得を実装しました。今回は所持アイテム同士を組み合わせる「アイテム合成」機能を実装します。「ライター」に「ライターオイル」を組み合わせると「火の点くライター」になる、という具体例をベースに、items.json の設定だけで使い回せる汎用的な仕組みとして作ります。



用意した画像(Chat-GPT生成)

ライターオイルを注入したので火が点くようになったライターの画像
ポップアップ表示用(300*300)
item_04_1_large.png

サムネイル用(100*100)
item_04_1.png


基本的な流れは

  • 合成元となるアイテムをポップアップで表示
  • 合成させるアイテムを選択状態
  • 上記の状態でポップアップのアイテム画像をタップ
  • 新しく合成されたアイテムを所持状態に
  • 合成の元となったアイテム2点は削除

となります。

1. アイテム合成の実装

プロンプト:
ライターをポップアップで表示してから、ライターオイルを選択状態にしてそのポップアップをタップすると、「ライター」と「ライターオイル」は削除状態にして、アイテム:"4"の「火の点くライター」を入手状態にしてください。items/item_04_*が対象画像です。items.jsonで設定して、処理は使いまわせるように。

アイテムパネルの長押しで開く既存の showItemPopup() をベースに、「ポップアップ表示中のアイテム」×「現在選択中(currentItem)のアイテム」の組み合わせをJSON側で定義できる仕組みを追加します。ItemDefcombineWith(合成相手のアイテム番号)と combineResult(合成結果のアイテム番号)を持たせ、items.json"combine" オブジェクトから読み込みます。

"2": {
  "name": "ライター",
  "image": "items/item_02_1.png",
  "image_large": "items/item_02_1_large.png",
  "combine": {
    "withItemNo": "3",
    "resultItemNo": "4"
  }
},
"4": {
  "name": "火の点くライター",
  "image": "items/item_04_1.png",
  "image_large": "items/item_04_1_large.png"
}
struct ItemDef {
    std::string name;
    std::string image;
    std::string imageLarge;
    std::string combineWith;
    std::string combineResult;
};
if (entry.HasMember("combine"))
{
    const auto& c     = entry["combine"];
    def.combineWith   = c.HasMember("withItemNo")   ? c["withItemNo"].GetString()   : "";
    def.combineResult = c.HasMember("resultItemNo") ? c["resultItemNo"].GetString() : "";
}

showItemPopup() のタップリスナーで、表示中アイテムの combineWith と現在選択中のアイテムが一致するかを判定し、一致すれば両アイテムを消し、合成結果アイテムを所持状態にします。

listener->onTouchBegan = [this, itemNo, def](Touch*, Event*) -> bool {
    if (!def.combineWith.empty())
    {
        auto* ud = ax::UserDefault::getInstance();
        std::string currentItem{ud->getStringForKey("currentItem", "0")};
        if (currentItem == def.combineWith)
        {
            ud->setStringForKey(("item_" + itemNo).c_str(), "0");
            ud->setStringForKey(("item_" + def.combineWith).c_str(), "0");
            if (!def.combineResult.empty())
                ud->setStringForKey(("item_" + def.combineResult).c_str(), "1");
            ud->setStringForKey("currentItem", "0");
            ud->flush();
            refreshItemPanel();
            this->removeChildByTag(200);
        }
    }
    return true;
};

combine は合成の起点になるアイテム(ここでは「ライター」)に定義するだけで、別のアイテムペアにもそのまま使い回せる設計です。


2. 合成結果ポップアップへの連続表示とタップ伝搬の防止

一発目のプロンプト指示で何点か気になる点があったので正していきます。

プロンプト:
アイテムを合成したらそのアイテムの詳細ポップを開いてください。またその前後で下のレイヤーにタップを伝搬させないでください。

合成成立時、古いポップアップを閉じた直後に合成結果アイテムの詳細ポップアップを開くよう showItemPopup() を再帰的に呼び出します。

refreshItemPanel();
// 閉じてから即座に合成結果のポップアップを開く。tag 200のオーバーレイが
// 常に存在し続けるため、この間もタップは下のレイヤー(ステージ/アイテムパネル)に
// 伝搬しない(onTouchesBeganはgetChildByTag(200)がある間は処理をスキップする)
this->removeChildByTag(200);
if (!def.combineResult.empty())
    showItemPopup(def.combineResult);

MainScene::onTouchesBegan は冒頭で this->getChildByTag(200) をチェックしてポップアップ表示中は処理をスキップする作りになっており、removeChildByTagshowItemPopup(内部で addChild(overlay, 10) しtag 200を付与)が同一タップイベント内で同期的に実行されるため、tag 200のオーバーレイが常に存在し続けます。加えてポップアップ自身のリスナーも setSwallowTouches(true) かつ onTouchBegan が常に true を返すため、処理中のタッチもその場で吸収され、下のレイヤーに一切伝搬しません。


3. 消費済みアイテムを再取得対象から除外

デバッグで合成元アイテムが再度取得できるようになっていたので正してもらいました。

プロンプト:
各ステージで取得可能なアイテムがあれば取得できるようになっているロジックがありますが、アイテム結合で削除されたアイテムは対象外にしてください

合成で削除したアイテムのステータスを "0"(未取得)に戻していたため、既存のステージ取得ロジック(status != "0" なら取得可能と判定)と区別がつかず、元の設置場所に戻ると再取得できてしまうバグがありました。

修正として、合成で消費されたアイテムは "0"(未取得)ではなく "2"(消費済み)に設定するよう変更します。取得判定・背景上のアイテム表示判定はどちらも status != "0" のときスキップする仕組みのため、"2" も自動的に対象外になります。アイテムパネル側の表示判定はもともと status == "1" のときだけ表示する作りなので、こちらは変更不要です。

if (currentItem == def.combineWith)
{
    // "2" = 消費済み("0"=未取得と区別し、ステージ上での再取得対象から外す)
    ud->setStringForKey(("item_" + itemNo).c_str(), "2");
    ud->setStringForKey(("item_" + def.combineWith).c_str(), "2");
    if (!def.combineResult.empty())
        ud->setStringForKey(("item_" + def.combineResult).c_str(), "1");
    // ...
}

既存ロジックを一切変更せず、アイテムステータスの値を1つ追加するだけで対応できました。


アイテムステータス値の一覧

意味 対象になる処理
"0"(デフォルト) 未取得 ステージ上のタップで取得可能・背景上に画像表示
"1" 所持中 アイテムパネルに表示・useitem で使用可能
"2" 消費済み(合成で削除) どこにも表示されず、再取得もされない

成果物

前回までで取得可能になった「ライター」と「ライターオイル」をゲット
スクリーンショット 2026-08-06 12.02.29.png

右側のアイテムスロットの「ライター」を長押しして詳細表示
スクリーンショット 2026-08-06 12.03.26.png

「ライターオイル」を選択状態にして・・・
スクリーンショット 2026-08-06 12.04.27.png

「ライター」の画像をタップすると、ライターオイルを注入したので火の点くライターになりました!
スクリーンショット 2026-08-06 11.54.48.png



まとめ

実装内容 対応ファイル
アイテム合成の汎用処理(combineWith/combineResult items.json / MainScene.cpp
合成成立時に結果アイテムのポップアップへ連続遷移 MainScene.cpp
タップ伝搬防止(tag 200オーバーレイの継続性) MainScene.cpp
消費済みアイテムの再取得防止(ステータス "2" の導入) MainScene.cpp

items.jsoncombine ブロックを追加するだけで新しい合成ペアを増やせる設計になっており、コード変更なしにアイテム同士の組み合わせパズルを拡張できます。

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?