"""第143〜147話の冒頭の動き(2026-11-08)。話ごとに違う動き(138〜142・134 とも重ねない) 143 右からまっすぐ突っ込んできて、ピタッと急停止(前のめりに揺れる)→「止マレ/マス」→ 得意げ 144 右から来て、左へスーッと通り過ぎかけ → 気づいてバックで戻る → 小さくおじぎ「ドウゾ」 145 来て止まり、大声でぴょんぴょん「オ薬/デス!」→ 縮こまって小さくなり、目が点で静かに待つ「…」 146 待っているところを左から小突かれ(ガクン・画面がゆれる)→ ×目 →「衝撃/検知」→ 右の空いた所へ移る 147 しゃがんで力をためる → 左へ猛ダッシュ(スピード線)→ 戻ってきて、しょんぼり「先ヲ/越サレ…」""" import sys import numpy as np import motion30 import hakobu_motion as M from hakobu_motion import T, ease, _approach class Stop143: dur = 3.6; empty = True HIT = 0.7 def motion(self, t): if t < self.HIT: return dict(dx=360 * (1 - t / self.HIT), dy=-6 * abs(np.sin(2 * np.pi * 5 * t)), rot=-3) v = t - self.HIT # ピタッ:前のめりに揺れて止まる k = np.exp(-v * 8) * np.cos(2 * np.pi * 4 * v) return dict(rot=-7 * k, dx=-6 * k) def face(self, t): if t < self.HIT: return ("open", "open"), None, "mid" if t < 1.5: return ("big", "big"), None, "o" if t < 2.6: return T("止マレ", "マス") return ("happy", "happy"), None, "closed" def ring(self, t): return 1.0 if self.HIT <= t < 1.0 else 0.6 def blur(self, t): return t < self.HIT def dim(self, t): return 1.0 def cam(self, t): v = t - self.HIT return (int(8 * np.exp(-v * 10) * np.sin(2 * np.pi * 18 * v)), 0) if 0 <= v < 0.35 else (0, 0) class Back144: dur = 3.6; empty = True def motion(self, t): if t < 1.1: return dict(dx=260 - 520 * ease(t / 1.1), dy=-4 * abs(np.sin(2 * np.pi * 4 * t))) # 左へ通り過ぎかける if t < 1.5: return dict(dx=-260, rot=2 * np.sin(2 * np.pi * 6 * (t - 1.1))) # 気づく if t < 2.4: # バックで戻る(小刻み) v = (t - 1.5) / 0.9 return dict(dx=-260 + 230 * ease(v), dy=-2 * abs(np.sin(2 * np.pi * 6 * t))) v = t - 2.4 # おじぎ nod = 0.05 * max(0.0, np.sin(np.pi * min(1, v / 0.6))) return dict(dx=-30, sy=1 - nod, dy=6 * nod / 0.05) def face(self, t): if t < 1.1: return ("open", "open"), None, "mid" if t < 1.5: return ("big", "big"), None, "o" if t < 2.4: return ("down", "down"), "trouble", "wave" if int(t * 8) % 2 else "wave2" return T("ドウゾ") def ring(self, t): return 0.6 def blur(self, t): return t < 1.1 def dim(self, t): return 1.0 def cam(self, t): return (0, 0) class Hush145: dur = 3.6; empty = True def motion(self, t): if t < 0.7: return _approach(t, 0.7, 220) if t < 2.0: # 大声でぴょんぴょん v = t - 0.7 return dict(dy=-26 * abs(np.sin(2 * np.pi * 2.2 * v)), sy=1 + 0.03 * np.sin(2 * np.pi * 4.4 * v)) v = min(1, (t - 2.0) / 0.4) # 縮こまって静かに待つ return dict(sx=1 - 0.06 * ease(v), sy=1 - 0.1 * ease(v), dy=12 * ease(v)) def face(self, t): if t < 0.7: return ("open", "open"), None, "mid" if t < 2.0: return T("オ薬", "デス!") if t < 2.6: return ("down", "down"), "trouble", "frown" return T("…") def ring(self, t): return 1.0 if 0.7 <= t < 2.0 else 0.4 def blur(self, t): return t < 0.7 def dim(self, t): return 1.0 def cam(self, t): return (int(4 * np.sin(2 * np.pi * 15 * t)), 0) if 0.7 <= t < 2.0 else (0, 0) class Poke146: dur = 3.6; empty = True HIT = 0.9 def motion(self, t): if t < self.HIT: return dict(dy=-2 * abs(np.sin(2 * np.pi * 0.8 * t))) # じっと待つ v = t - self.HIT if v < 0.8: # 左から小突かれてガクン k = np.exp(-v * 7) * np.cos(2 * np.pi * 4 * v) return dict(dx=30 * (1 - np.exp(-v * 12)), rot=8 * k) if t < 2.7: return dict(dx=30) w = min(1, (t - 2.7) / 0.6) # 右の空いた所へ移る return dict(dx=30 + 220 * ease(w), dy=-3 * abs(np.sin(2 * np.pi * 4 * t))) def face(self, t): if t < self.HIT: return T("受ケ取リ", "待チ") if t < self.HIT + 0.8: return ("x", "x"), None, "o" if t < 2.7: return T("衝撃", "検知") return ("open", "open"), None, "mid" def ring(self, t): return 1.0 if self.HIT <= t < self.HIT + 0.5 else 0.6 def blur(self, t): return t >= 2.7 def dim(self, t): return 1.0 def cam(self, t): v = t - self.HIT return (int(12 * np.exp(-v * 9) * np.sin(2 * np.pi * 20 * v)), 0) if 0 <= v < 0.4 else (0, 0) class Race147: dur = 3.6; empty = True def motion(self, t): if t < 0.8: # しゃがんで力をためる(小刻みにふるえる) v = ease(t / 0.8) return dict(sy=1 - 0.08 * v, dy=10 * v, dx=3 * np.sin(2 * np.pi * 12 * t)) if t < 1.3: return dict(dx=-700 * ease((t - 0.8) / 0.5), sx=1.06, sy=0.96) # 左へ猛ダッシュ if t < 1.9: return dict(dx=-700) # いなくなる if t < 2.6: return dict(dx=-700 + 700 * ease((t - 1.9) / 0.7), dy=-3 * abs(np.sin(2 * np.pi * 3 * t))) # とぼとぼ戻る v = min(1, (t - 2.6) / 0.4) # しょんぼり沈む return dict(dy=10 * ease(v), sy=1 - 0.04 * ease(v)) def face(self, t): if t < 0.8: return T("ヨーイ") if t < 1.9: return ("open", "open"), None, "o" if t < 2.6: return ("down", "down"), "trouble", "frown" return T("先ヲ", "越サレ…") def ring(self, t): return 1.0 if 0.8 <= t < 1.3 else 0.6 def blur(self, t): return 0.8 <= t < 1.3 def dim(self, t): return 1.0 def cam(self, t): return (0, 0) M.PATTERNS.update(ep143=Stop143, ep144=Back144, ep145=Hush145, ep146=Poke146, ep147=Race147) 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")