By completing this step in the Tagged Lights Puzzle tutorial, you'll learn how to detect when the player solves the puzzle so you can spawn an item and prevent further interaction with the puzzle
Detecting a Solved Puzzle
This section shows how to detect when the player solves the puzzle by finding the right combination of lights. When the puzzle is solved, the Item Spawner device should be enabled so that it can spawn its item.
Follow these steps to detect when the player solves the puzzle:
Add an editable
item_spawner_devicefield namedItemSpawnerto thetagged_lights_puzzleclass, to represent the Item Spawner device.@editable ItemSpawner : item_spawner_device = item_spawner_device{}Next, define what state the lights should be in to solve the puzzle. Since the representation of the lights’ state is a
logicarray, the solved state of the lights should also be alogicarray to easily compare the two. Create an editablelogicarray field namedSolvedLightsStateto thetagged_lights_puzzleclass. In this example, the player must turn on all the lights to solve the puzzle, so initialize each element with the valuetrueto represent the light being on.@editable SolvedLightsState : []logic = array{true, true, true, true}Save the file in Visual Studio Code.
In the UEFN toolbar, click Build Verse Scripts to update your Verse device in the level.
In the Outliner, select the tagged_lights_puzzle to open its Details panel.
In the Details panel, set the Item Spawner property to the Item Spawner device that’s in the level.
Next, develop the code that checks if the current
LightsStatematches theSolvedLightsState. Add a new method namedIsPuzzleSolved()to thetagged_lights_puzzleclass. This method should succeed if the puzzle is solved and fail if it’s not, to let its caller know the result of the check. This means the method should be marked as failable with thedecidesspecifier. Since the method has thedecidesspecifier, it must also have thetransactsspecifier, which means the actions performed by this method can be rolled back (as if the actions were never performed), if there’s a failure anywhere in the method.VerseIsPuzzleSolved()<decides><transacts> : void = Logger.Print(“Checking if puzzle is solved”)Verse uses success / failure for decision-making. For more details on how Verse uses failure, see Failure.
When should the device check if the puzzle has been solved? The best time is whenever the
LightsStatearray has just been updated, which happens inside theToggleLights()method. Once theforexpression finishes updating theLightsStatearray, call theIsPuzzleSolved[]method to determine whether the puzzle is solved and so spawn an item (by callingItemSpawner.Enable()). SinceIsPuzzleSolved[]is a failable expression (which is why the method has the[]instead of()when you call it) it must be called in a failure context. In this example, the failure context is an if expression so you can conditionally execute expressions based on whether the puzzle is solved.VerseToggleLights(LightIndices : []int) : void = for: LightIndex : LightIndices IsLightOn := LightsState[LightIndex] Light := Lights[LightIndex] do: Logger.Print("Turning light at index {LightIndex} {if (IsLightOn?) then "Off" else "On"}") NewLightState := if (IsLightOn?): Light.TurnOff()Next, make the
IsPuzzleSolved()method detect if the puzzle is solved. SinceIsPuzzleSolved()is a failure context, you can use failable expressions in the method body without needing to use another failure context, like anifexpression. In this case, you will need to check if the state of every light is the same as the solved puzzle state. To test if twologicvalues are the same, you can use the equals operator=, which is a failable expression. The first time two values you’re checking aren’t the same, the expression fails so then the method fails and returns to its caller’s context (in this case, theifexpression in theToggleLights()method).VerseIsPuzzleSolved()<decides><transacts> : void = Logger.Print(“Checking if puzzle is solved”) for: LightIndex -> IsLightOn : LightsState IsLightOnInSolution := SolvedLightsState[LightIndex] do: IsLightOn = IsLightOnInSolutionSave the changes 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 with the settings shown in this example, interacting with all the buttons once should solve the puzzle and spawn the item.
Removing Button Interaction when Puzzle Is Solved
After the player has completed the puzzle, they shouldn’t be able to interact with the buttons or turn the lights on / off. This section shows how to unsubscribe from an event so your event handler isn’t called anymore when a player interacts with the button.
When you call Subscribe() on a device event, the function call has a cancelable result. Calling Cancel() on a cancelable variable unsubscribes the function handling the event, so that the function will no longer be called when the event is dispatched.
Follow these steps to remove button interactions once the puzzle is solved:
Add a
cancelablearray variable field namedButtonSubscriptionsto thetagged_lights_puzzleclass to store the result of subscribing to each button’s event, and initialize the field with an empty array.var ButtonSubscriptions : []cancelable = array{}Since this is the last statement of the
forexpression, its return values for all successful iterations are collected in an array of the expression return type. As explained earlier, the return type of an eventSubscribecall iscancelable; this refers ToggleLights theforresults in an array ofcancelable([]cancelable). That matches theButtonsSubscriptiontype, so you can assign the result of theforexpression to theButtonsSubscriptionarray. Add this code in theOnBeginfunction, afterSetupPuzzleLights:VerseOnBegin<override>()<suspends> : void = SetupPuzzleLights() set ButtonSubscriptions = for: ButtonIndex -> Button : Buttons LightIndices := ButtonsToLights[ButtonIndex] do: Button.InteractedWithEvent.Subscribe(button_event_handler{Indices := LightIndices, PuzzleDevice := Self}.OnButtonPressed)Finally, remember the buttons must be disabled so that the player can’t change the
LightsStateanymore. That’s achieved by unsubscribing each button’sInteractedWithEventhandlers with a call to theircancelablesubscription. TheseButtonSubscriptionswere saved when the event handlers were created inOnBegin. All that’s left to do is iterate through this array and call cancel on eachButtonSubcription:VerseToggleLights(LightIndices : []int) : void = for: LightIndex : LightIndices IsLightOn := LightsState[LightIndex] Light := Lights[LightIndex] do: Logger.Print("Turning light at index {LightIndex} {if (IsLightOn?) then "Off" else "On"}") NewLightState := if (IsLightOn?): Light.TurnOff()Save the changes 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.
With the default properties, interacting with all buttons once should solve the puzzle, spawn the item, and disable further button interactions.
Next Step
In the last step of this tutorial, you can see the complete script for this tutorial and ideas to further change the example.