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 


UEFN Verse: Introdução à Persistência

Neste artigo vou apresentar noções básicas do uso de persistência no UEFN usando Verse.

Primeiro vamos esclarecer sobre o que estamos falando ao mencionar persistência. Em uma ilha padrão do Fortnite, quando um jogador sai da experiência, os seus dados relacionados a experiência, como pontos ou itens coletados, são perdidos.

O uso de persistência permite que alguns dados associados ao jogador sejam salvos na experiência. Quando o jogador retornar à experiência, estes dados podem ser recuperados permitindo que o jogador continue de onde parou.

Para persistir os dados dos jogadores, é preciso criar uma variável global do tipo weak_map usando o tipo player como chave. Weak map é um tipo de map que não permite a listagem de seus elementos para iteração. A única forma de acessar um elemento de um weak map é usando o índice. Se você não sabe o que é um map, eu sugiro a leitura do meu artigo:

UEFN Verse: Map (pt-BR)

A linha abaixo mostra como criar uma variável do tipo weak_map com o nome PlayerData que usa o tipo player como chave e o tipo float como valor. A declaração desta variável precisa estar fora de uma classe para que ela seja global.

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

Para ficar claro o uso da persistência, vamos começar com um exemplo simples usando um Timer Device e um Button Device. O Timer Device será configurado para contar para cima. Quando o jogador pressionar o botão, o valor atual do Timer será salvo. Quando o jogador entrar de novo na experiência, o valor do timer será recuperado e atribuido ao timer para que ele não comece a contagem do zero de novo.

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 persistence_device clique no botão Create Empty.

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

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{} 
    # O timer precisa contar para cima.
	
    @editable
    SaveButton : button_device = button_device{}

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

        SaveButton.InteractedWithEvent.Subscribe(ButtonPressed)

        if:   # Recupera dados salvos se existir.
            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("Dados salvos.")

A função OnBegin registra a função ButtonPressed no evento InteractedWithEvent do SaveButton e verifica se existe um valor salvo para o jogador atual acessando o weak_map com a expressão PlayerData[Player].

A função ButtonPressed obtém a contagem atual do Timer Device usando a função GetActiveDuration e salva no weak_map usando o jogador atual como chave.

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 persistence_device ao nível. Adicione um Timer Device e um Button Device e deixe eles próximos um do outro. 



Selecione o Timer Device no nível. Na aba Details selecione CountUp em Count Down Direction. Coloque um valor alto em Duration. No meu exemplo eu usei 600 segundos (10 minutos). Marque a opção Start at Game Start


Selecione o persistence_device no nível. Na aba Details adicione as referências do Timer Device e do Button Device.



Salve o nível e clique no botão Launch Session para carregar o nível no Fortnite. Espere por alguns segundos dentro do jogo e depois use o seu personagem para pressionar o botão no nível. Quando o botão for pressionado, o valor atual do timer será persistido.


Para você conseguir 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, o Timer Device iniciará sua contagem a partir do valor que foi salvo.

No próximo artigo, vou abordar mais alguns conceitos de persistência e mostrar a forma que ela é geralmente usada nas experiências.

Só um alerta. Evite fazer experimentações de persistência em ilhas que estão publicadas. Este é um tópico que necessita conhecimento e planejamento para evitar que alterações futuras nas variáveis usadas para salvar façam os jogadores perderem seus dados salvos. 

Sumário Verse 


segunda-feira, 8 de dezembro de 2025

UEFN Verse: Implementing an interface

An interface is a composite type, just like a class, that is used to specify what a class can do without specifying how it will be done. Let's analyze the interface example below.

challengeable := interface:

    IsChallengeCompleted():logic

This code defines the challengeable interface, which has the IsChallengeCompleted() function. Note that the function has no body, as it must be implemented by the class that implements the interface. This example has only one function, but the interface can have as many functions as needed.

Using an interface allows the creation of a type of protocol or contract that can be followed by various classes of different types. Somewhere in the project code, we can handle objects that implement the challengeable interface without needing to know the actual type of the class.

In Verse, implementing an interface is done in the same way as class inheritance. A Verse class can only have one parent class, but it can inherit (implement) multiple interfaces.

In the code below, the trigger_manager_device class inherits from the creative_device class and implements the challengeable interface. It needs to override the IsChallengeCompleted function of the interface.

trigger_manager_device := class(creative_device, challengeable):

    var IsAllTriggersActivated : logic = false

    IsChallengeCompleted<override>():logic=
        IsAllTriggersActivated
		
    ...

Each class that implements the challengeable interface will have its own rules for defining the result of the IsChallengeCompleted() function. The class above considers the challenge completed when all triggers are activated. We could have another class that performs some kind of puzzle using buttons.

The interface type can be used in object reference type conversion, also known as type cast. For example, CreativeDevice is a reference of type creative_device and therefore can point to an instance of any subclass of creative_device.

The code below tests whether the CreativeDevice reference points to an instance of a class that implements the challengeable interface. If the cast works, ChallengeableDevice will store a reference of type challengeable.

if( ChallengeableDevice := challengeable[ CreativeDevice ] ):

	IsChallengeCompleted := ChallengeableDevice.IsChallengeCompleted()

	...

Let's look at an example to illustrate the use of the interface with the trigger_manager_device that implements the challengeable interface.

This device will have 3 trigger_devices. When all trigger_devices are activated, the variable IsAllTriggersActivated receives the value true. The value of this variable is returned in the IsChallengeCompleted() function.

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

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

Let's create the challengeable interface in this same file. Copy the Verse code below into the trigger_manager_device file:

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

challengeable := interface:

    IsChallengeCompleted():logic

trigger_manager_device := class(creative_device, challengeable):

    var IsAllTriggersActivated : logic = false

    @editable
    Trigger1 : trigger_device = trigger_device{}

    @editable
    Trigger2 : trigger_device = trigger_device{}

    @editable
    Trigger3 : trigger_device = trigger_device{}

    OnBegin<override>()<suspends>:void=
        sync:
            Trigger1.TriggeredEvent.Await()
            Trigger2.TriggeredEvent.Await()
            Trigger3.TriggeredEvent.Await()

        set IsAllTriggersActivated = true   

    IsChallengeCompleted<override>():logic=
        IsAllTriggersActivated

In the OnBegin function, the sync expression is used to wait until all triggers are activated.

Save the file and compile the Verse code using the Verse > Compile Verse Code option from the UEFN menu.

Let's create another device that will use the IsChallengeCompleted() function from the challengeable interface to check if the challenge has been completed and activate the end_game_device.

In Verse Explorer, right-click the project name and choose the option Add new Verse file to project.

In Device Name, put interface_device and click the Create Empty button.

Copy the Verse code below into the interface_device file:

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

interface_device := class(creative_device):
    
    @editable
    CreativeDevice : creative_device = creative_device{}

    @editable
    EndGameDevice : end_game_device = end_game_device{}
    
    OnBegin<override>()<suspends>:void=

        if( ChallengeableDevice := challengeable[ CreativeDevice ] ):

            loop:
                IsChallengeCompleted := ChallengeableDevice.IsChallengeCompleted()

                if: 
                    IsChallengeCompleted?
                    PlayerAgent := GetPlayspace().GetPlayers()[0]
                then:
                    EndGameDevice.Activate(PlayerAgent)
                    break   #end loop

                Sleep(0.5)
        else:
            Print("The device doesn't implement the challengeable interface!")        

We are using a reference to creative_device, which is the parent class of creative devices. Therefore, this example will work for any type of device that implements the challengeable interface.

The OnBegin() function checks if we have an instance that implements the challengeable interface. Then a loop is started that checks every 0.5 seconds if the challenge has been completed.

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 trigger_manager_device and interface_device to the level.

Add one End Game Device and three Trigger Devices to the level. The image below shows the devices used in this example.


Select the trigger_manager_device at the level. In the Details tab, select the 3 instances of Trigger Device.


Select the interface_device at the level. In the Details tab, add the references for the trigger manager device and the End Game Device.


Save the level and click the Launch Session button to load the level in Fortnite. After activating the 3 triggers, the interface_device will detect that the challenge has been completed and will trigger the end of the match.