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.

UEFN Verse: Implementando uma interface

Uma interface é um tipo composto, assim como a classe, que é usada para especificar o que uma classe pode fazer sem especificar como será feito. Vamos analisar o exemplo abaixo de interface.

challengeable := interface:

    IsChallengeCompleted():logic

Este código define a interface challengeable que possui a função IsChallengeCompleted(). Observe que a função não tem corpo, pois ela tem de ser implementada pela classe que implementar a interface. Este exemplo possui apenas uma função, mas a interface pode ter quantos funções forem necessárias.

O uso de interface permite a criação de um tipo de protocolo ou contrato que pode ser seguido por diversas classes de diferentes tipos. Em alguma parte do código do projeto nós podemos lidar com objetos que implementam a interface challengeable sem precisar saber do tipo real da classe.

Em Verse, a implementação de uma interface é feita da mesma forma que a herança de classe. Uma classe Verse só pode ter uma classe pai, mas pode herdar (implementar) diversas interfaces.

No código abaixo, a classe trigger_manager_device herda da classe creative_device e implementa a interface challengeable. Ela precisa sobrescrever a função IsChallengeCompleted da interface.

trigger_manager_device := class(creative_device, challengeable):

    var IsAllTriggersActivated : logic = false

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

Cada classe que implementar a interface challengeable terá suas próprias regras para definir o resultado da função IsChallengeCompleted(). A classe acima considera que completou o desafio (challenge) quando todas as triggers forem ativadas. Podemos ter outra classe que faz algum tipo de puzzle usando botões.

O tipo da interface pode ser usada na conversão de tipo de referências de objetos, também conhecido como type cast. Por exemplo, CreativeDevice é uma referência do tipo creative_device e por isso pode apontar para uma instância de qualquer subclasse de creative_device

O código abaixo testa se a referência CreativeDevice aponta para uma instância de uma classe que implementa a interface challengeable. Se o cast funcionar, ChallengeableDevice armazenará uma referência do tipo challengeable.

if( ChallengeableDevice := challengeable[ CreativeDevice ] ):

	IsChallengeCompleted := ChallengeableDevice.IsChallengeCompleted()

	...


Vamos fazer um exemplo para ilustrar o uso da interface com o dispositivo trigger_manager_device que implementa a interface challengeable.

Este dispositivo terá 3 trigger_device. Quando todas as trigger_device forem ativadas, a variável IsAllTriggersActivated recebe o valor true. O valor desta variável é retornada na função IsChallengeCompleted().

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

Vamos criar a interface challengeable neste mesmo arquivo. Copie o código Verse abaixo para o arquivo trigger_manager_device:

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

Na função OnBegin é usado a expressão sync para aguardar até que todas as triggers sejam ativadas.

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

Vamos criar outro dispositivo que usará a função IsChallengeCompleted() da interface challengeable para verificar se o desafio foi concluído e ativar o end_game_device.

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

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

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("O dispositivo não implementa a interface challengeable!")        

Estamos usando uma referência para creative_device que é a classe pai dos dispositivos criativos. Então este exemplo vai funcionar para qualquer tipo de dispositivo que implemente a interface challengeable.

Na função OnBegin() é verificado se temos uma instância que implementa a interface challengeable. Depois é iniciado um loop que verifica a cada 0,5 segundos se o desafio foi concluído. 

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 os dispositivos trigger_manager_device e interface_device ao nível. 

Adicione um End Game Device e três Trigger Device no nível. A imagem abaixo mostra os dispositivos usados neste exemplo.


Selecione o trigger_manager_device no nível. Na aba Details, selecione as 3 instâncias de Trigger Device.


Selecione o interface_device no nível. Na aba Details adicione as referências do trigger manager device e do End Game Device.


Salve o nível e clique no botão Launch Session para carregar o nível no Fortnite. Após ativar as 3 triggers, o interface_device detectará que o desafio foi concluído e ativará o fim da partida.