This guide was originally written for scriptinghelpers. The original can be found here.
Forgot to post this one to the devforum so here ya go!
Most of the posts that I make tend to be very mathematical and aimed at experienced developers. Today’s post is instead going to be aimed at those of you who are newer to the platform, and are looking for a project to practice your skills on. We will be going over how we can make our very own game of Simon says!
Before you start reading this post I recommend you brush up on a few things if you aren’t familiar with them already.
Setup
The only thing I’ll be going into this with is the model I made for Simon. You are by no means forced to use the exact model hierarchy that I use, but it’s important that you have an idea of where my script is in relation to what.
-- defined at the top of the script
local model = script.Parent
local centerClick = model:WaitForChild("Center"):WaitForChild("ClickDetector")
-- looped = true on this sound
local sound = model:WaitForChild("BasePart"):WaitForChild("Sound")
-- store the colours as an array
local colours = model:WaitForChild("Colours"):GetChildren()
local numColours = #colours
The rules of the game
Edit: I actually learned these are not the rules of Simon says after I posted this on scripting helpers . It doesn’t matter much though from the standpoint of a learning tool so I haven’t bothered to change it.
For those of you who have never played Simon says the rules are as follows:
- Round n starts
- A pattern of n-length is showed to the player
- The player must repeat back the pattern in order
- If the player completes the pattern, they move onto round n+1
- If the player fails the pattern, they restart at round 1
Thus, functions we will need are as follows:
- Something to show we selected a colour
- Something to generate a pattern
- Something to play the pattern
- Something to show we won the game
- Something to show we lost the game
Selecting a colour
To start off we’ll work on our function that shows a colour has been selected. It will be used for when the player selects a colour and when Simon is playing back a pattern or showing that we won or lost.
We’ll define the function with three parameters. The first will be an index which represents which colour part we are using in the colours array. The second will be the time that the colour part lights up for, and the third will be the playback speed of the sound which we will use to control pitch.
function playColour(index, t, speed)
-- access the part from the array
local part = colours[index]
-- set the material to neon and play the sound
part.Material = Enum.Material.Neon
sound.PlaybackSpeed = speed
sound:Play()
-- wait t seconds
wait(t)
-- set the part back to plastic and stop the sound
part.Material = Enum.Material.Plastic
sound:Stop()
end
-- a quick test
while (true) do
for i = 1, #colours do
playColour(i, 0.2, i)
wait()
end
end
Generating a pattern
Our next step will be to write a function that generates a pattern of n-length. We can create this pattern with the Random
object and its method :NextInteger(min, max)
which gives a random integer between min and max inclusive.
function genPattern(length, seed)
local pattern = {}
local r = Random.new(seed)
for i = 1, length do
pattern[i] = r:NextInteger(1, #colours)
end
return pattern
end
We can note that for each element in the pattern we pick a random number between 1
and #colours
which means our pattern will be made up of indexes that correspond to a part in the colours array.
In other words, if our colour array and pattern looked like this for example:
colours = {blue, red, green, yellow}
pattern = {1, 3, 3, 2, 4, 1, 1}
-- then the pattern corresponds to
{blue, green, green, red, yellow, blue, blue}
Playing the pattern
Now that we have a pattern, playing it is as simple as iterating through it and playing each colour that corresponds to the index.
function playPattern(pattern)
for i = 1, #pattern do
playColour(pattern[i], 0.2, pattern[i])
wait()
end
end
-- a simple test
playPattern(genPattern(20, tick()))
Starting a round
We can now start to write the function that will start a round of Simon says.
-- define these near the top
local roundNum = 0
local currentPattern = {}
function startRound()
roundNum = roundNum + 1
local pattern = genPattern(roundNum, tick())
currentPattern = pattern
playPattern(pattern)
end
This covers steps one and two of the initial rules process we defined at the beginning. All that’s left to do is have the player repeat the pattern back.
Player input
For the player to repeat the pattern they need some way to interact with Simon. They way we will do this is with ClickDetectors
and the MouseClick
event. Our first step will be to create a click detector under each colour part in the colours array and connect a function that will fire when it’s clicked.
-- define this near the top
local clickDetectors = {}
function onClicked(player, index)
-- We'll write the code for this later
end
function initClickDetectors()
for i = 1, numColours do
local part = colours[i]
local cdetect = Instance.new("ClickDetector")
-- connect the function that defines what happens when the part is clicked
cdetect.MouseClick:Connect(function(player) onClicked(player, i) end)
cdetect.Parent = part
-- dictionary where the part is the key and the click detector is the value
clickDetectors[part] = cdetect
end
end
Sometimes throughout the game of Simon says we don’t want the player to be able to click any buttons. Thus, we’ll create a function that can enable or disable player input by setting the parent of the click detectors.
function setInputEnabled(bool)
-- iterate over the key and value
for part, cdetect in pairs(clickDetectors) do
if (bool) then
cdetect.Parent = part
else
cdetect.Parent = nil
end
end
end
Now that we have this function we’ll likely want to go back and adjust out pattern playing function so that the player can’t click anything when the pattern is playing.
function playPattern(pattern)
setInputEnabled(false)
for i = 1, #pattern do
playColour(pattern[i], 0.2, pattern[i])
wait()
end
setInputEnabled(true)
end
Verifying the pattern
In order to complete step three, we will have to verify that when a player clicks a colour part it was the part that was next in the pattern. As such we will need a counter that tells us how far along the player is into the pattern. We will simply reset this back to zero every time a round begins.
-- define near the top
local inputIndex = 0
function startRound()
inputIndex = 0 -- reset every round
roundNum = roundNum + 1
local pattern = genPattern(roundNum, tick())
currentPattern = pattern
playPattern(pattern)
end
function onClicked(player, index)
-- play the colour that was just selected
setInputEnabled(false)
playColour(index, 0.2, index)
setInputEnabled(true)
-- comparing our input to the next index in the pattern
inputIndex = inputIndex + 1
if (index ~= currentPattern[inputIndex]) then
-- what the player selected was not what was next in the pattern
-- lose the round!
elseif (inputIndex == #currentPattern) then
-- from our previous check we know the player selected correctly
-- we also know that the last input made was the total length of the pattern
-- win the round!
end
end
Win or lose the round
Our last two steps require we show the player they either won or lost and move them to the next round. Both these functions are quite easy to write by this point as we have the rest of the game logic in place.
-- define near the top
local timeBetweenRounds = 0.5
function winRound()
setInputEnabled(false)
-- play an animation that tells us we won!
for i = 1, 3 do
for j = 1, numColours do
playColour(j, 0.05, j)
end
end
wait(timeBetweenRounds)
setInputEnabled(true)
-- next round!
startRound()
end
function loseRound()
setInputEnabled(false)
-- play an animations that tells us we lost
for i = 1, 3 do
for j = numColours, 1, -1 do
playColour(j, 0.05, j/numColours)
end
end
wait(timeBetweenRounds)
setInputEnabled(true)
-- reset back to first round
roundNum = 0
startRound()
end
-- finally, plugging these functions into our onClicked function
function onClicked(player, index)
-- play the colour that was just selected
setInputEnabled(false)
playColour(index, 0.2, index)
setInputEnabled(true)
-- comparing our input to the next index in the pattern
inputIndex = inputIndex + 1
if (index ~= currentPattern[inputIndex]) then
loseRound()
elseif (inputIndex == #currentPattern) then
winRound()
end
end
Initiating the game
Congratulations! You now have all the functions needed to have your simon says working! The absolute last thing we need to do is make sure the game starts running. We’ll do this by having the player click the button in the middle.
initClickDetectors()
setInputEnabled(false)
-- wait for the player to click the center button
centerClick.MouseClick:Wait()
setInputEnabled(true)
-- start Simon says
startRound()
You can find an un-copylocked version of the game here:
Conclusion
So that’s it for now folks. Hope you enjoyed the post! As always, I’m looking for feedback, especially for this type of article as it’s quite different than what I normally post.
Until next time!