Quick answer: Build your clicker game around global variables for currency and click power, use instance variables on generator objects for auto-income, scale costs exponentially with the formula baseCost * 1.15 ^ owned, save game state with timestamps for offline progress, and add a prestige system for long-term retention. Construct 3’s event sheets handle all of this without any plugins.
Idle and clicker games are one of the most popular genres on web platforms, and Construct 3 is an excellent engine for building them. The core loop is simple: click to earn currency, buy upgrades that earn currency automatically, then scale the numbers into the stratosphere. But a good idle game needs careful economic design, smooth number formatting, offline progress, and a prestige layer to keep players engaged. This guide walks through every piece.
Core Click Mechanic and Currency
Start with the foundation: a clickable target and a currency counter. Create these global variables:
• Currency (number): The player’s current money, default 0
• ClickPower (number): How much each click earns, default 1
• TotalEarned (number): Lifetime earnings for prestige calculations, default 0
• PrestigeMultiplier (number): Permanent bonus from prestige resets, default 1
In your event sheet, the click handler is straightforward:
Event: ClickTarget → On clicked
• Add ClickPower * PrestigeMultiplier to Currency
• Add ClickPower * PrestigeMultiplier to TotalEarned
• Spawn FloatingText at Mouse position with text "+" & round(ClickPower * PrestigeMultiplier)
For the floating number feedback, create a small text object with the Fade behavior set to fade out over 0.8 seconds and the Bullet behavior angled upward at 90 degrees with speed 60. This creates the satisfying number-fly-up effect that every clicker game needs.
Auto-Generators and the Upgrade Shop
The idle part of the game comes from generators that produce currency without clicking. Create a sprite or UI element for each generator type with these instance variables:
• genName (text): Display name like "Lemonade Stand"
• baseCost (number): Starting price, e.g. 10
• owned (number): How many the player has bought, default 0
• baseRate (number): Currency per second per unit, e.g. 0.5
The cost scaling formula is the heart of idle game economics. Most successful idle games use exponential scaling:
// Cost formula: each purchase gets more expensive
function getGeneratorCost(baseCost, owned) {
return Math.floor(baseCost * Math.pow(1.15, owned));
}
// Income formula: linear scaling with count
function getGeneratorIncome(baseRate, owned, prestigeMultiplier) {
return baseRate * owned * prestigeMultiplier;
}
In your event sheet, run the income calculation every tick:
Event: Every tick
• For each Generator:
• Add Generator.baseRate * Generator.owned * PrestigeMultiplier * dt to Currency
• Add Generator.baseRate * Generator.owned * PrestigeMultiplier * dt to TotalEarned
The dt (delta time) is critical. It ensures income scales correctly regardless of frame rate. Without it, players on 144 Hz monitors would earn more than players on 60 Hz.
For the buy button logic:
Event: BuyButton → On clicked
• Pick Generator where Generator.UID = BuyButton.targetUID
• Compare: Currency ≥ floor(Generator.baseCost * 1.15 ^ Generator.owned)
• Subtract cost from Currency
• Add 1 to Generator.owned
• Update cost display text
Big Number Formatting
Once players accumulate thousands, millions, or billions of currency, raw numbers become unreadable. Format them with suffixes:
// Format large numbers with suffixes
function formatNumber(n) {
const suffixes = [
"", "K", "M", "B", "T",
"Qa", "Qi", "Sx", "Sp", "Oc"
];
if (n < 1000) return Math.floor(n).toString();
const tier = Math.floor(
Math.log10(Math.abs(n)) / 3
);
const suffix = suffixes[Math.min(tier, suffixes.length - 1)];
const scaled = n / Math.pow(10, tier * 3);
return scaled.toFixed(scaled < 10 ? 2 : 1) + suffix;
}
// Examples:
// formatNumber(950) -> "950"
// formatNumber(1500) -> "1.50K"
// formatNumber(2300000) -> "2.30M"
// formatNumber(1e12) -> "1.00T"
Call this function whenever you update a currency display. In event sheets, you can call scripting functions using the Call function action or inline expressions.
Offline Progress
Players expect idle games to keep running when they close the browser tab. You cannot actually run code while the tab is closed, but you can simulate it by calculating how much time passed and awarding the earnings on the next load.
// Save timestamp when leaving
function saveGameState() {
const state = {
currency: runtime.globalVars.Currency,
clickPower: runtime.globalVars.ClickPower,
totalEarned: runtime.globalVars.TotalEarned,
prestigeCount: runtime.globalVars.PrestigeCount,
prestigeMultiplier: runtime.globalVars.PrestigeMultiplier,
generators: [],
savedAt: Date.now()
};
// Save each generator's owned count
for (const gen of runtime.objects.Generator.getAllInstances()) {
state.generators.push({
name: gen.instVars.genName,
owned: gen.instVars.owned
});
}
return JSON.stringify(state);
}
// On load, calculate offline earnings
function calculateOfflineEarnings(savedState) {
const now = Date.now();
const elapsedMs = now - savedState.savedAt;
const elapsedSec = elapsedMs / 1000;
// Cap offline time at 24 hours
const maxOffline = 24 * 60 * 60;
const cappedSec = Math.min(elapsedSec, maxOffline);
// Calculate total per-second income
let totalPerSec = 0;
for (const gen of runtime.objects.Generator.getAllInstances()) {
totalPerSec += gen.instVars.baseRate * gen.instVars.owned;
}
totalPerSec *= savedState.prestigeMultiplier;
// Award offline earnings (optionally at reduced rate)
const offlineRate = 0.5; // 50% efficiency while offline
const earned = totalPerSec * cappedSec * offlineRate;
return {
earned: earned,
elapsed: cappedSec
};
}
Display the offline earnings in a popup when the player returns: “While you were away for 4 hours, your generators earned 1.25M coins!” This is one of the most satisfying moments in an idle game.
Prestige System
After players have been clicking and upgrading for a while, growth slows to a crawl. A prestige system solves this by letting the player reset their progress in exchange for a permanent multiplier.
// Prestige: reset progress for a permanent bonus
function canPrestige() {
// Require at least 1 million total earned
return runtime.globalVars.TotalEarned >= 1000000;
}
function calculatePrestigeBonus() {
// Bonus based on total earned this run
const earned = runtime.globalVars.TotalEarned;
return Math.floor(Math.sqrt(earned / 1000000));
}
function doPrestige() {
const bonus = calculatePrestigeBonus();
// Increment prestige counter
runtime.globalVars.PrestigeCount += 1;
// Apply permanent multiplier
runtime.globalVars.PrestigeMultiplier = 1 + (
runtime.globalVars.PrestigeCount * 0.25
) + bonus * 0.1;
// Reset progress
runtime.globalVars.Currency = 0;
runtime.globalVars.TotalEarned = 0;
runtime.globalVars.ClickPower = 1;
// Reset all generators
for (const gen of runtime.objects.Generator.getAllInstances()) {
gen.instVars.owned = 0;
}
}
The prestige multiplier applies to both click income and generator income, so the next run starts slow but scales much faster. This creates a compelling loop: “If I prestige now, I will get back to where I am much faster, plus go further.”
Related Issues
For save system best practices, see Game Save Best Practices for Construct 3. If your LocalStorage saves are not persisting, see Fix Construct 3 LocalStorage Data Not Persisting. For performance concerns as your game scales, see Construct 3 Performance Tips for Large Projects.
The most common mistake in idle games is making early progression too slow. Players should see meaningful numbers climbing within the first 30 seconds. Front-load the dopamine, then gradually extend the timescales as they invest more time.