How to create a simple, open ended Voting System Gui

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)
Screenshot (4154)

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!!!

31 Likes

Great tutorial!

This will surely help beginners and those who need that, good job you did there.

I also suggest maybe adding the number / percentage of votes if you want to add that feature or let them add it by themselves.

1 Like

That’s a great idea to show to the player’s what’s the highest tally. But i don’t think i will add it to the tutorial mainly because it’s pretty simple to make. All you would need to do is whenever the VotingEvent gets received just get the total votes of all the choices and perform the formula for percentage (TotalVotesPerChoice/TotalOverallVotes) * 100

1 Like

Correct. No problems, your model - you choose what to include and what not, good job!

I feel like this would be super good for a politics game :laughing:

One suggestion, can you explain the script like, choiceData.TotalVoting += 1 – explanation
Nice tutorial overall

It can be used in any game that have mechanics like gamemode/map voting. I might add comments explaining parts of the script in the final scripts, thanks for the idea

how would you make it were the choices were the players?