Scene Graph provides multiple ways to create flexible and dynamic gameplay through the many different interactions between entities and components. This template showcase a few of the ways you can manipulate entities at runtime through spawning and removing entities, and how you can use component events and querying to create communication between entities and build reusable gameplay elements.
Enter the Collision Events hall to help a UFO capture cows. Each example uses a different Scene Graph collision event:
Spawning and Removing Prefabs
In the second hall a script runs that spawns a prefab lantern when the player steps on the volume pad, and removes the prefab when the player steps off the pad. Scene Graph doesn't need to use hard coded coordinates or a fixed prefab to spawn. Instead you can select the spawn location with an editable entity reference named SpawnLocationEntity and select the prefab you want to spawn with the prefab picker, PrefabToSpawn.
In the SpawnPrefabDevice.verse file, SpawnLocationEntity and PrefabToSpawn spawn the LightPost prefab into the scene using the Volume device to trigger the spawning and removing actions.
Open up SpawnPrefabDevice.verse to check out how this code works. This creative device is ready to spawn the prefab when you launch the project.
At the top of the SpawnPrefabDevice class definition, an editable TriggerVolume, SpawnLocationEntity, and PrefabToSpawn references the trigger volume in the level.
# Tooltips shown in the Details panel.
SpawnPrefabTriggerVolumeTip<internal><localizes>:message = "The Volume device in the level. The prefab appears when a player enters it and is removed when they leave."
SpawnPrefabSpawnLocationTip<internal><localizes>:message = "Entity that marks where the prefab appears."
SpawnPrefabPrefabToSpawnTip<internal><localizes>:message = "The prefab to spawn."
# Spawns a prefab while a player is inside a volume. See the top of this file.
SpawnPrefabDevice := class(creative_device):When the game begins, the device uses the prefab picker to select the LightPost prefab to spawn and the editable entity reference to spawn the prefab at the location of the PrefabSpawnEntity entity.
OnBegin<override>()<suspends>:void =
SpawnTransform:transform = SpawnLocationEntity.GetGlobalTransform()
if:
SimulationEntity := GetSimulationEntity[]
then:
loop:
SpawnedPrefab:entity = PrefabToSpawn{}
TriggerVolume.AgentEntersEvent.Await()
SimulationEntity.AddEntities(array{SpawnedPrefab})
It then gets the simulation entity and creates a series of failure contexts to work in. It first uses loop to run the code inside repeatedly, followed by a race between two block statements.
The first block statement waits for an agent to enter the volume device. When they do, it adds the lantern prefab to the scene using AddEntities() and positions it correctly using SetGlobalTransform(). The second block waits for the agent to leave the volume and remove the prefab from its parent, in this case, the simulation entity.
Because these blocks are racing in a loop, each of them is always running so that the player can step in and out of the volume to respawn the light post any number of times!
Entity Entered Complete Script
Below is the complete for adding and removing an entity from the scene.
# SPAWN PREFAB DEVICE
#
# A Verse device that shows how to spawn and remove entities from a creative
# device. When a player enters TriggerVolume, a new copy of PrefabToSpawn
# appears at SpawnLocationEntity. The next time a player leaves the volume, it
# is removed.
#
# SETUP
# Place this device in the level, pick the Volume device in TriggerVolume,
# pick an entity that marks the spawn position in SpawnLocationEntity, and
Entity Entered/Exited
The second hall also has a series of examples that show off some of the different ways in which entities can interact. For example, entities with a mesh component can be alerted to when they overlap with another entity through the mesh component’s EntityEnteredEvent and EntityExitedEvent. Check out the UFO example in the second hall to see this functionality in action.
When the game begins, the cow plays an animation to lift itself up as if it’s being abducted by the UFO. The cow uses the mesh component’s EntityEnteredEvent to know when it overlaps with the UFO, and then sets itself to invisible to appear as if it has been abducted.
The code for this example is defined in EntityEnteredExampleComponent.verse. Open this file from the Verse Explorer.
The entity_entered_example_component class starts by defining an editable Keyframes array to build the cow abduction animation from. It also defines a StartTransform variable to indicate the cow’s starting position in the scene.
@editable {ToolTip := EntityEnteredKeyframesTip, Categories := array{CategoryMovement}}
Keyframes:[]keyframed_movement_delta = array{}
var StartTransform:transform = transform{}In OnBeginSimulation(), the script starts by retrieving the fort_round_manager. This is an interface to the Fortnite round manager that you can use to subscribe events to the start and end of a round. In this example, the scripts subscribes the OnRoundStarted() function to the start of the round using SubscribeRoundStarted(), meaning the function will run only when the game begins instead of when the entity begins simulation.
OnBeginSimulation<override>():void =
(super:)OnBeginSimulation()
if:
FortRoundManager := Entity.GetFortRoundManager[]
then:
FortRoundManager.SubscribeRoundStarted(OnRoundStarted)Then in OnRoundStarted(), the script starts by setting the StartTransform to the entity’s global transform, then subscribing the mesh component’s Entity.GetCOmponent to the mesh_component, which will be triggered whenever another mesh overlaps with the entity. It then spawns a PlayCowAbductionAnimation() function to start lifting the cow.
OnRoundStarted():void =
set StartTransform = Entity.GetGlobalTransform()
if:
Mesh := Entity.GetComponent[mesh_component]
then:
spawn {PlayCowAbductionAnimation()}The PlayCowAbductionAnimation() function simply sets the global transform of the cow to its starting transform and waits a short amount of time. It then gets the mesh and keyframe movement components from the entity, enables the mesh to make the cow visible, then sets the animation on the keyframed movement component and plays it.
PlayCowAbductionAnimation()<suspends>:void =
if:
Mesh := Entity.GetComponent[mesh_component]
MovementComponent := Entity.GetComponent[keyframed_movement_component]
then:
loop:
Entity.SetGlobalTransform(StartTransform)
set Mesh.Visible = true
MovementComponent.SetKeyframes(Keyframes, oneshot_keyframed_movement_playback_mode{})
MovementComponent.Play()
Finally, the EntityEnteredEvent() function is used to run code whenever the cow overlaps another entity, in this case the UFO. When it does, it again gets the mesh and keyframed movement components, but instead uses them to stop playing any ongoing animations and make the cow invisible to appear as if it has been abducted. It then spawns a new instance of PlayCowAbductionAnimation() to start the process over.
Entity Entered/Exited Complete Script
Below is the complete script for abducting a Cow mesh with a UFO mesh.
using { /Fortnite.com/Game }
using { /Verse.org }
using { /Verse.org/Native }
using { /Verse.org/SceneGraph }
using { /Verse.org/SceneGraph/KeyframedMovement }
using { /Verse.org/Simulation }
using { /Verse.org/SpatialMath }
EntityEnteredKeyframesTip<internal><localizes>:message = "Movement steps to play from the entity's starting position. When something touches its mesh, the entity disappears, then reappears at the start and plays them again."
Querying Overlap Hits
Entities can also query for other entities using overlap queries. Instead of detecting when an entity enters or exits a particular mesh, overlap queries can be used to return every entity and component overlapping a particular area.
This area can either be the entity itself, a given collision volume such as a sphere or box, or a position to simulate the entity from. It then returns a list of overlap_hit. Each overlap_hit gives you info about the component or volume overlapped by the source volume, and you can query these components to find their associated entity.
For example, try stepping on the interactive pad in front of the FindOverlapHits example. When you interact with the pad, the UFO spawns an invisible collision volume and uses it to perform an overlap query. If the collision volume overlaps the cow, the UFO abducts it!
The code for this example is defined in FindOverlapHitsExampleComponent.verse. Open this file from the Verse Explorer it out.
The class starts by defining an editable volume device named Trigger to reference the interactive pad the player steps on, as well as a logic variable IsAbducting for whether the cow is being abducted or not. When the component begins simulating, it uses volume_occupancy in the Occupancy variable to track which player is inside the trigger at the start of round.
using { /Fortnite.com/Devices }
using { /Verse.org }
using { /Verse.org/Colors }
using { /Verse.org/Native }
using { /Verse.org/SceneGraph }
using { /Verse.org/SceneGraph/KeyframedMovement }
using { /Verse.org/Simulation }
using { /Verse.org/SpatialMath }
The OnBeginSimulation() function subscribes the Occupancy's AgentEnterEvent, AgentExitEvent , and the StartTracking events from the Trigger to the OnTriggerEntered() and OnTriggerExited() functions respectively.
When the player steps on the pad, if the UFO is not currently abducting a cow, the OnTriggerEntered() function is called to pause any movement the UFO may be performing by retrieving its keyframed_movement_component and calling Pause(). It then calls EnableAbductionBeam() and PerformOverlapCheck() to enable the abduction beam and check if the cow is underneath the UFO.
OnTriggerEntered(Agent:agent):void =
if:
not IsAbducting?
MovementComponent := Entity.GetComponent[keyframed_movement_component]
then:
MovementComponent.Pause()
EnableAbductionBeam()
PerformOverlapCheck()
The actual logic of checking for overlaps is stored in the PerformOverlapCheck() function. To simulate an abduction beam, this function spawns a collision capsule and defines a CollisionTransform which is set to right underneath the UFO.
PerformOverlapCheck():void =
CollisionCapsule := collision_capsule{Radius := 36.0, Length := 328.0}
var CollisionTransform:transform = Entity.GetGlobalTransform()
set CollisionTransform.Translation.Up = CollisionTransform.Translation.Up - 164.0
Next, in a for expression, the function calls FindOverlapHits() to find and return any components or volumes. It passes the CollisionCapsule as the volume to check for collisions in and the CollisionTransform as the place to simulate that collision from. It then iterates through each overlap and checks if the overlapped component was a mesh component, specifically the SM_Toy_Cow mesh from the cow entity. If it is, it spawns an AbductCow() function passing the cow to be abducted.
# Perform the overlap check from the entity that contains the mesh_component
for:
Overlap : Entity.FindOverlapHits(CollisionTransform, CollisionCapsule)
# Cast to see if what was overlapped was the Cow
CowMesh := Meshes.SM_Toy_Cow[Overlap.TargetComponent]
CowPrefab := CowMesh.Entity
do:
spawn { AbductCow(CowPrefab) }To simulate abducting the cow, the entity enables and calls the custom Abduction Beam component. The AbductionBeam.verse script handles all functionality of the UFO's abduction beam, including the animation that plays on the cow entity. Because this code is being called from the component, the code needs to get AbductionBeam entity and its children, then enable them to play the animation and track the volume to find when the player is standing inside. It starts by calling the EnableAbductionBeam() function to get the entity and the FindDescendantComponents and enable them. It calls DisableAbductionBeam() when the player steps out of the volume. This stops the abduction beam from appearing and abducting the cow.
# Turns on every abduction_beam on this entity and its children.
EnableAbductionBeam():void =
for:
AbductionBeam : Entity.FindDescendantComponents(abduction_beam)
do:
AbductionBeam.Enable()
# Turns off every abduction_beam on this entity and its children.
DisableAbductionBeam():void =
for:
Because the keyframe used in the cow abduction animation aren’t set in the outliner, the code needs to build them based on the different in position between the UFO and the cow. It does this by getting the difference in translation between the cow and the UFO, then building a new keyframed_movement_delta from those values. It then sets that single keyframe as an array in the keyframed movement component and calls play to let the cow animate between its starting position and the UFO.
# Get the delta between Cow and UFO
DeltaTransform:transform = transform:
Translation:= Entity.GetGlobalTransform().Translation - CowEntity.GetGlobalTransform().Translation
Scale := vector3{Left:= 0.0, Up:= 0.0, Forward:= 0.0}
# Create a key frame
Keyframe := keyframed_movement_delta:
Transform := DeltaTransform
Duration := 2.0
Easing := ease_in_cubic_bezier_easing_function{}
The cow can animate to the UFO when abducted, but the code also needs to make it disappear when it overlaps the UFO itself. To do this, the code waits for the mesh component’s EntityEnteredEvent from the cow and then calls RemoveFromParent() to remove the cow entity from the scene. Since the cow is gone, the UFO can start patrolling again, so the code calls play on the UFO’s keyframed movement component to get it moving.
# Wait for Entity Entered Event
CowMesh.EntityEnteredEvent.Await()
# Remove Cow from world
CowEntity.RemoveFromParent()
# Resume UFO Patrol
set IsAbducting = false
if:
UFOMovementComponent := Entity.GetComponent[keyframed_movement_component]
Query Overlap Complete Script
Below is the complete script for querying overlap hits.
using { /Fortnite.com/Devices }
using { /Verse.org }
using { /Verse.org/Colors }
using { /Verse.org/Native }
using { /Verse.org/SceneGraph }
using { /Verse.org/SceneGraph/KeyframedMovement }
using { /Verse.org/Simulation }
using { /Verse.org/SpatialMath }
# Tooltips shown in the Details panel.
Querying Sweep Hits
Sweep hits provide another important way to query overlaps between entities. Sweeping refers to moving an object over a set distance along a particular vector. For example, moving a block across a platform to push players into a gap, or launching a missile straight forward to destroy a wall.
The FindSweepHits() function returns a list of sweep_hit. Each sweep_hit gives you the same info as an overlap_hit, such as the component or volume hit, and the source volume or component doing the sweep. It additionally provides info about the contact position, normal, face normal, and distance along the sweep where the hit occurred.
The template uses sweeps hits to build a more advanced version of the previous overlap hits example. Try stepping onto the final interactive pad in the second hall to check this example out. When you step on the pad, the UFO spawns an abduction beam. It then performs a sweep hit from the UFO mesh down, checking the first entity the sweep overlaps with. If the entity is a cow the UFO abducts it as normal. However if the cow is protected by a globe entity, the sweep will hit the globe first and be blocked, preventing the UFO from abducting the cow.
Open FindOverlapHitsExampleComponent.verse from the Verse Explorer to examine the code. The setup here is very similar to the overlap hits example above, with the same AbductionBeam.verse script used for abducting the cow as well as enabling and disabling the the abduction beam. The major difference comes in the OnTriggerEntered() function that runs when the player steps on the interactive pad in front of the example.
The code for this function starts similarly to the overlap hits example, retrieving the keyframed movement component from the entity and enabling the abduction beam.
OnTriggerEntered(Agent:agent):void=
if:
not IsAbducting?
MovementComponent := Entity.GetComponent[keyframed_movement_component]
then:
MovementComponent.Pause()
EnableAbductionBeam()
However since the function is using sweeps instead of overlaps, the logic for whether the cow is under the abduction beam is a bit different. It starts by getting the first child of the UFO entity, in this case the UFO’s mesh entity. It then creates a displacement vector from which to do the sweep from, pointing straight down from the UFO.
# Perform the sweep from the UFO Mesh
if (Child := Entity.GetEntities()[0]):
DisplacementVector := vector3{Left:=0.0, Up:=-300.0, Forward:=0.0}
FirstSweepHitEntity := FindFirstSweepHit(Child, DisplacementVector)
It then uses this displacement vector to call the FindFirstSweepHit() helper function, passing the UFO’s mesh and the vector. If the first component is the cow’s mesh component, it spawns the AbductCow() function to simulate abducting the cow.
# If the First Hit Entity is the Cow Mesh, then abduct the Cow
if (HitEntity := FirstSweepHitEntity?; HitEntity.GetComponent[Meshes.SM_Toy_Cow]):
spawn { AbductCow(HitEntity) }The FindFirstSweepHit() function takes the entity to sweep and the displacement vector to sweep it along. It calls FindSweepHits() to simulate a sweep, and then iterates through each returned sweep hit in a `for` expression. Since each sweep_hit contains either a component or a volume, you can query either the TargetComponent or TargetVolume to know which type it is. In this case, the code gets the owning entity of the TargetComponent and returns it as an option, meaning it will return `true` if the sweep hit a component and false otherwise.
# Moves InEntity's collision along DisplacementVector with FindSweepHits,
# and returns the first entity it hits, or nothing if it hits nothing.
FindFirstSweepHit(InEntity:entity, DisplacementVector:vector3):?entity =
for (SweepHit : InEntity.FindSweepHits(DisplacementVector)):
return option{ SweepHit.TargetComponent.Entity }
return falseQuery Sweep Hits Complete Script
Below is the complete script for Querying Sweep Hits.
using { /Fortnite.com/Devices }
using { /Verse.org }
using { /Verse.org/Colors }
using { /Verse.org/Native }
using { /Verse.org/SceneGraph }
using { /Verse.org/SceneGraph/KeyframedMovement }
using { /Verse.org/Simulation }
using { /Verse.org/SpatialMath }
# Tooltips shown in the Details panel.
Abduction Beam
The logic of the AbductionBeam.verse file controls the functionality of the UFO. This script tells the UFO's tractor beam when to turn the lights on and off, make the mesh visible, and when to supply the sounds on the entity and its children. The FindOverlapHits and FindSweepHits examples both turn the functionality of this script on and off.
Under the abduction_beam class there are two functions, Enable to turn the tractor beam on and Disable to turn the tractor beam off. The Enable function turns on all parts of the tractor beam. It locates every light component inside the beam and enables each light. It then finds the tractor beam mesh component and makes it visible. Lastly the function finds the cound components and enables each sound effect.
# Turns the beam on: enables the lights and sounds on this entity and its
# children, and shows its meshes.
Enable<public>():void =
for:
BeamLight : Entity.FindDescendantComponents(light_component)
do:
BeamLight.Enable()
for:
BeamMesh : Entity.FindDescendantComponents(mesh_component)
The Disable function basically reverses all the functionality of the Enable by finding every light component and turning them off, disabling all the tractor beam mesh components, and finally disabling the sound components.
# Turns the beam off: disables the lights and sounds on this entity and its
# children, and hides its meshes.
Disable<public>():void =
for:
BeamLight : Entity.FindDescendantComponents(light_component)
do:
BeamLight.Disable()
for:
BeamMesh : Entity.FindDescendantComponents(mesh_component)
Abduction Beam Complete Script
Below is the complete script for Abduction Beam.
using { /Verse.org }
using { /Verse.org/Native }
using { /Verse.org/SceneGraph }
using { /Verse.org/Simulation }
using { /Verse.org/SpatialMath }
# Turns the lights, meshes, and sounds of a beam on and off together.
abduction_beam<public> := class<final_super>(component):
# Turns the beam on: enables the lights and sounds on this entity and its