このチュートリアルは、「小道具の動きをアニメートする」チュートリアルの高度なシーン グラフ固有のバージョンです。 UEFN における Scene Graph 以外のアニメーションベースの移動に関する説明が必要な場合は、そのチュートリアルをチェックしてから、このチュートリアルに戻ってください!
移動プラットフォームは、ほとんどのプラットフォーム ゲームに共通し、プレイヤーは、ゴールに到達するためターゲット間を正確にジャンプすることが求められます。
UEFN では、小道具を動かす方法がいくつかあります。 TeleportTo[] や MoveTo() などの関数を使用して、トランスフォームを直接変更することや、小道具移動装置などの別の仕掛けを使用して小道具をプリセット パス上で動かすことができます。 ただし、アニメーションの形で別の便利なオプションもあります。
アニメーションには、小道具のトランスフォームを動かすことに関していくつかのメリットがあります。 アニメーションによる移動は通常、MoveTo() や TeleportTo() によるオブジェクトの移動よりも滑らかです。それは、ゲーム ティックごとに関数を呼び出すことによるネットワーク レイテンシーを回避できるためです。
また、アニメーションでは、プレイヤーや他のオブジェクトとのコリジョンがより一貫したものになり、小道具移動装置の仕掛けを使用する場合と比べて、オブジェクトがどこでどのように動くかをより細かく制御できます。 アニメーションを、ループして再生したり、ピンポン モードで順方向と逆方向に再生したりすることができます。
また、アニメーションでは補間タイプを選択することもできます。 補間タイプによって、アニメーションのイージングのタイプや、アニメーションが追従するアニメーション カーブが決まります。 たとえば、線形補間タイプでは、アニメーションが一定の速度で再生されますが、イーズイン タイプでは、低速で始まり、終点に近づくにつれて加速します。
使用するアニメーションに適切な補間タイプを選択することで、小道具がさまざまなポイントで、減速するか、加速するか、線形に動くかを指定できます。 この動作を実装するコンポーネントをレベルのエンティティに適用することで、プレイヤーが移動するための移動プラットフォームを作成できます。
まず、移動するプラットフォームがどのような動作を実行できるかを考えてみましょう。 特定の開始位置からアニメーションを開始し、複数のポイントに移動します。 移動の終了時には、開始位置に戻るか、その場に留まる必要があります。
特定の期間でこれらの移動を行い、移動の各ポイントで適切に回転およびスケールできるようにする必要があります。 これらの動作を実現するにはそれぞれ特定のコードが必要ですが、シンプルなクラスから始めて構築していくことで、さまざまなアイデアに素早く取り組むことができます。 アニメーションベースの移動コンポーネントを作成するには、次の手順に従います。
「
animate_to_targets_component」という Verse コンポーネントを新規作成し、Visual Studio Code で開きます。 独自の Verse コンポーネントを作成する方法の詳細については、「独自の Verse コンポーネントを作成する」を参照してください。/Verse.org/SpatialMath、/Verse.org/SceneGraph/KeyframedMovement、および/UnrealEngine.com/Temporary/SpatialMathモジュールの文を使用して追加します。 これらの関数は後で必要になりますVerseusing { /Verse.org } using { /Verse.org/Native } using { /Verse.org/SceneGraph } using { /Verse.org/Simulation } using { /Verse.org/SpatialMath } using { /Verse.org/SceneGraph/KeyframedMovement } using { /UnrealEngine.com/Temporary/SpatialMath } # Place this component to an entity to move between preset targets. animate_to_targets_component<public> := class<final_super>(component):このチュートリアルのこのセクションで使用したツールチップを以下に示します。 これらを
animate_to_targets_componentクラス定義の上にコピーして貼り付けることができます。Verse# Editor Tool Tips DefaultSpeedTip<localizes><public>:message = "Default speed simulation entity moves during any segment that does not specify a speed." SpeedTip<localizes><public>:message = "Speed simulation entity moves during this segment." AnimationDurationTip<localizes><public>:message = "The duration of the animation segment in seconds." EasingFunctionTip<localizes><public>:message = "Movement cubic-bezier easing function during this segment." DefaultEasingFunctionTip<localizes><public>:message = "Default movement cubic-bezier easing function during any segment that does not specify an easing function." PlaybackModeTip<localizes><public>:message = "Animation playback mode\n\tOneshot: Animation plays once.\n\tPingPong: Animation plays in order and reverse order repeating.\n\tLoop: Animation repeats in a loop. Tip: To construct an animation in a closed loop, make the last segment starting position the same as the first segment starting position." TargetsTip<localizes><public>:message = "Entities that are targets for the parent entity." RandomizeTargetsTip<localizes><public>:message = "Randomize the order of the segments.\n\tNever: Always use the order specified.\n\tOnBegin: Only randomize the order on begin simulation.\n\tEveryIteration: Randomize the order of the segments every loop iteration." PauseTip<localizes><public>:message = "Duration simulation entity pauses at the beginning of this segment."以下のフィールドを
animate_to_targets_componentのクラス定義に追加します。InitialPauseSecondsという名前の、編集可能な float 型変数。 これは、エンティティがアニメーションを開始するまでの時間です。 エンティティがアニメーションを開始する前に 10 秒間待機するように、これを10.0に設定します。
Verse# Amount of time to pause before the animation starts. @editable_number(float): ToolTip := SpeedTip MinValue := option{0.0} var InitialPauseSeconds<public>:float = 10.0編集可能な
keyframed_movement_playback_mode(名称AnimationPlaybackMode) です。 これはエンティティのアニメーションのアニメーション モードです。 これをloop_keyframed_movement_playback_modeに設定します。 つまり、デフォルトでは、アニメーションが完了すると、エンティティはループし、最初からアニメーションを再開します。
Verse# The playback mode used by this animation. @editable: ToolTip := PlaybackModeTip var PlaybackMode<public>:keyframed_movement_playback_mode = loop_keyframed_movement_playback_mode{}コードを保存してコンパイルします。
アニメーションをセグメントに分割する
コードでアニメーションを作成するには、キーフレームを使用します。 アニメーションは 1 つまたは複数のキーフレームから作成され、各キーフレームでは、アニメーションの特定のポイントでのオブジェクトの値が指定されます。 キーフレームを使用してアニメーションを作成することで、小道具を移動、回転、またはスケールするポイントを複数指定できます。
keyframed_movement_component はそのキーフレーム タイプとして keyframed_movement_delta を使用します。 この移動デルタには次の 3 種類の値があります。
Transform:前のキーフレームを基準としたトランスフォームの変化を指定します。Duration:キーフレームの所要時間 (秒) です。Easing:このキーフレームを再生する際に使用するイージング関数。
keyframed_movement_component はキーフレームの配列を受け取り、それらを再生するため、再生するアニメーションごとにすべてのキーフレームを一度に指定する必要があります。 次の 2 種類の方法があります。
コード内で複数のキーフレームの配列を作成し、それをキーフレーム移動コンポーネントに渡して単一のアニメーションを再生します。
単一のキーフレームを含む複数の配列を作成し、それらを個別にキーフレーム移動コンポーネントに渡すことで、複数のアニメーションを連続して再生します。
どちらの方法にもトレードオフがあります。 単一のキーフレームの配列では、キーフレーム間の演算を簡単に実行できますが、処理するコードが多く必要になります。 すべてのキーフレームを一度に構築すると管理は容易になりますが、アニメーションの再生中に操作を実行するのは難しくなります。 「小道具の動きをアニメートする」チュートリアルとこのチュートリアルの両方で、1 番目のアプローチについて説明していますが、2 番目のアプローチの実装も用意されています。
コードで個々のキーフレームを作成するには、segment クラスを定義します。 各セグメントは、エディタから作成できる keyframed_movement_component によって使用されるキーフレームを表します。 また、各キーフレーム間の待機時間など、追加のデータを含めることもできます。 以下の手順に従って、セグメント クラスを作成します。
segmentという新クラスを、使用する「animate_to_targets_component.verse」ファイルに追加します。<concrete>指定子を追加して、このクラスを@editable値として使用できるようにします。Verse# Defines a segment of animation, which includes a starting position, animation speed and duration, and easing function. # Each segment acts as a single animation, and multiple segments make up an animation sequence. segment<public> := class<concrete>:セグメント クラス定義に次のフィールドを追加します。
編集可能な
entityオプション (名称SegmentStartPosition)。 このエンティティは、エンティティがアニメートを開始するワールド位置の参照として機能します。
Verse# An entity that represents the starting position of this entity during the animation segement. @editable: ToolTip := SourceTip SegmentStartPosition:?entity = false編集可能な
float(名称AnimationDuration)。 このアニメーション セグメントの再生にかかる時間の長さです。 これを「2.0」に設定すると、各アニメーション セグメントの再生に 2 秒かかります。
Verse# The duration of the animation segment. @editable: ToolTip := AnimationDurationTip AnimationDuration:float = 2.0編集可能なイージング関数のオプション (名称
EasingFunction)。 これは、アニメーションのこのセグメント中に使用されるイージング関数です。
Verse# The easing function to use during this segment of animation. @editable: ToolTip := EasingFunctionTip EasingFunction:?easing_function = false各イージング関数は 3 次ベジェ曲線で定義されます。この曲線は、アニメーションで使用するイージング関数の種類を表す 4 つの数値で構成されます。 たとえば、イーズイン カーブのパラメータでは、開始時にアニメーションを低速にし、その後に加速できます。
線形カーブのパラメータでは、アニメーション再生を一定速度にできます。 これらの値を定義して独自のカスタム アニメーション カーブを作成できますが、この例では
KeyframedMovementモジュールで定義されている値を使用するため、独自に定義する必要はありません。編集可能な float オプション (名称
PauseSeconds)。 このアニメーション セグメントを開始する前に一時停止する時間 (秒) です。 これは、エンティティがそのパスに沿った各ポイントから移動する前に一時停止する時間と考えることができます。
Verse# The number of seconds to pause before starting this animation segment. @editable: ToolTip := PauseTip PauseSeconds:?float = falseanimate_to_targets_componentクラス定義に戻り、次のフィールドを追加します。segmentの編集可能配列 (名称 Segments)。 これは、エンティティが実行するアニメーション全体を構成するアニメーションの各セグメントを参照します。
Verse# Segments of the keyframed movement animation. @editable: ToolTip := SegmentsTip var Segments<private>:[]segment = array{}編集可能な
イージング関数(名称DefaultEasingFunction)。 アニメーション セグメントでイージング関数が指定されていない場合は、これがデフォルトで使用されます。 これをease_in_out_cubic_bezier_easing_functionに設定します。
Verse# Movement easing function between two targets @editable: ToolTip := DefaultEasingFunctionTip var DefaultEasingFunction<public>:easing_function = ease_in_out_cubic_bezier_easing_function{}コードを保存してコンパイルします。 エディタ内では、animate_to_targets_component がアタッチされたエンティティに
Segments配列が表示されます。
コードでキーフレームを作成する
セグメント クラスが完成したら、セグメントが定義するキーフレームを構築します。 キーフレームを個別に作成し、配列に追加して、その配列を keyframed_movement_component に渡します。 これにはいくつかの計算が必要ですが、ここで定義します。
数学演算はさまざまなシナリオで役立つため、そのロジックをユーティリティ ファイルに配置して、Verse のどのコンポーネントからでもアクセスできるようにすると便利です。 エンティティを使用する場合の Verse のベスト プラクティスの詳細については、「独自の Verse コンポーネントを作成する」を参照してください。 以下の手順に従って、ユーティリティ ファイルを作成します。
「
animate_to_targets.verse」ファイルにUtilitiesという名前の新しいモジュールを作成します。 このフォルダには、プロジェクト全体で使用する共通のロジックが格納されます。Verse# Module containing utility functions. Utilities<public> := module:VectorOnes</code> という名前の新しい、vector3型エイリアスを Utilities モジュールに追加します。これは、Left、<code class="inline-code">UpForwardがすべて1.0に設定されているvector3を作成します。 一部の数学演算が容易になるように後でこのベクターを使用します。その型エイリアスを定義することで、vector3{Left := 1.0, Up := 1.0, Forward := 1.0}を繰り返し記述する必要がなくなります。/Verse.org/SpatialMathおよび/UnrealEngine.com/Temporary/SpatialMathモジュールの両方をインポートしたため、これが/Verse.org/SpatialMathvector3であることを指定する必要があります。両方のモジュールにその定義が含まれているためです。Verse# Utility function for the identity of component-wise vector multiplication. VectorOnes<public>()<transacts>:(/Verse.org/SpatialMath:)vector3 = (/Verse.org/SpatialMath:)vector3{Left := 1.0, Up := 1.0, Forward := 1.0}GetDeltaTransform()という新しい関数をUtilitiesモジュールに追加します。 この関数は、2 つのトランスフォームの差を計算し、デルタを返します。 この関数に<transacts>モディファイヤを付加して、ロールバックできるようにします。/Verse.org/SpatialMathを各トランスフォームのモジュールとして指定します。エンティティ トランスフォーム間の差分を計算するためです。Verse# Get the delta transform between two given transforms. GetDeltaTransform<public>(TransformOne:(/Verse.org/SpatialMath:)transform, TransformTwo:(/Verse.org/SpatialMath:)transform)<transacts>:(/Verse.org/SpatialMath:)transform=GetDeltaTransform, で、新しい/Verse.org/SpatialMathtransformを初期化します。Translationを、各トランスフォームの平行移動の差分に設定します。回転をMakeComponentWiseDeltaRotation()を呼び出した結果にsetします。 この関数は/UnrealEngine.com/Temporary/SpatialMathディレクトリにあるため、/Verse.org/SpatialMathから変換する必要があります回転を/UnrealEngine.com/Temporary/SpatialMath回転に変換します。 これを行うには、FromRotation()関数を使用します。FromRotation()で変換した後に、各トランスフォームの回転を渡してMakeComponentWiseDeltaRotation()を呼び出します。 次に、この関数呼び出しの結果を再度FromRotation()を使用して変換し、/Verse.org/SpatialMathに変換し直します。回転させています 最後に、Scale を、最初のスケールで除算した 1 つ目のスケールと 2 つ目のスケールの差分にVectorOnesを加算した結果に設定します。 これにより、アニメート中にエンティティが正しくスケールされます。 完成したGetDeltaTransform()関数は次のようになります。Verse# Get the delta transform between two given transforms. GetDeltaTransform<public>(TransformOne:(/Verse.org/SpatialMath:)transform, TransformTwo:(/Verse.org/SpatialMath:)transform)<transacts>:(/Verse.org/SpatialMath:)transform= (/Verse.org/SpatialMath:)transform: Translation := TransformTwo.Translation - TransformOne.Translation Rotation := FromRotation(MakeComponentWiseDeltaRotation( FromRotation(TransformTwo.Rotation), FromRotation(TransformOne.Rotation))) Scale := VectorOnes() + ((TransformTwo.Scale - TransformOne.Scale) / TransformOne.Scale)最後に、
TryGetvalueOrDefault()という名前の関数をUtilitiesモジュールに追加し、<transacts>モディファイアを追加します。 この関数は、ある型のoption値および同じ型のデフォルト値を受け取り、そのデフォルト値か、存在する場合はValue内のアイテムを返します。 これは、クラス内の値が実際に初期化されているかどうかを確認したい場合に便利です。また、初期化されていない場合は、必ず何らかの値を返すことを保証します。TryGetValueOrDefault()内で、Valueに値が含まれるかどうかをチェックして、値を返します。 存在しない場合、Defaultを返します。 完成したUtilitiesモジュールとTryGetValurOrDefault()関数は次のようになります。Verse# Module containing utility functions. Utilities<public> := module: # Utility function for the identity of component-wise vector multiplication. VectorOnes<public>()<transacts>:(/Verse.org/SpatialMath:)vector3 = (/Verse.org/SpatialMath:)vector3{Left := 1.0, Up := 1.0, Forward := 1.0} # Get the delta transform between two given transforms. GetDeltaTransform<public>(TransformOne:(/Verse.org/SpatialMath:)transform, TransformTwo:(/Verse.org/SpatialMath:)transform)<transacts>:(/Verse.org/SpatialMath:)transform=
演算を定義したら、コードからキーフレームをビルドできます。
次の手順に従って、キーフレーム作成関数を作成します。
ConstructKeyframe()という新しい関数をanimate_to_targetsクラス定義に追加します。 この関数は、ソース エンティティ、ターゲット エンティティ、オプションのイージング関数、期間を受け取ります。keyframed_movements_deltaの配列も返します。Verse# Construct a single keyframe that animates between the Source and Destination entity using the given easing function over a set duration. ConstructKeyframe<private>(Source:entity, Destination:entity, Easing:?easing_function, Duration:float)<transacts><decides>:[]keyframed_movement_delta=まず、ConstructKeyframe()内でGetGlobalTransform()を呼び出して、変換元エンティティと変換先エンティティの両方のトランスフォームを取得します。Verse# Construct a single keyframe which animates between the Source and Destination entity using the given easing function over a set duration. ConstructKeyframe<private>(Source:entity, Destination:entity, EasingFunction:easing_function, Duration:float)<transacts><decides>:[]keyframed_movement_delta= var SourceTransform:(/Verse.org/SpatialMath:)transform = Source.GetGlobalTransform() var DestinationTransform:(/Verse.org/SpatialMath:)transform = Destination.GetGlobalTransform()keyframed_movement_deltaの単一メンバーで配列を初期化します。Transformを、変換元と変換先のトランスフォームを渡してGetDeltaTransform()を呼び出した結果にセットし、期間とイージングを、この関数に渡された値にセットします。 完全なConstructKeyframe()関数は次のようになります。Verse# Construct a single keyframe which animates between the Source and Destination entity using the given easing function over a set duration. ConstructKeyframe<private>(Source:entity, Destination:entity, EasingFunction:easing_function, Duration:float)<transacts><decides>:[]keyframed_movement_delta= var SourceTransform:(/Verse.org/SpatialMath:)transform = Source.GetGlobalTransform() var DestinationTransform:(/Verse.org/SpatialMath:)transform = Destination.GetGlobalTransform() array: keyframed_movement_delta: Transform := Utilities.GetDeltaTransform(SourceTransform, DestinationTransform) Duration := Duration Easing := EasingFunction
この関数により個別のキーフレームが作成されますが、完全なアニメーションを作成するには、さらにロジックが必要になります。
ConstructAndPlayAnimations()という新しい関数をanimate_to_targetsクラス定義に追加します。 この関数はセグメントの配列とアニメーション再生モードを受け取り、それらを使用してフルアニメーションを作成し再生します。 この関数が非同期で実行できるようにするために、この関数に<suspends>モディファイヤを追加します。Verse# Construct and play an animation from an array of animation segments. ConstructAndPlayAnimations<private>(InSegments:[]segment, AnimationPlayback:keyframed_movement_playback_mode)<suspends>:void=ConstructAndPlayAnimations()で、「ShouldBreakOut」という新しいlogic変数を定義し、それをfalseに初期化します。 3 つのキーフレーム移動の再生モードがあるため、それぞれを個別に処理する必要があります。loop式を使用してアニメーションを継続的に構築し、往復ループ モードを処理しますが、ワンショット モードでは、最初のイテレーションでループから抜け出す必要があります。 アニメーション再生モードがワンショット モードかどうかを確認し、そうであればShouldBreakOutを true に設定します。Verse# Construct and play an animation from an array of animation segments. ConstructAndPlayAnimations<private>(InSegments:[]segment, AnimationPlayback:keyframed_movement_playback_mode)<suspends>:void= var ShouldBreakOut:logic = false # If this is a oneshot animation, break out of loop after it plays once. if (oneshot := oneshot_keyframed_movement_playback_mode[AnimationPlayback]): set ShouldBreakOut = true次に、
if式で、変数KeyframedMovementComponentでエンティティのkeyframed_movement_componentを取得します。次に、InSegments配列の最初の要素を取得して、アニメーションの開始トランスフォームを変数StartingTransformに取得し、そのグローバル トランスフォームを取得します。Verse# Position this entity in the correct starting position. if: KeyframedMovementComponent := Entity.GetComponent[keyframed_movement_component] StartingTransform := FirstSegment := InSegments[0].SegmentStartPosition?.GetGlobalTransform()最後に、エンティティのグローバル トランスフォームを開始トランスフォームに設定して、エンティティを開始位置に配置し、アニメーションを再生する前に
InitialPauseSecondsの間スリープします。Verse# Position this entity in the correct starting position. if: KeyframedMovementComponent := Entity.GetComponent[keyframed_movement_component] StartingTransform := FirstSegment := InSegments[0].SegmentStartPosition?.GetGlobalTransform() then: Entity.SetGlobalTransform(StartingTransform) # Sleep for initial pause. Sleep(InitialPauseSeconds)アニメーションの元となるキーフレームの配列を構築します。 まず、
Keyframesという名前の、keyframed_movement_deltaの新しい変数配列を初期化します。 次に、for式でInSegments配列の各セグメントをイテレートし、Indexというローカル変数にセグメントとそのインデックスの両方を取得します。Verse# Build the array of keyframes to play the animation from. var Keyframes:[]keyframed_movement_delta = array{} for: Index -> Segment:InSegments SourceEntity := Segment.SegmentStartPosition? DestinationEntity := InSegments[Index + 1].SegmentStartPosition?ここで、
Segment.EasingFunctionとDefaultEasingFunctionを渡してTryGetValueOrDefault()を呼び出すことで、Easingという名前のローカル変数にこのキーフレームに使用されているイージング関数を取得します。 さらに、Segment.AnimationDurationからローカル変数のDurationでアニメーション セグメントの期間を取得します。 すべての値を配置したら、if式で、それぞれの値をConstructKeyframe[]に渡してキーフレームを作成し、結果をKeyframes配列に追加します。 すべてのキーフレームがビルドされたら、SetKeyframes()を呼び出してKeyframes配列とPlaybackModeを渡してキーフレーム移動コンポーネントにキーフレームの配列を設定します。Verse# Build the array of keyframes to play the animation from. var Keyframes:[]keyframed_movement_delta = array{} for: Index -> Segment:InSegments SourceEntity := Segment.SegmentStartPosition? DestinationEntity := InSegments[Index + 1].SegmentStartPosition? do: Easing := Utilities.TryGetValueOrDefault(Segment.EasingFunction, DefaultEasingFunction) Duration := Segment.AnimationDuration # Construct each keyframe and add it to the array.キーフレームの配列を設定したら、再生を開始します。 アニメーションはループで実行する必要がありますが、アニメーション モードがワンショットに設定されている場合、最初のイテレーションの後に停止する必要があります。 また、セグメントに
PauseSecondsがある場合、各キーフレームで一時停止を処理する必要があります。 これを処理するには、内部にfor式を含むループ式を設定します。for式内で、Keyframes配列内の各キーフレームをイテレートし、さらに変数KeyframeIndexで各キーフレームのインデックスを取得します。VerseKeyframedMovementComponent.SetKeyframes(Keyframes, PlaybackMode) # Loop playing the animation from the keyframed_movement_component, pausing at each # keyframe for a specified duration. Will break out of the loop if the animation mode # is set to oneshot. loop: for(KeyframeIndex -> Keyframe:Keyframes):for式内で、KeyframeIndexを使ってInSegments配列をインデックス処理することで、このキーフレームに関連付けられたセグメントを取得します。 次に、セグメントにPauseSecondsがある場合は、その期間だけSleep()を呼び出します。 その後、KeyframedMovementComponent.Play()を呼び出し、続いてKeyframeReachedEventを待機してKeyframedMovementComponent.Pause()を呼び出します。 これにより、各キーフレームでアニメーションをPauseSecondsの時間だけ一時停止した後に、再生して次のキーフレームを待機し、再び一時停止します。 最後に、loop式の最後でShouldBreakOutが true であるかどうかをチェックして true であれば、ループを抜けます。Verse# Loop playing the animation from the keyframed_movement_component, pausing at each # keyframe for a specified duration. Will break out of the loop if the animation mode # is set to oneshot. loop: for(KeyframeIndex -> Keyframe:Keyframes): if: SegmentReached := InSegments[KeyframeIndex] then: Sleep(SegmentReached.PauseSeconds) KeyframedMovementComponent.Play()完成した
ContstructAndPlayAnimations()関数は次のようになります。Verse# Construct and play an animation from an array of animation segments. ConstructAndPlayAnimations<private>(InSegments:[]segment, AnimationPlayback:keyframed_movement_playback_mode)<suspends>:void= var ShouldBreakOut:logic = false # If this is a oneshot animation, break out of loop after it plays once. if (oneshot := oneshot_keyframed_movement_playback_mode[AnimationPlayback]): set ShouldBreakOut = true # Position this entity in the correct starting position.
次のステップでは、アニメーション エンティティのプレハブを作成し、レベルでインスタンス化します。