In this article I will show you how to use a constructor to make it easier to update persisted data.
If you're not yet familiar with persistence using Verse, I recommend reading my previous article:
UEFN Verse: Introduction to Persistence
The best option for persisting player data is by using a class. Within a class, you can group multiple fields and associate an instance of that class with the player in the weak_map. Another advantage of using classes for persistence is that they allow for the addition of fields after the island has been published.
The classes used in persistence need to have the <persistable> and <final> specifiers. The <final> specifier indicate that the class cannot have subclasses.
Another important point is that the class cannot have variables, only constants. The code below is a simple example of a class used for persistence.
player_data := class<final><persistable>:
Version<public>:int = 1
PlayerXP<public>:int = 0
ItemsCollected<public>:int = 0
It's good practice to have a field that indicates the class version. This allows you to detect if a player's data is from an older version and perform a conversion if necessary.
The following line shows the weak_map type variable that associates the player type with the new player_data class. The values of this variable are persisted.
var PlayerDataMap:weak_map(player, player_data) = map{}
Since a class used for persistence cannot have variables, a new instance of the class will need to be created every time the data is updated. We can use a constructor to make this task easier.
A constructor is a type of function that creates an instance of a class. In our example, it will be used to make a copy of an instance, and then we can update only the fields that have been modified.
The code below is for a constructor that takes an instance of player_data as a parameter and returns a new instance with the copied values.
NewPlayerData<constructor>(OldData:player_data)<transacts> := player_data:
Version := OldData.Version
PlayerXP := OldData.PlayerXP
ItemsCollected := OldData.ItemsCollected
Note that the NewPlayerData function does not have a return type using the ":" operator. A constructor function receives an instance of a class using the assignment operator ":=".
A constructor function can be called during the archetype instantiation of a class. In the example below, the goal is to create a new instance of player_data and assign a new value to the ItemsCollected field:
set PlayerDataMap[Player] = player_data:
ItemsCollected := NewItemsCollected
NewPlayerData<constructor>(PlayerSavedData)
This is the approach used to update specific fields of a persisted class. In archetype instantiation, the constructor is executed first, making copies of all the data. Then, assignments are made to the fields, such as the ItemsCollected field.
To see these persistence codes in use, let's create an example using an Item Spawner Device. The player will be able to collect items. Each time an item is collected, the ItemsCollected value will be updated. When the player re-enters the experience, the count of collected items will continue from the value where it stopped in the last run.
Open Verse Explorer, right-click on the project name, and select the option Add new Verse file to project.
In Device Name put constructor_device and click the Create Empty button.
Copy the Verse code below into the constructor_device file:
using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }
using { /UnrealEngine.com/Temporary/Diagnostics }
player_data := class<final><persistable>:
Version<public>:int = 1
PlayerXP<public>:int = 0
ItemsCollected<public>:int = 0
NewPlayerData<constructor>(OldData:player_data)<transacts> := player_data:
Version := OldData.Version
PlayerXP := OldData.PlayerXP
ItemsCollected := OldData.ItemsCollected
var PlayerDataMap:weak_map(player, player_data) = map{}
constructor_device := class(creative_device):
@editable
ItemSpawnerDevice: item_spawner_device = item_spawner_device{}
OnBegin<override>()<suspends>:void =
ItemSpawnerDevice.ItemPickedUpEvent.Subscribe(HandleItemPickedUp)
for (Player : GetPlayspace().GetPlayers()):
if:
not PlayerDataMap[Player]
set PlayerDataMap[Player] = player_data{}
HandleItemPickedUp(Agent:agent):void=
if:
Player := player[Agent]
PlayerSavedData := PlayerDataMap[Player]
NewItemsCollected := PlayerSavedData.ItemsCollected + 1
set PlayerDataMap[Player] = player_data:
ItemsCollected := NewItemsCollected
NewPlayerData<constructor>(PlayerSavedData)
then:
Print("Items collected: {NewItemsCollected}")
The OnBegin function subscribes the HandleItemPickedUp function in the ItemPickedUpEvent of the ItemSpawnerDevice. This function will be executed when the player picks up an item. The for loop is used to check if all players in the experience have persisted data. If a player does not have data, a player_data will be created with default values and stored in the PlayerDataMap weak_map.
The HandleItemPickedUp function retrieves the persisted player data and stores it in PlayerSavedData. The new value for the ItemsCollected field is calculated and temporarily stored in NewItemsCollected. The PlayerDataMap weak_map receives a new instance of player_data that was created using the constructor along with the update of the ItemsCollected field.
Save the file and compile the Verse code using the Verse > Compile Verse Code option from the UEFN menu.
Access the Content Drawer and add the constructor_device to the level. Add an Item Spawner Device to the level and, in the Details tab, configure it as shown in the image so that a coin is generated every 3 seconds.
Select the constructor_device at the level. In the Details tab, add the reference to the Item Spawner Device.
Save the level and click the Launch Session button to load the level in Fortnite. Collect some coins. Each time a coin is collected, the saved coin counter will be updated and a message will be displayed with the current counter value.
To simulate local persistence, do not end your UEFN session. In Fortnite, press ESC to exit the current experience without exiting your UEFN session.
When you re-enter the experience with the same player and collect a coin, the count will continue from the last value that was saved before exiting the experience.


