Part of these game systems:
Idle Income Generator
Passive income system for idle/clicker games with offline earnings calculation, upgrade scaling, and prestige mechanics. Handles large numbers with K/M/B/T formatting.
How to Use
1
Create an empty GameObject and attach the IdleIncomeGenerator script
2
Set baseIncomePerSecond and tickInterval for your economy balance
3
Configure upgrade cost scaling with baseUpgradeCost and upgradeCostMultiplier
4
Connect onCurrencyChanged to a UI Text to display formatted currency
5
Call TryUpgrade() from an upgrade button and TryPrestige() from a prestige button
6
Offline earnings are calculated automatically on game start
Source Code
C#
using UnityEngine;
using UnityEngine.Events;
using System;
public class IdleIncomeGenerator : MonoBehaviour
{
[Header("Income Settings")]
[SerializeField] private double baseIncomePerSecond = 1.0;
[SerializeField] private float tickInterval = 1f;
[Header("Upgrade Settings")]
[SerializeField] private double baseUpgradeCost = 10.0;
[SerializeField] private double upgradeCostMultiplier = 1.15;
[SerializeField] private double upgradeIncomeBonus = 0.5;
[Header("Prestige")]
[SerializeField] private double prestigeThreshold = 1000000.0;
[SerializeField] private double prestigeBonusMultiplier = 0.1;
[Header("Offline")]
[SerializeField] private float offlineEarningRate = 0.5f;
[SerializeField] private float maxOfflineHours = 8f;
[SerializeField] private string lastPlayedKey = "LastPlayedTime";
[SerializeField] private string currencyKey = "Currency";
[SerializeField] private string levelKey = "IncomeLevel";
[SerializeField] private string prestigeKey = "PrestigeLevel";
[Header("Events")]
public UnityEvent<string> onCurrencyChanged;
public UnityEvent<double> onOfflineEarnings;
public UnityEvent<int> onPrestige;
public double Currency { get; private set; }
public int IncomeLevel { get; private set; }
public int PrestigeLevel { get; private set; }
public double IncomePerSecond => CalculateIncome();
private float tickTimer;
private void Start()
{
LoadData();
CalculateOfflineEarnings();
}
private void Update()
{
tickTimer += Time.deltaTime;
if (tickTimer >= tickInterval)
{
tickTimer -= tickInterval;
EarnIncome(CalculateIncome() * tickInterval);
}
}
private double CalculateIncome()
{
double baseIncome = baseIncomePerSecond + (IncomeLevel * upgradeIncomeBonus);
double prestigeBonus = 1.0 + (PrestigeLevel * prestigeBonusMultiplier);
return baseIncome * prestigeBonus;
}
private void EarnIncome(double amount)
{
Currency += amount;
onCurrencyChanged?.Invoke(FormatNumber(Currency));
}
public bool TryUpgrade()
{
double cost = GetUpgradeCost();
if (Currency < cost) return false;
Currency -= cost;
IncomeLevel++;
onCurrencyChanged?.Invoke(FormatNumber(Currency));
return true;
}
public double GetUpgradeCost()
{
return baseUpgradeCost * Math.Pow(upgradeCostMultiplier, IncomeLevel);
}
public string GetUpgradeCostFormatted()
{
return FormatNumber(GetUpgradeCost());
}
public bool CanPrestige()
{
return Currency >= prestigeThreshold;
}
public void Prestige()
{
if (!CanPrestige()) return;
PrestigeLevel++;
Currency = 0;
IncomeLevel = 0;
onPrestige?.Invoke(PrestigeLevel);
onCurrencyChanged?.Invoke(FormatNumber(Currency));
SaveData();
}
private void CalculateOfflineEarnings()
{
string lastPlayed = PlayerPrefs.GetString(lastPlayedKey, "");
if (string.IsNullOrEmpty(lastPlayed)) return;
DateTime lastTime;
if (!DateTime.TryParse(lastPlayed, out lastTime)) return;
TimeSpan elapsed = DateTime.UtcNow - lastTime;
double offlineSeconds = Math.Min(elapsed.TotalSeconds, maxOfflineHours * 3600);
if (offlineSeconds > 60)
{
double earnings = CalculateIncome() * offlineSeconds * offlineEarningRate;
Currency += earnings;
onOfflineEarnings?.Invoke(earnings);
onCurrencyChanged?.Invoke(FormatNumber(Currency));
}
}
public void SaveData()
{
PlayerPrefs.SetString(lastPlayedKey, DateTime.UtcNow.ToString("o"));
PlayerPrefs.SetString(currencyKey, Currency.ToString());
PlayerPrefs.SetInt(levelKey, IncomeLevel);
PlayerPrefs.SetInt(prestigeKey, PrestigeLevel);
PlayerPrefs.Save();
}
private void LoadData()
{
double.TryParse(PlayerPrefs.GetString(currencyKey, "0"), out double saved);
Currency = saved;
IncomeLevel = PlayerPrefs.GetInt(levelKey, 0);
PrestigeLevel = PlayerPrefs.GetInt(prestigeKey, 0);
}
public void AddCurrency(double amount)
{
Currency += amount;
onCurrencyChanged?.Invoke(FormatNumber(Currency));
}
public static string FormatNumber(double number)
{
if (number >= 1e12) return (number / 1e12).ToString("F1") + "T";
if (number >= 1e9) return (number / 1e9).ToString("F1") + "B";
if (number >= 1e6) return (number / 1e6).ToString("F1") + "M";
if (number >= 1e3) return (number / 1e3).ToString("F1") + "K";
return number.ToString("F0");
}
private void OnApplicationPause(bool paused)
{
if (paused) SaveData();
}
private void OnApplicationQuit()
{
SaveData();
}
private void OnDestroy()
{
SaveData();
}
}