コンテンツへスキップ

QualiArtsengineer blog

CEDEC2024 UnityのHumanoidと共存するキャラクタジョイント制御と制作ワークフロー

CEDEC2024 UnityのHumanoidと共存するキャラクタジョイント制御と制作ワークフロー

14 min read

はじめに

株式会社QualiArtsの塩塚です。 テクニカルアーティストとして、主にキャラクタアニメーション制御、物理シミュレーション、パイプライン整備を担当しています。

2024年8月21日から8月23日にかけて「CEDEC 2024」が開催されました。 この中で「UnityのHumanoidと共存するキャラクタジョイント制御と制作ワークフロー」のセッションを講演させていただきました。 本記事はその講演の補足記事になります。 講演本編に加え、時間の都合上カットした内容も含めて記載いたします。

導入

Unityには「Humanoid」と呼ばれる人型キャラクタを動かすための機能が備わっており、リターゲットやIKなど、キャラクタを汎用的に扱うシステムが整っています。 しかし補助骨や揺れ骨など独自のジョイント制御を導入する際にHumanoid特有の挙動と相性が悪く、思い通りの実現ができずパフォーマンス的にも最適でない選択を取らざるを得ない場合があります。 セッションでは、Humanoidのメリットを活かしながら表現力やパフォーマンスを損なわないジョイント制御手法を説明しました。 加えて補助骨を中心としてMayaとUnity間のワークフローについても紹介しました。

モバイルゲームに必要なキャラクタジョイント制御要素の整理

UnityHumanoidの紹介

UnityにはHumanoidと呼ばれる、人型キャラクターを効率よく動かすための、リグ構造が存在します。 キャラクタモデルのジョイントのローカル回転を直接制御するのではなく、肘の曲げなど人体の動きに即したパラメータに正規化された値で制御を行います。 この正規化されたパラメータをMuscleと呼びます。 ゲーム開発者としては、このHumanoidリグを通じてアニメーションの適用や、リターゲット、IK解決などを行います。

アバターのMapping(公式リンク) アバターのMuscle&Settings(公式リンク)
アバターのMapping アバターのMuscle&Settings

具体的な機能を2つ取り上げます。

捻り補正フルボディIK
捻り補正 フルボディIK
手首などの捻り回転が、親階層である肘に分割されます。捻りが手首に集中することを防ぎ、ボリューム減少を緩和することが期待できます。手足の位置回転に加えて、引っ張りや重心の操作が可能です。

キャラ制御に求められる要件の整理

キャラ制御に求められる要件として、主にカットシーンとスクリプト操作場面で必要な要素が異なると考えています。

  • カットシーン

    • UnityのTimelineをベースとしたシステムとの連携が必要
    • 時系列的な厳密性に加え、エディタ環境との調和が求められる
  • スクリプト操作

    • インゲームやアウトゲームなど、遊びやユーザー操作との連携が必要
    • ゲーム性により要件は異なるが、ゲームエンジニア視点での扱い易さ方法が求められる

また昨今のモバイルゲームではキャラの入れ替えや、衣装の入れ替えをプレイヤーが自由に行える仕様が採用されることがあります。 この仕様に対応するため、リアルタイムで身長差や衣装設定差をカバーするシステムであると、なお良いと考えています。

これらの要件を考え、本稿では次のようなシステムが必要であると整理します。

  • アニメーションの再生

    • Timelineとの親和性が高い
    • 他のシステムに干渉しない再生タイミングで完結
  • 基本骨補正

    • キャラ/衣装入れ替えへの対応
    • IKなど、Humanoidシステムと競合しないランタイム操作
  • 追加ジョイントの制御方法

    • Humanoidと連携し、計算コストを抑えた 小道具骨/補助骨/揺れ骨 の制御

アニメーションの再生

まずはアニメーションの再生要件を実現するために、処理の流れを簡単に示します。 こちらはUnityのオブジェクト操作に関する更新イベントの一部を抜粋したものです。

更新イベントの一部

  • Updateイベント
    • 基本的なゲーム内のロジック実装に使用されます。
  • ProcessAnimationイベント
    • アニメーションの再生を行うイベントであり、座標計算などもここで行われます。
  • LateUpdateイベント
    • アニメーションの結果に応じた、別オブジェクトの操作に使用します。
    • 例)キャラに追従するカメラの操作。ライトのエイム。

UnityにはProcessAnimationイベント内で任意の処理を実行できる「Animation C# Jobs」機能があります。 Unity標準のアニメーション再生イベントに紐づいているため、Timelineとの親和性が非常に高いです。 またUpdateやLateUpdateなど他のゲームロジックと別の更新タイミングであるため、いかなるゲーム性であっても更新処理が干渉せず、ゲームエンジニアから見た扱い易さを担保してくれます。

本稿では、このAnimation C# Jobs内であらゆるジョイント制御を行うものとします。

Playable API

PlayableGraph と呼ばれるツリー構造で、アニメーションやスクリプト等を、ミックス、ブレンド、変更し、1 つの出力として再生する仕組みです。 AnimatorControllerやTimelineも、このPlayableAPIで作成されています。

このツリー構造に任意の処理を追加できるため、本公演ではアニメーションのブレンド等が行われた後に、追加のジョイント制御を行います。 これにより、ゲーム性や再生方式によらない、汎用のキャラクタ制御システムとして動作することを担保します。

AnimationC#Jobsを使用し、アニメーションが確定する直前に独自の処理を追加するものとして、処理順に則って具体的な制御方法をご紹介します。

PlayableAPI

シーン内のPlayableGraphを表示ツールする、PlayableGraph Visualizer をTipsとして紹介しておきます。 https://github.com/Unity-Technologies/graph-visualizer

ランタイムの基本骨補正

衣装ボリュームに対応するIK

キャラ入れ替え/衣装入れ替えの仕様が採用される場合、汎用モーションを使用すると埋まりが発生してしまいます。

右の画像においては、ボリュームの大きい衣装では、手がスカートに埋まってしまいます。 全キャラ及び全衣装パターンのモーションを用意することは現実的ではないですし、揺れ骨のコライダで衣装側を動かす場合、アバレが発生する懸念があります。

埋まりなし埋まり発生
埋まりなし 埋まり発生

これに対応するため、衣装の概形に沿うコライダを作成し、手が衣装の内側に入らないようにするポーズ補正処理を追加しました。 これにより、同じモーションを使用してもある程度の埋まりを回避することができました。

埋まりなし埋まりなし
埋まりなし 埋まりなし

しかし、IK,FKの切り替え処理が走るため、コライダに触れる瞬間にカクツキが発生するケースがありました。

そこで、侵入不可領域と減衰領域の2つのコライダを作成しました。 減衰領域はローパスフィルタの役割を持ち、手が布に当たって少し沈むような挙動をとりつつ、カクツキを抑制することができました。

減衰領域

Muscleを使用した加算モーション

簡易的な会話場面において、キャラクタの発話時に自動的に頭部が頷くように動いてほしい要件がありました。 アニメーションとしては、首と頭のジョイントの傾げ曲げと頷き曲げの2軸をわずかに動かす動きになります。

Muscleを使用した加算モーション

シンプルな実装方法として、まずはUpdateイベントで音声の再生とFFTを用いた発話状況の取得を行います。 次にProcessAnimationイベントでアニメーションを流し、揺れ骨など他ジョイントの計算を行います。 最後にLateUpdateイベントで首と頭のローカル回転に加算を行います。

シンプルな実装方法

一見するとこの実装でもうまくいきそうですが、揺れ骨計算が終わったあとに頷き加算を行っているため、頷きによる頭部の動きが揺れ骨に反映されず、髪が固く見えてしまう問題点がありました。 この問題を解決するために、実装を変更します。

改善した実装方法

Updateイベントで音声の再生と、FFTを用いた発話状況の取得を行うのは変わりませんが、ProcessAnimationイベントにて加算処理も行います。 この時にローカル回転ではなく、頷き曲げ、傾げ曲げに関するMuscle値に対して加算処理を行います。 揺れ骨の計算前にMuscle制御で加算を行うことで、揺れ骨の計算に頷きの動きを反映させることが可能です。 これによりシステム的な加算処理を、表現力を損なわずに実装することができました。

実装サンプルを記載します。 これを首と頭の、傾げ曲げと頷き曲げの、4つそれぞれに行います。

public void ExecuteNodAdditive(AnimationStream stream)
{
    AnimationHumanStream humanStream = stream.AsHuman();
    # アニメーションから指定されたMuscle値を取得
    float currentMuscleValue = humanStream.GetMuscle(muscleHandle);
    # 加算したい回転値を、Muscle値に変換。(±40度のローカル回転を、±1のMuscle値とする)
    float additiveDegree = additiveDegree.GetFloat(stream);
    float additiveMuscleValue = additiveDegree / 40.0f;
    # 最終的なMuscleを設定
    humanStream.SetMuscle(muscleHandle, currentMuscleValue + additiveMuscleValue);
}

補助骨の仕様とワークフロー

モバイルゲーム開発、特に衣装入れ替えが想定されるゲームにおいては、補助骨は、ゲームエンジン内でリアルタイム駆動することが望ましいと考えています。 基本骨のモーションデータのみを管理することで、データサイズの削減にも貢献します。 弊社ではMayaとUnityで同じように駆動する補助骨システムを実装しています。

本稿では簡単な補助骨としては、以下の2種を取り上げます。 揺れ骨のサポートに関するものと、人体形状の保持に関するものです。

揺れ骨のサポート補助骨

スカートや髪などの揺れものはUnityにてリアルタイムの物理挙動が実装されていますが、その特性上アバレが発生してしまう可能性があります。 また、コライダによる衝突がないような微細な姿勢変化には反応しづらく、硬い印象を抱いてしまいます。 そこで衣装の動きをサポートする補助骨を使用しています、代表例はスカート補助骨です。

実装としては足の回転値を取得し、ステレオ投影法で軸ごとの曲げに分解します。 得られた前後曲げに対しては前後のスカートのジョイントを連動して動かし、左右曲げに対しては左右のスカートのジョイントを連動して動かしています。

足が少しだけ動くような範囲ではアソビを持たせて緩やかに追従し、足とスカートが当たる範囲ではスカートが持ち上げられるように追従ウェイトを変化させています。 揺れ骨だけでなく補助骨でもこの動きを行うことで、コライダ抜けやアバレの防止、わずかな動きでも揺れ骨が反応するリッチな表現を実現しています。 こちらはHumanoid関係なく、汎用的に使用されている形式だと思います。

スカート補助骨挙動処理の流れ
スカート補助骨挙動動作アニメーション 1.太腿の回転取得、2.ステレオ投影法で軸ごとの曲げ分解、3.前後or左右曲げ連動計算、4.前後or左右スカートの回転設定

Humanoidと人体補助骨の両立

人体に関して、特に前腕を取り上げます。 前述の通り、Humanoidでは四肢の捻りに関して補正が行われてるため、この補正と競合しない制御が必要です。

Humanoidの捻り補正の無効化

肘は骨の形状から、曲げた際に外側に出っ張りが出来ます。 これを表現するため、肘を曲げた際に、その動きを打ち消すような回転を与えます。 この補助骨を実現するために、手首の捻りを肘にも分解する機能は邪魔になるため、捻り分解をオフにします。 こちらの対策は簡単で、アバター設定のLowerArmTwistを0にすることで実現できます。

肘補助骨挙動設定
肘補助骨挙動アニメーション Upper Arm Twist: 0.5, Lower Arm Twist: 0, Upper Leg,Twist: 0.5,Lower Leg Twist: 0.5,Arm Stretch: 0.05, Leg Stretch: 0.05, Feet Spacing: 0, Translation Dof: false

当然、捻りは手首の集中するため、ボリューム減少は発生します。

捻り補正規定値捻り補正無効
捻り補正規定値動作アニメーション捻り補正無効動作アニメーション

補助骨の計算としては肘の回転角を参照し、打ち消すような回転を補助骨に与える簡易なもので実現可能です。

独自の手首の捻り補助骨の追加

ボリューム減少対策のため前腕部に捻り補助骨を入れたいです。 Humanoid標準の捻り補正とは別の概念として、任意の数だけ補助骨を入れることを想定します。 前腕部の捻り補助骨は手首の捻り回転を参照し、任意の係数を掛けて前腕部の補助骨に与える簡易な実装で実現可能です。

ただしここで、Humanoidならではの問題点が現れます。 手首のローカル回転を取得したいのですが、Muscle値で制御がされているため、Job内で正確なローカル回転値を得ることができません。

そこでローカル回転ではなくMuscle値を取得し、ローカル回転値に読み替えます。 読み替えた値を元に連動計算を行い、前腕捻り補助骨の回転値を設定します。 このようにMuscle値をベースに計算を行うことで、Humanoid補正と競合しない補助骨駆動が可能です。

手首補助骨挙動処理の流れ
手首補助骨挙動動作アニメーション 1.手首捻りのMuscle値を取得、2.Muscle値から回転値に変換、3.捻り運動計算、4.前腕捻り補助骨に回転値を設定

ちなみに、±90度のローカル回転を、±1のMuscle範囲とするのが標準設定なのですが、±66度で変換するのが良い結果を得られました。 こちらは前述のLowerArmTwistの設定を0にする調整により、最適な読み替え係数が変化してものだと考えています。 この66度という数字は、弊社のモデル/モーションにおいて経験系に算出した設定であるため、他環境では異なる可能性があります。

実装サンプルを記載します。

public void ExecuteHandTwist(AnimationStream stream)
{
  AnimationHumanStream humanStream = stream.AsHuman();
  # アニメーションから指定されたMuscle値を取得
  float muscleValue = humanStream.GetMuscle(muscleHandle);
  # 駆動する回転値を、Muscle値から算出。(±66度のローカル回転を、±1のMuscle値とする)
  float handTwistDegree = muscleValue * 66;
  Quaternion driverQuat= Quaternion.Euler(handTwistDegree * driverCoefficient, 0, 0);
  # 最終的なMuscleを設定
  driverHandle.SetLocalRotation(stream, driverQuat);
}

補助骨ワークフロー

補助骨はDCCツール(Maya)とゲームエンジン(Unity)の両方で作業を行いますが、それぞれで組み込みや調整を行うのは作業コストが大きいです。 前述の通り、UnityではMuscleモードでの調整もあるため、組み込み作業が煩雑になってしまいます。 そこで設定ファイルを介し、組み込みや調整を移植可能にしています。

補助骨ワークフロー

Maya補助骨ワークフロー

独自のノードを開発しており、簡単な算術計算を行う汎用的なノードと、特殊な衣装専用の固有ノードがあります。 それぞれタイプごとに1つの独立したノードになっており、部位単位でのテンプレ化が容易になっています。 また1補助骨1ノードであるため、手動でのノード接続も可能で、挙動検証時には手動でさまざまなバリエーションを自由に試すことが可能です。 セットアップ、バリデータ、セレクタ、そしてUnityへの書き出しなど、効率化と事故防止を行うツールも当然用意しています。

Maya補助骨ワークフロー

Unity補助骨ワークフロー

UnityではMuscleモードへの対応や、座標系の差異の吸収を自動で行っており、補助骨に付与したコンポーネントが設定を保持しています。 Mayaでは1補助骨1ノードであったように、Unityでは1補助骨1コンポーネントに対応しています。 検証が容易な点はもちろんですが、挙動が把握しやすい点もメリットがあります。

セットアップ時にはキャラクタルート以下の補助骨コンポーネントを収集してセットアップしているため、 衣装の着せ替えや、武器や尻尾なども追加パーツによらず、一様にセットアップが可能です。

Unity補助骨ワークフロー

このように設定ファイルでのやり取りに加え、MayaとUnityで概念を合わせることができる点も、アーティストの学習コストを抑えられる利点だと考えています。

揺れ骨と高速化/汎用化テクニック

Unity標準の高速化

C# Job System、Animation C# Jobs、Burstコンパイラがキャラクタ制御の高速化に寄与してくれます。 詳細な説明は割愛しますが、これらを正しく活用すれば、実用上十分な実行速度が得られると思います。 弊社ではこれらをベースに高速化を行っており、追加としてJobの統一も行っています。

Jobは非常に高速な仕組みですが、複数のjobが並んだ際に、job切り替えにわずかに時間を取られてしまう性質があります。 これはどうしても発生してしまうオーバーヘッドであり、jobが多ければ多いほどオーバーヘッドは積み重なっていきます。 これを解決するため、キャラクタ制御に関わる処理をすべてまとめ、1つのjobにすることで、切り替えのオーバーヘッドを削減しています。

また異なるジョイントとの連携が取りやすい点も副次的なメリットとして挙げられます。 例えば旗のように小道具骨の先に揺れ骨がついていたり、武器の変形ギミックに補助骨が付属しているようなケースでも、実行順番をjob内で制御することが、柔軟性も確保しています。

キャラ制御システムを1つにまとめることで、高速化や表現力向上に貢献しています。

job統合

座りモーションの課題

続いて汎用化について、座りモーションへの対応を取り上げます。 キャラクタのバリエーションのあるゲームおいて、椅子などオブジェクトとの干渉は難易度の高い課題になります。 弊社では主に2点が課題となりました。

身長に応じたモーション補正

Unityには手足のIKに加えて、重心の位置を操作する機能があります。 この機能を用いて、椅子の上の指定位置に腰を移動させることが可能です。 加えて足のIKを用いれば自然と膝が曲がり、異なる身長でも同じ椅子に座ることができます。

ちなみにUnityの重心計算方法は非公開ですが、ジョイント位置の加重平均である程度再現可能であることを確認しています。 弊社ではMayaでもこの重心を再現しており、重心操作の有効度などを、Maya側で調整できるようなワークフローになっています。

通常重心操作と足のIK
通常 重心操作と足のIK

椅子形状に沿った衣装形状変化

ヒューリスティックな拘束条件として、スカートジョイントが足に吸着するような引力を実装しました。

座りモーション時には、この引力を有効にし、椅子形状に沿ったスカートの形状を実現しています。 これにより工数負担を抑えた上で、衣装バリエーションにも対応可能です。 もちろん実際の物理現象に即さない力であり、完璧な見た目にはなっていません。 ただし厳密なクロスシミュレーションの計算と比較して非常に安い計算コストで実現可能であり、実用上十分に妥協できるラインを確保できました。

通常足に吸着するような引力
通常 足に吸着するような引力

まとめ

AnimationC#Jobsを使用することで、Unity標準のアニメーションシステムと親和性の高く、ゲーム性に依らないキャラクタ制御システムを実現しました。 Muscle値を使用した加算アニメーションと補助骨計算を行うことで、Humanoidと共存する表現力の高いジョイント制御手法を実現しました。 補助骨を中心としてMayaとUntiy間のワークフローについても紹介し、揺れ骨を中心として高速化と汎用化の1事例を紹介しました。

UnityのHumanoidは独特な仕様もありますが、非常に有用な機能であり、 うまく付き合っていくことでメリットを享受しながら、独自の表現を行うことが可能です。

この知見がモバイルゲームでHumanoidを扱う皆様のお役に立てれば幸いです。

スライド資料

2020年にサイバーエージェントにUnityクライアントエンジニアとして新卒入社。QualiArtsテクニカルアーティスト室に所属し、テクニカルアーティストとしてクリエイターの業務効率化に携わる。