The Infiltrator's invisibility creates an interesting problem when it comes to grabbing the Defender's objective. How are the Defenders going to find an invisible player who might be sprinting back to base with their objective? To solve this issue, you can use a visual aid, in this case a prop, to show the Defenders where the Infiltrator is.
Follow the steps below to learn how to create an object that floats above a player's head when they're holding an objective.
Creating the Item Capture Manager
- Create a new Verse device named item_capture_manager using Verse Explorer, and drag the device into the level.
- At the top of the
item_capture_managerfile:- Add
using { /UnrealEngine.com/Temporary/SpatialMath }to access thevector3struct. You'll use this to know where to teleport the indicators that floats above a player's head. Also addusing { /Fortnite.com/Characters }to access a player'sfort_character.using { /Fortnite.com/Devices } using { /Verse.org/Simulation } using { /Fortnite.com/Characters } using { /UnrealEngine.com/Temporary/Diagnostics } using { /UnrealEngine.com/Temporary/SpatialMath }
- Add
- In the
item_capture_managerclass definition, add the following fields:- An editable array of Capture Item Spawner devices named
CaptureItemSpawners. This array holds the Capture Item Spawner device for the Infiltrators.item_capture_manager := class(creative_device): Logger:log = log{Channel := triad_item_capture_log_channel} # Capture item spawner that spawns the item to capture. @editable CaptureItemSpawner:capture_item_spawner_device = capture_item_spawner_device{} - An editable creative prop named
CaptureItemIndicator. This is the prop that will float above an Infiltrator's head when they grab the objective.# Capture item spawner that spawns the item to capture. @editable CaptureItemSpawner:capture_item_spawner_device = capture_item_spawner_device{} # Prop that floats above a players head when they're holding the item from # the CaptureItemSpawner. @editable CaptureItemIndicator:creative_prop = creative_prop{} - An editable map indicator device named
MapIndicator. This will sit under the CaptureItemSpawner in the level, and display on the map where the objectives for each team are.# Prop that floats above a players head when they're holding the item from # the CaptureItemSpawner. @editable CaptureItemIndicator:creative_prop = creative_prop{} # Indicator that displays on the map where the objectives for each team are @editable MapIndicator:map_indicator_device = map_indicator_device{} - Two editable floats
UpdateRateSecondsandVerticalOffset. The first controls how quickly the position of theCaptureItemIndicatorchanges, and the second controls how far above a player's head theCaptureItemIndicatorfloats.# Indicator that displays on the map where the objectives for each team are @editable MapIndicator:map_indicator_device = map_indicator_device{} # How often the CaptureItemIndicator updates its position. @editable UpdateRateSeconds:float = 0.15 # How high above a player's head the CaptureItemIndicator floats. @editable VerticalOffset:float = 180.0 - An editable hud message device named
ItemGrabbedMessageDevice. This sends a message to each player when an objective is picked up.# How high above a player's head the CaptureItemIndicator floats. @editable VerticalOffset:float = 180.0 # Displays a message when a player grabs the Capture Item. @editable ItemGrabbedMessageDevice:hud_message_device = hud_message_device{} - An editable score manager device named
ScoreManagerDevice. This awards score to a team whenever a player captures the obje# Displays a message when a player grabs the Capture Item. @editable ItemGrabbedMessageDevice:hud_message_device = hud_message_device{} # Awards score when a player captures the capture Item. @editable ScoreManagerDevice:score_manager_device = score_manager_device{} - An editable float named
ReturnTime. If the capture item has a return time before returning to the CaptureItemSpawner, you need to track how long the length of that return time to know when to return the indicators back to the CaptureItemSpawner.
- An editable array of Capture Item Spawner devices named
- Add a new method
FollowCharacter()to theitem_capture_managerclass definition. This method takes afort_characterand tracks them using the indicators above their head. Add the<suspends>specifier to this function, since you want to spawn one of these for a player whenever they're holding an objective.# Causes the CaptureItemIndicator to continuously follow a player above their head. # Races between the update loop for the CaptureItemIndictator, and whether the player # captures the item, drops the item, or is eliminated. FollowCharacter(FortCharacter:fort_character)<suspends>:void= Logger.Print("Spawned FollowCharacter function")
Running a Race While Holding the Objective
It's important to think about what happens when a player grabs the objective. The player can:
- Move around, in which case you need your CaptureItem and Map indicators to continuously update to the player's position. This can be done in a loop.
- Capture the objective, in which case you need your indicators to return to the CaptureItemSpawner somewhere out of sight since they shouldn't be visible unless a player is holding the capture item.
- Drop the objective or be eliminated, in which case the indicators need to stay where the item was dropped, and return to the CaptureItemSpawner when the capture item returns.
To achieve this, you're going to set up a race expression. By using a race between the three conditions above, you can continue to update the position of the indicators while waiting for the player to either drop or capture the objective.
- Add a
raceexpression toFollowCharacter(). Set up the race to run between aloop, anAwait()for theCaptureItemSpawner'sItemCapturedEvent, anAwait()for theCaptureItemSpawner'sItemCapturedDroppedEvent, and anAwait()for theFortCharacter'sEliminatedEvent().FollowCharacter(FortCharacter:fort_character)<suspends>:void= Logger.Print("Spawned FollowCharacter function") race: loop: CaptureItemSpawner.ItemCapturedEvent.Await() CaptureItemSpawner.ItemDroppedEvent.Await() FortCharacter.EliminatedEvent().Await() - In the
loop, get the position ofFortCharacterand save it in a variableTransform.loop: Transform := FortCharacter.GetTransform() - Now spawn a
MoveTo()to move bothCaptureItemIndicatorandMapIndicatorto theTransform's translation and rotation plus theVerticalOffsetyou set up earlier over anUpdateRateSecondsamount of time. You want toSpawn{}bothMoveTo()functions because both theCaptureItemIndicatorandMapIndicatorneed to move at the exact same time, rather than waiting for each other's expression to complete. Because the translation is avector3consisting ofX,Y, andZcoordinates, you'll have to put theVerticalOffsetinside a newvector3. Since theVerticalOffsetis the vertical distance above a player's head, set it as theZvalue of thevector3.loop: Transform := FortCharacter.GetTransform() spawn{CaptureItemIndicator.MoveTo(Transform.Translation + vector3{Z := VerticalOffset}, Transform.Rotation, UpdateRateSeconds)} spawn{MapIndicator.MoveTo(Transform.Translation + vector3{Z := VerticalOffset}, Transform.Rotation, UpdateRateSeconds)} - Finally, sleep for
0.0seconds. This ensures the loop runs only once per simulation update, and does not go out of control spawningMoveTo()functions. YourFollowCharacter()code should now look like:# Causes the CaptureItemIndicator to continuously follow a player above their head. # Races between the update loop for the CaptureItemIndictator, and whether the player # captures the item, drops the item, or is eliminated. FollowCharacter(FortCharacter:fort_character)<suspends>:void= Logger.Print("Spawned FollowCharacter function") race: loop: Transform := FortCharacter.GetTransform() spawn{CaptureItemIndicator.MoveTo(Transform.Translation + vector3{Z := VerticalOffset}, Transform.Rotation, UpdateRateSeconds)} spawn{MapIndicator.MoveTo(Transform.Translation + vector3{Z := VerticalOffset}, Transform.Rotation, UpdateRateSeconds)} # We want to make sure this loop runs only once per simulation update, so we sleep for one game tick. Sleep(0.0) CaptureItemSpawner.ItemCapturedEvent.Await() CaptureItemSpawner.ItemDroppedEvent.Await() FortCharacter.EliminatedEvent().Await() Logger.Print("Objective dropped or captured")Resetting the Indicators
- When the capture item is captured or returned, you need to return the indicators to the
CaptureItemSpawnersomewhere out of sight. In this case, you'll teleport them high above theCaptureItemSpawner. To do this, add a function namedReturnIndicators()to theitem_capture_managerclass definition.# Returns the map and capture item indicators back to their initial positions above the spawners. ReturnIndicators(InAgent:agent):void= - Get the transform of the
CaptureItemSpawnerand save it in a variableSpawnerTransform. Then spawn aMoveTo()for theCaptureItemIndicatorandMapIndicatorto theCaptureItemSpawner's transform and rotation, adding theVerticalOffsetin the same way as you did in theloopto put them above theCaptuerItemSpawnwer. If you want your prop to stay far away out of sight, you can multiply theVerticalOffsetby a large number, in this case 10. Your completedReturnIndicators()method should look like:# Returns the map and capture item indicators back to their initial positions above the spawners. ReturnIndicators():void= SpawnerTransform := CaptureItemSpawner.GetTransform() # Teleport back to spawner, hiding the CaptureItemIndicator and MapIndicator above the map out of site. spawn{CaptureItemIndicator.MoveTo(SpawnerTransform.Translation + vector3{Z := VerticalOffset * 10.0}, SpawnerTransform.Rotation, UpdateRateSeconds)} spawn{MapIndicator.MoveTo(SpawnerTransform.Translation + vector3{Z := VerticalOffset * 10.0}, SpawnerTransform.Rotation, UpdateRateSeconds)} Logger.Print("Returned Indicators to capture spawner")
Handling Players Grabbing, Dropping, and Capturing the Objective.
- Add a new method,
OnItemPickedUp()to theitem_capture_managerclass definition. This method takes anagentand spawns an instance ofFollowCharacter()for that character.# Signal each player when a player grabs the objective OnItemPickedUp(InAgent:agent):void= Logger.Print("Objective Grabbed") - Get the
FortCharacterforInAgent, and spawn aFollowCharacter()function using thatFortCharacter. Your completedOnItemPickedUp()method should look like:# Signal each player when a player grabs the objective OnItemPickedUp(InAgent:agent):void= Logger.Print("Objective Grabbed") if(FortCharacter := InAgent.GetFortCharacter[]): ItemGrabbedMessageDevice.Show() spawn{FollowCharacter(FortCharacter)} - Add a new method
OnItemCaptured()to theitem_capture_managerclass definition. This method takes theagentwho captured the objective.# When the item is captured, award score to the capturing team, and return the indicators. OnItemCaptured(CapturingAgent:agent):void= Logger.Print("Objective Captured") - In
OnItemCaptured(), activate theScoreManagerDeviceto award the capturing player's team score, and callReturnIndicators()to return the indicators.# When the item is captured, award score to the capturing team, and return the indicators. OnItemCaptured(CapturingAgent:agent):void= Logger.Print("Objective Captured") ScoreManagerDevice.Activate() ReturnIndicators() - Add a new method
OnItemDropped()to theitem_capture_managerclass definition. This method takes theagentwho dropped the item.# When a player drops an item, spawn a WaitForReturn() function # if the ReturnTime is greater than 0. OnItemDropped(InAgent:agent):void= Logger.Print("Objective Dropped") - When the objective is dropped, the indicators should remain near the objective until the objective is either picked up or returns to the
CaptureItemSpawner. To know when to return the indicators, you'll use theReturnTimevariable you set up earlier. IfReturnTimeis greater than or equal to0.0, you want to wait that amount of time, then return the indicators. IfReturnTimeis negative, the objective doesn't have a retun time, so you don't need to move in the indicators. To move the indicatrs back, spawn a new helper function namedWaitForReturn(), which you'll define in the next step.# When a player drops an item, spawn a WaitForReturn() function # if the ReturnTime is greater than 0. OnItemDropped(InAgent:agent):void= Logger.Print("Objective Dropped") if(ReturnTime >= 0.0): spawn{WaitForReturn()} else: Logger.Print("The dropped objective does not return") - Add a new method
WaitForReturn()to theitem_capture_managerclass definitin. This function waits aReturnTimeamount of time, then returns the if the objective was not picked up before the wait completed. Add the<suspends>modifier to this method to allow it toSleep().# Wait a ReturnTime amount of Time, then return the indicators. WaitForReturn()<suspends>:void= Logger.Print("Waiting to return the indicators...") - Whether you need to return the indicators or not depends on whether the objective was picked up before
ReturnTimeends. If it was, you don't want to return the indicators since they would immediately jump back to the player, could cause some strange visuals. To solve this you'll use a logic variable where the value is equal to the result of a race.# Wait a ReturnTime amount of Time, then return the indicators. WaitForReturn()<suspends>:void= Logger.Print("Waiting to return the indicators...") # Return the CaptureItem and Map indicators if the capture item # is not picked up before time expires. ShouldReturn:logic := race: - Your
WaitForReturn()function needs to race between two conditions. TheReturnTimeruns out and the objective returns to theCaptureItemSpawner, in which case you need to return the indicators andShouldReturnshould betrue. Or the objective is picked up beforeReturnTimeexpires, in which caseShouldReturnshould befalse. Since each of these conditions returns a value, you'll run the race using two seperateblocks.ShouldReturn:logic := race: block: block: - In the first block, call
Sleep()for aReturnTimeamount of time, then returntrue. In the second block,Await()theCaptureItemSpawner.ItemPickedUpEvent, and return false. TheShouldReturnvariable will now be initialized to whichever one of these completes first.ShouldReturn:logic := race: block: Sleep(ReturnTime) true block: CaptureItemSpawner.ItemPickedUpEvent.Await() false - If
ShouldReturnis true you need to return the indicators. CallReturnIndicators()ifShouldReturnevaluates totrue. Your completedWaitForReturn()code should now look like:# Wait a ReturnTime amount of Time, then return the indicators. WaitForReturn()<suspends>:void= Logger.Print("Waiting to return the indicators...") # Return the CaptureItem and Map indicators if the capture item # is not picked up before time expires. ShouldReturn:logic := race: block: Sleep(ReturnTime) true block: CaptureItemSpawner.ItemPickedUpEvent.Await() false if(ShouldReturn?): ReturnIndicators() - Now in
OnBegin(), subscribe theCaptureItemSpawner'sItemPickedUpEventtoOnItemPickedUp(), theItemCapturedEventtoOnItemCaptured(), and theItemDroppedEventtoOnItemDropped().OnBegin<override>()<suspends>:void= CaptureItemSpawner.ItemPickedUpEvent.Subscribe(OnItemPickedUp) CaptureItemSpawner.ItemCapturedEvent.Subscribe(OnItemCaptured) CaptureItemSpawner.ItemDroppedEvent.Subscribe(OnItemDropped) SpawnerTransform := CaptureItemSpawner.GetTransform() - Finally in
OnBegin(), put the indicators in their starting positions when the script runs by callingMoveTo()on theCaptureItemIndicatorandMapIndicator. YourOnBegin()code should now look like:OnBegin<override>()<suspends>:void= CaptureItemSpawner.ItemPickedUpEvent.Subscribe(OnItemPickedUp) CaptureItemSpawner.ItemCapturedEvent.Subscribe(OnItemCaptured) CaptureItemSpawner.ItemDroppedEvent.Subscribe(OnItemDropped) SpawnerTransform := CaptureItemSpawner.GetTransform() # Teleport back to spawner, hiding the CaptureItemIndicator beneath the map out of site. CaptureItemIndicator.MoveTo(SpawnerTransform.Translation + vector3{Z := VerticalOffset * 10.0}, SpawnerTransform.Rotation, UpdateRateSeconds) MapIndicator.MoveTo(SpawnerTransform.Translation + vector3{Z := VerticalOffset * 10.0}, SpawnerTransform.Rotation, UpdateRateSeconds) -
Back in the editor, save the script, build it, and drag the device into the level. Choose an appropriate prop you want to serve as the
CaptureItemIndicatorinto your level. This could be anything as long as it's visible enough. In this example, you'll use a Diamond. In the Details panel, assign CaptureItemSpawner to the InfiltratorCaptureSpawner, and CaptureItemIndicator to the prop you chose. Also assign MapIndicator to the Infiltrator's map indicator, ItemGrabbedMessageDevice to the Infiltrator's HUD message device, and ScoreManagerDevice to the Infiltrator's score manager. Set ReturnTime to a negative number, sinc the Infiltrator's capture item doesn't return.You should also set up an instance of
item_capture_managerfor the Attackers. Remember to change the CaptureItemIndicator to a prop that's different from the the Infiltrator props to avoid visual confusion for the teams, and make sure to assign all other devices. Set ReturnTime to a positive number, since the Attacker's capture item returns after a set time. - Click Launch Session in the UEFN toolbar to playtest the level. When you playtest your level, a player should have a prop above their head when they grab an objective. The prop should move around with the player, and when they drop or capture the objective, the prop should teleport back to the capture item spawner.

Next Step
In the next step of this tutorial, you'll learn how to quickly tell players what they should be doing in a game, and what to focus on when improving player experience.