Hey Kyle, this is nice, thanks for posting!
Here are a couple of things:
- You should be able to use
and
instead of&&
(same withor
instead of||
) in your conditional checks. It reads better and I believe using the&&
form will result in an error soon. - In your
GetRandom
function, it seems you don’t need to saveRandomNum
torandomNumber
. This would remove the need for therandomNumber
variable altogether. - Also in
GetRandom
, you may want to useelse if
for your conditions, since only one will be true. Another good option is acase
expression:
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.
TotalA
instead 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 int
s.
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.