"""第148〜152話の冒頭の動き(2026-11-14)。話ごとに違う動き(143〜147・138〜142 とも重ねない) 148 来て止まり「積ミ込ミ/待チ」→ 左右をきょろきょろ(誰も来ない)→ 時計の針のように画面に「…」が増え、ゆっくり沈む 149 隅でじっと待つ → ピコン!と跳ね上がって「呼バレタ?」→ 何も来ない → しぼんで「…呼バレテ/ナイ」 150 画面の数字が 1,2,3…7 と数える → 止まらず 8…28 と速くなり、体がふるえる → 「28!」で×目、くらっと傾く 151 扉の前(左)に止まって「到着/シマシタ」→ 前のめりに3回コツコツ → 返事がなく、画面が小さく「到着…」 152 右へ進んで止まり、くるっと左へ → 左へ進んで止まり、くるっと右へ(速くなる)→ 真ん中で「ココ/ドコ?」""" import sys import numpy as np import motion30 import hakobu_motion as M from hakobu_motion import T, ease, _approach class Wait148: dur = 3.6; empty = True def motion(self, t): if t < 0.7: return _approach(t, 0.7, 220) if t < 2.2: # きょろきょろ(左右に向きを変える) v = t - 0.7; side = 1 if int(v / 0.375) % 2 == 0 else -1 return dict(flip=(side < 0), rot=side * 1.5) v = min(1, (t - 2.2) / 1.0) # ゆっくり沈む return dict(dy=14 * ease(v), sy=1 - 0.05 * ease(v), rot=3 * ease(v)) def face(self, t): if t < 0.7: return ("open", "open"), None, "mid" if t < 2.2: return T("積ミ込ミ", "待チ") if t < 1.2 else (("open", "open"), None, "mid") return T("…" * (1 + min(2, int((t - 2.2) / 0.4)))) def ring(self, t): return 0.6 if t < 2.2 else 0.3 def blur(self, t): return t < 0.7 def dim(self, t): return 1.0 if t < 2.2 else 1.0 - 0.15 * min(1, (t - 2.2) / 1.0) def cam(self, t): return (0, 0) class Hope149: dur = 3.6; empty = True PING = 1.0 def motion(self, t): if t < self.PING: return dict(dy=-2 * abs(np.sin(2 * np.pi * 0.7 * t))) v = t - self.PING if v < 0.6: return dict(dy=-50 * np.sin(np.pi * v / 0.6), sy=1 + 0.05 * np.sin(np.pi * v / 0.6)) # ピョン! if t < 2.4: return dict(rot=1.5 * np.sin(2 * np.pi * 1.5 * (t - 1.6))) # 待つ w = min(1, (t - 2.4) / 0.6) # しぼむ return dict(sx=1 - 0.05 * ease(w), sy=1 - 0.1 * ease(w), dy=14 * ease(w)) def face(self, t): if t < self.PING: return ("down", "down"), None, "closed" if t < 2.4: return T("呼バレタ?") return T("…呼バレテ", "ナイ") def ring(self, t): return 1.0 if self.PING <= t < 1.6 else 0.4 def blur(self, t): return False def dim(self, t): return 1.0 def cam(self, t): return (0, 0) class Count150: dur = 3.6; empty = True def n(self, t): if t < 1.4: return 1 + int(t / 0.2) # 1〜7 をゆっくり if t < 2.6: return min(28, 8 + int((t - 1.4) / 1.2 * 21)) # 8〜28 を速く return 28 def motion(self, t): if t < 1.4: return dict(sy=1 - 0.02 * abs(np.sin(2 * np.pi * 2.5 * t))) if t < 2.6: r = (t - 1.4) / 1.2 return dict(dx=r * 6 * np.sin(2 * np.pi * 14 * t), rot=r * 2 * np.sin(2 * np.pi * 11 * t)) v = t - 2.6; k = min(1, v / 0.4) return dict(rot=-9 * ease(k), dx=-12 * ease(k)) def face(self, t): if t < 2.6: return T(str(self.n(t))) if t < 2.9: return T("28!") return ("x", "x"), None, "wave" def ring(self, t): return 0.5 + 0.5 * np.sin(2 * np.pi * 6 * t) if 1.4 <= t < 2.6 else 0.6 def blur(self, t): return False def dim(self, t): return 1.0 def cam(self, t): return (int(5 * np.sin(2 * np.pi * 17 * t)), 0) if 2.0 <= t < 2.6 else (0, 0) class Knock151: dur = 3.6; empty = True KN = (1.2, 1.55, 1.9) def motion(self, t): if t < 0.8: return _approach(t, 0.8, 240) for k in self.KN: # 前のめりにコツッ if k <= t < k + 0.25: return dict(rot=-6 * np.sin(np.pi * (t - k) / 0.25), dx=-8 * np.sin(np.pi * (t - k) / 0.25)) if t >= 2.4: w = min(1, (t - 2.4) / 0.5) return dict(sy=1 - 0.04 * ease(w), dy=8 * ease(w)) return {} def face(self, t): if t < 0.8: return ("open", "open"), None, "mid" if t < 2.4: return T("到着", "シマシタ") return T("到着…") def ring(self, t): return 1.0 if any(k <= t < k + 0.25 for k in self.KN) else 0.5 def blur(self, t): return t < 0.8 def dim(self, t): return 1.0 def cam(self, t): return (0, 0) class Lost152: dur = 3.6; empty = True TURN = (0.0, 0.9, 1.6, 2.1, 2.45) # 向きを変える時刻(だんだん速く) STOP = 2.75 def motion(self, t): if t >= self.STOP: v = t - self.STOP; k = np.exp(-v * 8) * np.cos(2 * np.pi * 4 * v) return dict(rot=3 * k) i = sum(1 for f in self.TURN if t >= f) - 1 a = self.TURN[i]; b = self.TURN[i + 1] if i + 1 < len(self.TURN) else self.STOP u = ease((t - a) / (b - a)); side = -1 if i % 2 == 0 else 1 # 左へ・右へ x0 = 0 if i == 0 else (-120 if i % 2 == 1 else 120) x1 = -120 if side < 0 else 120 if i == len(self.TURN) - 1: x1 = 0 return dict(dx=x0 + (x1 - x0) * u, flip=(side > 0), dy=-3 * abs(np.sin(2 * np.pi * 4 * t))) def face(self, t): if t < self.STOP: return ("open", "open"), None, "o" return T("ココ", "ドコ?") def ring(self, t): return 0.6 def blur(self, t): return t < self.STOP def dim(self, t): return 1.0 def cam(self, t): return (0, 0) M.PATTERNS.update(ep148=Wait148, ep149=Hope149, ep150=Count150, ep151=Knock151, ep152=Lost152) if __name__ == "__main__": for arg in sys.argv[1:]: nm, _, kv = arg.partition(":") pat = M.PATTERNS[nm]() for item in filter(None, kv.split(",")): k, v = item.split("=", 1) if k in motion30.STR: setattr(pat, k, v) else: setattr(pat, k, float(v)) M.render(pat, f"ハコブくん_{nm}.mp4")