"""第138〜142話の冒頭の動き(2026-11-05)。話ごとに違う動き。 138 出ようとして、ガクッと止まる(2回)→ しょんぼり → 左の脇へスッと寄る(柱のわき) 139 走ってきて、見えない物に鼻先でぶつかりそうになり、のけぞって止まる →「!?」→ じりじり下がる 140 止まって「障害物?」→ じりじり横へ大回り(小刻みに)→「?」 141 その場でくるくる回る(横に縮んで裏返る)→ 目が回る →「次ノ階デス」 142 勢いよく走ってきて、見えない壁にゴツン → 跳ね返って揺れる →「…何モ?」""" import sys import numpy as np import motion30 import hakobu_motion as M from hakobu_motion import T, ease, _approach class Stall138: dur = 3.8; empty = True def motion(self, t): for t0 in (0.5, 1.4): # 前へ出かけて、ガクッ if t0 <= t < t0 + 0.7: v = t - t0 if v < 0.18: return dict(dx=-40 * ease(v / 0.18), rot=-2) k = np.exp(-(v - 0.18) * 10) * np.cos(2 * np.pi * 6 * (v - 0.18)) return dict(dx=-40, rot=4 * k, sy=1 - 0.04 * k) if t < 1.4: return dict(dx=-40) if t >= 1.2 else {} if t < 2.6: return dict(dx=-40, dy=8 * ease(min(1, (t - 2.1) / 0.3)) if t > 2.1 else 0) v = t - 2.6 return dict(dx=-40 - 300 * ease(min(1, v / 0.6)), dy=-4 * abs(np.sin(2 * np.pi * 3 * v))) def face(self, t): if t < 0.5: return T("案内", "待機中") if t < 2.1: return ("big", "big"), None, "o" if t < 2.6: return ("down", "down"), "trouble", "frown" return ("happy", "happy"), None, "closed" def ring(self, t): return 0.6 def blur(self, t): return t >= 2.6 def dim(self, t): return 1.0 def cam(self, t): return (0, 0) class Door139: dur = 3.6; empty = True def motion(self, t): if t < 0.8: return dict(dx=240 * (1 - ease(t / 0.8)), dy=-5 * abs(np.sin(2 * np.pi * 4 * t))) if t < 1.5: # のけぞって止まる v = t - 0.8; k = np.exp(-v * 7) * np.cos(2 * np.pi * 4 * v) return dict(rot=7 * k, dx=12 * (1 - np.exp(-v * 9))) v = t - 1.5 # じりじり下がる return dict(dx=12 + 120 * ease(min(1, v / 1.6)) + 3 * np.sin(2 * np.pi * 5 * v), rot=0.8 * np.sin(2 * np.pi * 5 * v)) def face(self, t): if t < 0.8: return ("open", "open"), None, "mid" if t < 1.5: return T("!?") return ("down", "down"), "trouble", "wave" if int(t * 8) % 2 else "wave2" def ring(self, t): return 0.6 def blur(self, t): return t < 0.8 def dim(self, t): return 1.0 def cam(self, t): return (0, 0) class Creep140: dur = 3.8; empty = True def motion(self, t): if t < 0.8: return _approach(t, 0.8, 200) if t < 1.5: return {} v = t - 1.5 # 小刻みに、じりじり大回り(左へ・少し傾く) steps = np.floor(v * 5) + ease((v * 5) % 1) x = -min(steps * 22, 230) return dict(dx=x, rot=-2.5 * min(1, v / 0.4), dy=-2 * abs(np.sin(2 * np.pi * 5 * v))) def face(self, t): if t < 0.8: return ("open", "open"), None, "mid" if t < 1.5: return T("障害物?") if t < 3.0: return ("dot", "dot"), "trouble", "wave" if int(t * 6) % 2 else "wave2" return T("?") def ring(self, t): return 0.6 def blur(self, t): return t < 0.8 def dim(self, t): return 1.0 def cam(self, t): return (0, 0) class Spin141: dur = 3.8; empty = True def motion(self, t): if t < 0.8: return _approach(t, 0.8, 230) if t < 2.4: # その場でくるくる(横に縮んで裏返る) v = t - 0.8; ph = 2 * np.pi * 1.4 * v c = np.cos(ph) return dict(sx=max(0.08, abs(c)), flip=c < 0, dy=-3 * abs(np.sin(ph))) v = t - 2.4 # 目が回って、ふらふら return dict(rot=4 * np.sin(2 * np.pi * 1.6 * v) * np.exp(-v * 1.2), dx=6 * np.sin(2 * np.pi * 1.6 * v) * np.exp(-v * 1.2)) def face(self, t): if t < 0.8: return ("open", "open"), None, "mid" if t < 2.4: return ("open", "open"), None, "closed" if t < 3.0: return ("x", "x"), None, "wave" if int(t * 8) % 2 else "wave2" return T("次ノ階", "デス") def ring(self, t): return 0.6 def blur(self, t): return 0.8 <= t < 2.4 def dim(self, t): return 1.0 def cam(self, t): return (0, 0) class Bonk142: dur = 3.6; empty = True HIT = 0.6 def motion(self, t): if t < self.HIT: return dict(dx=330 * (1 - t / self.HIT), dy=-6 * abs(np.sin(2 * np.pi * 5 * t)), rot=-3, sx=1.05, sy=0.97) v = t - self.HIT # ゴツン:つぶれて跳ね返り、揺れる k = np.exp(-v * 5) * np.cos(2 * np.pi * 3.5 * v) return dict(dx=40 * (1 - np.exp(-v * 8)), sx=1 - 0.08 * k, sy=1 + 0.05 * k, rot=6 * k) def face(self, t): if t < self.HIT: return ("open", "open"), None, "mid" if t < 1.6: return ("x", "x"), None, "o" return T("…何モ?") 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(10 * np.exp(-v * 9) * np.sin(2 * np.pi * 20 * v)), 0) if 0 <= v < 0.4 else (0, 0) M.PATTERNS.update(ep138=Stall138, ep139=Door139, ep140=Creep140, ep141=Spin141, ep142=Bonk142) 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")