アニメーション
概要
Quantumには、アニメーションの処理方法が2通り用意されています。
- Unityからゲームステートをポーリング
- Custom Animatorを使用した決定性アニメーション
Polling Based Animation(ポーリングペースアニメーション)
多くのゲームでは、プレイヤーへのオブジェクトのステート通信にアニメーションを使用しています。例えば、プレイ中のキャラクターが歩いていたり、ジャンプしていたりする場合、そのアニメーションは実際のところインプレースアニメーションであり、起こる動作はコードによって操作されたものです。
つまり、キャラクターのそれぞれの(Unity)アニメーターを管理しているスクリプトは、ステートレスであり、アニメーションパラメータとして(Quantumで)ゲームのシミュレーションからポーリングされたデータをベースに値を派生させパスしているということになります。
注意: ゲームプレイシステムが Root Motion と紐ついていたり、アニメーションステートを関係のある場合は、次のセクションまで飛んでください。
API Sampleにポーリングベースアニメーションのコンセプトが導入されました。以下のスニペットはPlayerAnimations
スクリプトから抜粋されたもので、移動アニメーションを発生させます。
- エンティティがインスタンス化されると、
Initialize()
メソッドが呼び出され、エンティティのEntityRef
と現在のQuantumGame
をキャッシュします。QuantumGame
は便宜上のためのものです。 - Unityの
Update()
ごとに、MovementAnimation()
関数が呼び出されます。 MovementAnimation()
関数が、前回キャッシュされたEntityRef
を使用してCharacterController3D
からデータをポーリングします。- 関連するアニメーターパラメータがポーリングされたデータから派生されます。
- 計算されたデータがUnity Animatorへパスされます。
C#
// This snippet is extracted from the Quantum API Sample.public unsafe class PlayerAnimation : MonoBehaviour
{
[SerializeField] private Animator _animator = null;
private EntityRef _entityRef = default;
private QuantumGame _game = null;
// Animator Parameters
private const string FLOAT_MOVEMENT_SPEED = "floatMovementSpeed";
private const string FLOAT_MOVEMENT_VERTICAL = "floatVerticalMovement";
private const string BOOL_IS_MOVING = "boolIsMoving";
// This method is called from the PlayerSetup.cs Initialize() method which is registered
// to the EntityView's OnEntityInstantiated event located on the parent GameObject public void Initialize(PlayerRef playerRef, EntityRef entityRef){
_playerRef = playerRef;
_entityRef = entityRef;
_game = QuantumRunner.Default.Game;
}
// Update is called once per frame
void Update(){
MovementAnimation();
}
private void MovementAnimation() {
var kcc = _game.Frames.Verified.Unsafe.GetPointer<CharacterController3D>(_entityRef);
bool isMoving = kcc->Velocity.Magnitude.AsFloat > 0.2f;
_animator.SetBool(BOOL_IS_MOVING, isMoving);
if (isMoving) {
_animator.SetFloat(FLOAT_MOVEMENT_SPEED, kcc->Velocity.Magnitude.AsFloat);
_animator.SetFloat(FLOAT_MOVEMENT_VERTICAL, kcc->Velocity.Z.AsFloat);
}
else {
_animator.SetFloat(FLOAT_MOVEMENT_SPEED, 0.0f);
_animator.SetFloat(FLOAT_MOVEMENT_VERTICAL, 0.0f);
}
}
イベントのトリガー
アニメーションの中には、ゲームの中で発生する特定のイベントがベースになっているものもあります。例:プレイヤーがジュンプを押す、敵にヒットされる、など。これらのケースでは通常、イベントをシミュレーションから発生させ、ビューにそれをリッスンさせるのが好ましいです。これにより、デカップリングとポーリングベースアニメーション方式と連動した動作がうまくいくようになります。
イベントとコールバックについての詳細な説明についてはManual内のQuantum ECS > Game Callbacks
ページを参照してください。
API Sampleでは、イベントを使用して、画面上にその瞬間に反映にすべきアクションが発生した際の通信を行っています。例:プレイ可能なキャラクターのジャンプなど。
QuantumのMovementSystem
は、プレイヤーの入力を読み、移動の値をCharacterController3D
にパスする前に計算します。このシステムはまた、Jumpキーの押下もリッスンします。キーがWasPressed
の場合は、PlayerJump
イベントを発生させ、CharacterController3D
のJump()
メソッドを呼び出します。
C#
using Photon.Deterministic;
namespace Quantum
{
public unsafe struct PlayerMovementFilter
{
public EntityRef EntityRef;
public PlayerID* PlayerID;
public Transform3D* Transform;
public CharacterController3D* Kcc;
}
unsafe class MovementSystem : SystemMainThreadFilter<PlayerMovementFilter>
{
public override void Update(Frame f, ref PlayerMovementFilter filter)
{
var input = f.GetPlayerInput(filter.PlayerID->PlayerRef);
// Other Logic
if (input->Jump.WasPressed)
{
f.Events.PlayerJump(filter.PlayerID->PlayerRef);
filter.Kcc->Jump(f);
}
// Other Logic
}
}
}
Unity側では、PlayerAnimation
スクリプトがPlayerJump
イベントをリッスンし、応答します。以下はそれに必要なステップとなります。
- イベントを受信できるメソッドを定義する-
void Jump(EventPlayerJump e)
- 問題のイベントをサブスクライブする
- イベントを受信したら、イベントの中にある
PlayerRef
と前回キャッシュしたものを比較して、スクリプトが設置されているGameObjectを確認する - Unity Animatorでパラメータをトリガー・セットする
C#
// This snippet is extracted from the Quantum API Sample.
public unsafe class PlayerAnimation : MonoBehaviour
{
[SerializeField] private Animator _animator = null;
private PlayerRef _playerRef = default;
private QuantumGame _game = null;
// Animator Parameters
private const string TRIGGER_JUMP = "triggerJump";
public void Initialize(PlayerRef playerRef, EntityRef entityRef)
{
_playerRef = playerRef;
// Other Logic
QuantumEvent.Subscribe<EventPlayerJump>(this, Jump);
}
private void Jump(EventPlayerJump e)
{
if (e.PlayerRef != _playerRef) return;
_animator.SetTrigger(TRIGGER_JUMP);
}
ヒント
- モデルとアニメーターコンポーネントを子オブジェクトに設置する。
- イベントはゲームステートの一部ではないため、後から入ったプレイヤーや再参加したプレイヤーには適用されない。そのため、ゲームが既に開始されている場合は、まずアニメーションステートを最新のゲームステートで初期化することが推奨される。
- 100%正確にトリガーされるべきアニメーション(勝利メッセージなど)には同期済みイベントを使用する。
- パパッと表示されるべきアニメーション(ヒットを受けた時など)には非同期の通常のイベントを使用する。
- キャンセルされた非同期イベントからトリガーされたアニメーションを停止するには、
EventCanceled
コールバックを使用する。
予測の一部として発生したイベントが、認証フレームの間にロールバックされたときなどに起こる可能性がある。
決定性アニメーション
決定性アニメーションシステムの主なメリットは、全クライアント間で100%同期されるティックのズレがないアニメーションと、ロールバックの際には正確なステートへぱっと戻れることにあります。理想的に聞こえますが、アニメーションとステートはシミュレーション済みゲームステートの一部となっているため、パフォーマンスに影響が出ます。辞しあのところ、決定性アニメーションシステムの恩恵にあずかるゲームは少数で、ファイティングゲームやスポーツゲームの一部といったところです。
決定性アニメーションを有効にするツールがCustom Animatorです。UnityのMecanim Controllerから情報をベイクし、ステートやステート間遷移、モーションクリップなどの各設定をインポートすることで作用します。
Custom Animatorの開発は、Unityのmecanimと絡んだ依存関係により止まっていましたが、コードがオープンソース化されたことでAddons > Custom Animator
ページからダウンロード可能になりました。本ページではCustom Animatorの概要とCustom Animatorのインポート方法と使用についてのクイックガイドもご紹介しています。
この機能は限定的で、自分のニーズに合わせて調整する必要がある傾向がある点にご注意ください。
ヒント
- Custom Animatorをご使用になる前に、アニメーションがゲームプレイに紐ついているものか、単にビジュアル面での表現の一つであるのか、ご確認ください。前者であれば、Custom Animatorはソリューションとして適していますが、そうでなければポーリングベースのアニメーションがいいでしょう。