By completing this step in the Time Trial: Pizza Pursuit tutorial, you’ll learn how to manage the score when a player picks up items and delivers them, and to update the UI to display scores. To learn more about creating an in-game UI in Verse, see Creating an In-Game UI.
The score manager will track and display:
Total Points: Represents the overall points the player has scored in-game.
Pending Points: Represents the points the player has accumulated for the current set of pickups.
Pickup Level: Represents the current pickup level.
Creating the UI
Follow these steps to create the UI for the score manager in Verse:
Create a new empty Verse file and name it score_manager.verse.
Create a new class named
score_manager, and add the following fields to the class:An optional
agentnamedMaybePlayerto store a reference to the player.MaybePlayer<internal> : ?agent = falseAn optional
player_uinamedMaybePlayerUIto store a reference to the player’s UI.MaybePlayerUI<internal> : ?player_ui = falseA
score_manager_deviceto store a reference for the Score Manager device that this class is built around. (Note, this is not an editable reference because that needs to be connected to the Verse-authored devicegame_coordinator_device.ScoreManagerDevice<internal> : score_manager_device = score_manager_device{}An integer variable named
TotalGameScorethat represents all the points the player has scored in the game overall.var TotalGameScore<private> : int = 0An integer variable named
PendingScorethat represents the points the player has currently accumulated for this set of pickups.var PendingScore<private> : int = 0An integer variable named
PickupLevelthat represents the current pickup level.var PickupLevel<private> : int = 0
Your score_manager class definition should now look like:
score_manager := class: MaybePlayer<internal> : ?agent = false MaybePlayerUI<internal> : ?player_ui = false ScoreManagerDevice<internal> : score_manager_device = score_manager_device{} var TotalGameScore<private> : int = 0 var PendingScore<private> : int = 0 var PickupLevel<private> : int = 0Create the UI when the class is first created. You can do this by adding a
blockexpression to the class definition, which will execute whenever you create an instance of the class. Add the following variables for generating the UI:A
canvasvariable namedCanvasthat has theinternalspecifier to store the custom canvas widget.var Canvas<internal> : canvas = canvas{}A
text_blocknamedTotalGameScoreWidgetthat has theinternalspecifier, to store the text widget for displaying all the points the player has scored in the game overall, as represented by the variableTotalGameScore. Set the text block’s default text color to white.TotalGameScoreWidget<internal> : text_block = text_block{DefaultTextColor := NamedColors.White}A
text_blocknamedPendingScoreWidgetthat has theinternalspecifier, to store the text widget for displaying the points the player has currently accumulated for this set of pickups, as represented by the variablePendingScore. Set the text block’s default text color to white.PendingScoreWidget<internal> : text_block = text_block{}A
text_blocknamedPickupLevelWidgetthat has theinternalspecifier, to store the text widget for displaying the current pickup level, as represented by the variablePickupLevel. Set the text block’s default text color to white.PickupLevelWidget<internal> : text_block = text_block{}A function returning a
messagenamedTotalGameScoreTextthat creates localizable text that can be displayed in the UI for the overall points the player has scored in the game.TotalGameScoreText<localizes>(CurrentTotalGameScore : int) : message = "Total Points: {CurrentTotalGameScore}"A function returning a
messagenamedPendingScoreTextthat creates localizable text that can be displayed in the UI for the points the player has currently accumulated for this set of pickups.PendingScoreText<localizes>(CurrentPendingScore : int) : message = "Pending Points: {CurrentPendingScore}"A function returning a
messagenamedPickupLevelTextthat creates localizable text that can be displayed in the UI for the current pickup level.PickupLevelText<localizes>(CurrentPickupLevel : int) : message = "Pickup Level: {CurrentPickupLevel}"Add a
blockexpression that creates the canvas widget and positions the text stacked vertically on the left of the screen.<# Since we won't recreate the canvas during the score manager lifetime, do it once anytime an object of this type is created. #> block: set Canvas = canvas: Slots := array: canvas_slot: Anchors := anchors{Minimum := vector2{X := 0.0, Y := 0.25}, Maximum := vector2{X := 0.0, Y := 0.25} } Offsets := margin{Top := 0.0, Left := 25.0, Right := 0.0, Bottom := 0.0} Alignment := vector2{X := 0.0, Y := 0.0} SizeToContent := true Widget := stack_box: Orientation := orientation.Vertical Slots := array: stack_box_slot: HorizontalAlignment := horizontal_alignment.Left Widget := TotalGameScoreWidget stack_box_slot: HorizontalAlignment := horizontal_alignment.Left Widget := PendingScoreWidget stack_box_slot: HorizontalAlignment := horizontal_alignment.Left Widget := PickupLevelWidget
Your code for
score_managershould now look like:using { /UnrealEngine.com/Temporary/UI } using { /Fortnite.com/UI } using { /Verse.org/Colors } score_manager := class: var Canvas<internal> : canvas = canvas{} TotalGameScoreWidget<internal> : text_block = text_block{DefaultTextColor := NamedColors.White} PendingScoreWidget<internal> : text_block = text_block{DefaultTextColor := NamedColors.White} PickupLevelWidget<internal> : text_block = text_block{DefaultTextColor := NamedColors.White} MaybePlayer<internal> : ?agent = false MaybePlayerUI<internal> : ?player_ui = false ScoreManagerDevice<internal> : score_manager_device = score_manager_device{} PickupLevelText<private><localizes>(InLevel : int) : message = "Pickup Level: {InLevel}" PendingScoreText<private><localizes>(InPoints : int) : message = "Pending Points: {InPoints}" TotalGameScoreText<private><localizes>(InPoints : int) : message = "Total Points: {InPoints}" var TotalGameScore<private> : int = 0 var PendingScore<private> : int = 0 var PickupLevel<private> : int = 0 <# Since we won't recreate the canvas during the score manager lifetime, do it once anytime an object of this type is created. #> block: set Canvas = canvas: Slots := array: canvas_slot: Anchors := anchors{Minimum := vector2{X := 0.0, Y := 0.25}, Maximum := vector2{X := 0.0, Y := 0.25} } Offsets := margin{Top := 0.0, Left := 25.0, Right := 0.0, Bottom := 0.0} Alignment := vector2{X := 0.0, Y := 0.0} SizeToContent := true Widget := stack_box: Orientation := orientation.Vertical Slots := array: stack_box_slot: HorizontalAlignment := horizontal_alignment.Left Widget := TotalGameScoreWidget stack_box_slot: HorizontalAlignment := horizontal_alignment.Left Widget := PendingScoreWidget stack_box_slot: HorizontalAlignment := horizontal_alignment.Left Widget := PickupLevelWidgetCreate a function named
UpdateUI()that has theprivatespecifier, which updates the text in the UI with the latest score values and current pickup level.UpdateUI<private>() : void = if (PlayerUI := MaybePlayerUI?): PickupLevelWidget.SetText(PickupLevelText(PickupLevel)) PendingScoreWidget.SetText(PendingScoreText(PendingScore)) PendingScoreWidget.SetText(TotalGameScoreText(TotalGameScore))Create a function named
AddScoreManagerToUI()which updates the player’s UI with the custom score manager UI.AddScoreManagerToUI<public>() : void = if (PlayerUI := MaybePlayerUI?): PlayerUI.AddWidget(Canvas) UpdateUI()Create a function for each value that’s displayed in the UI so the game loop can update the values:
A function named
AddPendingScoreToTotalScore()that has thepublicspecifier. This function should add the pending score to the total game score and reset the pending score value to0. You candeferresettingPendingScoreand updating the UI after theTotalGameScoreis updated. This avoids using a temporary variable to hold the value ofPendingScorebefore it resets.Verse<# Adds PendingScore to TotalGameScore and resets PendingScore to 0.#> AddPendingScoreToTotalScore<public>() : void = defer: set PendingScore = 0 UpdateUI() set TotalGameScore += PendingScoreA function named
UpdatePendingScore()that has thepublicspecifier and an integer parameter namedPoints, which the function will add to the current pending score.Verse<# Adds the given amount of points to the pending points. #> UpdatePendingScore<public>(Points : int) : void = set PendingScore += Points UpdateUI()A function named
UpdatePickupLevelthat has thepublicspecifier and an integer parameter namedLevel, which is the new value for the current pickup level.VerseUpdatePickupLevel<public>(Level : int) : void = set PickupLevel = Level UpdateUI()Create a function named
AwardScore()that has thepublicspecifier. This function awards the score to the player using the Score Manager device, and activates the device.Verse<# Awards the score to the player with the Score Manager device, by activating it. #> AwardScore<public>() : void = ScoreManagerDevice.SetScoreAward(TotalGameScore) if (AwardedPlayer := MaybePlayer?): ScoreManagerDevice.Activate(AwardedPlayer)
Your
score_managerclass should now look like:Versescore_manager := class: <# Since we won't recreate the canvas during the score manager lifetime, do it once anytime an object of this type is created.> block: set Canvas = canvas: Slots := array: canvas_slot: Anchors := anchors{Minimum := vector2{X := 0.0, Y := 0.25}, Maximum := vector2{X := 0.0, Y := 0.25} } Offsets := margin{Top := 0.0, Left := 25.0, Right := 0.0, Bottom := 0.0} Alignment := vector2{X := 0.0, Y := 0.0} SizeToContent := trueNow that you have created your
score_managerclass, create a constructor for the class to initialize the player variables from the game. Note that you must type cast the player reference fromagenttoplayerto get a reference to the player’s UI.VerseMakeScoreManager<constructor><public>(InPlayer : agent, InScoreManagerDevice : score_manager_device) := score_manager: MaybePlayer := option{InPlayer} MaybePlayerUI := option{GetPlayerUI[player[InPlayer]]}Your score_manager.verse file should now look like:
Verseusing { /UnrealEngine.com/Temporary/SpatialMath} using { /UnrealEngine.com/Temporary/UI } using { /Fortnite.com/Devices } using { /Fortnite.com/UI } using { /Verse.org/Colors } using { /Verse.org/Simulation } MakeScoreManager<constructor><public>(InPlayer : agent, InScoreManagerDevice : score_manager_device) := score_manager: MaybePlayer := option{InPlayer} MaybePlayerUI := option{GetPlayerUI[player[InPlayer]]}
Updating the Score and UI in the Game Loop
Follow these steps to create and update your UI during the game in the game_coordinator_device.verse file:
Add the following properties to the
game_coordinator_deviceclass:A
score_managervariable namedScoreManagerthat has theprivatespecifier. This instance manages the player’s score and UI.var ScoreManager<private> : score_manager = score_manager{}An editable
score_manager_devicethat you can set to the Score Manager device in the level. This is the device that thescore_managerclass will use.@editable ScoreManagerDevice<public> : score_manager_device = score_manager_device{}An editable integer array named
PointsForPickupLevelthat has thepublicspecifier, to define the points the player can score for each pickup level.@editable # Maps how many points a pickup is worth based on its pickup level. PointsForPickupLevel<public> : []int = array{1, 2, 3}
In the
StartGamefunction, initialize the score manager variable by calling the constructorMakeScoreManager()with a reference to the player and Score Manager device, and generate the UI for the player to see.VerseStartGame<private>()<suspends> : void = Logger.Print("Trying to start the game...") # We construct a new countdown_timer that'll countdown from InitialCountdownTime once started. # Also construct a new score_manager that'll keep track of the player's score and pickup level. # The countdown_timer and score_manager require a player to show their UI to. # We should have a valid player by now: the one that entered the vehicle, triggering the game start. if (ValidPlayer := MaybePlayer?): Logger.Print("Valid player, starting game...")In the game loop
PickupDeliveryLoop()function, update the UI whenever the pickup level changes and the player finishes a pickup or delivery:VersePickupDeliveryLoop<private>()<suspends> : void = PickupZonesTags : []pickup_zone_tag = array{pickup_zone_level_1_tag{}, pickup_zone_level_2_tag{}, pickup_zone_level_3_tag{}} MaxPickupLevel := PickupZonesTags.Length - 1 FirstPickupZoneCompletedEvent := event(){} loop: var PickupLevel : int = 0 var IsFirstPickup : logic = true # Every time the loop restarts, we should reset the pickup level UI through the ScoreManager.Now, when the countdown ends, award the player their score. In
HandleCountdownEnd(), callScoreManager.AwardScore().HandleCountdownEnd<private>(InPlayer : player)<suspends>:void= TotalTime := CountdownTimer.CountdownEndedEvent.Await() ScoreManager.AwardScore() EndGame.Activate(InPlayer)Your game_coordinator_device.verse file should now look like:
Verseusing { /Verse.org/Simulation } using { /Fortnite.com/Devices } using { /Fortnite.com/Vehicles } using { /Fortnite.com/Characters } using { /Fortnite.com/Playspaces } using { /Verse.org/Random } using { /UnrealEngine.com/Temporary/Diagnostics } using { /UnrealEngine.com/Temporary/SpatialMath } using { /EpicGames.com/Temporary/Curves } using { /Verse.org/Simulation/Tags }