このページでは、trigger_component という Scene Graph コンポーネントを新規作成する方法について説明します。 このコンポーネントは、triggerable インターフェースを実装する Scene Graph コンポーネントがあるエンティティに対してアクションをトリガーします。 trigger_component は、volume_device を介して Scene Graph とインタラクトする手段をエージェントに提供します。 エージェントが volume_device に入ったときまたは出たときに、該当する AgentEnteredEvent および AgentExitedEvent に通知され、該当するコールバックが呼び出されて、Scene Graph コンポーネントとエンティティに対してアクションが行われます。
次の画像では、地面にある白い台が Scene Graph エンティティであり、台の周りに trigger_component と見えない volume_device があります。
プレイヤーが台を踏むと、trigger_component が右端の 2 つのライトをトリガーします。
プレイヤーが台から降りて volume_device を離れても、ライトは点灯したままです。
このページで使用されている Verse 言語機能の詳細については、以下のドキュメントを参照してください。
トリガー コンポーネントを定義する
Verse Explorer に移動し、プロジェクト名を右クリックして [Add new Verse file to project (新規 Verse ファイルをプロジェクトに追加)] を選択します。
「Scene Graph Component」テンプレートを選択し、[Component Name (コンポーネント名)] を「trigger_component」に変更します。
trigger_componentクラスを定義し、関連付けられているvolume_deviceを参照するフィールド、このコンポーネントが子だけをトリガーするか、トリガー可能なすべての子孫をトリガーするかを指定するフィールド、このコンポーネントとインタラクトするすべてのエージェントを参照する配列のフィールドを追加します。Versetrigger_component := class<final_super>(component): # volume_device associated with this trigger_component @editable InteractionVolume<private>:volume_device = volume_device{} # whether or not this triggers on components on child entities @editable OnlyTriggerChildren:logic = falseエージェントがこのコンポーネントと関連付けられている
volume_deviceに入ったときおよび出たときにどうなるかを定義する、OnAgentEntersVolumeとOnAgentExitsVolumeという関数を追加します。 エージェントがボリュームに入ったときに、インタラクトしているエージェントを記録し、triggerableインターフェースを実装しているコンポーネントがある子孫エンティティを探し、それらのエンティティのコンポーネントをトリガーします。 エージェントがボリュームに入ったときに、インタラクトしているエージェントを、このボリュームとインタラクトしているエージェントのリストから削除します。Verse# Event that is triggered when an Agent enters the InteractionVolume OnAgentEntersVolume(Agent:agent):void= for: ChosenEntity : Entity.FindDescendantEntities(entity) ChosenEntityComponent : ChosenEntity.GetComponents() CastToInterface := triggerable[ChosenEntityComponent] do: if (OnlyTriggerChildren?): if (Entity = ChosenEntity.GetParent[]): CastToInterface.Trigger()OnAgentEntersVolume関数とOnAgentExitsVolume関数をvolume_deviceに接続します。そうするには、それらの関数を、このコンポーネントのOnSimulate関数内で、AgentEntersEventとAgentExitsEventのコールバック関数として登録します。Verse# Runs when the component should start simulating in a running game. OnBeginSimulation<override>():void = # Run OnBeginSimulation from the parent class before # running this component's OnBeginSimulation logic (super:)OnBeginSimulation() # Runs when the component should start simulating in a running game. # Can be suspended throughout the lifetime of the component. Suspensions # will be automatically cancelled when the component is disposed or the # game ends.
トリガー可能コンポーネントを定義する
次に、前に作成した triggerable インターフェースを実装するコンポーネントを定義します。 これらは、puzzle_component があるエンティティの子孫であり、trigger_component がある祖先エンティティによってトリガーされる、Scene Graph エンティティに追加されるコンポーネントです。 このチュートリアルでは、以下の 3 つの異なる triggerable コンポーネントを定義します。
triggerable_mesh_component:エンティティのmesh_componentの可視性を切り替えます。triggerable_light_component:エンティティのsphere_light_componentのオン/オフを切り替え、ライトがオン/オフのどちらであるかに応じて、エンティティのmesh_componentを、エミッシブ カラーありまたはなしのものに交換します。triggerable_movement_component:keyframed_movement_componentを使用して、エンティティのmesh_componentのトランスフォーメーションをトリガーします。
これら 3 つの異なるコンポーネントでは、多くの機能が共有され、多数の関数が同じように定義されます。 まず、「TriggerableComponents.verse」という空の Verse ファイルを新規作成します。
以下のモジュールをインクルードします。
/Verse.org/SceneGraph:このファイルで Scene Graph コンポーネントとシーン イベントを使用しているため。
/Verse.org/SceneGraph/KeyframedMovement:このファイルで keyframed_movement_delta を使用しているため。
/Verse.org/Simulation:このファイルで @editable 属性を使用しているため。
/Verse.org/SpatialMath:このファイルで Verse トランスフォームを使用しているため。
Verseusing { /Verse.org/SceneGraph } using { /Verse.org/SceneGraph/KeyframedMovement } using { /Verse.org/Simulation } using { /Verse.org/SpatialMath }3 つのコンポーネント
triggerable_mesh_component、triggerable_light_component、triggerable_movement_componentを定義します。 これらのコンポーネントはすべて、基本コンポーネントクラスを継承しており、triggerable インターフェースを実装しています。3 つのコンポーネントすべてで、次の 3 つの同じイベントを実装します。
OnReceive:このコンポーネントがシーン イベントを受け取ったときに呼び出されます。OnBeginSimulation:コンポーネントがシミュレートを開始する必要があるときに呼び出されます。OnSimulate:コンポーネントがシミュレートを開始するときに呼び出されます。
3 つのコンポーネントすべてに以下の実装を追加します。
Verse# Runs when this component receives a scene event OnReceive<override>(SceneEvent:scene_event):logic= if (PuzzleSolvedEvent := puzzle_solved_event[SceneEvent], not PuzzlePiece?): Trigger() true false # Runs when the component should start simulating in a running game. OnBeginSimulation<override>():void = # Run OnBeginSimulation from the parent class before3 つのコンポーネントすべてで、
triggerableインターフェースのPostTriggerオーバーライドも同様の方法で実装します。これを行うには、triggerableコンポーネントがパズル ピースである場合にtriggered_eventを送ります。triggerableインターフェース自体の内部で、これを実現することはできません。その理由は、どのエンティティがトリガーされているかを Verse が把握している必要がありますが、[Entity (エンティティ)]フィールドは、コンポーネントクラスの一部であるため、そのコンポーネントクラスに対する依存関係を追加することなくtriggerableインターフェースで利用することはできないためです。VersePostTrigger<override>():void = if (PuzzlePiece?): Event := triggered_event: Triggered := Triggered TriggeredEntity := option{Entity} Entity.SendUp(Event)
トリガー可能メッシュ コンポーネント
triggerable_mesh_component は、エンティティの mesh_component の可視性を切り替えます。 そうするには、triggerable インターフェースの以下の関数に対するオーバーライドを提供する必要があります。
SetInitialTriggeredStatePerformActionPerformReverseAction
トリガー状態の初期値は、メッシュ コンポーネントの詳細パネルでの設定に応じてメッシュがゲーム ワールドで見える状態で開始されるかどうかをチェックすることよって決まります。
triggerable_mesh_componentに対するアクションは、mesh_componentメッシュを見えるようにすることです。 そうするには、該当するエンティティのmesh_componentを取得し、そのメッシュを見えるように設定してから、triggerable_mesh_componentを、トリガーされている状態に設定します。Verse# Determine the initial triggered state of this component SetInitialTriggeredState<override>():void= if (MeshComponent := Entity.GetComponent[mesh_component], MeshComponent.Visible?): set Triggered = truetriggerable_mesh_componentに対する逆のアクションは、mesh_componentメッシュを見えないようにすることです。 そうするには、該当するエンティティのmesh_componentを取得し、そのメッシュを見えないように設定してから、triggerable_mesh_componentを、トリガーされていない状態に設定します。VersePerformReverseAction<override>():void = if (MeshComponent := Entity.GetComponent[mesh_component]): if (MeshComponent.Visible?): set MeshComponent.Visible = false set Triggered = false
トリガー可能移動コンポーネント
triggerable_movement_component は、keyframed_movement_component を使用して、エンティティをトリガーして、ある位置から他の一連の位置に順番に移動させます。 そうするには、triggerable インターフェースの以下の関数に対するオーバーライドを提供する必要があります。
SetInitialTriggeredStatePerformActionPerformReverseAction
また、このコンポーネントは以下のヘルパー関数を使用します。
GetMovementStates:このエンティティに対する一連のトランスフォーメーションを定義している子エンティティのトランスフォームを取得します。MakeDeltaTransform:2 つの入力トランスフォームの間のデルタ トランスフォームを作成します。ConstructKeyframeDeltas:keyframed_movement_componentへの入力用の、keyframed_movement_deltaオブジェクトの配列を作成します。SetAndPlayAnimation:キーフレームを割り当てて、移動アニメーションを再生します。
キーフレーム化されたアニメーションの持続時間を決定する編集可能な float 値をこのクラスに追加します。
Verse@editable Duration:float = 2.0このクラスは
triggerableインターフェースを実装するため、PerformAction関数とPerformReverseAction関数に対するオーバーライドを提供する必要があります。これらの関数にはデフォルトの実装はありません。 トリガー状態の初期値はなく、アクションは位置の間の移動であるため、トリガー状態のデフォルトの初期値は false です。 これは、triggerableインターフェースの基本実装で行われることであるため、このコンポーネント クラスではこの関数のオーバーライドは必要ありません。triggerable_movement_componentに対するアクションは、子エンティティで定義されている一連のトランスフォームに沿ってエンティティを動かすことです。VersePerformAction<override>():void = MovementStates := GetMovementStates() AnimationFrames:[]keyframed_movement_delta = ConstructKeyframeDeltas(MovementStates) SetAndPlayAnimation(AnimationFrames)triggerable_movement_componentに対する逆のアクションは、子エンティティで定義されている一連のトランスフォームに沿って逆の順序でエンティティを動かすことです。 この関数は、PerformActionと似ていますが、トランスフォーム配列が逆の順序になっています。VersePerformReverseAction<override>():void = MovementStates := GetMovementStates() ReversedMovementStates := for: Index := 0..MovementStates.Length - 1 ReversedElement := MovementStates[MovementStates.Length - 1 - Index] do: ReversedElement AnimationFrames := ConstructKeyframeDeltas(ReversedMovementStates) SetAndPlayAnimation(AnimationFrames)このコンポーネントが使用するヘルパー関数を定義します。 まず、この移動先であるトランスフォームを取得する、
GetMovementStatesという関数を作成します。VerseGetMovementStates():[]transform= for (ChildEntity : Entity.FindDescendantEntities(entity), Entity = ChildEntity.GetParent[]): ChildEntity.GetGlobalTransform()2 つのトランスフォームの間のデルタ トランスフォームを作成する、
MakeDeltaTransformというヘルパー関数を定義します。 トランスフォーム A からトランスフォーム B へのデルタ トランスフォーム D は、トランスフォーム A にトランスフォーム D が適用されたときにトランスフォーム B となるようなトランスフォームです。これを拡張するために、現在のトランスフォームが A である Scene Graph エンティティがあり、トランスフォーム B になるようにそのエンティティをスケール、回転、平行移動させると想定します。 デルタ トランスフォーム D は、
keyframed_movement_deltaがトランスフォーム A からトランスフォーム B までのエンティティの動きをアニメートするのに必要とされるトランスフォームです。VerseMakeDeltaTransform(InTransformOne:transform, InTransformTwo:transform):transform = transform: Translation := InTransformTwo.Translation - InTransformOne.Translation Rotation := MakeComponentWiseDeltaRotation(InTransformOne.Rotation, InTransformTwo.Rotation) Scale := vector3: Forward := InTransformTwo.Scale.Forward / InTransformOne.Scale.Forward Right := InTransformTwo.Scale.Right / InTransformOne.Scale.Right Up := InTransformTwo.Scale.Up / InTransformTwo.Scale.Upkeyframed_movement_componentアニメーションへの入力用のkeyframed_movement_deltaオブジェクトの配列を作成する、ConstructKeyframeDeltaというヘルパー関数を定義します。VerseConstructKeyframeDeltas<public>(MovementStates:[]transform):[]keyframed_movement_delta = var LastTransform:transform = Entity.GetGlobalTransform() for (EntityIndex -> CurrentTransform : MovementStates): DeltaTransform := MakeDeltaTransform(LastTransform, CurrentTransform) set LastTransform = CurrentTransform keyframed_movement_delta: Transform := DeltaTransform Duration := Duration Easing := if (MovementStates.Length > 1): if (EntityIndex = 0):keyframed_movement_delta配列をkeyframed_movement_componentアニメーションに割り当ててそれを再生する、SetAndPlayAnimationという関数を作成します。VerseSetAndPlayAnimation<private>(Frames:[]keyframed_movement_delta):void = if (KeyframedMovementComponent := Entity.GetComponent[keyframed_movement_component]): KeyframedMovementComponent.SetKeyframes(Frames, oneshot_keyframed_movement_playback_mode{}) KeyframedMovementComponent.Play()
トリガー可能ライト コンポーネント
triggerable_light_component は、ライトのオン/オフを切り替えます。 そうするには、triggerable インターフェースの以下の関数に対するオーバーライドを提供する必要があります。
SetInitialTriggeredStatePerformActionPerformReverseAction
ライトのトリガー状態の初期値はライトがオンであるかどうかによって決まるため、
SetInitialTriggeredStateは、ライトがすでにオンであるかどうかをチェックし、オンであれば、それをトリガー状態の初期値に設定します。Verse# Determine the initial triggered state of this light SetInitialTriggeredState<override>():void= for: DescendantEntity : Entity.FindDescendantEntities(entity) Entity = DescendantEntity.GetParent[] MeshComponent := DescendantEntity.GetComponent[mesh_component] LightComponent := DescendantEntity.GetComponent[light_component] MeshComponent.IsEnabled[] LightComponent.IsEnabled[] do:triggerable_light_componentが行うアクションは、ライトをオンにし、ライトがオンであることを表すメッシュに切り替えることです。VersePerformAction<override>():void= if (not IsLightOn?): for (DescendantEntity:Entity.FindDescendantEntities(entity), Entity = DescendantEntity.GetParent[]): if (MeshComponent := DescendantEntity.GetComponent[mesh_component]): if (LightComponent := DescendantEntity.GetComponent[light_component]): MeshComponent.Enable() LightComponent.Enable() else: MeshComponent.Disable() set IsLightOn = truetriggerable_light_componentが行うアクションは、ライトをオフにし、エミッシブでないmesh_componentを見えるように設定することです。VersePerformReverseAction<override>():void= if (IsLightOn?): for (DescendantEntity:Entity.FindDescendantEntities(entity), Entity = DescendantEntity.GetParent[]): if (MeshComponent := DescendantEntity.GetComponent[mesh_component]): if (LightComponent := DescendantEntity.GetComponent[light_component]): MeshComponent.Disable() LightComponent.Disable() else: MeshComponent.Enable() set IsLightOn = false
次のステップ
ここまでで、すべての Verse コードを記述したので、Verse コードをビルドします。 次は、パズル体験を作成するために UEFN で再利用できるベース エンティティとコンポーネントを使用して、プレハブを構築します。
最終コード
TriggerComponent.verse
using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }
using { /Verse.org/SceneGraph }
using { Logging }
trigger_component<public> := class<final_super>(component):
@editable
InteractionVolume<private>:volume_device = volume_device{}
TriggerableComponents.verse
using { /Verse.org/SceneGraph }
using { /Verse.org/SceneGraph/KeyframedMovement }
using { /Verse.org/Simulation }
using { /Verse.org/SpatialMath }
EnabledToolTip<public><localizes>:message = "Whether or not this component is enabled."
triggerable_mesh_component<public> := class<final_super>(component, triggerable):
<# Triggerable interface #>