Event driven approach with Lua and Roblox (2)
In the previous post, we looked at how to make a simple lock puzzle in Roblox and Lua. I highlighted some pitfalls where the code was not scalable; in this post, we will look at how to refactor the system to make it more flexible and extensible.
Context
In the context of the lock system, scaling the system up mainly involves adding more unlocker pads to it. There are also issues of duplicate code due to each unlocker pad requiring its own script. Let us look at the relevant parts of the code which causes these issues.
In the first chunk, we have to manually find all 4 unlocker pads' scripts to access the event that they will fire. Then we have to manually set the event handlers in response. How can we avoid this?
Eliminating Code Duplication
The first step is to eliminate the code duplication, which in a way is also the source of the scalability problem. This means that instead of every unlocker having a script to fire off the event, we only want to use one script. First, I organized all the unlocker pads into a folder:
Structure of the lock system v2.
Notice how the Unlockers no longer have any script inside them, and they are all handled by a single script UnlockerPadModule. Inside UnlockerPadModule, we will iterate through the folder, and then create events for each pad in the folder UnlockPads. Since we are not identifying any of the unlockers by their name, how will event handler script get these events? To do so, we will keep a list of the events we created for the pads which can get acquired through a getter function.
In the first part of the script, we get a list of all unlocker pads in our folder, then define a function called setupPad(part, padID) which takes in a pad as well as its assigned ID, and creates an event which will fire when that specific part (pad) is touched. In the last part of the script, we call setupPad() for every unlocker pad in our list of pads. Using a loop to set up the pads' events means that we can now create as many unlocker pads as we want in the folder, and we no longer need to rewrite or duplicate the script.
Sending Information via Events
You may have noticed that we passed in the padID into our setUpPad() function, and when we fire the event, we put it in as an argument. This is to facilitate the event handler's job; unlike the previous version, we are now no longer manually defining events and writing hard-coded event handlers for them.
If that is the case, how do we know what is the number assigned to the pad? The padID ensures that the event handler knows which is the number assigned to the particular pad that has fired off the event.
In the last for loop of the UnlockerModule script, we set up event handlers for all the events that we generated in UnlockerPadModule. Notice how padID can be received as a parameter into the event handler function, which we can use to concatenate onto the user input.
Secret Sequence
In our original implementation, our secret sequence length was fixed to 4. However, this is no longer the case if we are intending to scale the system up to encompass more than 4 pads.
Thus, this size value must be retrieved from a getter function, getNoOfPads() in UnlockerPadModule. This results in some minor changes to our generateSecret() and authenticate() functions.
Debounce
Typically, debounce is used within a script as a programming pattern when we are trying to write code for firing a single event. However, notice how our events are now written in a loop for each unlocker pad, and there is no local debounce variable for each of them.
In our particular case, we also want to enforce the debounce such that it does not allow our players to fire the event more than once. How can we do this for each unlocker pad when we only have 1 for loop?
The perfect solution here was to use Instance Attributes given by the Roblox API. It allows us to set any kind of local variable inside a Roblox part, thus I stored the debounce variable inside each of the pads during the creation of the event. In a sense, we have transformed a programming pattern which was originally used in a script tied to a specific part into an instance attribute/property. This is necessary since we no longer have any script that is tied to any pads.
Closing Thoughts
And just like that, we have successfully refactored our lock puzzle system into one that is more scalable. Here's an example with 8 pads:
Wow, it's getting difficult to remember the sequence!
Now, all we need to do is to simply add more pads into the UnlockPads folder, and the system should still function as we expect. We can see how refactoring the code to be scalable makes our lives a lot easier by making the system more flexible, so we can change it according to our needs.
There are some other fun ideas that we could use to extend this little puzzle; for example, what if we have more than 1 player? Each pad could light up in a different colour and the player assigned to it has to press that pad in order to authenticate. But that's for another time!
Comments