In this chapter, you will create your very own simulator game. You will use many of the systems that you learned about in previous chapters. Besides this, you will learn how to combine all of these individual systems into one complete game. This way, you will learn, for example, how your GUIs can display data that is stored in your data stores by creating safe RemoteEvents or RemoteFunctions.
The following topics will be covered in this chapter:
By the end of this chapter, you will have made your own simulator game based on several requirements that your employer could provide you with. You will know how to create proper data stores that will save all the data required for your game. In addition, you will learn how to create the server scripts for all the systems in your game. Besides this, you will know which RemoteEvents and RemoteFunctions to implement to ensure that all the user actions can be performed. Finally, you will also learn how to use these RemoteEvents and RemoteFunctions to make your GUIs work.
To start programming with Luau, you need access to a device with internet access. This can either be a Windows or a Mac device.
You need to download the following software:
This chapter’s code examples can be found on GitHub at https://github.com/PacktPublishing/Mastering-Roblox-Coding.
The CiA video for this chapter can be found at https://bit.ly/3OD2wsp.
Because we learned a lot about scripting in Luau throughout the previous chapters, we will create an entire game together in this chapter. Therefore, this chapter is divided into multiple sections. Each section creates a part of our game. However, before we can start creating this, we need to understand the game we will make.
In this chapter, we will create a simulator game. Simulators are a popular genre on Roblox. In essence, players collect some sort of currency in their backpacks. Then, they can sell this currency for in-game money. When they do this, they can purchase upgrades, pets, or even rebirth their character to restart the game with a money boost.
Now that we know what simulators are, let us look at the game we will make ourselves. Because this book is about programming, we will provide a great map that was uniquely made for this book. This map was made by Cole Tucker (1Coai):
Figure 10.1 – Our simulator map
You can download this map and the GUIs from the GitHub page for this book: https://github.com/PacktPublishing/Mastering-Roblox-Coding/tree/main/Games.
Permission
Because we will make a game in this chapter, you might want to release it once you have finished creating it. This is perfectly fine. You are allowed to use the map and the code that was provided in the example answer for your own game.
Now that we know what a simulator game is and in which map we will create our game, let us look at the game’s specifications. In our game, we will have orbs. Orbs are the circles on the map that players must collect. Players can convert these orbs into money at a selling point. With this money, they can upgrade the speed of their character. When players upgrade their speed, they will walk faster and therefore be able to collect more orbs in a shorter amount of time. Besides this, players can purchase an orb multiplier. When players upgrade their orb multiplier, they get more orbs when collecting orbs.
Besides upgrades, players must also have the ability to rebirth. Rebirthing in a simulator resets all the progress that they have made. This includes all the orbs, money, and upgrade progress. When players rebirth, they get more money from the selling point in their “second” life. Rebirths should stack. This means that players can rebirth multiple times. The more they rebirth, the higher the multiplier at the selling point becomes.
Because programmers that make a game usually get a list of requirements that need to be implemented into a game, we will follow one as well. Here is the list of requirements for our game:
General requirements:
The previous General requirements are usually provided by the person that hires you. Since they generally do not know much about programming-related specifications, they are usually not provided. Regardless, we want to create high-quality scripts. Therefore, these will be our additional programming-related requirements:
Data store requirements:
GUI requirements:
Security requirements:
Monetization requirements:
All these requirements may seem overwhelming. However, if you followed all the best practices throughout the book, none of the technical requirements would surprise you. The only thing we need to pay much attention to is the general requirements. After all, this is what we must make. So, to assist you in getting a better view of what our final product will be, here is the published Roblox game that we will make:
https://www.roblox.com/games/9917292298/Simulator
We now know what sort of game we are expected to make. Like all the exercises in this book, you can try and make this game without following this chapter’s steps. However, remember that this game is far from easy to implement. In addition, we highly encourage you to improve upon and give your personal touch to the game. Finally, remember that this is a complete game, and it will likely take multiple days for you to finish it. Therefore, do not get frustrated if you cannot do multiple sections of this chapter daily.
Throughout the following sections, we will look at our game’s aspects. We will look at what we are expected to make and implement it. Because we have explained all the systems, such as datastores and GUIs, in other chapters, we will not explain how they work but rather how we can perfect them for our game. We will start by creating the data stores.
In this section, we will look at how to design our data stores. In Chapter 8, Building Data Stores, we made a base DataManager in the Creating a DataManager section. We will use this base version for our game. However, there are still a few things we have to change. First, we must figure out which data we will save in our data stores.
When we look at the requirements for our game, we can conclude that we need to implement a key for the following items:
For money and orbs, we will obviously use number as the data type. However, what about the others? You might think of storing the multiplier value for the upgrades and rebirths. This could be something like 1.1 for a 10% bonus. While this might be your initial thought, this is a bad practice. Rather than storing the effect, you should store the number of rebirths, for example, that the player has done. This way, you must constantly convert the number of rebirths into a multiplier. As a result, you can change the multiplier for rebirths or upgrades without having to change anything in the data stores.
Just imagine that we would store the multiplier for the speed upgrade in the data stores. The default walking speed for players is 16. Maybe we initially thought it would be a good idea to double the walking speed every time that the player upgraded. This would mean that the new walking speed is 32 when the player upgrades once. But then we figure out that by the fifth upgrade, the walking speed is way too high. Unfortunately, we have already shipped the game and have many players playing. How would we fix this? Do we load their data and divide each player’s walking speed by half? But what if they upgraded with the new multiplier rate?
As you can imagine, this becomes a complete mess. Therefore, we should never store multiplier values in our data stores. Instead, we store the number of upgrades. When the player has upgraded two times, we store the number 2. Then, when we load the data, we might multiply this number by 16. This means that the walking speed is 32 again. But when we want to change our multiplier and change it to 8, we still get the number 2 from the data store and multiply it with the new multiplier, which is 8. This results in a new walking speed of 16 for each player, whether they played before our change or not.
Knowing this, we can conclude that for each key we originally thought of, we must save a number. If we change the DEFAULT_PLAYER_DATA constant in the DataManager, it will look like this:
local DEFAULT_PLAYER_DATA = {
-- Currency data
["money"] = 0,
["orbs"] = 0,
-- Upgrade data
["speed_upgrade"] = 0,
["orb_multiplier"] = 0,
-- Rebirth data
["rebirths"] = 0
}
Now that we know which keys and data types to use, let us continue by looking at which functions we need in our DataManager module.
In the base version of our DataManager, we already have a few functions. These are the :DataLoaded() function, the :Get() function, the :Set() function, and the :Increment() function. While this covers everything that we might need, there is one thing that we might need to take a look at. Most of these functions may throw an error. Therefore, we should wrap our calls in a pcall(). While this is not a big deal, we get a lot of duplicate code when we use the :Get() function in a pcall(). This is because when an error occurs, we might want to set some sort of default value, like this:
local money
local suc, err = pcall(function()
DataManager:Get(player, "money")
end)
if not suc then
warn(err)
money = 0 -- Default value
end
The preceding code snippet is all the code that is required when we use the :Get() function. As you can imagine, this results in a lot of duplicate code fairly quickly. Therefore, we should introduce a new function into our DataManager. This is the :GetWithDefault() function. The :GetWithDefault() function will use the :Get() function and will basically do what we showed in the preceding code snippet.
When we use the :GetWithDefault() function, we try to load the data using the :Get() function. However, when an error occurs, we return the default value. This could be a parameter. Let us take a look at the following code snippet:
function DataManager:GetWithDefault(player, key, default)
-- Declaring variable
local data
-- Default parameter
if default == nil then
default = 0
end
-- Querying data store
local suc, err = pcall(function()
data = DataManager:Get(player, key)
end)
if not suc then
warn(err)
return default
end
-- Returning data
return data
end
We removed many possible duplicate codes by implementing this function into our DataManager but you might be wondering why we do not just replace the :Get() function to have a default. This is because errors are not always a bad thing.
Imagine we are writing code that relies on the :Get() function having an actual result. When the :Get() function fails, we might want to stop changing the data stores completely. When using the :GetWithDefault() function, we will always continue as if an error would never occur.
We have now looked at the functions in the DataManager and updated them so they meet the required criteria for our game. We prevented possible duplicate code by implementing a new :GetWithDefault() function. If you are making the game while reading, please do not forget to implement this function in your own code. In the next section, we will analyze whether the events in our DataManager suffice.
The base version of the DataManager has just one event, the .PlayerAdded event. This event gets fired when the player data gets successfully loaded. We need to use the DataManager.PlayerAdded event in favor of the Players.PlayerAdded event. After all, we cannot let players play our game without their data being loaded. However, are there more events that we might need?
When we look at the requirements for our game, we see that we must make leader stats for our game. The DataManager is the module that knows when this data changes. Therefore, we need to do something in the DataManager so that the leader stats update. Now, we have two methods of doing this:
When we include everything related to leader stats in our DataManager, the module starts doing two things. First, it handles everything related to data stores and leader stats. Generally, module scripts should only have one purpose. This way, the name of the module can contain everything it does. Other developers should instantly know what to expect from this module without looking at the code. This is no longer the case if we decide to include leader stats. Therefore, this is not the best option.
The other option is to create an additional event. This event fires when data for a player is updated. This means that we need to fire our event in both the :Set() function and the :Increment() function. This also means that we would fire this event when keys are not necessarily in the leader stats. This is fine. After all, if we were to prevent this, the DataManager would know which keys are used in the leader stats. This would once again mean that the DataManager knows the leader stats. We do not want this, as we want the DataManager to be a standalone module.
Let us look at what we need to change in our DataManager to add our new event:
local DataLoadedEvent = Instance.new("BindableEvent")
local DataUpdatedEvent = Instance.new("BindableEvent")
function setupEvents()
DataManager["PlayerAdded"] = DataLoadedEvent.Event
DataManager["DataUpdated"] = DataUpdatedEvent.Event
end
function DataManager:Set(player, key, value)
-- ... Function code ...
-- Firing data updated
DataUpdatedEvent:Fire(player, key, value)
end
function DataManager:Increment(player, key, value)
-- ... Function code ...
-- Firing data updated
DataUpdatedEvent:Fire(player, key, cachedData[key])
end
Please remember that the preceding code snippet is just the updated code in our base DataManager module. Do not forget to include this in your own DataManager.
In the preceding code snippet, we created a new DataUpdatedEvent BindableEvent. In the setupEvents() function, we ensured that other scripts can listen to this new event. Then, in both the :Set() function and the :Increment() function, we fired the event with the following three arguments:
Now, we have set up all the events that might be useful when making the rest of our game. Throughout the previous sections, we looked at which keys our data store needs and whether we implemented all the functions and events in the DataManager. This means we have now finished with everything related to our data stores. In the following sections, we will build the rest of our game. This will rely heavily on our data stores. After all, we want their progress to be saved. Luckily, we did a standalone module. This means we only have to use the functions and events of this module without having to worry about any of its internal complexities.
Now that our data stores are finished, we can implement the scripts that run on the server. This means we will start thinking of RemoteEvents and RemoteFunctions that the client will need to use. Then, we will implement these.
Implementing this will be a lot of work without seeing any visible results. When making your own game from scratch, you might make systems feature-based. This means that you would make both the client and server sides at the same time, instead of making the entire server side first. However, to improve the readability of this chapter, we have chosen to start with the server side and finish this in its entirety. Then, once our server side is finished, we have finished RemoteEvents and RemoteFunctions that we can call from the client that will instantly work.
In the following sections, we will analyze what has to be programmed together. After that, we will provide steps similar to the way we have done with the exercises in the previous chapters, which will help you script what we just analyzed.
First, we will implement leader stats into our game. According to the requirements that were specified in the Introduction to the game section, we need leader stats for the following data keys:
Since all the data types for these keys are numbers, we must create NumberValues inside the leaderstats model. We will create this leaderstats model when the player joins the game. Please keep in mind that you must listen to the DataManager.PlayerAdded event instead of the Players.PlayerAdded event. This is because there is no point in getting leader stats when the data that has to be displayed has not loaded yet.
Of course, we must also listen to the DataManager.DataUpdated event. In the DataManager events section, we made this event so that we could update our leader stats whenever data was updated.
Now that we know this, let us try to implement this script. Follow these steps:
When you implement the previous steps, your leader stats should be created and updated accordingly. Then, of course, we need to test whether this works. Because the rest of our game is not completely done yet, we cannot play the game to see whether it works. Therefore, we have to execute a small test. Create a new script in ServerScriptService that includes the following code snippet:
local ServerScriptService = game:GetService("ServerScriptService")
local DataManager = require(PATH_TO_DATAMANAGER)
DataManager.PlayerAdded:Connect(function(player)
DataManager:Set(player, "money", 1)
DataManager:Increment(player, "orbs", 1)
DataManager:Set(player, "rebirths", 10)
task.wait(5)
DataManager:Increment(player, "money", 100)
DataManager:Set(player, "orbs", 5)
DataManager:Increment(player, "rebirths", 1)
end)
When you run the preceding code snippet, you should see changes in the leader stats. If this does not happen, look at possible errors in the Developer Console frame or the Output frame and try to solve them. It is highly recommended that you try to figure out what goes wrong. If, for whatever reason, you are unable to understand what is going on, look at the example answer, which can be found on the GitHub page for this chapter. The link for the GitHub page can be found in the Technical requirements section.
Congratulations, you just made your first script for your very own game!
Next, we will implement the monetization scripts for our game. Usually, this is something you do last. However, we will implement this first to prevent us from having to implement the monetization aspects of our game while working on different systems.
There are not many complicated things that we must implement into our game to meet the set of requirements regarding monetization. As for game passes, we must create one that doubles the money when selling orbs. This is the only game pass we have to implement. You are free to add more if you’d like. A few example game passes could be Instant Sell, Double Orbs, Double Speed, or anything else that comes to mind. While implementing these is an outstanding practice, it is recommended that you wait until we have finished the game before you implement them.
Besides game passes, we must also implement a few developer products that allow players to purchase in-game money. To create a bit of variation, we will create three developer products. These developer products will give 500, 1,000, or 2,500 coins.
Base scripts
In Chapter 9, Monetizing Your Game, we created a base script for game passes and developer products. We will use these scripts. This way, we do not have to remake something we already made.
We now know which game passes and developer products we have to implement. Follow these steps:
Tip for 4: In the exercise of Chapter 9, Monetizing Your Game, we did something similar.
Tip for 7: According to the requirements, there are no multipliers through game passes or premium benefits that would reward players with more money. Therefore, you can just increment their money with the same amount as the developer product specifies.
This is everything we need to do to implement monetization features into our game. Obviously, future scripts will still have to look at whether players own this game pass, but this is something for later.
We need to perform two tests to check that our script works. First, to test whether the developer products work, we can simply create a prompt for each developer product to check whether the money increases. Since our leader stats work, we can simply see whether these increase.
Then, to test game passes, we must do something else. If you created the game pass yourself, you should have this in your own inventory. This means you own the game pass. You can check whether you own the game pass by clicking on the game pass from the Store section of your game. Then, you should see a message that says Item Owned. You can see this in Figure 10.2:
Figure 10.2 – Item Owned
If you own this game pass, use the Properties frame in Roblox Studio to verify that the 2xMoney attribute on your Player instance is checked, as seen in Figure 10.3:
Figure 10.3 – The 2xMoney attribute
When both tests work, we can continue to the next section. If either does not work, try to look for errors in the Developer Console frame or the Output frame. If, for whatever reason, you are unable to understand what is going on, look at the example answer, which can be found on the GitHub page for this chapter. The link for the GitHub page can be found in the Technical requirements section.
Next, we will create the most complex system of the entire game: character upgrades. Even though it is the most complex system, do not worry; we will walk through it step by step. According to the requirements specified in the Introduction to the game section, we need to make two upgrades for characters. Players must be able to upgrade their speed and orb multiplier. In our data stores, we save an upgrade number. We have to convert these upgrade numbers into a multiplier. Because there is no requirement that there should be an infinite number of upgrades, we can just decide ourselves how many upgrades we want to implement.
First, we will create a dictionary that stores the price and multiplier for each upgrade. This dictionary will store all the upgrade data for each individual upgrade. Please keep in mind that this is not the player’s data. The player’s data will refer to one of these indexes. This is what a dictionary that contains all the upgrade data looks like:
local upgrades = {
["speed_upgrade"] = {
[0] = { price = 0, multiplier = 1 }, -- Default
-- ...
[10] = { price = 25_000, multiplier = 5 }
},
["orb_multiplier"] = {
[0] = { price = 0, multiplier = 1 }, -- Default
-- ...
[10] = { price = 75_000, multiplier = 15 },
}
}
In the preceding code snippet, we can see the upgrade data for both the speed upgrade and the orb multiplier upgrade. The data that is on index 0 is the default data. This means that when the player has never upgraded, their multiplier will be 1.
When a player wants to upgrade, we look at the upgrade number that is one higher than the current number. When we want to check how much it costs to upgrade, we add 1 to the current upgrade number, for example, 0 + 1 equals 1. Then, we check if this upgrade number exists. If it does not, the player cannot upgrade any further. However, if this new index does exist, the player can upgrade. When the upgrade does exist, we look at the price key for this upgrade. This way, we know how much it costs to upgrade to this new level.
Now that we know how to implement the data structure for the upgrades, let us implement it into our game. Follow these steps:
Now that we have created our RemoteEvents and RemoteFunction, let us start implementing them. First, we will create our RemoteFunction. This RemoteFunction should return specific data. The client will use this data to update the information that is displayed in the GUI. Because of this, it is recommended that we take a look at the Upgrades GUI to see what information this GUI needs:
Figure 10.4 – Upgrades GUI
GUIs
The GUIs for this game were provided in the same file that the map is in. The GUIs are stored in the StarterGui service. Feel free to style them to your liking.
In Figure 10.4, we see a lot of data that needs to be displayed in the GUI. Here is a list of data that is required if we want to update our GUI accurately:
We now know what the RemoteFunction could return. When we put this into a dictionary, it looks like this:
local data = {
["Speed Upgrade"] = {
upgrade_name = "speed_upgrade",
current_progress = 0,
max_progress = 0,
upgrade_cost = 0,
multiplier = 0
}
}
In the preceding code snippet, we see that this is a data dictionary containing all the data for the speed upgrade. The upgrade_name key contains the speed_upgrade value. This is because this is what the key in our DataManager is called. By doing this, we can tell the client which upgrade key to call when the player tries to upgrade without converting Speed Upgrade into speed_upgrade.
You might have noticed that the previous code snippet contains a multiplier key that was not required for our GUI. We added this because we could also use this dictionary on the server. After all, when players have to upgrade, all this data is also required.
Now that we know this, let us implement the RemoteFunction.
Tip for 4: First, get the current progress using the DataManager. Once you have this, figure out what the multiplier for this progress number is. Then, find out how much it costs to upgrade. Finally, calculate how many indexes there are in the table for the current upgrade. This will be your maximum progress number.
Now that we have implemented our GetUpgradeData RemoteFunction, let us continue with the Upgrade RemoteEvent. This is the RemoteEvent that the client will use to upgrade. However, before we can implement this, there is something we need to take a look at.
When a player upgrades, the information displayed in the GUI should change. Now, there are two options. We could either call our RemoteFunction directly after the RemoteEvent is fired, or create a new RemoteEvent that provides the exact same data as the RemoteFunction. However, the server fires this RemoteEvent to the client this time. This way, the server decides when the information in the GUI is updated.
We have decided to use the additional RemoteEvent. This RemoteEvent is called UpgradeDataUpdated. Follow the next step to implement our Upgrade RemoteEvent and our UpgradeDataUpdated RemoteEvent.
Tip for 5: Use the function made in the previous step. This function contains a lot of the required data.
We have now implemented all the previously made RemoteEvents and RemoteFunction. If you got stuck somewhere in these steps, please look at the example answer you can find on the GitHub page for this chapter. This is the most complex system in the entire game. Therefore, it is understandable if you run into some issues.
In the following sections, we might need to use some of the multiplier data we made in this script. For instance, when claiming orbs, the amount of orbs the player gets is based on the orb multiplier upgrade that we made here. To transfer this multiplier between scripts, we use BindableFunctions. We learned about BindableFunctions in Chapter 3, Event-Based Programming.
Let’s implement our BindableFunction.
Tip for 8: To get the multiplier, use the function that we made two steps ago.
We have now finished implementing our upgrade system. Once again, this is the most complex system in our game. If you did not manage to implement this system, please take a look at the example answer, which can be found on the GitHub page for this chapter.
In the next section, we will implement the system that lets us claim the orbs that spawn inside the map.
Inside the map, there are many different circles. These are orbs. You can see an image of these orbs in Figure 10.5:
Figure 10.5 – Orbs on the map
All of these orbs are in a folder called Orbs. This folder is a direct descendant of the Workspace service.
When a player touches one of these orbs, the system should check whether this orb is claimable. An orb is considered claimable when players have not touched it for at least five seconds. When an orb is not claimable, it should be invisible. The challenge when implementing this is to ensure that each orb has its own five-second cooldown.
Luckily, we learned about attributes. We can create an attribute on these orbs that specifies the last time they were claimed. This way, we can read the attribute, check whether it was not touched for at least five seconds, reward the player, and reset the countdown.
When an orb is claimed, the orbs key in the DataManager should be incremented by 1. This is our base value. However, we must also check the orb multiplier upgrade. This upgrade multiplier must be multiplied against the base value 1.
Now that we know this, let us implement our system. Follow these steps:
We have now finished our orbs system. You can test whether it works by playing the game. Then, walk into the orbs on the map and watch them change transparency. Besides this, ensure the number of orbs in the leader stats increases. If this does not happen, check the Developer Console frame or the Output frame for possible errors. If, for whatever reason, you are unable to understand what is going on, look at the example answer, which can be found on the GitHub page for this chapter. The link for the GitHub page can be found in the Technical requirements section.
We can move on to the next section when you have tested your code. Next, we will create our rebirths system.
While it is weird that we implement rebirths before selling orbs, we will need our rebirthing multiplier before implementing our selling system. We will implement this first to prevent us from having to change the orbs selling system after implementing the rebirthing system.
First, we will start by creating the required RemoteEvents and RemoteFunction for this system:
According to the requirements from the Introduction to the game section, the Rebirthing GUI should open when a player touches a BasePart in the RebirthParts folder in the Workspace service. We can implement this on both the server and the client. However, since our game must be compatible with StreamingEnabled, it is easier to implement it on the server. Therefore, when a player touches one of these BasePart, we should fire our OpenFrame RemoteEvent. That way, we can open the Rebirthing GUI based on this trigger.
Now that we know this, let us implement the code related to opening the Rebirthing GUI.
Next, we will implement the functions related to the GetRebirthData RemoteFunction. To figure out which data the client needs, let us take a look at the Rebirthing GUI:
Figure 10.6 – The Rebirthing GUI
In Figure 10.6, we need to display the price of the rebirth. The price of rebirthing should be displayed at x. However, because we can also use this function inside our own script, we might want to add a few keys to our dictionary. Instead of only including the price of rebirth, we also want to know how many times the player has been rebirthed already. In addition, it might be helpful to know the current rebirth multiplier. The dictionary could look like this:
local data = {
current_rebirth = 0,
rebirth_price = 0,
rebirth_multiplier = 1
}
Even though the current GUIs do not need this data, it might be informative to let the players know this anyway. Feel free to change the GUI so that it displays all of the data that is provided in the preceding code snippet.
Besides this, the rebirth multiplier and price should increase every time the player decides to rebirth, according to the requirements in the Introduction to the game section.
Here are example formulae that you could use to do this. Feel free to use different formulae:
Both formulae increase exponentially. This means the multiplier and price increase significantly every time the player decides to rebirth. As previously mentioned, feel free to write a different formula.
Now that we know what this RemoteFunction should do, let us implement it.
Tip for 6: First, get the current_rebirth value using the DataManager. Based on this, use the previously mentioned formulae to calculate the rest.
Next, we will make it so players can actually rebirth. All player data except for the rebirth multiplier should be reset when rebirthing, according to the requirements in the Introduction to the game section. Let us implement this.
Tip for 8: The function made two steps ago contains the rebirth price.
We have now implemented all the RemoteEvents and RemoteFunctions. However, as previously mentioned, we had to create our rebirths system before implementing the system allowing us to sell the orbs. This was because the selling orbs system requires the rebirths multiplier. For the selling orbs system to get this multiplier, we must implement a BindableFunction.
Tip for 12: The function made in Step 6 contains the multiplier for the current rebirth.
We have now finished our rebirthing system. This was a complex system. If your code does not work instantly, try to fix possible errors that show up in the Developer Console frame or the Output frame. If, for whatever reason, you are unable to understand what is going on, look at the example answer, which can be found on the GitHub page for this chapter.
You can move on to the next section when you have tested your code. Next, we will create a system that allows players to sell their orbs.
Finally, we will implement our selling orbs system. When a player touches one of the BasePart inside the SellParts folder, the number of orbs should automatically be sold. Each orb is worth one dollar. Please remember that this has to be multiplied against the rebirth multiplier that we made in the previous section. In addition, there is a 2xMoney game pass, which doubles this even more. But, before we worry about this, let us implement the setup() function. Follow these steps:
We know that a player wants to sell their orbs. Now, we need to convert all of the orbs into money. First, we need to get the number of orbs. Back in the DataManager functions section, we decided to create a :GetWithDefault() function, instead of updating the :Get() function. This was because sometimes we do not want to continue our script when an error occurs. When selling orbs, we need to get the number of orbs the player has. When the DataManager fails to get this amount of orbs, we do not want to continue our script. After all, we cannot convert something if we get nothing.
Now that we know this, let us implement the system that implements the conversion of orbs into money.
We have now implemented the system that sells your orbs and converts them into money. While this is done, the requirements in the Introduction to the game section contain one more thing that we need to do. The requirements specify that players must be able to teleport to the nearest sell location.
Since our GUIs have a Sell button that allows players to do this, we must implement a RemoteEvent to teleport players in this direction. Since our game uses StreamingEnabled, we must call the :RequestStreamAroundAsync() function on the player before we can teleport them there. We learned about this function in Chapter 5, Optimizing Your Game.
Now that we know this, let us implement this into our game.
The following is an example code for the previous step:
local distance = ( sellPoint.Position - player.Character.PrimaryPart.Position ).Magnitude
Tip for 9: To convert a Vector3 data type into a CFrame, use the following code:
CFrame.new(Vector3.new(0, 0, 0))
We have now finished our selling system. You can test whether the selling system works by walking up to a selling point. You should see the number of orbs and money changes in the leader stats. If this does not happen, check the Developer Console frame or the Output frame for possible errors. If, for whatever reason, you are unable to understand what is going on, look at the example answer, which can be found on the GitHub page for this chapter. The link for the GitHub page can be found in the Technical requirements section.
We have now finished all of our server scripts. Throughout the past sections, we made our leader stats, created scripts to reward game passes and developer products, and made it so that players can upgrade their character, rebirth, and claim and sell orbs. Most of these scripts use RemoteEvents and RemoteFunctions. We will need to use these when programming our GUIs. Now, let us continue programming our game’s GUIs.
Now that we have finished all of the RemoteEvents and RemoteFunctions, we can start programming our GUIs. The primary thing that we need to do is use the RemoteEvents and RemoteFunctions at the correct times. This way, our GUIs will be updated correctly, and buttons will do what they are supposed to do.
Before we start implementing each GUI, we need to create a system that allows each GUI to open and close. In the Exercise 6.1 – Creating a Shop GUI section of Chapter 6, Creating User Interfaces for All Devices, we made this system already. At the time, there were two example answers for this exercise: a default and an advanced version. For our game, we will use the advanced version. Since it is not required to rescript these modules completely, we can copy and paste them into our new GUIs.
Tip
It is recommended that you read through all of the code for these modules. If you cannot figure out what specific modules do or how they work, redo Exercise 6.1.
When you copy the UIHandler module and the FrameHandler module from Exercise 6.1, your StarterGui should look like this:
Figure 10.7 – Implemented UIHandler and FrameHandler modules
As shown in Figure 10.7, you should not paste the FrameHandler module in the SideButtons ScreenGui.
Now that we have implemented the modules that will help us change between frames, let us start programming each separate GUI. First, we will start with the SideButtons GUI.
We will start by creating simple GUIs and slowly move on to the more difficult ones. Therefore, we will start with the SideButtons GUI. The SideButtons GUI are the buttons on the left side of your screen:
Figure 10.8 – Side buttons
Here is a list of what should happen when each button is pressed:
When we need to open a GUI, we use the UIHandler module, which is a child of the StarterGui service. In this module, we can use the :ToggleFrame() function. The UIHandler module will ensure that frames do not overlap, as required by the requirements in the Introduction to the game section.
Follow these steps to program the SideButtons GUI:
We have now implemented all the buttons that we previously discussed. However, there is one more thing we should do. In the Implementing rebirths section, we created an OpenFrame RemoteEvent. When this event on this RemoteEvent gets fired, we should open the Rebirthing GUI. Let us implement this into our current script.
We have now implemented our SideButtons GUI. First, check whether all the buttons work and that they do what they should do. If it does not work instantly, try to fix possible errors that show up in the Developer Console frame or the Output frame. If, for whatever reason, you are unable to understand what is going on, look at the example answer, which can be found on the GitHub page for this chapter.
We can move on to the next section when you have tested that the buttons work. Next, we will program the Rebirthing GUI.
Next, we will move to a more complex GUI, the Rebirthing GUI. As displayed in Figure 10.9, we need to display the price for each rebirth in the GUI. Back in the Implementing rebirths section, we made a GetRebirthData RemoteFunction, which we could invoke to get a dictionary containing the rebirth price.
Figure 10.9 – The Rebirthing GUI
Besides this, we also made a RebirthDataUpdated RemoteEvent. When this RemoteEvent gets fired, the price in the GUI should be updated. Let us start by implementing the code related to updating the information displayed in the GUI. Follow these steps:
Tip for 3: If you forgot what data is inside the rebirthData parameter, look at the dictionary we made inside the GetRebirthData RemoteFunction.
The data in our GUI should now be updated. Try walking to the rebirth building and wait until the Rebirthing GUI shows up. When it does, ensure that the correct price is displayed.
Next, we need to make our RebirthButton work. To do this, we must listen to the .MouseButton1Click event on the RebirthButton. When this happens, fire the Rebirth RemoteEvent. Besides being able to rebirth, we must also program the Close button. When a player presses this button, the frame should close. To do this, use the :CloseFrame() function on the FrameHandler module that you inserted into the Rebirthing GUI.
Now that we know this, let us start programming the GUI.
Closing frames
All the GUIs we will program for this game will have a close button. To prevent repetition, we will not include a step to close each frame in the upcoming sections. However, this does not mean that you should not implement it.
We have now programmed our second GUI. In the next section, we will program our DeveloperProducts GUI.
In the previous sections, the complexity of the GUIs slowly built up. In this section, we will do something we have never done before. We will generate GUI Elements.
Inside the Storage folder, there is a Template TextButton. Each developer product gets its own template. Inside of each template, we must change the Title, the Description, and the ImageLabel, so it displays accurate information for the developer product.
Follow these steps to program our DeveloperProducts GUI:
Tip: Add rbxassetid:// in front of the IconImageAssetId key.
Figure 10.10 – The Currency GUI
We now prompt and generate buttons for the developer products. However, there is one more button on our GUI. At the bottom of the GUI, as shown in Figure 10.10, there is a button allowing users to purchase game passes. This button will close the DeveloperProducts GUI and open the GamePasses GUI. To change to the GamePasses frame, use the :ToggleFrame() function in the UIHandler module inside the StarterGui. Follow these instructions:
We have now implemented the DeveloperProducts GUI. First, open the DeveloperProducts GUI by pressing the Currency button on the left side of your screen. Once it is open, press any developer products for your game. When you purchase a developer product, your money should increase. You can check the leader stats to see whether this happens.
If it does not work, try to fix possible errors that show up in the Developer Console frame or the Output frame. If, for whatever reason, you are unable to understand what is going on, look at the example answer, which can be found on the GitHub page for this chapter.
We can move on to the next section when you have tested that the developer products work. Next, we will program our Game Passes GUI.
Creating the GamePasses GUI will be very similar to how we created our DeveloperProducts GUI. However, there are a few slight changes. Follow these steps:
Tip: Add rbxassetid:// in front of the IconImageAssetId key.
We have now implemented the GamePasses GUI. Open the GamePasses GUI. Once it is open, press any created game pass buttons and confirm that a prompt appears. If it does not work, try to fix possible errors that show up in the Developer Console frame or the Output frame. If, for whatever reason, you are unable to understand what is going on, look at the example answer, which can be found on the GitHub page for this chapter.
You can move on to the next section when you have tested that the game pass buttons work. Next, we will program the Upgrade GUI.
Finally, we must also program the Upgrade GUI. This will be the most complex GUI to script but do not worry, we will do this together. In the Implementing character upgrades section, we created a few RemoteEvents and RemoteFunctions. We will use these to make our GUI. But, first, we need to generate a TextButton for each upgrade, similar to how we did so in the previous chapters.
Before we can start generating TextButtons, we first need to figure out which upgrades are in our game. To get this information, we must invoke the GetUpgradeData RemoteFunction. If you forgot how this RemoteFunction works, view the code we made for the RemoteFunction.
Follow these steps to generate the TextButtons for each upgrade:
Your loop code could look like this:
for upgradeName, upgradeInfo in pairs(upgradeData) do
So far, we have generated TextButtons that display the title of each upgrade. Besides this, we made it so that players are able to upgrade once they press the TextButton. However, the progress bar and Progress TextLabels do not receive updated information yet. Why not? Are we not supposed to show the latest data in the GUI instantly? Yes, we are. However, there is a RemoteEvent that will do this as well. This is the UpgradeDataUpdated event. We will create a new function that both the setup() function and the UpgradeDataUpdated RemoteEvent will use to prevent duplicate code.
Follow these next steps to make our update function.
Tip for 3E: To calculate the correct size, use the following code snippet:
UDim2.new(upgradeInfo.current_progress / upgradeInfo.max_progress, 0, 1, 0)
Now, we also update the TextButtons to display the most accurate information. To test whether it works, open the Upgrading GUI. If all the information is generated and updated correctly, test whether this is also the case when you try to upgrade. Make sure that all the information updates instantly. If it does not work, try to fix possible errors that show up in the Developer Console frame or the Output frame. Once again, this is the most complex GUI in the game. If you did not manage to implement this system, please take a look at the example answer, which can be found on the GitHub page for this chapter.
We have now programmed all the GUIs for the entire game. In the next section, we will take a look at the next steps you must take before releasing your game.
You have now finished your very own game. Congratulations! But, before you can release games, you must test them. How well you test your game can determine how many bugs end up in the final product. It is highly recommended that you play your game with a few friends and listen to their feedback. Once you have fixed all known bugs and implemented the feedback from your friends, you can release your game.
You might think your work is done now. However, this could not be further from the truth. The actual journey of your game starts now. Hopefully, many players will play and enjoy your game. At some point, these players will have finished their game. However, to prevent them from ever finishing your game, you must update your game with new content. Luckily, there are many things that you can include in your simulator.
Throughout the previous sections, we implemented many systems into our game, which we had previously learned about throughout the entire book. However, not everything we learned was used in this chapter but that does not mean that we cannot still implement it.
Here is a list of things that you could update your game with:
Besides these features, there is still so much to explore in programming and the best part is that it’s constantly evolving. Cool stuff is constantly added for you to explore and improve your games with.
Throughout this chapter, you learned how to make your very own simulator game. Together, we made a fully functioning game that uses data stores, server scripts, GUIs, developer products, and game passes based on a set of requirements.
Once we knew the requirements for our game, we started creating the data stores for our game. We saw how to analyze which keys we need to store in the data stores. In addition, we saw why it is a bad practice to store multipliers inside the data stores. Instead, we should save the amount of, for example, rebirths the players did. Then, we can convert the number of rebirths into a multiplier.
After implementing our data stores, we started creating the server scripts for our game. First, we analyzed what information should be displayed in the GUIs and which actions players can perform. Based on this, we created RemoteEvents and RemoteFunctions. These RemoteEvents and RemoteFunctions perform certain actions, such as converting orbs into money. While implementing these, we looked at server checks that we needed to implement to secure our game.
Because we implemented the RemoteEvents and RemoteFunctions before creating the GUIs, we could use these and directly display the correct information or perform the right actions. Furthermore, while creating these GUIs, we learned how to generate information that was displayed. This way, we have GUIs that automatically update to display the latest information without having to change anything for our GUIs.
Finally, we also looked at possible updates for our game. We have seen that we can still implement many cool features for our players to enjoy.
This wraps up the Mastering Roblox Coding book. In Chapter 1, Getting Up To Speed with Roblox and Luau Basics, we learned the first things to know about Roblox and Roblox Luau. Remember how we started with learning what data types are and how to do simple math operations with numbers? Look where you are now! We just finished our very first simulator game. You should be incredibly proud of yourself for achieving this. This proves that anything is possible if you invest enough time into it. Your future looks bright.
Roblox is constantly evolving and changing. It is important to stay up to date with what is going on. The following links are useful resources that every Roblox programmer uses:
https://devforum.roblox.com/
https://create.roblox.com/docs
https://talent.roblox.com/
18.222.200.143