Creating Your Own Simon Says!
Posted on November 8, 2018 by EgoMoose
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
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!
Commentary
Leave a Comment