箱が2つ、くっついたり離れたり
柔術の動きをシミュレートするゲームを作っている。3D 化で手が止まっている話は別に書いたが、今回はその手前、ブラウザで動く 2D のほうの話をする。
最初のプロトタイプは、箱が2つ画面にあるだけだった。技のロジックは動いていて、テストも通っている。でも見ていると何が起きているのか分からない。箱がくっついて、少し経つと離れる。それだけ。
モーションデータは持っていない。3D モデルも無い。あるのはシミュレーションの状態(誰がどの技に入っていて、どこを掴んでいて、姿勢がどれくらい崩れているか)だけだ。
そこから毎フレーム姿勢を計算して描く、プロシージャルなやり方で「生きている」感をどこまで出せるかを試した。結果として6段階ぶん積み上がったので、効いた順に書く。
まず関節を入れる
出発点は、箱を関節付きのリグに置き換えることだった。肩と肘、股と膝、体幹、頭。それぞれの角度をシミュレーションの状態から毎フレーム決めて描く。
これだけで、止まっている絵がかなり変わる。ただし手はまだ相手に当たっていない。「この技ならこの方向」という決め打ちの角度に腕を向けているだけなので、よく見ると手が空中を掴んでいる。
次の段でここを潰した。順運動学で全関節のワールド座標を再構築して、手が実際に相手の体の位置へ届くように解く。いわゆる接触 IK だ。
見た目のインパクトが一番大きかったのはここだった。手が相手に「当たる」だけで、絵が急に嘘くさくなくなる。 逆に言うと、ここが合っていない状態で細部を足しても意味がない。あとから振り返ると、この順番は正しかった。
掴んだら、相手の腕も動かす
次に入れたのがグリップの相互拘束で、これが自分の中では一番の発見だった。
片方が相手の袖や手首を掴んでいるとき、掴まれた側の腕を掴んでいる手の位置から逆算して解き直す。掴む側だけを動かすと、2つの手が微妙にずれて別々のものに見える。両方を同じ点に拘束すると、引っぱりが両方の体に伝わって見える。
同時に、作用反作用と予備動作も入れた。動く前にわずかに逆へ溜める。引けば引かれた側も動く。物理シミュレーションではなく、あくまで見た目の辻褄合わせだ。それでも「力がかかっている」ように見える。
2体が別々に動いているか、組み合っているかの差はここで決まった。接触 IK が「触れている」なら、相互拘束は「絡んでいる」に近い。
細部を足す
ここから先は細かい積み増しになる。
手の開閉を入れた。腕の姿勢に握りのチャンネルを1本足して、0 なら開いた手、1 なら握りこぶし。掴みに行くときは開き、触れた瞬間に指が回り、固まると握力に応じて握りこむ。足首の関節も足した。カメラも固定をやめて、わずかに手持ちっぽく揺らした。
そのあと、技ごとのエントリー予備動作を入れた。それまでは「次に何か仕掛ける」という汎用の溜めだったのを、候補になっている技ごとに別の溜めに変えた。三角絞めなら腰が上がって近い側の脚が越えてくる。見ている側は、技名が出る前に何が来るか分かる。
崩されたときのマットポストも入れた。姿勢の崩れが一定を超えていて、かつその手が空いているなら、手を投げ出して床につく。実際に組んでいるときに一番反射的に出る動きで、これが入ると倒れ方が急に人間らしくなる。
同じ動きを二度させない
最後の2段は、ばらつきの話だ。
アイドル状態がずっと同じループだったのを崩した。閉じたガードで止まっているとき、じっとしているのではなく、位置をずらし、握りを組み替え、呼吸も一定にしない。待っている時間に何もしないのが、一番「死んでいる」ように見える。
そして実行スタイルのばらつき。同じ技を同じ状況で出しても、毎回まったく同じ再生になるのをやめたかった。
ここでランダム関数をそのまま呼ばなかったのが、我ながら良い判断だった。状態機械がその状態に入った時刻からハッシュを取って、動作ごとのシードにした。 こうすると毎回違う見え方になるのに、同じ入力からは同じ結果が出る。テストが壊れない。
プロシージャルなアニメーションに乱数を入れると、だいたい再現性を失ってデバッグができなくなる。シードを状態から導出しておけば、「さっきの変な動き」をもう一度出せる。
積む順番のほうが大事だった
振り返ると、効いた順番はこうだった。関節を入れる、接触を合わせる、2体を拘束する、細部を足す、ばらつきを入れる。
もし逆からやっていたら、たぶん全部無駄になっていた。手が空中を掴んでいる状態で呼吸の揺らぎを足しても、誰も気づかない。土台が嘘だと、細部の精度は見えない。
もうひとつ思ったのは、リアルさは精度ではないということだ。ここで足したものの多くは、物理的な正確さではなく「予測可能性を少しだけ壊すもの」だった。呼吸のばらつき、握りの組み替え、実行スタイルの差。同じ入力に同じ出力を返すのは、計算としては正しいが、生き物としては間違っている。
そして全部、ブラウザで数値を出しながら詰められた。テストが通り、結果がテキストで返ってくる。この環境だったから何十周も回せた。ここを 3D に移した瞬間にループが切れて止まったわけだが、それはまた別の話だ。