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

作为“1天1部署”的新作,我制作了移动纸片剪影、让影子与样本重合的3幕解谜游戏 ピタッ!おばけ影絵劇場。
左侧是灯笼,中央是纸片剪影,右侧是和纸幕布。拖动剪影时,幕布上的影子会随其与灯笼的距离变大或变小。
第1幕是“妖怪猫”,第2幕是“南瓜马车”,第3幕是“月夜之龙”。不仅要靠位置和大小,还要运用旋转和多个影子的重叠来完成。

玩法
- 按下“拉开帷幕”
- 点击选择纸片剪影
- 拖动调整位置和阴影大小
- 用左右旋转按钮调整角度
- 增加“重叠”、减少“超出”,完成第三幕
在 PC 上可用方向键微调,Q / E 旋转,Enter 确认。
不会在阴影进入基准的瞬间就结束,稳定 500ms 后才会显示“严丝合缝!”。这是为了避免只是偶然经过正确位置就通关。
本次升级在于将“相似”拆分为两个数字
在影绘的完成判定中,会将样本和当前的影子绘制到160×220的小Canvas上。根据各自的alpha值,统计样本的像素、当前影子的像素、两者重叠的像素,以及超出样本之外的像素。
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 的背景被涂成了黑色。
在遮罩判定中,我们看的是 alpha 值,而不是颜色。即使看起来是黑色,只要不透明,alpha 值就是 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开发中可用的实用技巧
如果请求AI“只要和样本形状相同就判为正确”,有时会在判定方法仍然模糊的情况下就被实现。事先用文字把相似的状态和不相似的漏洞分开说明并交给它,会更容易修正。
見本と現在の影を、同じ固定サイズの透明Canvasへ白で描いてください。
合格条件:
- 見本を覆った割合が88%以上
- 現在の影が見本外へ出た割合が12%以下
- 現在の影の面積が見本の0.68〜1.38倍
- 条件を500ms保ったらクリア
必ず不合格にする例:
- 小さい影を見本の中央へ置いただけ
- 大きな影で見本と背景を全部覆った
- 正解位置をドラッグ中に一瞬通過しただけ
確認:
- マスクCanvasの背景は透明のまま
- DPR 1と2で結果が変わらない
- pointercancel後にドラッグ状態が残らない
关键在于不仅要“说明正确答案”,还要一并给出看起来像正确答案的错误答案。
AI的自动测试也一样,只让完成形态通过一次是不够的。准备像中央小阴影、全面覆盖、高DPI、输入中断这样容易让实现出错的反例,就更容易发现仅在数值上合格的状态。
总结
这次我制作了一个移动剪影部件、完成三幅影子图案的拼图。
我继承了前作的做法:缩短初次操作、明确输入对象、失败后也能立刻继续调整。在此基础上,我加入了光的投影和蒙版比较,并从重叠、超出、面积三个方向来衡量“相似”。
把背景的黑色也算作影子这一失败,教会了我把画面颜色与判定数据分开思考的重要性。负责制作外观的 Canvas,和负责测量正确答案的 Canvas。明确各自的角色后,即使和 AI 一起制作,也能具体地传达需要修改的地方。
我每天发布一个小游戏或实用工具。
来源:zenn · 游戏开发社区 · zenn.dev