Now that you can detect when a player has collected a coin, this section of the tutorial teaches you how to count how many coins players have collected, and make that amount visible on a leaderboard.
Create a Module Script to Record Coin Collection
To handle the storage and management of each player's coin collection data, you need to create a ModuleScript object to contain a data structure and functions that access coin collection data for every player. Module scripts are reusable code that other scripts can require. In this case, the CoinService requires this module script so that it can update coin collection data when players touch coins.
To create a module script:
In the Explorer window, hover over ServerStorage and click the ⊕ button. A contextual menu displays.
From the contextual menu, select ModuleScript. A new module script displays under ServerStorage. You are placing a module script into ServerStorage because you want to manage the coin collection logic on the server.
Rename the module script to PlayerData.
Replace the default code with the following code:
local PlayerData = {}PlayerData.COIN_KEY_NAME = "Coins"local playerData = {--[[[userId: string] = {["Coins"] = coinAmount: number}]]}local DEFAULT_PLAYER_DATA = {[PlayerData.COIN_KEY_NAME] = 0}local function getData(player)local data = playerData[tostring(player.UserId)] or DEFAULT_PLAYER_DATAplayerData[tostring(player.UserId)] = datareturn dataendfunction PlayerData.getValue(player, key)return getData(player)[key]endfunction PlayerData.updateValue(player, key, updateFunction)local data = getData(player)local oldValue = data[key]local newValue = updateFunction(oldValue)data[key] = newValuereturn newValueendreturn PlayerDataCode ExplanationThe module script defines a PlayerData table that contains zero or many playerData tables, which represent coin collection data for a player. Every script that requires this module script receives the same copy of the PlayerData table, allowing multiple scripts to modify and share coin collection data.
Declaring the Data Structures
The module script begins with a declaration of an empty table, PlayerData, which is returned at the end of the script. It also contains accessor methods to get and set values in the table.
The playerData table contains comments that describe the structure of the table, which makes the code easier to understand. In this case, a playerData table contains a userId and a corresponding field named Coins that represents the amount of collected coins for that player.
local PlayerData = {}PlayerData.COIN_KEY_NAME = "Coins"local playerData = {--[[[userId: string] = {["Coins"] = coinAmount: number}]]}...return PlayerDataDefining a local data accessor
getData() is a local function that retrieves data for a specific playerData table. If a player hasn't collected a coin, it returns a DEFAULT_PLAYER_DATA table to ensure that every player has some data associated with them. A common convention is to create simple, public-facing functions that offload logic to locally-scoped functions that do the heavy lifting.
local DEFAULT_PLAYER_DATA = {[PlayerData.COIN_KEY_NAME] = 0}local function getData(player)local data = playerData[tostring(player.UserId)] or DEFAULT_PLAYER_DATAplayerData[tostring(player.UserId)] = datareturn dataendDefining public data accessors
getValue() and updateValue() are public-facing functions that other scripts that require this module script can call. In our case, the CoinService uses these functions to update a player's coin collection data whenever that player touches a coin.
function PlayerData.getValue(player, key)return getData(player)[key]endfunction PlayerData.updateValue(player, key, updateFunction)local data = getData(player)local oldValue = data[key]local newValue = updateFunction(oldValue)data[key] = newValuereturn newValueend
Implement a Leaderboard
You can represent the coin collection data visually with an on-screen leaderboard. Roblox includes a built-in system that automatically generates a leaderboard using a default UI.
To create the leaderboard:
In the Explorer window, create a ModuleScript in ServerStorage, then rename the module script to Leaderboard.
Replace the default code with the following code:
local Leaderboard = {}-- Creating a new leaderboardlocal function setupLeaderboard(player)local leaderstats = Instance.new("Folder")-- 'leaderstats' is a reserved name Roblox recognizes for creating a leaderboardleaderstats.Name = "leaderstats"leaderstats.Parent = playerreturn leaderstatsend-- Creating a new leaderboard stat valuelocal function setupStat(leaderstats, statName)local stat = Instance.new("IntValue")stat.Name = statNamestat.Value = 0stat.Parent = leaderstatsreturn statend-- Updating a player's stat valuefunction Leaderboard.setStat(player, statName, value)local leaderstats = player:FindFirstChild("leaderstats")if not leaderstats thenleaderstats = setupLeaderboard(player)endlocal stat = leaderstats:FindFirstChild(statName)if not stat thenstat = setupStat(leaderstats, statName)endstat.Value = valueendreturn LeaderboardCode ExplanationThe following sections describe how the leaderboard works in more detail.
Creating a Leaderboard
The setupLeaderboard() function creates a new folder instance named leaderstats and sets it as a child of the specified player. Roblox automatically recognizes a folder named leaderstats as a container of stats and creates a UI element to display the stats. It requires the values in leaderstats to be stored as "value" objects (such as StringValue, IntValue or NumberValue).
-- Creating a new leaderboardlocal function setupLeaderboard(player)local leaderstats = Instance.new("Folder")-- 'leaderstats' is a reserved name Roblox recognizes for creating a leaderboardleaderstats.Name = "leaderstats"leaderstats.Parent = playerreturn leaderstatsend-- Creating a new leaderboard stat valuelocal function setupStat(leaderstats, statName)local stat = Instance.new("IntValue")stat.Name = statNamestat.Value = 0stat.Parent = leaderstatsreturn statendUpdating Player Stats
setStat() is the only public function in the Leaderboard module. It creates stat values for a specified player or the leaderboard itself if it doesn't already exist.
FindFirstChild() takes the name of an object and returns the object if it exists, or nil if it doesn't. It's a common, safe method of finding out if an object exists before you use it.
-- Updating a player's stat valuefunction Leaderboard.setStat(player, statName, value)local leaderstats = player:FindFirstChild("leaderstats")if not leaderstats thenleaderstats = setupLeaderboard(player)endlocal stat = leaderstats:FindFirstChild(statName)if not stat thenstat = setupStat(leaderstats, statName)endstat.Value = valueend
Integrate the Module Scripts
With both the PlayerData and Leaderboard module scripts complete, require them in the CoinService script to manage and display player coin data. To update CoinService:
In the Explorer window, open the CoinService script.
Replace the existing code with the following code:
-- Initializing services and variableslocal Workspace = game:GetService("Workspace")local Players = game:GetService("Players")local ServerStorage = game:GetService("ServerStorage")-- Moduleslocal Leaderboard = require(ServerStorage.Leaderboard)local PlayerData = require(ServerStorage.PlayerData)local coinsFolder = Workspace.World.Coinslocal coins = coinsFolder:GetChildren()local COIN_KEY_NAME = PlayerData.COIN_KEY_NAMElocal COOLDOWN = 10local COIN_AMOUNT_TO_ADD = 1local function updatePlayerCoins(player, updateFunction)-- Update the coin tablelocal newCoinAmount = PlayerData.updateValue(player, COIN_KEY_NAME, updateFunction)-- Update the coin leaderboardLeaderboard.setStat(player, COIN_KEY_NAME, newCoinAmount)end-- Defining the event handlerlocal function onCoinTouched(otherPart, coin)if coin:GetAttribute("Enabled") thenlocal character = otherPart.Parentlocal player = Players:GetPlayerFromCharacter(character)if player then-- Player touched a coincoin.Transparency = 1coin:SetAttribute("Enabled", false)updatePlayerCoins(player, function(oldCoinAmount)oldCoinAmount = oldCoinAmount or 0return oldCoinAmount + COIN_AMOUNT_TO_ADDend)task.wait(COOLDOWN)coin.Transparency = 0coin:SetAttribute("Enabled", true)endendend-- Setting up event listenersfor _, coin in coins docoin:SetAttribute("Enabled", true)coin.Touched:Connect(function(otherPart)onCoinTouched(otherPart, coin)end)endCode ExplanationThe changes to the original CoinService script include:
- Declaring COIN_AMOUNT_TO_ADD as the number of coins to add when a player collects a coin, and COIN_KEY_NAME as the key name defined in PlayerData.
- Creating the helper function updatePlayerCoins() to update the player's coin count and associated leaderboard stat.
- Replacing the placeholder print() statement in onCoinTouched() with a call to updatePlayerCoins().
Playtest
It's time to see if the coin collection is working as intended. When you touch and collect a coin in the game, you should be able to see the amount of coins you've collected on the leaderboard UI. To playtest your experience:
In the menu bar, click the Play button. Studio enters playtest mode.
Move your character to touch a coin. If your scripts are working correctly, the leaderboard UI displays and increase your coin count as you collect more coins.