跳到正文
zenn · 游戏开发社区· 来源· boraemon2000·· 3 小时前AI 评分63

【每天部署1次】制作了分别测量重叠与溢出的「贴紧!妖怪影子戏剧场」

【1日1デプロイ】重なりとはみ出しを別々に測る「ピタッ!おばけ影絵劇場」を作った

正文 · AI 翻译

ランタンの光でおばけの影を作る、ピタッ!おばけ影絵劇場

作为“1天1部署”的新作,我制作了移动纸片剪影、让影子与样本重合的3幕解谜游戏 ピタッ!おばけ影絵劇場。

▶ 游玩ピタッ!おばけ影絵劇場

左侧是灯笼,中央是纸片剪影,右侧是和纸幕布。拖动剪影时,幕布上的影子会随其与灯笼的距离变大或变小。

第1幕是“妖怪猫”,第2幕是“南瓜马车”,第3幕是“月夜之龙”。不仅要靠位置和大小,还要运用旋转和多个影子的重叠来完成。

切り抜きと見本の影を重ねている実際のプレイ画面

玩法

  1. 按下“拉开帷幕”
  2. 点击选择纸片剪影
  3. 拖动调整位置和阴影大小
  4. 用左右旋转按钮调整角度
  5. 增加“重叠”、减少“超出”,完成第三幕

在 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