【每天部署1次】制作了分别测量重叠与溢出的「贴紧!妖怪影子戏剧场」
【1日1デプロイ】重なりとはみ出しを別々に測る「ピタッ!おばけ影絵劇場」を作った

「1日1デプロイ」の新作として、紙の切り抜きを動かして影を見本へ重ねる3幕パズル ピタッ!おばけ影絵劇場 を作りました。
左にランタン、中央に紙の切り抜き、右に和紙の幕があります。切り抜きをドラッグすると、ランタンとの距離に応じて幕の影が大きくなったり小さくなったりします。
第1幕は「おばけネコ」、第2幕は「かぼちゃ馬車」、第3幕は「月夜のドラゴン」。位置と大きさだけでなく、回転や複数の影の重なりも使って完成させます。

遊び方
- 「幕を開ける」を押す
- 紙の切り抜きをタップして選ぶ
- ドラッグで位置と影の大きさを調整する
- 左右の回転ボタンで角度を合わせる
- 「重なり」を増やし、「はみ出し」を減らして3幕を完成させる
PCでは矢印キーで微調整、Q / Eで回転、Enterで決定できます。
影が基準へ入った瞬間に終わらせず、500ms安定すると「ぴたり!」になります。偶然、正解位置を横切っただけでクリアしないためです。
今回のレベルアップは「似ている」を2つの数字へ分けたこと
影絵の完成判定では、見本と現在の影を160×220の小さなCanvasへ描きます。それぞれのアルファ値から、見本の画素、現在の影、両方が重なった画素、見本の外へ出た画素を数えます。
const coverage = targetPx ? intersection / targetPx : 0;
const spill = shadowPx ? outside / shadowPx : 1;
coverageは、見本をどれだけ覆えたかです。spillは、現在の影のうち見本の外へ出た割合です。
合格条件は次の3つを組み合わせました。
function passesClear(metrics) {
const sizeRatio =
metrics.shadowPx / Math.max(1, metrics.targetPx);
const sizeOk =
sizeRatio >= 0.68 && sizeRatio <= 1.38;
return (
metrics.coverage >= 0.88 &&
metrics.spill <= 0.12 &&
sizeOk
);
}
重なりだけを見ると、小さな影を見本の中央へ置く方法でも高得点になり得ます。反対に、大きな影で見本全体を覆えばcoverageは上がりますが、見本外の影も増えます。
そこで、次の3方向から判定しました。
- 見本を十分に覆っている
- 見本の外へ大きくはみ出していない
- 影全体の大きさが見本とかけ離れていない
数字をひとつ増やすのではなく、異なる失敗を別々に測ることで、画面で「合っている」と感じる状態へ近づけました。
黒い背景が、透明度の判定を全部正解にしていた
工程1で最も大きかった不具合は、判定用Canvasの背景を黒く塗っていたことです。
マスク判定では色ではなくアルファ値を見ていました。黒でも不透明ならアルファ値は255です。そのため、背景を黒く塗ると画面全体が「影あり」と判定され、coverageが常に100%近くなっていました。
修正後は、毎回透明に戻してから白い影だけを描きます。
function renderMask(canvas, pieces, mode) {
const ctx = canvas.getContext("2d");
ctx.clearRect(0, 0, MASK_W, MASK_H);
for (const piece of pieces) {
drawShape(ctx, piece, transformOf(piece, mode));
}
}
見た目では「黒い背景に黒い影」でも、判定に必要なのはRGBではなく透明か不透明かです。
修正後は、中央に小さな影を置くケース、画面全体を覆うケース、DPR 2の環境をテストしました。小さい影の抜け道は不合格になり、DPRが変わっても指標差は0でした。
ランタンとの距離を、影の大きさへ変換する
切り抜きの影は、ランタンからの距離を使って投影しています。
const dx = piece.x - LANTERN.x;
const dy = piece.y - LANTERN.y;
const lightDist = Math.max(MIN_LIGHT, Math.hypot(dx, dy));
let scale =
(stageSpan / lightDist) *
0.42 *
(1.1 - proximity * 0.35);
scale = Math.min(SCALE_MAX, Math.max(SCALE_MIN, scale));
ランタンへ近づけるほど影が大きくなり、幕側へ動かすほど小さくなります。ただし、距離が極端に小さいと倍率が急増するため、最小距離と倍率の上下限を設けました。
第2幕では、かぼちゃを横へ動かすだけでは完成しません。ランタンへ寄せて影を大きくし、車輪との比率を合わせる必要があります。投影計算を、見た目の演出だけでなく次の一手を変える仕組みにしました。
3つの方針で、調整回数の違いを確かめる
初見確認では、説明を読まずに幕を開け、ネコの耳を寄せる操作を5回行いました。5回とも357〜395msで最初のドラッグを終え、重なりは35%から48%へ13ポイント上がりました。
実Pointerを通す検証ランナーでは、異なる3方針で3幕、結果、リプレイまで確認しました。
| 方針 | 幕別の重なり | 調整回数 | 時間 | 評価 |
|---|---|---|---|---|
| 大きさを先に合わせる | 99 / 100 / 100% | 8回 | 13秒 | 影絵座の名演出家 |
| 位置・回転を先に合わせる | 99 / 100 / 100% | 13回 | 15秒 | 影絵座の名演出家 |
| わざとはみ出して復帰 | 99 / 100 / 100% | 14回 | 16秒 | 影絵座の名演出家 |
結果の一致率は近くても、合わせ方によって調整回数が変わりました。失敗ルートでもライフを失わせず、「はみ出しを減らそう」というヒントから同じ幕を続けられます。
通し確認後、正解状態を保つ時間を450msから500msへ変更しました。ドラッグ直後は影の表示が少し遅れて追従するため、450msでは安定判定が途中で切れることがあったからです。
撮影状態のcapture=1も通常と同じ投影、マスク判定、保持判定を通します。実測は13.20秒、調整5回、評価は「影絵座の名演出家」でした。
デザインは、昭和の移動影絵座と紙工作
ビジュアルは 昭和の移動影絵座 × ハロウィンの紙工作 × 夜の公民館舞台 を組み合わせました。
左の古いランタン、中央の段ボール切り抜き、右の和紙幕を横に並べ、遊びの仕組みを舞台装置として見せています。切り抜きには紙の繊維、割りピン、操作棒を付け、操作する紙と幕に映る影を区別しました。
ブラピさんは黒いベレー帽と操作棒を持つ舞台助手です。白いカードには入れず、透過画像を舞台の光へなじませています。
最終幕でドラゴンが完成すると、数値を合わせる画面から短い公演へ切り替わります。影が金色になり、客席が拍手し、ブラピさんが一礼します。正解を数字だけで終わらせず、調整してきた形がひとつの演目になるようにしました。
非エンジニア初心者がAI開発で使えるTIPS
AIへ「見本と同じ形なら正解にして」と頼むと、判定方法が曖昧なまま実装されることがあります。先に、似ている状態と、似ていない抜け道を文章で分けて渡すと修正しやすくなります。
見本と現在の影を、同じ固定サイズの透明Canvasへ白で描いてください。
合格条件:
- 見本を覆った割合が88%以上
- 現在の影が見本外へ出た割合が12%以下
- 現在の影の面積が見本の0.68〜1.38倍
- 条件を500ms保ったらクリア
必ず不合格にする例:
- 小さい影を見本の中央へ置いただけ
- 大きな影で見本と背景を全部覆った
- 正解位置をドラッグ中に一瞬通過しただけ
確認:
- マスクCanvasの背景は透明のまま
- DPR 1と2で結果が変わらない
- pointercancel後にドラッグ状態が残らない
ポイントは「正解を説明する」だけでなく、一見正解に見える不正解も一緒に渡すことです。
AIの自動テストも、完成形を1回通すだけでは不足します。中央小影、全面覆い、高DPI、入力中断のように、実装が間違えやすい反例を用意すると、数値上だけ合格する状態を見つけやすくなります。
まとめ
今回は、切り抜きを動かして3つの影絵を完成させるパズルを作りました。
前作から、最初の操作を短くすること、入力対象を明確にすること、失敗後もすぐ調整を続けられることを引き継ぎました。その上で、光の投影とマスク比較を追加し、「似ている」を重なり、はみ出し、面積の3方向から測りました。
背景の黒まで影として数えた失敗は、画面の色と判定データを分けて考える大切さを教えてくれました。見た目を作るCanvasと、正解を測るCanvas。それぞれの役割を明確にすると、AIと一緒に作るときも、修正すべき場所を具体的に伝えられます。
毎日ひとつ、小さなゲームや便利ツールを公開しています。
来源:zenn · 游戏开发社区 · zenn.dev