So, the past couple of days I have been trying to get the RNG Device to trigger each number once, then reset once all of the numbers have been picked. I thought the “Reset on Game Start” setting on pick each number once would do the trick because my game has 25 rounds to it, but that did not work. After some looking I saw somewhere on reddit a user suggesting to use verse for anything RNG related, so I tried that.
As a complete beginner to anything coding I consulted the AI within UEFN to help generate potential verse devices that could help my situation. I tried “pick each number once randomly and remove it from the array and reset when all numbers in the index are exhausted”, “shuffle all of the numbers in the index and pick one number in order every round”, tired some methods with persistence, and a lot of other variations. But, I cannot seem to get things to work, and I have no idea whether it is because the game resets per round, so it resets the array every round or what.
To clarify, I need a verse device that activates automatically on round start, picks a number between 1 - 8, and that number cannot be picked again once all of the other numbers are exhausted. Also these numbers are represented by triggers. How can I achieve this?
Indeed! What you need to do is to use Verse to implement that kind of random selection!
Here is the exact code you should use in order to avoid repetitive numbers:
using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }
using { /UnrealEngine.com/Temporary/Diagnostics }
# Added Random module
using { /Verse.org/Random }
random_number := class(creative_device):
# Maximum number value (range will be 1 to MaxNumber)
@editable
var MaxNumber : int = 8
# Array that stores numbers that haven't been chosen yet
var AvailableNumbers : []int = array{}
# Array of trigger devices corresponding to each number
@editable
var Triggers : []trigger_device = array{}
# Button device that triggers the random number generation
@editable
MyButton : button_device = button_device{}
# Called when the device begins - sets up the button subscription and initializes available numbers
OnBegin<override>()<suspends>:void=
# Subscribe the GetRandomNumber function to the button interaction event
MyButton.InteractedWithEvent.Subscribe(GetRandomNumber)
# Initialize the array with all available numbers (1 to MaxNumber)
InitializeAvailableNumbers()
# Populates the AvailableNumbers array with values from 1 to MaxNumber
InitializeAvailableNumbers() : void =
# Create a temporary array to build the number list
var TempArray : []int = array{}
# Loop from 1 to MaxNumber and add each number to the array
for (i := 1..MaxNumber):
set TempArray += array{i}
# Assign the completed array to AvailableNumbers
set AvailableNumbers = TempArray
# Generates a random number from the available pool and removes it
GetRandomNumber(Agent:agent) : void =
# If all numbers have been chosen, reset the available numbers pool
if (AvailableNumbers.Length = 0):
InitializeAvailableNumbers()
# Generate a random index within the range of available numbers
var RandomIndex : int = GetRandomInt(0, AvailableNumbers.Length - 1)
# Get the number at the random index
if (ChosenNum := AvailableNumbers[RandomIndex]):
# Create a new array excluding the chosen number
var NewAvailable : []int = array{}
# Loop through all available numbers
for (i -> Num : AvailableNumbers):
# Add all numbers except the one at RandomIndex
if (i <> RandomIndex):
set NewAvailable += array{Num}
# Update AvailableNumbers with the new array (chosen number removed)
set AvailableNumbers = NewAvailable
# Process and display the chosen number
ActivateTrigger(ChosenNum)
# Displays the chosen number and remaining available numbers count / You can replace this function to fit your needs
ActivateTrigger(RandomNum:int) : void =
Print("----------------------Chosen Number: {RandomNum}")
Print("----------------Number of Available Numbers: {AvailableNumbers.Length}")
# Activate the corresponding trigger based on the chosen number
if(TriggerToActivate := Triggers[RandomNum - 1]):
TriggerToActivate.Trigger()
Off course you can change the way of triggering ithis device, I’m using a button for testing purposes only.
As an additional comment, your Triggers array’s length must be the same as your MaxNumber (8 triggers for your 8 MaxNumber in this case)
Hey @BRGJuanCruzMK, I am good! Thank you for reaching out, I implemented the verse and made it so a trigger device activates it on game start instead of the button. Unfortunately, I am still getting repeated numbers across rounds (Ex. Index 2 triggering on both Round 2 & 5). I have the verse enabled at game start, and I filled indexs 0-7 with the appropriate triggers. Does it repeat because I am testing it through a session on UEFN? Or is there something else I am missing entirely? Here is the code I have in case: using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }
using { /UnrealEngine.com/Temporary/Diagnostics }
using { /Verse.org/Random }
# Device that generates random numbers and triggers corresponding devices
random_number := class(creative_device):
\# Maximum number value (range will be 1 to MaxNumber)
@editable
var MaxNumber : int = 8
\# Array that stores numbers that haven't been chosen yet
var AvailableNumbers : \[\]int = array{}
\# Array of trigger devices corresponding to each number
@editable
var Triggers : \[\]trigger_device = array{}
\# Main trigger device that starts the random number generation
@editable
MainTrigger : trigger_device = trigger_device{}
\# Called when the device begins - sets up the trigger subscription and initializes available numbers
OnBegin<override>()<suspends>:void=
\# Subscribe the GetRandomNumber function to the trigger's TriggeredEvent
MainTrigger.TriggeredEvent.Subscribe(GetRandomNumber)
\# Initialize the array with all available numbers (1 to MaxNumber)
InitializeAvailableNumbers()
\# Populates the AvailableNumbers array with values from 1 to MaxNumber
InitializeAvailableNumbers() : void =
\# Create a temporary array to build the number list
var TempArray : \[\]int = array{}
\# Loop from 1 to MaxNumber and add each number to the array
for (i := 1..MaxNumber):
set TempArray += array{i}
\# Assign the completed array to AvailableNumbers
set AvailableNumbers = TempArray
\# Generates a random number from the available pool and removes it
GetRandomNumber(Agent:?agent) : void =
\# If all numbers have been chosen, reset the available numbers pool
if (AvailableNumbers.Length = 0):
InitializeAvailableNumbers()
\# Generate a random index within the range of available numbers
var RandomIndex : int = GetRandomInt(0, AvailableNumbers.Length - 1)
\# Get the number at the random index
if (ChosenNum := AvailableNumbers\[RandomIndex\]):
\# Create a new array excluding the chosen number
var NewAvailable : \[\]int = array{}
\# Loop through all available numbers
for (i -> Num : AvailableNumbers):
\# Add all numbers except the one at RandomIndex
if (i <> RandomIndex):
set NewAvailable += array{Num}
\# Update AvailableNumbers with the new array (chosen number removed)
set AvailableNumbers = NewAvailable
\# Process and display the chosen number
ActivateTrigger(ChosenNum)
\# Displays the chosen number and remaining available numbers count / You can replace this function to fit your needs
ActivateTrigger(RandomNum:int) : void =
Print("----------------------Chosen Number: {RandomNum}")
Print("----------------Number of Available Numbers: {AvailableNumbers.Length}")
\# Activate the corresponding trigger based on the chosen number
if(TriggerToActivate := Triggers\[RandomNum - 1\]):
TriggerToActivate.Trigger()
As you are using a Trigger device to get the random number, you are getting a “multiple subscriptions” issue!
As the “OnBegin” funcions is called every start of the round, you are subscribing multiple times to that trigger, so it is triggering multiple times and deplenishing your available numbers array too quick, giving you repeated numbers!
There is an extremely easy way to solve this issue! You need to add a IsSubscribed logic variable with its default value set on “false” and then check if it is false before subscribing to your trigger. It should look like this:
# Main trigger device that starts the random number generation
@editable
MainTrigger : trigger_device = trigger_device{}
var IsSubscribed : logic = false
# Called when the device begins - sets up the trigger subscription and initializes available numbers
OnBegin<override>()<suspends>:void=
# Subscribe the GetRandomNumber function to the trigger's TriggeredEvent
if(IsSubscribed = false):
MainTrigger.TriggeredEvent.Subscribe(GetRandomNumber)
set IsSubscribed = true
This way, you will only subscribe to the trigger once per match, not per round! That is because you are setting the IsSubscribed variable to “true” immediately after subscribing to your trigger, avoiding future subscriptions as the condition to subscribe is that variable being “false”
Hey @BRGJuanCruzMK, I am still running into the same issue of getting repeat numbers
I tried setting up a timer to go into the trigger to see if that was the issue and I also tried a round settings device to trigger that device on round 1, but both didn’t help. Here are some screenshots of the trigger device and the verse device if that helps, and the updated code I used. Just to clarify, I wanted to use this verse as like a random loot pool selector whenever a round begins, but of course that loot pool cannot be selected again until all of the other ones have been selected. So like trigger on round start → verse → random loot pool for the round, if that makes sense? And by like round nine the numbers are reset?
# Device that generates random numbers and triggers corresponding devices
random_number := class(creative_device):
\# Maximum number value (range will be 1 to MaxNumber)
@editable
var MaxNumber : int = 8
\# Array that stores numbers that haven't been chosen yet
var AvailableNumbers : \[\]int = array{}
\# Array of trigger devices corresponding to each number
@editable
var Triggers : \[\]trigger_device = array{}
\# Main trigger device that starts the random number generation
@editable
MainTrigger : trigger_device = trigger_device{}
\# Track if we've already subscribed to the trigger
var IsSubscribed : logic = false
\# Called when the device begins - sets up the trigger subscription and initializes available numbers
OnBegin<override>()<suspends>:void=
\# Only subscribe once per match
if (IsSubscribed = false):
MainTrigger.TriggeredEvent.Subscribe(GetRandomNumber)
set IsSubscribed = true
\# Initialize the array with all available numbers (1 to MaxNumber)
InitializeAvailableNumbers()
\# Populates the AvailableNumbers array with values from 1 to MaxNumber
InitializeAvailableNumbers() : void =
\# Create a temporary array to build the number list
var TempArray : \[\]int = array{}
\# Loop from 1 to MaxNumber and add each number to the array
for (i := 1..MaxNumber):
set TempArray += array{i}
\# Assign the completed array to AvailableNumbers
set AvailableNumbers = TempArray
\# Generates a random number from the available pool and removes it
GetRandomNumber(Agent:?agent) : void =
\# If all numbers have been chosen, reset the available numbers pool
if (AvailableNumbers.Length = 0):
InitializeAvailableNumbers()
\# Generate a random index within the range of available numbers
var RandomIndex : int = GetRandomInt(0, AvailableNumbers.Length - 1)
\# Get the number at the random index
if (ChosenNum := AvailableNumbers\[RandomIndex\]):
\# Create a new array excluding the chosen number
var NewAvailable : \[\]int = array{}
\# Loop through all available numbers
for (i -> Num : AvailableNumbers):
\# Add all numbers except the one at RandomIndex
if (i <> RandomIndex):
set NewAvailable += array{Num}
\# Update AvailableNumbers with the new array (chosen number removed)
set AvailableNumbers = NewAvailable
\# Process and display the chosen number
ActivateTrigger(ChosenNum)
\# Displays the chosen number and remaining available numbers count / You can replace this function to fit your needs
ActivateTrigger(RandomNum:int) : void =
Print("----------------------Chosen Number: {RandomNum}")
Print("----------------Number of Available Numbers: {AvailableNumbers.Length}")
\# Activate the corresponding trigger based on the chosen number
if(TriggerToActivate := Triggers\[RandomNum - 1\]):
TriggerToActivate.Trigger()
Ok, I was completely sure the variables had some kind of persistence between rounds… Spoiler alert: I was wrong…
So I had to change the approach and use real persistence for the variables. Most of the code remains the same, but now I’m storing the AvailableNumbers array and the IsInitialized logic variable into a persitable class instead as putting them directly inside our main class.
If you want to know more about persistence I highly recommend you to watch this list of videos.
But here is the new code:
using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }
using { /UnrealEngine.com/Temporary/Diagnostics }
using { /Verse.org/Random }
# ==================================================================================
# GLOBAL PERSISTENCE MAP - DO NOT MOVE OR RENAME
# ==================================================================================
# This weak_map stores NumberManager data for each player across rounds
# weak_map allows automatic cleanup when players leave
# NEVER MOVE THIS TO A DIFFERENT FILE OR FOLDER
# NEVER CHANGE THE VARIABLE NAME
var NumberManagerMap : weak_map(player, NumberManager) = map{}
# ==================================================================================
# NUMBER MANAGER CLASS - PERSISTABLE DATA STRUCTURE
# ==================================================================================
# NEVER MOVE THIS TO A DIFFERENT FILE OR FOLDER
# NEVER CHANGE THE VARIABLE NAMES OR THE CLASS NAME BELOW
# The <persistable> specifier makes this class's data survive round resets
# <final> prevents inheritance from this class
NumberManager := class<final><persistable>:
# Stores the list of numbers that haven't been selected yet
AvailableNumbers : []int = array{}
# Flag to track if this manager has been initialized for a player
IsInitialized : logic = false
# ==================================================================================
# CONSTRUCTOR FOR NUMBER MANAGER
# ==================================================================================
# Creates a new NumberManager instance with copied data
# <transacts> indicates this function modifies game state
MakeNumberManager<constructor>(NM:NumberManager)<transacts>:=NumberManager:
AvailableNumbers := NM.AvailableNumbers
IsInitialized := NM.IsInitialized
# ==================================================================================
# PLAYER EXTENSION FUNCTIONS - THESE SHOULD BE OUTSIDE ANY CLASS
# ==================================================================================
# Gets the NumberManager data associated with a specific player
# Returns existing data if found, otherwise creates new instance
(Player:player).GetNumberManager():NumberManager=
# Safety check: ensure player is still active in the game
if(not Player.IsActive[]): # Avoid crashes
return NumberManager{}
# Check if player already has a NumberManager in the map
if(NM:=NumberManagerMap[Player]):
return NM
else:
# Create a new NumberManager for this player
NewNumberManager:=NumberManager{}
# Attempt to store it in the map
if. set NumberManagerMap[Player] = NewNumberManager
else. Print("Error: Failed to get Number Manager data")
return NewNumberManager
# Updates the NumberManager data for a specific player
# This saves the current state to the persistent map
(Player:player).SetNumberManagerData(NewNumberManager:NumberManager):void=
# Safety check: ensure player is still active in the game
if(not Player.IsActive[]): # Avoid crashes
return
# Attempt to update the player's data in the map
if. set NumberManagerMap[Player] = NewNumberManager
else. Print("Error: Failed to set Number Manager data")
# ==================================================================================
# MAIN DEVICE CLASS - RANDOM NUMBER GENERATOR
# ==================================================================================
# Device that generates random numbers and triggers corresponding devices
random_number := class(creative_device):
# ================== EDITABLE PROPERTIES ==================
# Maximum value for random number generation (range: 1 to MaxNumber)
@editable
MaxNumber : int = 8
# ================== RUNTIME STATE VARIABLES ==================
# Current list of available numbers (synced from player's NumberManager)
var AvailableNumbers : []int = array{}
# Flag to track initialization status
var IsInitialized : logic = false
# Array of trigger devices, one for each possible number (1 to MaxNumber)
@editable
var Triggers : []trigger_device = array{}
# The trigger device that initiates random number generation
@editable
MainTrigger : trigger_device = trigger_device{}
# ================== INITIALIZATION ==================
# Called when the device is initialized (at match start and each round)
OnBegin<override>()<suspends>:void=
# Get all players currently in the game
Players := GetPlayspace().GetPlayers()
# Initialize data for each existing player
for (Player : Players):
InitializePlayerData(Player)
# Subscribe to event that fires when new players join mid-game
GetPlayspace().PlayerAddedEvent().Subscribe(OnPlayerAdded)
# Subscribe to the main trigger's activation event
MainTrigger.TriggeredEvent.Subscribe(GetRandomNumber)
# ================== PLAYER DATA INITIALIZATION ==================
# Sets up or retrieves existing data for a player
InitializePlayerData(Player : player):void =
# Get the player's persistent NumberManager data
NM:=Player.GetNumberManager()
# Copy data from persistent storage to local variables
set AvailableNumbers = NM.AvailableNumbers
set IsInitialized = NM.IsInitialized
# If this is the first time, populate the available numbers
if(IsInitialized = false):
InitializeAvailableNumbers(AvailableNumbers)
set IsInitialized = true
# ================== NEW PLAYER HANDLER ==================
# Called when a new player joins the game mid-match
OnPlayerAdded(Player : player):void =
# Get the new player's NumberManager (creates new if doesn't exist)
NM:=Player.GetNumberManager()
# Create a new NumberManager with current game state
NewNM:=NumberManager:
AvailableNumbers := AvailableNumbers
IsInitialized := IsInitialized
MakeNumberManager<constructor>(NM)
# Save this data to the player's persistent storage
Player.SetNumberManagerData(NewNM)
# ================== NUMBER POOL INITIALIZATION ==================
# Fills the available numbers array with values from 1 to MaxNumber
InitializeAvailableNumbers(AuxAvailableNumbers:[]int) : void =
# Create a temporary array to build the number list
var TempArray : []int = array{}
# Loop from 1 to MaxNumber and add each number to the array
for (i := 1..MaxNumber):
set TempArray += array{i}
# Assign the completed array to AvailableNumbers
set AvailableNumbers = TempArray
# ================== POST-GENERATION SYNC ==================
# Syncs the current state to all players' persistent data after number selection
OnRandomNumberGenerated():void=
# Get all active players
Players := GetPlayspace().GetPlayers()
# Update persistent data for each player
for (Player : Players):
# Get player's current NumberManager
NM:=Player.GetNumberManager()
# Create updated NumberManager with new state
NewNM:=NumberManager:
AvailableNumbers := AvailableNumbers
IsInitialized := IsInitialized
MakeNumberManager<constructor>(NM)
# Save updated state to persistent storage
Player.SetNumberManagerData(NewNM)
# ================== RANDOM NUMBER GENERATION ==================
# Generates and removes a random number from the available pool
GetRandomNumber(Agent:?agent) : void =
# If all numbers have been used, refill the pool
if (AvailableNumbers.Length = 0):
InitializeAvailableNumbers()
# Generate a random index within the available numbers array
var RandomIndex : int = GetRandomInt(0, AvailableNumbers.Length - 1)
# Retrieve the number at the random index
if (ChosenNum := AvailableNumbers[RandomIndex]):
# Create a new array without the chosen number
var NewAvailable : []int = array{}
# Loop through all available numbers
for (i -> Num : AvailableNumbers):
# Add all numbers except the one at RandomIndex
if (i <> RandomIndex):
set NewAvailable += array{Num}
# Update AvailableNumbers with the chosen number removed
set AvailableNumbers = NewAvailable
# Trigger the corresponding device and display info
ActivateTrigger(ChosenNum)
# Sync the updated state to all players' persistent data
OnRandomNumberGenerated()
# ================== TRIGGER ACTIVATION ==================
# Activates the trigger device corresponding to the chosen number
# You can replace this function to fit your needs
ActivateTrigger(RandomNum:int) : void =
# Print the chosen number to the console
Print("----------------------Chosen Number: {RandomNum}")
# Print how many numbers are still available
Print("----------------Number of Available Numbers: {AvailableNumbers.Length}")
# Activate the trigger device at index (RandomNum - 1)
# Arrays are 0-indexed but our numbers start at 1
if(TriggerToActivate := Triggers[RandomNum - 1]):
TriggerToActivate.Trigger()
As you can see, I added a weak_map, 2 new classes, and 2 new functions that are outside any class. All those things are explained in the playlist I shared.
In addition, I added new funcions inside our main class to handle those changes and a lot of comments and separators so you can read an understand the code.
With this changes, you should be able to get random numbers without any repetition until you run out of options!
Just tested it and it worked! Honestly extremely excited that it finally worked. I watched the videos you sent and they were pretty informative! I noticed that persistence data is per player only, and there isn’t such thing as global persistence? Would that mess things up in a multiplayer game? Also, if I were to change the amount of numbers in the array in the future, would I remove every instance of “8” and replace it with like 7 or 9 for example? Last thing, if I were to use this with another set of triggers and trigger that triggers the device, could I use the same code? Or should I create a new verse file and copy and paste the code?