By completing this step in the Tagged Lights Puzzle tutorial, you'll learn how to use Gameplay Tags to find actors marked with a specific tag while the game is running. Gameplay Tags let you work with multiple devices without having to set up their references in the editor. This can open up interesting gameplay opportunities where, for example, your code dynamically changes which devices are active as the player progresses through the game.
Follow these steps to create a new Gameplay Tag and assign it to all the lights in the level for the puzzle:
- Open Verse Explorer and double-click tagged_lights_puzzle.verse to open the script in Visual Studio Code.
- At the top of the code file:
- Add
using { /Verse.org/Simulation/Tags }
to reference thetag
class and and use theGetCreativeObjectsWithTag()
function. -
Add
using { /Verse.org/Simulation }
to be able to make editable properties.using { /Fortnite.com/Devices } using { /Verse.org/Native } using { /UnrealEngine.com/Temporary/Diagnostics } using { /Verse.org/Simulation/Tags } using { /Verse.org/Simulation } log_tagged_lights_puzzle := class(log_channel){}
- Add
-
Above the
log_tagged_lights_puzzle
class, add a new subclass namedpuzzle_light
that inherits from thetag
class. The inherited class name becomes a custom Gameplay Tag for you to use on any creative device.# Derive from the `tag` class in the Verse.org/Simulation/Tags module to create a new Gameplay Tag. puzzle_light := class(tag){} log_tagged_lights_puzzle := class(log_channel){}
- In the UEFN toolbar, click Build Verse Scripts to compile your code and your new
puzzle_light
Gameplay Tag to your project. - In the UEFN Outliner, select a Customizable Light Device to open its Details panel.
- In the Details panel:
- Click Add New Component and choose Verse Tag Markup.
- Select the VerseTagMarkup Component to view its settings in the Details panel.
-
Under Gameplay Tags edit the Tags property and add the
puzzle_light
tag.Multiple tags can be added to the same device, so each device can belong to multiple groups at the same time. For example, a device with
tag1
andtag2
will be found when calling eitherGetCreativeObjectsWithTag(tag1{})
orGetCreativeObjectsWithTag(tag2{})
.
- In the
tagged_lights_puzzle
class definition, add two variable array fields:-
An editable
logic
variable array namedLightsState
to represent the current state of all lights (whether they’re turned off or on). It's also used to set the initial state of the lights, so its number of elements should match the number of lights tagged with thepuzzle_light
tag. In this example, all lights are turned off by default so the starting value for all lights isfalse
.@editable var LightsState : []logic = array{false, false, false, false}
-
An editable
customizable_light_device
variable array namedLights
to store all the Customizable Light devices tagged with thepuzzle_light
Gameplay Tag.@editable var Lights : []customizable_light_device = array{}
-
-
When the game begins, the device should set up the lights to match the initial configuration specified in the
LightsState
array, and save the references in theLights
array so they can be updated when the game state changes. This work is going to be done in a method namedSetupPuzzleLights() : void
and called in theOnBegin()
method so that the lights are set up when the game starts.SetupPuzzleLights() : void = Logger.Print(“Setting up in-game lights”) OnBegin<override>()<suspends> : void = SetupPuzzleLights()
- In
SetupPuzzleLights()
, find all devices with thepuzzle_light
tag by callingGetCreativeObjectsWithTag(puzzle_light{})
and save them in an array namedTaggedActors
. SinceTaggedActors
is a constant array whose scope is local to the methodSetupPuzzleLights()
, you don’t need to explicitly specify a type for the array because it can be inferred in this context.SetupPuzzleLights() : void = Logger.Print("Setting up in-game lights") TaggedActors := GetCreativeObjectsWithTag(puzzle_light{})
Different calls of the function
GetCreativeObjectsWithTag()
may place the devices in different orders in the array result, because there's no guaranteed order when retrieving actors with Gameplay Tags. -
Now that you’ve collected all the devices that have the
puzzle_light
tag, make sure each light matches the initial state specified by theLightsState
array. You can use afor
loop to iterate through all the tagged devices.for: ActorIndex -> TaggedActor : TaggedActors do: TaggedActor
- The function
GetCreativeObjectsWithTag()
returns an array of typecreative_object_interface
. In this example, you’ll want to interact with eachTaggedActor
as acustomizable_light_device
so you can turn the light on or off.-
You can convert a class to one of its subclasses (called type casting) using the syntax
NewDeviceReference := device_type_to_cast_to[DeviceReference]
, wheredevice_type_to_cast_to
is the device type you want, which in this example iscustomizable_light_device
. This is a failable expression because the type conversion will fail if the device can’t be converted to that type (for example if it’s a different type of device).LightDevice := customizable_light_device[TaggedActor]
The function
GetCreativeObjectsWithTag()
has the return type[]creative_object_interface
because the function can return different types of actors, so its return type is the interface all actors must implement to be returned byGetCreativeObjectsWithTag()
. See gameplay tags to learn more. -
With
for
expressions, you can use failable expressions as a filter and create new variables that you can then use in thefor
code block. In this case, add the type conversion tocustomizable_light_device
from the previous step to the iteration expression.for: ActorIndex -> TaggedActor : TaggedActors LightDevice := customizable_light_device[TaggedActor] do: LightDevice
-
The last expression in a code block is the code block’s result. The
for
expression returns the result of the code block from each iteration in an array, so the result of thisfor
expression is an array ofcustomize_light_device
references that were tagged withpuzzle_light
. This means you can update theLights
array with the result of thefor
expression directly.set Lights = for: ActorIndex -> TaggedActor : TaggedActors LightDevice := customizable_light_device[TaggedActor] do: LightDevice
-
This
for
loop should also callTurnOn()
/TurnOff()
on the lights to match their initialLightsState
setup in the editor. Thefor
expression can return the index used to get the current tagged device (ActorIndex
in the example), which you can use to index into theLightsState
array to see whether the light should be on or off.set Lights = for: ActorIndex -> TaggedActor : TaggedActors LightDevice := customizable_light_device[TaggedActor] ShouldLightBeOn:= LightsState[ActorIndex] do: LightDevice
-
Next, call
TurnOn()
/TurnOff()
depending on whetherShouldLightBeOn
istrue
/false
. You can use anif
expression to execute different expressions based on a condition (specifically a failable expression). In this case, the failable expression can use the query operator?
withIsLightOn
, which will succeed ifShouldLightBeOn
istrue
(so callTurnOn()
), and fail ifShouldLightBeOn
isfalse
(so callTurnOff()
).set Lights = for: ActorIndex -> TaggedActor : TaggedActors LightDevice := customizable_light_device[TaggedActor] ShouldLightBeOn := LightsState[ActorIndex] do: if (ShouldLightBeOn?) then LightDevice.TurnOn() else LightDevice.TurnOff() LightDevice
- It’s a good idea to also print the index of the light and its starting value so you can verify your code is working as expected and compare to what you see in the level.
-
When you use
{}
in the middle of a string, the expression between the{}
is evaluated first and its value is added to the string. So you can use anif
expression in the middle of a string to conditionally add values.set Lights = for: ActorIndex -> TaggedActor : TaggedActors LightDevice := customizable_light_device[ActorIndex] ShouldLightBeOn := LightsState[ActorIndex] do: Logger.Print("Adding Light at index {ActorIndex} with State:{if (ShouldLightBeOn?) then "On" else "Off"}") if (ShouldLightBeOn?) then LightDevice.TurnOn() else LightDevice.TurnOff() LightDevice
-
-
Your
SetupPuzzleLights()
method should now look like this:SetupPuzzleLights() : void = TaggedActors := GetCreativeObjectsWithTag(puzzle_light{}) <# For each device with the puzzle_light tag, check if it's a customizable_light_device by trying to cast it to that type. If it is, get its initial LightState to TurnOn() or TurnOff() the LightDevice. Save all the tagged customizable_light_device in the Lights array. #> set Lights = for: ActorIndex -> TaggedActor : TaggedActors LightDevice := customizable_light_device[TaggedActor] ShouldLightBeOn := LightsState[ActorIndex] do: Logger.Print("Adding Light at index {ActorIndex} with State:{if (ShouldLightBeOn?) then "On" else "Off"}") if (ShouldLightBeOn?) then LightDevice.TurnOn() else LightDevice.TurnOff() LightDevice
- Save the script in Visual Studio Code.
- In the UEFN toolbar, click Build Verse Scripts to compile your code.
- Click Play in the UEFN toolbar to playtest the level.
When you playtest your level, you should see each light that’s added to the Lights
array, along with its initial state, printed to the output log.
Next Step
In the next step of this tutorial, you’ll learn how to toggle a specific set of lights when the player presses the buttons.