Hey Kyle, this is nice, thanks for posting!
Here are a couple of things:
- You should be able to use
andinstead of&&(same withorinstead of||) in your conditional checks. It reads better and I believe using the&&form will result in an error soon. - In your
GetRandomfunction, it seems you don’t need to saveRandomNumtorandomNumber. This would remove the need for therandomNumbervariable altogether. - Also in
GetRandom, you may want to useelse iffor your conditions, since only one will be true. Another good option is acaseexpression:
case(RandomNum):
1 =>
set winA = true
TeleA.Teleport(Player)
Logger.Print("=== Winner A ===")
2 =>
set winB = true
TeleB.Teleport(Player)
Logger.Print("=== Winner Bb ===")
3 =>
set winC = true
TeleC.Teleport(Player)
Logger.Print("=== Winner C ===")
_ => # Default case is needed to avoid failure
- We recommend naming variables in PascalCase (e.g.
TotalAinstead oftotalA). We’re still working on a code-style guide to help the community align with best practices and common patterns.
More Voting Options using Arrays
Lastly, if you wanted to extend this to work with a customizable amount of buttons, you could convert the script to use arrays of device references.
Let’s see how you could do that.
Note: we have a gameplay example tutorial being published next week that explains a similar process in more detail, so I’ll go over this in less detail here.
First, you need an array (a “list”) of button references that you can modify from the editor.
Add these lines to your device class to expose an array of button devices:
@editable
Buttons:[]button_device = array{}
Note: you should do the same for other devices like your A, B, C lights
You’ll also need to store the total votes for each button. You can add another variable array of ints.
var TotalVotes:[]int = array{}
We’ll initialize this in a bit so the votes start from 0
A general Voting function
I see you’re familiar with functions already.
Let’s put these two pieces together by making a function that increases the votes for the button at a specific index. This function takes in an index and increases the TotalVotes at that index.
VotedButtonIndex(ButtonIndex:int):void=
if:
# Total will be the result of the set expression,
# so we can print the total for this button.
Total := set TotalVotes[ButtonIndex] += 1
then:
Logger.Print("Button{ButtonIndex} total votes: {Total}")
How do we pass in the correct ButtonIndex? We must find a way to subscribe an event handler to the InteractedWithEvent that also has knowledge about which button from the Buttons array called the handler.
That means we’ll have a mapping from Buttons[X] to TotalVotes[X].
I’m going to show two ways to achieve the same result, so you’re exposed to different approaches you could use.
Option A: Using a Custom Event Handler
As said earlier, we need a way to pass along which button (the index X of Buttons[X]) triggered the InteractedWithEvent.
Let’s create a class that encapsulates all this info.
I’m calling it event_handler here, but you’re free to name it however you’d like.
Add this outside the vote_device class.
event_handler := class:
ButtonIndex:int
# We need a reference to a vote_device to call functions on it.
VoteDevice:vote_device
HandlerFunction(Player:player):void=
VoteDevice.VotedButtonIndex(ButtonIndex)
As you can see, when we create an object of this class, we’ll save in it a ButtonIndex, and a reference to a vote_device (the one placed in the level in this case).
The HandlerFunction is what we’ll Subscribe to each Button’s InteractedWithEvent, and that’s why its signature expects a Player:player passed in.
HandlerFunction just calls the VotedButtonIndex function of the VoteDevice we’ve defined earlier, passing along the ButtonIndex.
Almost there.
Creating a handler object for each button
Now, we just need to go through the Buttons and, for each one of them, InteractedWithEvent.Subscribe the HandlerFunction of an object of the custom event_handler class we’ve created.
We should do this when the game starts:
OnBegin<override>()<suspends>:void=
for (X -> Button:Buttons): # X -> gives us the index of Button in the Buttons array.
set TotalVotes += array{0} # Add a new element to the votes array initialized to 0.
# Create a new handler that'll hold this button's data.
Handler := event_handler:
ButtonIndex := X
# Self is a special keyword that references this device (the enclosing class) so the event handler can call functions on it.
VoteDevice := Self
Button.InteractedWithEvent.Subscribe(Handler.HandlerFunction)
That’s it! Each button will have its own custom event handler, to which you can add any data you need.
Option B: Using Concurrency
Note: I’m unsure if it’s available in the build you’re using yet. If not, it should come soon!
This can be a somewhat simpler approach, but it could take some time to wrap your head around it while you’re learning concurrency and thinking asynchronously (it sure took some time for me!).
We’ve recently exposed the ability to Await() device events like InteractedWithEvent. Awaiting means pausing the currently executing <suspends> context (like a function) until what we’re awaiting is ready.
In this case, we’ll Await() until the InteractedWithEvent is fired.
The idea is to spawn a function, passing in a Button and its ButtonIndex, and inside the function, Await() for the InteractedWithEvent to fire.
After that happens, we call the VotedButtonIndex created earlier.
We do this in a loop to keep awaiting InteractedWithEvent.
Effectively, what spawn does is create another execution context that “splits” off and runs concurrently to the function it’s called from.
Note: we must use spawn here for now, but in the future, we recommend using branch instead for performance reasons. branch can’t be used inside for and loop at the moment.
The function we’ll spawn looks like:
HandleVoteButton(Button:button_device, ButtonIndex:int)<suspends>:void=
loop:
Button.InteractedWithEvent.Await() # Execution stops here until the button is pressed
Logger.Print("Button {ButtonIndex} pressed")
VotedButtonIndex(ButtonIndex)
Notice the <suspends> effect: saying this is an async context. It means we can use Await inside of it.
Spawning HandleVoteButton
We can now use the HandleVoteButton instead of the custom event_handler, similar to what we did earlier.
We spawn a HandleVoteButton for each Button in the Buttons array:
OnBegin<override>()<suspends>:void=
for (X -> Button:Buttons):
set TotalVotes += array{0} # Add a new element to the votes array initialized to 0
spawn{ HandleVoteButton(Button, X) }
Conclusion
Phew, we made it!
This is just part of what you’ll need if you want to implement a customizable voting system that can be extended to any number of options.
I’ll leave it to you to implement the missing parts if you’d like:
- Lights and teleporters arrays
- VoteResults
This system can actually make it easier to work with draws as you can iterate over the TotalVotes array to find either:
- A single winner
- Multiple draws
As a hint, I suggest looking at the for documentation page and imagine how you could use the for expression result and filtering.
I hope this was informative, please feel free to reach out if you have any questions or doubts ![]()
Relevant code
event_handler := class:
ButtonIndex:int
# We need a reference to a vote_device to call functions on it.
VoteDevice:vote_device
HandlerFunction(Player:player):void=
VoteDevice.VotedButtonIndex(ButtonIndex)
...
...
# Inside vote_device
@editable
Buttons:[]button_device = array{}
var TotalVotes:[]int = array{}
OnBegin<override>()<suspends>:void=
for (X -> Button:Buttons):
set TotalVotes += array{0} # Add a new element to the votes array initialized to 0
# Create a new handler that'll hold this button's data.
Handler := event_handler:
ButtonIndex := X
# Self is a reference to this device so the event handler can call functions on it.
VoteDevice := Self
Button.InteractedWithEvent.Subscribe(Handler.HandlerFunction)
# spawn{ HandleVoteButton(Button, X)}
# PlayerRef := StartVote.InteractedWithEvent.Await() # You could also use this ;)
HandleVoteButton(Button:button_device, ButtonIndex:int)<suspends>:void=
loop:
Button.InteractedWithEvent.Await()
Logger.Print("Button {ButtonIndex} pressed")
VotedButtonIndex(ButtonIndex)
VotedButtonIndex(ButtonIndex:int):void=
if:
# Total will be the result of the set expression,
# so we can print the total for this button
Total := set TotalVotes[ButtonIndex] += 1
then:
Logger.Print("Button{ButtonIndex} total votes: {Total}")
P.S. There are improvements coming that’ll make it easier to match the number of elements between all the arrays you need (Lights, Buttons, Teleporters); for now, you’ll need to match them in-editor.
P.P.S In the future, I’ll post a countdown timer class that can serve as a simple replacement for a timer device. All Verse, plus UI.