マニュアル
Animator View Updater
QuantumEntityViewUpdate
スクリプトは、AnimatorComponent
がアタッチされた各エンティティに対して、Animate
コールバックをトリガーします。エンティティは、IAnimatorEntityViewComponent
インターフェースを実装したQuantumEntityViewComponent
を持つ必要があります。AnimatorアドオンにはAnimatorMecanim
とAnimatorPlayables
が含まれていて、そのまま使用することも、よりカスタマイズされたワークフローのために拡張することもできます。
Animator Mecanim
Quantum AnimatorとUnityのAnimator間の連携を提供し、アニメーションのスムーズな遷移とフレーム単位の同期を可能にします。フレームレートの制御や、AnimatorPlayables
ではサポートされていないアニメーションレイヤーの使用も可能にします。

遷移のミュート
AnimatorMecanim
使用時、ごくまれに偽陽性の(誤検知による)遷移が発生する可能性があり、わずかなビジュアルの乱れが生じます。これを防ぐには、AimatorGraph
設定のMute Graph Transitions On Export
を有効にしてください。AnimatorGraph
をベイクすると、AnimatorController
のすべての遷移がミュートされます。
Animator Playables
UnityのAnimatorのかわりにPlayableGraph
を使用してアニメーションを制御することで、Quantum Animatorによるアニメーション更新で、よりカスタマイズしたアプローチを取ることができます。ただし、アニメーションレイヤーなど、UnityのAnimatorのすべての機能がサポートされているわけではありません。
Animatorイベント
Quantum Animatorは、Instant
(即時)イベントとTime-Window
(時間帯)イベントの両方をサポートします。指定クリップの再生時間が、イベント作成時に設定された時間になった際に、イベントコールバックがトリガーされます。つまり、同じクリップを利用する2つのステートは、イベントを共有することになります。
Instant Event
C#
public abstract class AnimatorInstantEventAsset : AnimatorEventAsset, IAnimatorEventAsset
{
/// <inheritdoc cref="AnimatorEventAsset.OnBake"/>
public new AnimatorEvent OnBake(AnimationClip unityAnimationClip, AnimationEvent unityAnimationEvent)
{
var quantumAnimatorEvent = new AnimatorInstantEvent();
quantumAnimatorEvent.AssetRef = Guid;
quantumAnimatorEvent.Time = FP.FromFloat_UNSAFE(unityAnimationEvent.time);
return quantumAnimatorEvent;
}
}
Instant Eventは、Animatorがイベントをベイクしたクリップを再生した際に、Execute()
関数を一度だけトリガーします。基底クラスAnimatorInstantEventAsset
を継承し、独自Frame.Signals
などによる異なる処理でクラスメソッドをオーバーライドするのが、良いパターンです。
イベントアセットは、Create->Quantum->Assets...
を使用して作成できます。

Time-Window Event
作成手順はInstant Eventと似ていますが、Unityのクリップ内で、イベント実行終了を示す同じ種類の2つ目のイベントも作成する必要があります。このイベントは、Unityのクリップで定義された開始フレームから終了フレームの間、毎フレームExecute
コールバックがトリガーされます。

OnEnter()
・Execute()
・OnExit()
と同様の動作を持つ新しいイベントを作成したい場合は、AnimatorTimeWindowEventAsset
クラスを継承してください。
C#
/// <summary>
/// SampleTimeWindowEvent使用方法のサンプルです。
/// AnimatorInstantEventAssetを継承したクラスを作成し、独自ロジックを実装してください。
/// </summary>
[Serializable]
public class ExampleTimeWindowEventAsset : AnimatorTimeWindowEventAsset
{
public override unsafe void OnEnter(Frame frame, AnimatorComponent* animatorComponent, LayerData* layerData)
{
Debug.Log($"[Quantum Animator ({frame.Number})] OnEnter animator time window event.");
}
public override unsafe void Execute(Frame frame, AnimatorComponent* animatorComponent, LayerData* layerData)
{
Debug.Log($"[Quantum Animator ({frame.Number})] Execute animator time window event.");
}
public override unsafe void OnExit(Frame frame, AnimatorComponent* animatorComponent, LayerData* layerData)
{
Debug.Log($"[Quantum Animator ({frame.Number})] OnExit animator time window event.");
}
}
イベントのセットアップ
アニメーションクリップでイベントを定義するには、以下の手順に従ってください。
QuantumEntityPrototype
内のAnimator
コンポーネントを持つゲームオブジェクト自身に、AnimationEventData
コンポーネントを追加します。

- Animationウィンドウでクリップを選択し、新しいUnityの
AnimationEvent
を追加します。

- 作成したイベントを選択し、以下の画像のように
Function
とObject
を指定します。

Animatorステートシグナル
Animatorステートシグナルは、遷移やFadeTo
でステートが変更される際に呼び出されます。これらはAnimatorBehaviourSystem
で実装されていて、他のシステムからでも実装可能です。
C#
// Animatorがステートに遷移した際の、最初のUpdateで呼び出される
void OnAnimatorStateEnter(Frame frame, EntityRef entity, AnimatorComponent* animator, AnimatorGraph graph, LayerData* layerData, AnimatorState state, FP time);
// Animatorがステートを更新している間、毎フレーム呼び出される
void OnAnimatorStateUpdate(Frame frame, EntityRef entity, AnimatorComponent* animator, AnimatorGraph graph, LayerData* layerData, AnimatorState state, FP time, AnimatorStateType stateType);
// Animatorが別のステートに遷移する前の、最後のUpdateで呼び出される
void OnAnimatorStateExit(Frame frame, EntityRef entity, AnimatorComponent* animator, AnimatorGraph graph, LayerData* layerData, AnimatorState state, FP time);
これらのシグナルは、以下の値を提供します。
Frame
:シグナルが呼び出された現在のFrame
EntityRef
:シグナルに渡されたAnimatorComponent
が関連するEntityRef
AnimatorComponent
のポインタAnimatorGraph
とAnimatorState
の参照LayerData
のポインタ- シグナルが呼び出された際の
LayerData
の対応する時間
OnAnimatorStateUpdate
はAnimatorStateType
も提供します。このenum
の値は次の通りです。
None
FromState
CurrentState
ToState
遷移中は、ToState
とFromState
の両方がOnAnimatorStateUpdate
シグナルを送信するため、どちらのステートが更新されたのかを判別するのにAnimatorStateType
が役立ちます。例えば、これを使用して、遷移終了中にAnimatorStateBehaviour
が更新を実行することを防ぐことができます。
Animator State Behaviour
Animatorイベントとは対照的に、Animator State Behaviour
は同じクリップを使用するステート間で独立していて、OnStateEnter
・OnStateUpdate
・OnStateExit
をサポートします。
セットアップ
- UnityのAnimatorウィンドウで、ステートを選択し、そこに
AnimatorStateBehaviourHolder
スクリプトをアタッチします。

- Animatorパッケージに含まれる
DebugBehaviour
の例のように、AnimatorStateBehaviourAssets
リストに新しい要素を追加します。

- メニューオプション
Tool->Quantum Animator->Bake All Graph Assets
から、グラフアセットをベイクします。 AnimatorBehaviourSystem
をDefault Config Systems
アセットに追加します。- ステートが再生されるたびに、デバッグメッセージがログに表示されます。
AnimatorStateBehaviour
は抽象クラスなので、継承したら以下のメソッドを実装する必要があります。
C#
bool OnStateEnter(Frame frame, EntityRef entity, AnimatorComponent* animator, AnimatorGraph graph, LayerData* layerData, AnimatorState state, FP time);
bool OnStateUpdate(Frame frame, EntityRef entity, AnimatorComponent* animator, AnimatorGraph graph, LayerData* layerData, AnimatorState state, FP time, AnimatorStateType stateType);
bool OnStateExit(Frame frame, EntityRef entity, AnimatorComponent* animator, AnimatorGraph graph, LayerData* layerData, AnimatorState state, FP time);
これらは前述のAnimatorステートシグナルとほぼ同等ですが、bool
が返されます。AnimatorBehaviourSystem
使用時にtrue
を返すと、AnimatorState
の残りのAnimatorStateBehaviour
のチェックが停止されます。例えば、AnimatorStateBehaviour
がFadeTo
を使用して新しいステートに遷移する場合、true
を返すことで、残りのAnimatorStateBehaviour
がOnAnimatorStateUpdate
を実行することを防ぐことができます。
FadeTo
FadeTo
は、条件とは独立して別のステートに遷移するために使用できます。この機能はAllowFadeToTransitions
で有効にできます。
C#
// public void FadeTo(Frame frame, AnimatorComponent* animatorComponent, string stateName, bool setIgnoreTransitions, FP deltaTime)
// 使用例
if (input->Run.WasPressed)
{
graph.FadeTo(frame, filter.AnimatorComponent, "Running", true, 0);
}
ルートモーション
Quantum Animatorは、UnityのAnimationClip
情報を使用して、モーションカーブデータをAnimatorGraph
アセットにベイクします。ベイクされた情報はシミュレーションで使用され、再生中のクリップに応じてエンティティの位置や回転を変更します。

使用方法
モーションデータを処理できるようにするには、Root Motion
オプションを有効にする必要があります。モデルのAnimation
タブで、ルートモーション設定を正しくセットアップすることも重要です。詳細な情報は、Unityドキュメントのルートモーションをご覧ください。

ステートのルートモーション無効化
デフォルトでは、Root Motion
オプションが有効である場合、AnimatorGraph
のすべてのステートでモーションデータが有効になります。ステートにAnimator Disable Root Motion Behaviour
を追加することで、そのステートはルートモーションの計算から除外されます。

使用例
Quantum Animatorは、各フレームのモーションの差分のみを計算します。ユーザーは、生成されたデータを使用して、エンティティのtransform
を必要に応じて更新します。以下のコードは、OnAnimatorRootMotion3D
/OnAnimatorRootMotion2D
の応用例を示します。
C#
// 3Dルートモーションの処理
public void OnAnimatorRootMotion3D(Frame frame, EntityRef entity, AnimatorFrame deltaFrame, AnimatorFrame currentFrame)
{
// モーション差分がない場合は終了する
if (deltaFrame.Position == FPVector3.Zero && deltaFrame.RotationY == FP._0) return;
if (frame.Unsafe.TryGetPointer<Transform3D>(entity, out var transform))
{
// 現在フレームのY軸の回転の逆を表すQuaternionを作成する
var currentFrameRotation = FPQuaternion.CreateFromYawPitchRoll(currentFrame.RotationY, 0, 0);
currentFrameRotation = FPQuaternion.Inverse(currentFrameRotation);
// 移動を揃えるために、差分位置を逆回転させる
var newPosition = currentFrameRotation * deltaFrame.Position;
// 新しい位置にtransformの回転を適用して、ワールド座標系の位置を取得する
var displacement = transform->Rotation * newPosition;
var kccSettings = frame.FindAsset<KCCSettings>(frame.Unsafe.GetPointer<KCC>(entity)->Settings);
// レイキャスト用に対象のヒット位置を計算する
var targetHitPosition =(displacement.XOZ.Normalized * FP._0_33 * 2 ) + displacement;
// 意図したモーション方向にレイキャストを飛ばし、静的オブジェクトとの潜在的な衝突を検知する
var hits = frame.Physics3D.RaycastAll(transform->Position, targetHitPosition.XOZ, targetHitPosition.Magnitude, -1,
QueryOptions.HitStatics);
if (hits.Count <= 0)
{
// 衝突がない場合、キャラクターコントローラーを一時的に無効にする
if (frame.Unsafe.TryGetPointer<KCC>(entity, out var kcc))
{
kcc->SetActive(false);
}
// transformにモーションと回転を適用する
transform->Position += displacement;
transform->Rotate(FPVector3.Up, deltaFrame.RotationY * FP.Rad2Deg);
}
else
{
// 衝突がない場合、キャラクターコントローラーを有効にする
if (frame.Unsafe.TryGetPointer<KCC>(entity, out var kcc))
{
kcc->SetActive(true);
}
}
}
}
public void OnAnimatorRootMotion2D(Frame frame, EntityRef entity, AnimatorFrame deltaFrame, AnimatorFrame currentFrame)
{
// モーション差分がない場合は終了する
if (deltaFrame.Position == FPVector3.Zero && deltaFrame.RotationY == FP._0) return;
if (frame.Unsafe.TryGetPointer<Transform2D>(entity, out var transform))
{
// 差分を適用して、新しい回転を計算する
FP newRotation = transform->Rotation + deltaFrame.RotationY;
// 回転を正規化して[-π, π]範囲に収める
while (newRotation < -FP.Pi) newRotation += FP.PiTimes2;
while (newRotation > FP.Pi) newRotation += -FP.PiTimes2;
// 新しい回転に基づいて、移動差分を回転する
var deltaMovement = FPVector2.Rotate(deltaFrame.Position.XZ, newRotation);
// transformに移動と回転を適用する
transform->Position += deltaMovement;
transform->Rotation = newRotation;
}
}
Back to top