domingo, 29 de março de 2026

UEFN Verse: Logging and Log channel

Log messages can help identify issues in your Verse code. These messages can be viewed in the in-game log tab and in the UEFN Output Log window. 

Logging in Verse requires two classes: log and log_channel.

The log_channel is used only to identify the source of the message. Its name is added to the beginning of the message. 

You need to create a subclass of log_channel at the module scope, that is, outside of classes or functions. The code below shows an example of a log_channel

log_general := class(log_channel){}

The next step is to create an instance of the log class inside a class or function, specifying the log_channel to be used:

LoggerGeneral:log = log{Channel:=log_general}

There are five log levels used to categorize messages: Debug, Verbose, Normal, Warning and Error.

Logs with Debug and Verbose levels appear only in the in-game log tab. They do not appear in the UEFN Output Log.

Logs with Warning and Error levels are displayed in different colors to draw attention.

A default log level can be defined when the log is created: 

LoggerGeneral:log = log{Channel:=log_general, DefaultLevel:=log_level.Warning}

To write to the log, use the Print function of the log class:

LoggerGeneral.Print("Experience started")

You can pass the log level as a parameter:

LoggerGeneral.Print("Data not found", ?Level := log_level.Error)

Another useful piece of information for debugging is the Call Stack, which shows the sequence of function calls leading to the current point in the code. Use the PrintCallStack function from the log class to log the Call Stack. 

LoggerGeneral.PrintCallStack()

To illustrate the use of logging, we will create a device that uses two logs. One will be used for general system messages, and the other for logging messages when buttons are pressed by the player. Different log levels will be used.

Open Verse Explorer, right-click on the project name, and select the option Add new Verse file to project.

In Device Name put log_example_device and click the Create Empty button.

Copy the Verse code below into the log_example_device file:

using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }
using { /UnrealEngine.com/Temporary/Diagnostics }

log_general := class(log_channel){}
log_buttons := class(log_channel){}

log_example_device := class(creative_device):

    LoggerGeneral:log = log{Channel:=log_general}
    LoggerButtons:log = log{Channel:=log_buttons, 
                            DefaultLevel := log_level.Warning}

    var BtnWarningCount: int = 0
    var BtnErrorCount: int = 0

    @editable
    ButtonWarning : button_device = button_device{}

    @editable
    ButtonError : button_device = button_device{}

    OnBegin<override>()<suspends>:void=
        ButtonWarning.InteractedWithEvent.Subscribe(ButtonWarningPressed)
        ButtonError.InteractedWithEvent.Subscribe(ButtonErrorPressed)
        LoggerGeneral.Print("Experience started.")

    ButtonWarningPressed(Agent:agent):void=
        set BtnWarningCount += 1
        LoggerButtons.Print("ButtonWarning was pressed {BtnWarningCount} times")

    ButtonErrorPressed(Agent:agent):void=
        set BtnErrorCount += 1
        LoggerButtons.Print("ButtonError was pressed {BtnErrorCount} times", 
                            ?Level := log_level.Error)
        LoggerButtons.PrintCallStack()

The log_example_device contains two button_device instances that are used to log messages when pressed. One button is used for Warning level messages, and the other for Error level messages.

The OnBegin function subscribes the functions to be called on the buttons when they are pressed, and then logs a message using LoggerGeneral.

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 log_example_device to the level. Then add two Button Devices to the level. 



Select the log_example_device at the level. In the Details tab, Select the references to the buttons in the level. 


Save the level and click the Launch Session button to load the level in Fortnite. Press the buttons a few times. Then switch to UEFN and check the messages logged in the Output Log.



UEFN Verse: Logging e Log channel

Mensagens escritas no log podem ajudar a identificar problemas no seu código Verse. Estas mensagens podem ser visualizadas na aba de log dentro do jogo e na janela Output Log do UEFN.

Para escrever logs em Verse, você precisa usar duas classes: log e log_channel.

O log_channel é usado apenas para identificar a origem da mensagem. Seu nome é adicionado ao início da mensagem.

É necessário criar uma subclasse de log_channel no escopo de módulo, ou seja, fora de classes ou funções. O código abaixo mostra um exemplo de log_channel:

log_general := class(log_channel){}

O próximo passo é criar uma instância da classe log dentro de uma classe ou função, informando o log_channel que será utilizado:

LoggerGeneral:log = log{Channel:=log_general}

Existem 5 níveis de log que podem ser usados para categorizar a mensagem: Debug, Verbose, Normal, Warning e Error.

Logs com níveis Debug e Verbose aparecem apenas na aba de log dentro do jogo. Eles não aparecem no Output Log do UEFN.

Logs com níveis Warning e Error são exibidos em cores diferentes para chamar mais atenção.

É possível definir um nível padrão para um log no momento de sua criação: 

LoggerGeneral:log = log{Channel:=log_general, DefaultLevel:=log_level.Warning}

Para escrever no log, use a função Print da classe log:

LoggerGeneral.Print("Experience started")

Você pode passar como parâmetro o nível de log da mensagem:

LoggerGeneral.Print("Data not found", ?Level := log_level.Error)

Outra informação útil na análise de problemas é o Call Stack (pilha de chamadas) que mostra as funções chamadas até chegar ao ponto atual do código. Use a função PrintCallStack da classe log para registrar o Call Stack. 

LoggerGeneral.PrintCallStack()

Para ilustrar o uso de logs, vamos criar um dispositivo que utiliza dois logs. Um deles será usado para mensagens gerais do sistema e o outro log será para registrar mensagens quando os botões forem pressionados pelo jogador. Serão usados diferentes níveis de log.

Abra o Verse Explorer, clique com o botão-direito no nome do projeto e escolha a opção Add new Verse file to project.

Em Device Name coloque log_example_device clique no botão Create Empty.

Copie o código Verse abaixo para o dispositivo log_example_device:

using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }
using { /UnrealEngine.com/Temporary/Diagnostics }

log_general := class(log_channel){}
log_buttons := class(log_channel){}

log_example_device := class(creative_device):

    LoggerGeneral:log = log{Channel:=log_general}
    LoggerButtons:log = log{Channel:=log_buttons, 
                            DefaultLevel := log_level.Warning}

    var BtnWarningCount: int = 0
    var BtnErrorCount: int = 0

    @editable
    ButtonWarning : button_device = button_device{}

    @editable
    ButtonError : button_device = button_device{}

    OnBegin<override>()<suspends>:void=
        ButtonWarning.InteractedWithEvent.Subscribe(ButtonWarningPressed)
        ButtonError.InteractedWithEvent.Subscribe(ButtonErrorPressed)
        LoggerGeneral.Print("Experience started.")

    ButtonWarningPressed(Agent:agent):void=
        set BtnWarningCount += 1
        LoggerButtons.Print("ButtonWarning was pressed {BtnWarningCount} times")

    ButtonErrorPressed(Agent:agent):void=
        set BtnErrorCount += 1
        LoggerButtons.Print("ButtonError was pressed {BtnErrorCount} times", 
                            ?Level := log_level.Error)
        LoggerButtons.PrintCallStack()

O dispositivo log_example_device possui dois button_device que serão usados para registrar mensagens no log quando forem pressionados. Um botão será usado para mensagens com o nível Warning e o outro para mensagens com o nível Error.

A função OnBegin registra, nos botões, as funções que serão chamadas quando eles forem pressionados e, em seguida, escreve uma mensagem no log usando o LoggerGeneral.

Salve o arquivo e compile o código Verse usando a opção Verse > Compile Verse Code do menu do UEFN. 

Acesse o Content Drawer e adicione o dispositivo log_example_device ao nível. Em seguida, adicione dois Button Devices ao nível. 



Selecione o log_example_device no nível. Na aba Details, selecione as referências aos botões que estão no nível. 


Salve o nível e clique no botão Launch Session para carregar o nível no Fortnite. Pressione os botões algumas vezes. Em seguida, alterne para o UEFN e verifique no Output Log as mensagens registradas.  



quinta-feira, 19 de março de 2026

UEFN Verse: Extension Methods

Methods are functions that belong to a class, just as fields or attributes represent class data, such as variables and constants. This terminology comes from Object-Oriented Programming.

The Verse language allows the creation of extension methods, which are functions that can be added to existing classes without the need for inheritance.

This can be useful when you need to add functionality to classes that cannot be extended, such as player, agent, and team in UEFN.

In addition to classes, extension methods can be used with any type in Verse. The example below adds a function to the int type that calculates the power of a number.

(Base:int).MyPowFunction(Exponent:int):int=
    if (Exponent < 0):
        return 0
    var PowResult: int = 1
    for (Ind := 1..Exponent):
        set PowResult *= Base
    PowResult

Inside the parentheses is a constant of the type being extended. This constant is used in the function body as if it were a parameter.

The following code shows how to use the function:

IntValue := 2
Result := IntValue.MyPowFunction(4)


We will use extension methods to add functionality to the player class in UEFN. To do this, a new class and a map will be used to associate additional fields with a player instance.

Open Verse Explorer, right-click on the project name, and select the option Add new Verse file to project.

In Device Name put extension_device and click the Create Empty button.

Copy the Verse code below into the extension_device file:

using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }
using { /UnrealEngine.com/Temporary/Diagnostics }

player_fields := class:
    var Score : int = 0
    var CoinsCollected : int = 0
    var GameLevel : int = 1
    var CoinsPerLevel : int = 3    

extension_device := class(creative_device):

    @editable
    ItemSpawnerArray : []item_spawner_device = array{}

    var PlayerFieldsMap<public> : [player]player_fields  = map{}

    OnBegin<override>()<suspends>:void =
        for(ItemSpawner : ItemSpawnerArray):
            ItemSpawner.ItemPickedUpEvent.Subscribe(HandleCoinCollected)

    HandleCoinCollected(Agent:agent):void=
        if ( Player := player[Agent], Player.CollectCoin[] ) {}

    (Player:player).GetPlayerFields()<decides><transacts>:player_fields=
        if(PlayerFields := PlayerFieldsMap[Player]):
            PlayerFields
        else:
            NewPlayerFields := player_fields{}
            set PlayerFieldsMap[Player] = NewPlayerFields
            NewPlayerFields

    (Player:player).CollectCoin()<decides><transacts>:void=
        PlayerFields := Player.GetPlayerFields[]
        set PlayerFields.CoinsCollected += 1
        set PlayerFields.Score += PlayerFields.GameLevel * 100
        if ( Mod[PlayerFields.CoinsCollected, PlayerFields.CoinsPerLevel] = 0 ):
            set PlayerFields.GameLevel += 1
        Print("Score:{PlayerFields.Score} | GameLevel:{PlayerFields.GameLevel}")

The player_fields class contains the new fields that will be associated with a player instance through the PlayerFieldsMap map.

The extension_device contains an array of item_spawner_device used to store references to the Item Spawners in the level. The OnBegin function registers HandleCoinCollected on each instance in the item_spawner_device array so it is executed when the player collects a coin.

The HandleCoinCollected function receives the agent that collected the coin as a parameter. It is cast to player, and then the CollectCoin extension method created for the player class is called. The if expression is used only as a failure context.

GetPlayerFields is an extension method created for the player class. It returns the player_fields instance associated with the player. If it does not exist yet, a new player_fields instance is created, stored in the map, and returned by the method.

CollectCoin is another extension method for the player class. It retrieves the player_fields instance to update its values. In this example, GameLevel increases every three coins collected, and the score awarded for each coin depends on it.

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.


Make copies of the Item Spawner Device by holding Alt and dragging one of the axis arrows on the selected device. In my example, there are four Item Spawners.


Select the extension_device at the level. In the Details tab, add elements to the array and select the Item Spawner Device references for each element.


Save the level and click the Launch Session button to load the level in Fortnite. When a coin is collected, a message is displayed showing the player's current Score and GameLevel.



UEFN Verse: Métodos de extensão

Métodos são as funções que pertencem a uma classe, assim como campos ou atributos representam dados da classe, como variáveis e constantes. Esta nomenclatura começou a ser usada na Programação Orientada a Objetos.

A linguagem Verse permite a criação de métodos de extensão que são funções que podem ser adicionadas a classes existentes sem precisar usar a herança.

Isto pode ser útil quando você precisar adicionar funcionalidade as classes que não podem ser estendidas como player, agent e team do UEFN.

Além de classes, os métodos de extensão podem ser usados em qualquer tipo do Verse. O exemplo abaixo adiciona uma função ao tipo int que calcula a potência de um número:

(Base:int).MyPowFunction(Exponent:int):int=
    if (Exponent < 0):
        return 0
    var PowResult: int = 1
    for (Ind := 1..Exponent):
        set PowResult *= Base
    PowResult

Entre parênteses, há a declaração de uma constante do tipo que está sendo estendido. Esta constante é usada no corpo da função como se fosse um parâmetro.

O código abaixo mostra como a função pode ser usada:

IntValue := 2
Result := IntValue.MyPowFunction(4)


Vamos usar métodos de extensão para adicionar funcionalidades à classe player do UEFN. Para isso, será utilizada uma nova classe e um map para associar novos campos a uma instância de player.

Abra o Verse Explorer, clique com o botão-direito no nome do projeto e escolha a opção Add new Verse file to project.

Em Device Name coloque extension_device clique no botão Create Empty.

Copie o código Verse abaixo para o dispositivo extension_device:

using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }
using { /UnrealEngine.com/Temporary/Diagnostics }

player_fields := class:
    var Score : int = 0
    var CoinsCollected : int = 0
    var GameLevel : int = 1
    var CoinsPerLevel : int = 3    

extension_device := class(creative_device):

    @editable
    ItemSpawnerArray : []item_spawner_device = array{}

    var PlayerFieldsMap<public> : [player]player_fields  = map{}

    OnBegin<override>()<suspends>:void =
        for(ItemSpawner : ItemSpawnerArray):
            ItemSpawner.ItemPickedUpEvent.Subscribe(HandleCoinCollected)

    HandleCoinCollected(Agent:agent):void=
        if ( Player := player[Agent], Player.CollectCoin[] ) {}

    (Player:player).GetPlayerFields()<decides><transacts>:player_fields=
        if(PlayerFields := PlayerFieldsMap[Player]):
            PlayerFields
        else:
            NewPlayerFields := player_fields{}
            set PlayerFieldsMap[Player] = NewPlayerFields
            NewPlayerFields

    (Player:player).CollectCoin()<decides><transacts>:void=
        PlayerFields := Player.GetPlayerFields[]
        set PlayerFields.CoinsCollected += 1
        set PlayerFields.Score += PlayerFields.GameLevel * 100
        if ( Mod[PlayerFields.CoinsCollected, PlayerFields.CoinsPerLevel] = 0 ):
            set PlayerFields.GameLevel += 1
        Print("Score:{PlayerFields.Score} | GameLevel:{PlayerFields.GameLevel}")

A classe player_fields contém os novos campos que serão associados a uma instância de player por meio do map PlayerFieldsMap.

O dispositivo extension_device possui um array de item_spawner_device usado para armazenar as referências dos Item Spawner presentes no nível. A função OnBegin registra a função HandleCoinCollected em cada uma das instâncias do array item_spawner_device para ser executada quando o jogador coletar uma moeda. 

A função HandleCoinCollected recebe como parâmetro o agente que coletou a moeda. É feito o cast (conversão) para player e, em seguida, é chamado o método de extensão CollectCoin, criado para a classe player. A expressão if está sendo usada apenas como contexto de falha.

GetPlayerFields é um método de extensão criado para a classe player. Ele retorna a instância de player_fields associada ao player. Caso ainda não exista, uma nova instância de player_fields é criada, armazenada no map e retornada pelo método.

CollectCoin é outro método de extensão criado para a classe player. O método obtém a instância de player_fields para atualizar seus valores. Neste exemplo, o GameLevel aumenta a cada três moedas coletadas. A pontuação obtida ao coletar cada moeda depende do GameLevel

Salve o arquivo e compile o código Verse usando a opção Verse > Compile Verse Code do menu do UEFN. 

Acesse o Content Drawer e adicione o dispositivo extension_device ao nível. Adicione um Item Spawner Device no nível e na aba Details configure conforme a imagem para que uma moeda seja gerada a cada 3 segundos.


Faça cópias do Item Spawner Device usando a tecla Alt e arrastando uma das setas de eixo do dispositivo selecionado. No meu exemplo, utilizei quatro Item Spawners.


Selecione o extension_device no nível. Na aba Details, adicione elementos ao array e selecione as referências aos Item Spawner Devices em cada elemento do array.


Salve o nível e clique no botão Launch Session para carregar o nível no Fortnite. Ao coletar uma moeda, será exibida uma mensagem informando os valores atuais de Score e GameLevel do jogador.



terça-feira, 24 de fevereiro de 2026

UEFN Verse: Constructor and Persistence

In this article I will show 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.

Table of Contents Verse 

 

UEFN Verse: Construtor e Persistência

Neste artigo vou mostrar como usar um construtor para facilitar a atualização de dados que são persistidos.

Se você ainda não conhece nada sobre persistência usando Verse, eu recomendo a leitura do meu artigo anterior:

UEFN Verse: Introdução à Persistência

A melhor opção para persistir os dados do jogador é usando uma classe. Na classe você pode reunir vários campos e associar a instância desta classe ao jogador no weak_map. Outra vantagem do uso de classes na persistência é que elas permitem a adição de campos depois da publicação da ilha.

As classes usadas na persistência precisam ter os especificadores <persistable> e <final>, que indica que a classe não pode ter subclasses. 

Outro ponto importante é que a classe não pode ter variáveis, somente constantes. O código abaixo é um exemplo simples de classe usada para persistência. 

player_data := class<final><persistable>:
    Version<public>:int = 1
    PlayerXP<public>:int = 0
    ItemsCollected<public>:int = 0

É uma boa prática ter um campo que indique a versão da classe. Desta forma, você pode detectar se os dados de um jogador ainda estão em uma versão anterior e fazer algum tipo de conversão se for necessário.

A linha a seguir mostra a variável do tipo weak_map que associa o tipo player à nova classe player_data. Os valores desta variável são persistidos.

var PlayerDataMap:weak_map(player, player_data) = map{}

Como uma classe usada para persistência não pode ter variáveis, será preciso criar uma nova instância da classe toda vez que houver uma atualização dos dados. Podemos usar um construtor para facilitar esta tarefa.

Um construtor é um tipo de função que cria uma instância de uma classe. Em nosso exemplo, ele será usado para fazer uma cópia de uma instância e depois poderemos atualizar apenas os campos que foram modificados.

O código abaixo é de um construtor que recebe como parâmetro uma instância de player_data e retorna uma nova instância com os valores copiados.

NewPlayerData<constructor>(OldData:player_data)<transacts> := player_data:
    Version := OldData.Version
    PlayerXP := OldData.PlayerXP
    ItemsCollected := OldData.ItemsCollected

Observe que a função NewPlayerData não tem um tipo de retorno usando o operador ":". Uma função do tipo construtor recebe uma instância de uma classe usando o operador de atribuição ":=".

Uma função do tipo construtor pode ser chamada na instanciação de arquétipo de uma classe. No exemplo abaixo, o objetivo é criar uma nova instância de player_data e atribuir um novo valor ao campo ItemsCollected

set PlayerDataMap[Player] = player_data:
                ItemsCollected := NewItemsCollected
                NewPlayerData<constructor>(PlayerSavedData)

Esta é a forma usada para atualizar campos específicos de uma classe persistida. Na instanciação de arquétipo, o construtor é executado primeiro fazendo as cópias de todos os dados. Depois são feitas as atribuições nos campos como o do ItemsCollected.

Para ver estes códigos de persistência em uso, vamos criar um exemplo usando um Item Spawner Device. O jogador poderá coletar itens. Cada vez que um item for coletado, será atualizado o valor de ItemsCollectedQuando o jogador entrar de novo na experiência, a contagem dos itens coletados continuará a partir do valor que parou na última execução.

Abra o Verse Explorer, clique com o botão-direito no nome do projeto e escolha a opção Add new Verse file to project.

Em Device Name coloque constructor_device clique no botão Create Empty.

Copie o código Verse abaixo para o dispositivo constructor_device:

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("Itens coletados: {NewItemsCollected}")

A função OnBegin registra a função HandleItemPickedUp no evento ItemPickedUpEvent do ItemSpawnerDevice. Esta função será executada quando o jogador coletar um item. O laço for é usado para verificar se todos os jogadores que estão na experiência possuem dados persistidos. Se um jogador não possuir os dados, será criado um player_data com valores padrões e armazenado no PlayerDataMap weak_map. 

A função HandleItemPickedUp recupera os dados persistido do jogador e armazena em PlayerSavedData. O novo valor para o campo ItemsCollected é calculado e armazenado temporiamente em NewItemsCollected. O PlayerDataMap weak_map recebe uma nova instância de player_data que foi criada usando o construtor junto com a atualização do campo ItemsCollected.  

Salve o arquivo e compile o código Verse usando a opção Verse > Compile Verse Code do menu do UEFN. 

Acesse o Content Drawer e adicione o dispositivo constructor_device ao nível. Adicione um Item Spawner Device no nível e na aba Details configure conforme a imagem para que uma moeda seja gerada a cada 3 segundos.


Selecione o constructor_device no nível. Na aba Details adicione a referência ao Item Spawner Device.


Salve o nível e clique no botão Launch Session para carregar o nível no Fortnite. Colete algumas moedas. Cada vez que uma moeda for coletada, o contador de moedas que está persistido será atualizado e uma mensagem será exibida com o valor atual do contador.

Para simular o uso da persistência localmente não encerre a sessão do UEFN. No Fortnite aperte ESC para sair da experiência atual sem sair da sessão do UEFN. 


Quando entrar novamente na experiência com o mesmo jogador e coletar uma moeda, a contagem continuará a partir do último valor que foi salvo antes de sair da experiência.

Sumário Verse 


domingo, 15 de fevereiro de 2026

UEFN Verse: Introduction to Persistence

In this article I will present the basics of using persistence in UEFN using Verse.

First, let's clarify what we mean by persistence. On a standard Fortnite island, when a player leaves the experience, their experience-related data, such as points or collected items, is lost.

The use of persistence allows some data associated with the player to be saved in the experience. When the player returns to the experience, this data can be retrieved, allowing the player to continue from where they left off.

To persist player data, you need to create a global variable of type weak_map using the player type as the key. A weak map is a type of map that does not allow listing its elements for iteration. The only way to access an element of a weak map is by using its index. If you don't know what a map is, I suggest reading my article:

UEFN Verse: Map

The line below shows how to create a weak_map variable named PlayerData that uses the player type as the key and the float type as the value. This variable declaration needs to be outside of a class for it to be global.

var PlayerData:weak_map(player, float) = map{}

To make the use of persistence clear, let's start with a simple example using a Timer Device and a Button Device. The Timer Device will be configured to count upwards. When the player presses the button, the current Timer value will be saved. When the player re-enters the experience, the timer value will be retrieved and assigned to the timer so that it doesn't start counting from zero again.

Open Verse Explorer, right-click on the project name, and select the option Add new Verse file to project.

In Device Name put persistence_device and click the Create Empty button.

Copy the Verse code below into the persistence_device file:

using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }
using { /UnrealEngine.com/Temporary/Diagnostics }

var PlayerData:weak_map(player, float) = map{}

persistence_device := class(creative_device):

    @editable  
    TimerDevice: timer_device = timer_device{} 
    # The timer needs to count upwards.
	
    @editable
    SaveButton : button_device = button_device{}

    OnBegin<override>()<suspends>:void =

        SaveButton.InteractedWithEvent.Subscribe(ButtonPressed)

        if:   # Retrieves saved data if it exists.
            Player := GetPlayspace().GetPlayers()[0]
            TimeElapsed := PlayerData[Player]
        then:
            TimerDevice.SetActiveDuration(TimeElapsed, Player)

    ButtonPressed(Agent:agent):void=	
        if: 
            Player := player[Agent]
            set PlayerData[Player] = TimerDevice.GetActiveDuration(Agent)
        then:
            Print("Data saved.")

The OnBegin function subscribes the ButtonPressed function in the InteractedWithEvent event of the SaveButton and checks if a value has been saved for the current player by accessing the weak_map with the PlayerData[Player] expression.

The ButtonPressed function retrieves the current count from the Timer Device using the GetActiveDuration function and saves it to the weak_map using the current player as the key.

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 persistence_device to the level. Add a Timer Device and a Button Device and place them close to each other.



Select the Timer Device at the level. In the Details tab, select CountUp under Count Down Direction. Set a high value for Duration. In my example, I used 600 seconds (10 minutes). Check the Start at Game Start option.


Select the persistence_device at the level. In the Details tab, add the Timer Device and Button Device references.



Save the level and click the Launch Session button to load the level in Fortnite. Wait a few seconds in-game and then use your character to press the button in the level. When the button is pressed, the current timer value will be persisted.


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, the Timer Device will start counting from the saved value.

In the next article, I will discuss some more concepts of persistence and show how it is generally used in experiences.

Just a word of caution. Avoid experimenting with persistence on published islands. This topic requires careful planning to prevent future changes to the save variables from causing players to lose their data.

Table of Contents Verse