Build one complete game — a clicker or a pet-collecting game — with a shop, persistent saves, and polish. Every system in this course in one place.
This is it: the whole course in one build. You're going to assemble a complete game using every system you've built. First, pick which one you want to build. If you can finish this, you can build anything in Roblox.
Two games, same engine: a server that owns the money, remotes that carry the messages, a module that does the math, a data store that remembers, and a client that only reports and shows.
CLIENT SERVER
your action ──FireServer──▶ Handler: verify, decide, apply
UI labels ◀──FireClient─── every change pushes updated totals
SaveSystem: load on join, save on leave
Pick the game you want to build — you can switch later from the sidebar, and the rest of this lesson will only show your game.
This is saved on this device — you can switch later from the sidebar.
Every game starts as a folder tree. In ReplicatedStorage, create the folder structure for the game you chose:
GameConfig is the single source of truth for every number in both games:
local config = {
-- Coin Tycoon
coinsPerClick = 1,
upgradeBaseCost = 25,
-- The Collector
coinsPerPickup = 1,
petBonusPerLevel = 1,
petBaseCost = 30,
-- shared
coinsKey = "Coins",
levelKey = "Level",
}
return config
In Coin Tycoon, Level is your click multiplier. In The Collector, Level is how many pets you own. Change one number here and the whole economy reprices.
The RemoteEvents live in a folder named so server and client both reach them.
Both games run on the same module — it just serves two economies. All the money math in one testable place:
local GameMath = {}
-- Coin Tycoon: what one click is worth
function GameMath.rewardForClick(multiplier)
return 1 * multiplier
end
-- The Collector: what one pickup is worth
function GameMath.coinsPerPickup(petLevel)
return 1 + petLevel
end
-- shared: how prices climb
function GameMath.upgradeCost(baseCost, currentLevel)
return baseCost * (currentLevel + 1)
end
return GameMath
Every script that needs a price or a reward calls these functions — no duplicated formulas. If the economy feels wrong, you change one file.
Finish the GameMath module so require hands back the table of functions.
local GameMath = {}
function GameMath.rewardForClick(multiplier)
return 1 * multiplier
end
-- your line here
The persistence layer, using your datastores knowledge. It saves two values as one table — and it's identical for both games, because both games track Coins and Level:
local Players = game:GetService("Players")
local DataStoreService = game:GetService("DataStoreService")
local Shared = game:GetService("ReplicatedStorage").Shared
local GameConfig = require(Shared.GameConfig)
local saveStore = DataStoreService:GetDataStore("FinalProjectSaves")
function createStats(player, coins, level)
local stats = Instance.new("Folder")
stats.Name = "leaderstats"
stats.Parent = player
local coinsValue = Instance.new("NumberValue")
coinsValue.Name = GameConfig.coinsKey
coinsValue.Value = coins
coinsValue.Parent = stats
local levelValue = Instance.new("NumberValue")
levelValue.Name = GameConfig.levelKey
levelValue.Value = level
levelValue.Parent = stats
end
Players.PlayerAdded:Connect(function(player)
local data = saveStore:GetAsync(player.UserId)
if data == nil then
data = { coins = 0, level = 1 }
end
createStats(player, data.coins, data.level)
end)
Players.PlayerRemoving:Connect(function(player)
local coins = player.leaderstats[GameConfig.coinsKey].Value
local level = player.leaderstats[GameConfig.levelKey].Value
saveStore:SetAsync(player.UserId, { coins = coins, level = level })
end)
Load on join, save on leave, default for new players — the datastores lesson, upgraded to a table.
Why save both values as one table instead of two separate SetAsync calls?
Clicks or pickups and purchases, all through the server. Each handler is tiny: verify, decide, apply, report. All the noise lives in the modules.
The whole client: build the UI, send actions, receive updates.
A game you just assembled still feels flat. Add feel with a tween — something that responds the instant the player does something.
Why does the polish step tween on the client?
Run this as your final test — the exact loop every game must pass: earn, spend, verify, save, reload.
The course is complete when step 5 shows your exact coins and level.
Stretch goals — in Coin Tycoon, an auto-income loop using GameMath.rewardForClick; in The Collector, different pets with different costs; in both, a "power" boost, a coin tween that flies to the corner, a third NumberValue for the leaderboard. Every one is a variation of the pipeline you've mastered.
Common mistakes, final edition — skipping modules (the split is the debuggability); renaming a stat and missing the saves; testing saves without Studio access enabled; letting the client label decide anything (it's a mirror, not a bank).
After reloading, you should see your saved state. Write the five key actions of the playtest in order: earn, spend, verify, save, reload.
-- the playtest loop
You built a real, publishable game from zero. Everything from here is the same systems, bigger ambitions.