はじめに
前回の記事では額縁の4隅タップギミックとライターの取得を実装しました。今回は room1-3 の置き時計をタップして時計部屋(room1-3-1)に入り、時計盤の針を8時20分に合わせるギミックを解いて、隠されたアイテム「ライターオイル」を取得するまでを実装します。
この記事の、絵合わせ系+トリガを別ボタン+判定ボタンで、時計ギミックを実装します
- 絵合わせ系 ... 8時20分という見た目(絵)に合わせる
- トリガを別ボタン ... 時計の針自体がボタンだったとしてタップさせようとすると、もう一つの針へのタップと干渉してしまうため
- 判定ボタン ... 自動判定ですと、短針が正しい位置にある時に、長針を20分まで移動すると強制的に正解になってしまうので、判定ボタンを配置
- 第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】 絵合わせ系+別ボタンギミック(時計)の実装 -(今回)
用意した画像(Chat-GPT生成)
1. 時計部屋への入室導線
プロンプト:
"room1-3" で中央の時計をタップしたら、"room1-3-1" に遷移。bgはbg_stage_006_2.png。backはroom1-3。
claudeはこのプロジェクトをだいぶ学習してきてますので、このような重要なキーワードだけでも理解してくれます。
bg_stage_006.png をピクセル解析して置き時計本体の位置を特定し、タップすると room1-3-1(bg_stage_006_2.png)へ遷移するタップ領域を追加します。
"room1-3": [
{
"bg": "bg_stage_006.png",
"touches": [
{ "rect": [875, 211, 130, 360], "stage": "room1-3-1" }
],
"left": "room1-1"
}
]
2. 時計下部(振り子)のタップ領域
プロンプト:
"room1-3-1" で時計の振り子のある箇所をタップしたら、"room1-3-2" に遷移。bgはbg_stage_006_4.png。backはroom1-3-1。
"room1-3-1": [
{
"bg": "bg_stage_006_2.png",
"touches": [
{ "rect": [840, 191, 180, 360], "stage": "room1-3-2" }
],
"back": "room1-3"
}
],
"room1-3-2": [
{
"bg": "bg_stage_006_4.png",
"touches": [],
"back": "room1-3-1"
}
]
3. 時計の針を合わせるギミック(type "3")の実装
プロンプト:
room1-3-1 で時計盤をタップしたら、ポップアップ無しのギミックに遷移。時計盤の右に、上からclock/btn_hour.png, clock/btn_minute.png, btn_ok.png を配置。clock/hourhand.png, minutehand.pngを針がそれぞれ12時を指すように配置。btn_hour.pngがタップされれば1回毎にhourhand.pngを時計回りに30度回転。btn_minute.pngがタップされれば1回毎にminutehand.pngを時計回りに30度回転。btn_ok.pngがタップされれば答え合わせ。さらに、時計盤左にbtn_reload.pngを配置して、タップされればhourhand.png, minutehand.pngの位置をリセット。答えは hourhand = 8時、minutehand.png = 20分。
長々と重要キーワードとその動作を簡潔に指示しても、ある程度形にしてくれます
type: "1"(ポップアップ式ダイヤル錠)・type: "2"(額縁の4隅タップ)に続き、type: "3" として時計の針合わせギミックを追加します。ポップアップ演出は使わず、ギミック自身の bg をフル表示し、その上に針とボタンをZ軸で重ねる構成です。
針の回転はタップ回数をカウントして setRotation(count * 30) で絶対角度指定する方式にしました。reloadでカウントごとリセットできるほか、状態管理がシンプルになります。
if (hourBtnRect.containsPoint(localBase))
{
*hourCount = (*hourCount + 1) % 12;
auto* hand = baseNode->getChildByTag(270);
if (hand)
hand->setRotation(*hourCount * 30.0f);
return true;
}
// minuteBtnRect も同様に30度刻みで回転
if (reloadBtnRect.containsPoint(localBase))
{
*hourCount = 0;
*minuteCount = 0;
baseNode->getChildByTag(270)->setRotation(0.0f);
baseNode->getChildByTag(271)->setRotation(0.0f);
return true;
}
if (okBtnRect.containsPoint(localBase))
{
if (*hourCount == def.collectHour && *minuteCount == def.collectMinute)
{
// 正解処理へ
}
return true;
}
30度刻みなので、8時は8タップ(240度)、20分は文字盤の「4」の目盛り=4タップ(120度)で表現できます。
"clockHands": {
"type": "3",
"bg": "bg_stage_006_2.png",
"hourHand": "clock/hourhand.png",
"minuteHand": "clock/minutehand.png",
"clockCenter": [930, 651],
"collectHour": 8,
"collectMinute": 4,
"touches": [],
"stage": "room1-3-1",
"stageStatus": 1,
"goStage": "room1-3-1"
}
ボタン配置は clockCenter からの相対オフセットで計算し、右列(hour→minute→ok)と左(reload)をコードで組み立てます。
constexpr float btnGapX = 250.0f;
constexpr float btnGapY = 110.0f;
auto makeClockButton = [&](const std::string& image, const Vec2& pos) -> Rect {
auto sp = Sprite::create(image);
sp->setAnchorPoint(Vec2::ANCHOR_MIDDLE);
sp->setPosition(pos);
base->addChild(sp, 1);
auto cs = sp->getContentSize();
return Rect(pos.x - cs.width / 2, pos.y - cs.height / 2, cs.width, cs.height);
};
Rect hourBtnRect = makeClockButton("clock/btn_hour.png", Vec2(cx + btnGapX, cy + btnGapY));
Rect minuteBtnRect = makeClockButton("clock/btn_minute.png", Vec2(cx + btnGapX, cy));
Rect okBtnRect = makeClockButton("btn_ok.png", Vec2(cx + btnGapX, cy - btnGapY));
Rect reloadBtnRect = makeClockButton("btn_reload.png", Vec2(cx - btnGapX, cy));
4. ギミック背景をズーム画像に切り替え、レイアウトを再調整
ギミック時の背景画像をしていなかったので、改めて指示しました。AIを使わずに修正するとなるとモチベーションが下がる修正ですが、この辺りもしっかりやってもらえます。
プロンプト:
ギミック時はbg_stage_006_3.pngを背景として使用。それに合わせてボタン位置、サイズを調整。
bg_stage_006_3.png は時計盤を大きくズームした構図のため、clockCenter をピクセル解析で再計測し、ボタンの間隔とスケールも拡大します。
"bg": "bg_stage_006_3.png",
"clockCenter": [922, 410],
constexpr float btnGapX = 440.0f;
constexpr float btnGapY = 220.0f;
constexpr float btnScale = 1.8f;
auto makeClockButton = [&](const std::string& image, const Vec2& pos) -> Rect {
auto sp = Sprite::create(image);
sp->setAnchorPoint(Vec2::ANCHOR_MIDDLE);
sp->setPosition(pos);
sp->setScale(btnScale);
base->addChild(sp, 1);
auto cs = sp->getContentSize();
float w = cs.width * btnScale;
float h = cs.height * btnScale;
return Rect(pos.x - w / 2, pos.y - h / 2, w, h);
};
5. 正解状態の針を汎用「layer」機能で表示
プロンプト:
room1-3-1のステップ2でclock/hourhand.pngを8時、clock/minutehand.pngを20分に合わせて表示してください。layerというキーで設定
アイテムのような「所持状態で表示/非表示」ではなく、常に固定表示したい装飾画像向けに、StageDef へ新しく layer という汎用フィールドを追加します。画像・位置・回転角を指定できます。
struct LayerImage {
std::string image;
ax::Vec2 pos;
float rotation = 0.0f;
};
struct StageDef {
// ...
std::vector<StageItem> items;
std::vector<LayerImage> layer;
};
// layer: 固定装飾画像(回転指定可)を背景上に重ねて表示
for (const auto& li : def.layer)
{
auto layerSp = Sprite::create(li.image);
if (layerSp)
{
layerSp->setAnchorPoint(Vec2::ANCHOR_MIDDLE);
layerSp->setPosition(li.pos);
layerSp->setRotation(li.rotation);
sp->addChild(layerSp, 1);
}
}
"layer": [
{ "image": "clock/hourhand.png", "pos": [922, 410], "rotation": 240 },
{ "image": "clock/minutehand.png", "pos": [922, 410], "rotation": 120 }
]
6. 正解後の遷移構造を整理(room1-3-3の新設)
プロンプト:
room1-3-1のステップ2は、bg_stage_006_2.pngにして、時計盤をタップした場合は、room1-3-3に遷移してください。layerはroom1-3-3に移行してください。
room1-3-1 のステップ2は通常表示(bg_stage_006_2.png)に戻し、時計盤をタップしたときだけズーム画像+針の layer 表示(room1-3-3)に遷移する構成に整理します。
"room1-3-1": [
{ "...": "ステップ1: 通常表示 + clockHandsギミック" },
{
"bg": "bg_stage_006_2.png",
"touches": [
{ "rect": [850, 576, 160, 160], "stage": "room1-3-3" },
{ "rect": [840, 191, 180, 360], "stage": "room1-3-2" }
],
"back": "room1-3"
}
],
"room1-3-3": [
{
"bg": "bg_stage_006_3.png",
"touches": [],
"layer": [
{ "image": "clock/hourhand.png", "pos": [922, 410], "rotation": 240 },
{ "image": "clock/minutehand.png", "pos": [922, 410], "rotation": 120 }
],
"back": "room1-3-1"
}
]
7. ギミック成功時に複数ステージを同時に進める
プロンプト:
clockHandsのギミックを解いた際に、room1-3-2のステップを進めてください。bgはbg_stage_006_5.pngで。
既存の GimmickDef は成功時の保存先ステージを1つ(stage/stageStatus)しか持てなかったため、saveStage2/saveStatus2 を追加し、2つのステージを同時に進められるようにします。
struct GimmickDef {
// ...
std::string saveStage;
int saveStatus = 0;
std::string saveStage2;
int saveStatus2 = 0;
};
auto* ud = ax::UserDefault::getInstance();
if (!def.saveStage.empty())
ud->setIntegerForKey(("stageData_" + def.saveStage).c_str(), def.saveStatus);
if (!def.saveStage2.empty())
ud->setIntegerForKey(("stageData_" + def.saveStage2).c_str(), def.saveStatus2);
ud->flush();
"clockHands": {
"type": "3",
"stage": "room1-3-1",
"stageStatus": 1,
"stage2": "room1-3-2",
"stageStatus2": 1,
"goStage": "room1-3-1"
}
"room1-3-2": [
{ "...": "ステップ1: bg_stage_006_4.png" },
{
"bg": "bg_stage_006_5.png",
"touches": [],
"back": "room1-3-1"
}
]
8. アイテム「ライターオイル」の追加
プロンプト:
アイテム"3"に「ライターオイル」を追加。サムネイルは、item_03_1*.png。room1-3-2のステップ2の時、item_006.pngを表示して、該当箇所をタップでアイテム取得できるように
"3": {
"name": "ライターオイル",
"image": "items/item_03_1.png",
"image_large": "items/item_03_1_large.png"
}
item_006.png はステージ背景と同じ1870×841のフル透過画像です。ピクセル解析でオイル缶の描画位置を特定し、タップ判定用の rect をそこに合わせます。
{
"bg": "bg_stage_006_5.png",
"touches": [],
"back": "room1-3-1",
"items": [
{ "image": "item_006.png", "itemNo": "3", "rect": [913, 392, 110, 59] }
]
}

時計のギミックに正解することで振り子の扉を開くことができました
何かあります・・・
成果物
あからさまに怪しい時計・・・
時計の針を8時20分に合わせると、振り子の扉が開き、ライターオイルを獲得!
まとめ
| 実装内容 | 対応ファイル |
|---|---|
| 時計部屋への入室・振り子タップ導線 | stages.json |
| 時計の針合わせギミック(type "3") |
gimmicks.json / MainScene.cpp
|
| ギミック背景ズームに合わせたレイアウト再調整 |
gimmicks.json / MainScene.cpp
|
固定装飾画像用の layer 機能 |
MainScene.cpp / stages.json
|
| 正解後遷移の構造整理(room1-3-3) | stages.json |
| ギミック成功時の複数ステージ同時更新 |
MainScene.cpp / gimmicks.json
|
| ライターオイルアイテムの追加 |
items.json / stages.json
|
type: "1" のポップアップ式ダイヤル錠から始まり、type: "2" のポップアップ無し4隅タップ、そして今回の type: "3" の針合わせと、ギミックの表現の幅が広がりました。座標やタップ領域は毎回Pythonでの画像解析(Image.crop + グリッド描画、alpha_composite での事前合成確認)を通して実際の描画位置に合わせています。
















