Hello, this is my first tutorial so i would love to hear feedback about it and if i made any spelling errors or better method’s to use. Now that that’s out of the way, let’s begin the tutorial!!
Step 1: Setting up
Place a Remote event named VotingEvent
inside ReplicatedStorage.
Next, Create a ScreenGui in StarterGui named VotingGui
. You are free to customize your gui and how you display the choices however you please. But for the sake of this tutorial i will keep the design very minimal.
Now, create a container gui named Container
for where we will display the buttons the player clicks to vote on things.
Image of what it looks like in explorer:
Next, insert a UiListLayout into the container, so that we don’t need to manually organize the TextButtons. Here are the configurations i used(You don’t need to directly copy them)
Lastly, a template TextButton that we can copy from and display our choices on. Create a TextButton named TemplateVoteButton
inside the container gui and configure it’s design until you find something you like. When your done, place it in ReplicatedStorage for quick access.
Now that we’re done setting up our gui and remote event, let’s move to step 2.
Step 2: The Server
Place a ModuleScript in ServerScriptService. Name it VotingSystem
Next, let’s prepare the necessary variables. It should look something like this now
local RepStorage = game:GetService("ReplicatedStorage")
local VotingEvent = RepStorage.VotingEvent
local VotingSystem = {}
return VotingSystem
Let’s add our main function, VotingSystem:SendVote()
local RepStorage = game:GetService("ReplicatedStorage")
local VotingEvent = RepStorage.VotingEvent
local VotingSystem = {}
function VotingSystem:SendVote(Choices: {any})
Votes = {}
VotedPlayers = {}
for _, Choice in ipairs(Choices) do
table.insert(Votes, {Choice = Choice, TotalVoted = 0})
end
VotingEvent:FireAllClients(Choices, true)
end
return VotingSystem
Let’s see what we added,
Votes
: Is a table that will contain a dictionary with our tally and it’s corresponding choice.
VotedPlayers
: Pretty self explanatory, a table that will contain the players that have already voted, so we can avoid double votes.
The for loop will add the dictionary to the Votes
table
And we perform :FireAllClients()
to send the players our choices and a bool value determining the start and end of a vote, i will explain more about that later.
Now that we have organized our votes with their tally’s, let’s have an event to receive who voted which inside the SendVote
function.
function VotingSystem:SendVote(Choices: {any})
local Votes = {}
local VotedPlayers = {}
for _, Choice in ipairs(Choices) do
table.insert(Votes, {Choice = Choice, TotalVoted = 0})
end
VotingEvent:FireAllClients(Choices, true)
local votingCon = nil
votingCon = VotingEvent.OnServerEvent:Connect(function(player, votedChoice)
if table.find(Choices, votedChoice) and not table.find(VotedPlayers, player) then
table.insert(VotedPlayers, player)
for i, choiceData in ipairs(Votes) do
if choiceData.Choice == votedChoice then
choiceData.TotalVoted += 1
break
end
end
end
end)
end
Here we create a temporary connection called votingCon
in which we receive the player and what they voted for.
Add an if statement to check if what they voted for is legitimate. And if it is, add the player to the VotedPlayers
table and add their vote to what choice they picked.
Now that we have a way to send and receive votes, we need to determine the highest voted choice, in which we create a new function GetHighestVote
. Our script should look like this now:
local RepStorage = game:GetService("ReplicatedStorage")
local VotingEvent = RepStorage.VotingEvent
function GetHighestVote(Votes)
local highest = 0
local highestVote
for i, choiceData in ipairs(Votes) do
if choiceData.TotalVoted > highest then
highest = choiceData.TotalVoted
highestVote = choiceData.Choice
end
end
if highest == 0 or highestVote == nil then
local randomChoice = Votes[math.random(1, #Votes)]
highestVote = randomChoice.Choice
end
return highestVote
end
local VotingSystem = {}
function VotingSystem:SendVote(Choices: {any})
local Votes = {}
local VotedPlayers = {}
for _, Choice in ipairs(Choices) do
table.insert(Votes, {Choice = Choice, TotalVoted = 0})
end
VotingEvent:FireAllClients(Choices, true)
local votingCon = nil
votingCon = VotingEvent.OnServerEvent:Connect(function(player, votedChoice)
if table.find(Choices, votedChoice) and not table.find(VotedPlayers, player) then
table.insert(VotedPlayers, player)
for i, choiceData in ipairs(Votes) do
if choiceData.Choice == votedChoice then
choiceData.TotalVoted += 1
break
end
end
end
end)
task.wait(10)
votingCon:Disconnect()
VotingEvent:FireAllClients(nil, false)
return GetHighestVote(Votes)
end
return VotingSystem
Here we wait 10 seconds which is our voting duration, change this if you want, and after the time had passed we Disconnect our votingCon
connection to invalidate any more incoming votes and to prevent a memory leak. After that we use :FireAllClients()
once more but to close the voting period on their screen.
Our GetHighestVote
function will return the Highest vote in the choices between the Votes
table and will return a random one if no one in the server voted for anything(quite unlikely to happen but we don’t want to break the system even if ever does).
Well that was a long discussion. Let’s finally get on to the last part of this tutorial!!!
Step 3: The Clients
Place a localscript under the VotingGui screengui and write down the necessary variables to start.
local RepStorage = game:GetService("ReplicatedStorage")
local VotingEvent = RepStorage:WaitForChild("VotingEvent")
local votingTemplate = RepStorage:WaitForChild("TemplateVoteButton")
local Container = script.Parent.Container
VotingEvent.OnClientEvent:Connect(function(choices, status: boolean)
end)
Let’s start scripting the logic
Inside the OnClientEvent scope, add an if statement to determine if we should show the voting gui to start the vote or to hide the gui and delete the buttons when a voting period had ended.
VotingEvent.OnClientEvent:Connect(function(choices, status: boolean)
if status then
Container:TweenPosition(UDim2.fromScale(0.5,1), Enum.EasingDirection.Out,Enum.EasingStyle.Sine)
else
for i, child in ipairs(Container:GetChildren()) do
if not child:IsA("UIListLayout") then
child:Destroy()
end
end
Container:TweenPosition(UDim2.fromScale(0.5,1.4), Enum.EasingDirection.Out,Enum.EasingStyle.Sine)
end
end)
Lastly, create a function that will clone the buttons from the template in ReplicatedStorage and create connections for each of them
function SetUpButtons(choices: {any})
for i, Choice in ipairs(choices) do
local NewButton = votingTemplate:Clone()
NewButton.Text = Choice
NewButton.Parent = Container
NewButton.Activated:Connect(function()
VotingEvent:FireServer(Choice)
end)
end
end
VotingEvent.OnClientEvent:Connect(function(choices, status: boolean)
if status then
Container:TweenPosition(UDim2.fromScale(0.5,1), Enum.EasingDirection.Out,Enum.EasingStyle.Sine)
SetUpButtons(choices)
else
for i, child in ipairs(Container:GetChildren()) do
if not child:IsA("UIListLayout") then
child:Destroy()
end
end
Container:TweenPosition(UDim2.fromScale(0.5,1.4), Enum.EasingDirection.Out,Enum.EasingStyle.Sine)
end
end)
Now that when the voting period starts, the client can clone as many buttons as needed and when it ends, we hide the gui and clear all the children except the UiListLayout.
Now that the voting system has been finished, let’s create a simple script in ServerScriptService to require and start the voting:
local VotingSystem = require(script.Parent.VotingSystem)
local Choices = {"Hello world", "banana", "Turkey leg", "Funny joke"}
task.wait(10)
local Win = VotingSystem:SendVote(Choices)
print(Win, "won the vote!!!!!!")
Final Product:
Final Scripts
Server Module:
local RepStorage = game:GetService("ReplicatedStorage")
local VotingEvent = RepStorage.VotingEvent
function GetHighestVote(Votes)
local highest = 0
local highestVote
for i, choiceData in ipairs(Votes) do
if choiceData.TotalVoted > highest then
highest = choiceData.TotalVoted
highestVote = choiceData.Choice
end
end
if highest == 0 or highestVote == nil then
local randomChoice = Votes[math.random(1, #Votes)]
highestVote = randomChoice.Choice
end
return highestVote
end
local VotingSystem = {}
function VotingSystem:SendVote(Choices: {any})
local Votes = {}
local VotedPlayers = {}
for _, Choice in ipairs(Choices) do
table.insert(Votes, {Choice = Choice, TotalVoted = 0})
end
VotingEvent:FireAllClients(Choices, true)
local votingCon = nil
votingCon = VotingEvent.OnServerEvent:Connect(function(player, votedChoice)
if table.find(Choices, votedChoice) and not table.find(VotedPlayers, player) then
table.insert(VotedPlayers, player)
for i, choiceData in ipairs(Votes) do
if choiceData.Choice == votedChoice then
choiceData.TotalVoted += 1
break
end
end
end
end)
task.wait(10)
votingCon:Disconnect()
VotingEvent:FireAllClients(nil, false)
return GetHighestVote(Votes)
end
return VotingSystem
Client gui handle
local RepStorage = game:GetService("ReplicatedStorage")
local VotingEvent = RepStorage:WaitForChild("VotingEvent")
local votingTemplate = RepStorage:WaitForChild("TemplateVoteButton")
local Container = script.Parent.Container
function SetUpButtons(choices: {any})
for i, Choice in ipairs(choices) do
local NewButton = votingTemplate:Clone()
NewButton.Text = Choice
NewButton.Parent = Container
NewButton.Activated:Connect(function()
VotingEvent:FireServer(Choice)
end)
end
end
VotingEvent.OnClientEvent:Connect(function(choices, status: boolean)
if status then
Container:TweenPosition(UDim2.fromScale(0.5,1), Enum.EasingDirection.Out,Enum.EasingStyle.Sine)
SetUpButtons(choices)
else
for i, child in ipairs(Container:GetChildren()) do
if not child:IsA("UIListLayout") then
child:Destroy()
end
end
Container:TweenPosition(UDim2.fromScale(0.5,1.4), Enum.EasingDirection.Out,Enum.EasingStyle.Sine)
end
end)
The benefit for the way this voting system is made is that you basically don’t have a limit to how many picks you can make. And that you can use this for map voting/gamemode voting/ vote kicking system’s/etc
Thanks for reading the tutorial, hoped you learnt something, and have a nice day!!!