はじめに
この記事は、Axmol Engineを使ってバイブコーディングでどこまでできるかを、検証と勉学を兼ねて試している連載です。
- 第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 - アイテムの使用 -(今回)
前回の記事ではアイテムの選択トグル・長押しポップアップを実装しました。今回は「アイテムを使って扉を開ける」システムを実装します。
用意した画像(ChatGPT作成)
扉の鍵穴の近接。
鍵を開ける演出用。
扉の開放後。
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"で指定されたステージに遷移
- そのアイテムのアイテムステータスを"itemResultStatus"に設定
- アイテム番号とアイテムステータスが選択アイテムとそのアイテムステータスが同じなら
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 | ポップアップフレーム |
成果物
「金の鍵」を取得済みです。
扉領域をタップして鍵穴に近接します。「金の鍵」を選択していないので、鍵穴をタップしても無反応です。
「金の鍵」をタップして選択状態にします。
鍵穴をタップして選択アイテムの「金の鍵」を使用します。
扉が開きました。
まとめ
| ファイル | 追加内容 |
|---|---|
stages.json |
touches に useitem フィールドを追加 |
useitems.json |
アイテム使用条件・結果・演出を定義する新規JSON |
MainScene.cpp |
UseItemDef 構造体、loadUseItemDefs()、showUseItemPopup()
|
useitems.json に新しいキーと attractions 配列を追加するだけで、演出付きのアイテム使用シナリオをコード変更なしで拡張できます。







