はじめに
前回の記事では置き時計の針合わせギミックとライターオイルの取得を実装しました。今回は所持アイテム同士を組み合わせる「アイテム合成」機能を実装します。「ライター」に「ライターオイル」を組み合わせると「火の点くライター」になる、という具体例をベースに、items.json の設定だけで使い回せる汎用的な仕組みとして作ります。
- 第1回【Axmol Engine】 スプラッシュ画面シーンを実装する
- 第2回【Axmol Engine】 タイトルシーンを作って画像を表示する
- 第3回【Axmol Engine】 タップした領域への移動
- 第4回【Axmol Engine】 ノードを使用してポップアップを実装する
- 第5回【Axmol Engine】 テクスチャアトラスでダイヤル錠UIを実装する
- 第6回【Axmol Engine】 リファクタリング(メモリ消費対応、ステージをJSON管理、処理修正)+戻るボタン設置
- 第7回【Axmol Engine】 ローカルストレージでステージ進捗管理・ギミックをJSONで管理
- 第8回【Axmol Engine】 アイテムシステムの実装1 - アイテムの取得 -
- 第9回【Axmol Engine】 アイテムシステムの実装2 - アイテムの選択・表示 -
- 第10回【Axmol Engine】 アイテムシステムの実装3 - アイテムの使用 -
- 第11回【Axmol Engine】 BGM・効果音の実装 -
- 第12回【Axmol Engine】 左右移動ボタンの実装 -
- 番外編1【Axmol Engine】 ギミックについて -
- 第13回【Axmol Engine】 順番押し系ギミックの実装 -
- 第14回【Axmol Engine】 絵合わせ系+別ボタンギミック(時計)の実装 -
- 第15回【Axmol Engine】 アイテム合成の実装 -(今回)
用意した画像(Chat-GPT生成)
ライターオイルを注入したので火が点くようになったライターの画像
ポップアップ表示用(300*300)

基本的な流れは
- 合成元となるアイテムをポップアップで表示
- 合成させるアイテムを選択状態
- 上記の状態でポップアップのアイテム画像をタップ
- 新しく合成されたアイテムを所持状態に
- 合成の元となったアイテム2点は削除
となります。
1. アイテム合成の実装
プロンプト:
ライターをポップアップで表示してから、ライターオイルを選択状態にしてそのポップアップをタップすると、「ライター」と「ライターオイル」は削除状態にして、アイテム:"4"の「火の点くライター」を入手状態にしてください。items/item_04_*が対象画像です。items.jsonで設定して、処理は使いまわせるように。
アイテムパネルの長押しで開く既存の showItemPopup() をベースに、「ポップアップ表示中のアイテム」×「現在選択中(currentItem)のアイテム」の組み合わせをJSON側で定義できる仕組みを追加します。ItemDef に combineWith(合成相手のアイテム番号)と 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) をチェックしてポップアップ表示中は処理をスキップする作りになっており、removeChildByTag と showItemPopup(内部で 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" |
消費済み(合成で削除) | どこにも表示されず、再取得もされない |
成果物
前回までで取得可能になった「ライター」と「ライターオイル」をゲット

「ライター」の画像をタップすると、ライターオイルを注入したので火の点くライターになりました!

まとめ
| 実装内容 | 対応ファイル |
|---|---|
アイテム合成の汎用処理(combineWith/combineResult) |
items.json / MainScene.cpp
|
| 合成成立時に結果アイテムのポップアップへ連続遷移 | MainScene.cpp |
| タップ伝搬防止(tag 200オーバーレイの継続性) | MainScene.cpp |
消費済みアイテムの再取得防止(ステータス "2" の導入) |
MainScene.cpp |
items.json に combine ブロックを追加するだけで新しい合成ペアを増やせる設計になっており、コード変更なしにアイテム同士の組み合わせパズルを拡張できます。


