Scene Graph チュートリアルのランタンは、Verse で作成した複数のコンポーネントを使い、 動的でインタラクティブなプレハブを作成しています。 これは、Verse で作成したコンポーネントに移動とインタラクティブ性をプログラミングすることで実現します。
シンプルな移動をシミュレートする
ライトポスト プレハブでは、ランタンの移動をシミュレートするために、2 つの異なるコンポーネントを使用しています。 これらはどちらもピボット エンティティにアタッチされています。このピボット エンティティは、ランタンが動く際のピボット ポイントとなります。 1 つ目は keyframed_movement_component です。これがあることで、キーフレームからアニメーションを作成し、それを再生してエンティティをアニメートできます。 ただし、このコンポーネントは単独では機能しないので、キーフレームを提供してアニメーションを開始するための別のコードが必要です。 そこで、カスタムの simple_movement_component を使用します。 このスクリプトは、シーン内のさまざまなゲーム オブジェクトの動きを制御するために、プロジェクト全体で繰り返し使用します。
アニメーションを使用してオブジェクトを動かす方法の詳細については、「小道具の動きをアニメートする」を参照してください。
simple_movement_component を詳しく見てみましょう。 Verse Explorer から「SimpleMovementComponent.verse」スクリプトを Visual Studio Code で開きます。
アニメーションではさまざまな再生モードを使用して、アニメーション完了時の処理を定義します。 これらはファイルの先頭にある movement_mode 列挙型で定義します。
One Shot (ワンショット) - アニメーションが終了すると、オブジェクトは動きを停止します。
Loop (ループ) - アニメーションが最後まで到達したら、アニメーションを最初からやり直します。
Ping Pong (ピンポン) - オブジェクトがアニメーションを逆向きに再生し、開始から終了までを行ったり来たりします。
movement_mode<public> := enum:
OneShot
Loop
PingPongbasic_movement_component クラスも、アニメーションの作成に必要な変数を定義します。 これらにはそれぞれ @editable 属性があり、アウトライナーから編集できます。
Keyframes:アニメーションの作成に使用されるkeyframed_movement_deltaの配列。 それぞれが、特定のキーフレームにおけるトランスフォームの変化、キーフレームの持続時間、使用する移動イージングのタイプをトラックします。AutoPlay:シミュレーションの開始時にエンティティをアニメートするかどうかを指示するロジック変数。MovementMode:エンティティが示す移動動作。
# A Verse-authored component that can be added to entities
basic_movement_component<public> := class<final_super>(component):
@editable
var Keyframes<public>: []keyframed_movement_delta = array{}
@editable
var AutoPlay: logic = true
OnSimulate() のコードは、コンポーネントがシーンに追加されるたび、およびゲームが開始されたときに実行を開始します。 この場合、このコードはまず短時間の Sleep() 呼び出しを使用して、確実にすべてのエンティティとコンポーネントが適切に初期化されるようにしてから次の処理に進みます。 次に、if 式でエンティティに keyframed_movement_component があるかどうかを確認します。
ある場合は InitializeKeyFramedMovementComponent() 関数を呼び出して、コンポーネントを適切な値に設定します。 エンティティに keyframed_movement_component がない場合はどうなるでしょうか。 その場合、ランタイム時にコンポーネントを 1 つ動的に作成し、AddComponents() 関数を使用してエンティティに追加します。 このようにして、ゲーム開始時にエンティティが適切に設定されることが保証されます。
OnSimulate<override>()<suspends>:void =
Sleep (0.1)
if:
KeyframedMovementComponent := Entity.GetComponent[keyframed_movement_component]
then:
InitializeKeyframedMovementComponent(KeyframedMovementComponent)
else:
NewKeyFramedMovementComponent := keyframed_movement_component { Entity := Entity }
Entity.AddComponents of array { NewKeyFramedMovementComponent }
InitializeKeyframedMovementComponent(NewKeyFramedMovementComponent)InitializeKeyframedMovementComponent() 関数は、エディタで割り当てた値に基づいてキーフレーム移動コンポーネントを設定します。 まず、新しい keyframed_movement_playback_mode 変数を作成し、このアニメーション中に使用する再生モードを決定します。 これは oneshot_keyframed_movement_playback_mode 値で初期化されます。つまり、アニメーションは一度だけ再生されます。
InitializeKeyframedMovementComponent(InKeyframedMovementComponent:keyframed_movement_component):void =
var PlaybackMode:keyframed_movement_playback_mode = oneshot_keyframed_movement_playback_mode{}次に、case 文を使用して、エディタで設定された MovementMode 値に基づいて PlaybackMode を設定します。 MovementMode 列挙型の各値は、SceneGraph/KeyframedMovement モジュールで定義されているさまざまな再生モードに対応します。
case (MovementMode):
movement_mode.OneShot =>
set PlaybackMode = oneshot_keyframed_movement_playback_mode{}
movement_mode.Loop =>
set PlaybackMode = loop_keyframed_movement_playback_mode{}
movement_mode.PingPong =>
set PlaybackMode = pingpong_keyframed_movement_playback_mode{}最後に、キーフレーム移動コンポーネントのキーフレームと移動モードを設定し、アニメーションを再生する準備が整います。 AutoPlay 変数が true に設定されている場合、キーフレーム移動コンポーネントの Play() 関数を呼び出してアニメーションが直ちに始まります。
InKeyframedMovementComponent.SetKeyframes(Keyframes, PlaybackMode)
if:
AutoPlay?
then:
InKeyframedMovementComponent.Play()エディタに戻ると、simple_movement_component がピボット エンティティに追加され、以下の値が設定されています。 これで、ゲームを開始すると、ランタンが前後に揺れ始めます。
完全なスクリプト
using { /Verse.org }
using { /Verse.org/Native }
using { /Verse.org/SceneGraph }
using { /Verse.org/Simulation }
using { /Verse.org/SceneGraph/KeyframedMovement }
movement_mode<public> := enum:
OneShot
Loop
ランタンとインタラクトする
ランタンは前後に揺れることはできますが、プレイヤーがランタンにインタラクトするためには別の要素が必要です。 それは、インタラクト可能コンポーネントをランタン エンティティにアタッチすることと、ランタンのオン/オフを切り替えられるようにするカスタム Verse lantern_interaction_component をライトポストにアタッチすることです。
Verse Explorer から「LanternInteractionComponent.verse」スクリプトを開きます。 このスクリプトは、「Assets.digest.verse」で定義した LightPost モジュールのインポートから始まります。 ここに、プレハブなどのライトポスト関連のすべてのアセットが格納されています。 ライトポストとランタンのマテリアルとメッシュにアクセスするための LightPost.Materials モジュールもインポートします。
using { /Verse.org }
using { /Verse.org/Native }
using { /Verse.org/SceneGraph }
using { /Verse.org/Simulation }
LightPost := module:
Materials<public> := module:
lantern_interaction_component クラスは、MaterialInstance という名前の変数の定義から始まります。この変数は、LightPost.Materials.MI_Lantern_01 に初期化されます。 これは、ランタンのマテリアルに関連するすべての変数を含むマテリアル インスタンスです。
lantern_interaction_component<public> := class<final_super>(component):
var MaterialInstance:LightPost.Materials.MI_Lantern_01 = LightPost.Materials.MI_Lantern_01{}OnBeginSimulation() のスクリプトは最初に、interactable_component がアタッチされた子孫エンティティの各コンポーネントを検出します。 lantern_interaction_component がライトポストにアタッチされているため、ライトポストの子孫であるランタン エンティティの interactable_component が返されます。
OnBeginSimulation<override>():void =
(super:)OnBeginSimulation()
InteractabeleComponents := Entity.FindDescendantComponents(interactable_component)次に、検出されたすべてのインタラクト可能コンポーネントを for 式でイテレートし、SucceededEvent にこのファイルの後の部分で定義される OnInteractFinished() 関数をサブスクライブします。 これで、プレイヤーがランタンとのインタラクトを終了すると OnInteractFinished() が起動します。
InteractabeleComponents := Entity.FindDescendantComponents(interactable_component)
for (InteractableComponent : InteractabeleComponents):
InteractableComponent.SucceededEvent.Subscribe(OnInteractFinished)その後、OnBeginSimulation() 関数が再び FindDescendantComponents を呼び出し、LightPost.SM_Lightpost_Lantern_01 コンポーネントがアタッチされているすべてのエンティティを検出します。 これは、ランタンにアタッチされたメッシュ コンポーネントです。 その後、前に定義した MaterialInstance にメッシュ コンポーネントを設定し、ランタンのメッシュを適切に初期化します。
MeshComponents := Entity.FindDescendantComponents(LightPost.SM_Lightpost_Lantern_01)
for (MeshComponent : MeshComponents):
set MeshComponent.M_Lantern = MaterialInstanceOnInteractFinished() 関数がインタラクションの発信者としてエージェント (プレイヤー) を取得します。 この関数が行うのは、ランタンのオン/オフを切り替える ToggleLight() 関数を呼び出すだけです。
ToggleLight() 関数は、ランタンのオン/オフ切り替えで最も手間がかかる部分を処理します。 まず、if 式で MaterialInstance のエミッシブ レベルが 0.0 かどうか、つまりライトがオフかどうかを確認します。 オフになっている場合、エミッシブ レベルを 1.0 に設定します。 その後の 2 つの for 式では、子孫エンティティの light_component と particle_system_component をすべて検出し、Enable() と Play() をそれぞれ呼び出してライトをオンにします。
ToggleLight():void =
if (MaterialInstance.Emissive_Multiply = 0.0):
set MaterialInstance.Emissive_Multiply = 1.0
for:
Light : Entity.FindDescendantComponents(light_component)
do:
Light.Enable()
for:
ライトがすでにオンの場合、この関数は正反対の処理を else 文内で行います。 その際、マテリアルのエミッシブ レベルを 0.0 に設定して、ライトと子孫エンティティのパーティクル システム コンポーネントを無効にします。
else:
set MaterialInstance.Emissive_Multiply = 0.0
for:
Light : Entity.FindDescendantComponents(light_component)
do:
Light.Disable()
for:
Particle : Entity.FindDescendantComponents(particle_system_component)
このスクリプトでは複数の関数を組み合わせて、プレイヤーがランタンとインタラクトしたときに複数のコンポーネントのオン/オフを切り替え、ランタンを動的なものにしています。 ランタンでオン/オフの切り替えが行われていますが、lantern_interaction_component が設定されているのは親のライトポスト エンティティであることに留意してください。
1 つのライトポストに複数のライトが設置されており、1 つのボタンを押すだけで各ライトのオン/オフが切り替わるようにするにはどうすればよいか考えてみましょう。 このコードは特定のコンポーネントの型を持つ子孫エンティティを検出することで動作するため、この機能の実装に追加の Verse コードは必要ありません。 子ランタン エンティティそれぞれにライト コンポーネントまたはパーティクル コンポーネントが付いていれば OK です。
完全なスクリプト
using { /Verse.org }
using { /Verse.org/Native }
using { /Verse.org/SceneGraph }
using { /Verse.org/Simulation }
LightPost := module:
Materials<public> := module: