Getting Started with Code
This page is for developers who want to write C# code to integrate Simple Idle Forge's runtime components into their game. If you are using the no-code wizard workflow exclusively, you do not need this page — the forges handle everything. But if you want custom game controllers, UI integration, save/load systems, or advanced features, this is your complete reference.
All runtime classes are pure C# — not MonoBehaviours. You create them in your own
game controller and manage their lifecycle. Every class lives in the
SimpleIdleForge namespace.
The Basic Game Controller Pattern
Here is the standard pattern for wiring everything together. Your MonoBehaviour holds
references to the generated ScriptableObject databases (assigned in the Inspector), creates
the runtime trackers in Start(), and ticks them in Update().
using SimpleIdleForge;
using UnityEngine;
using System;
using System.Collections.Generic;
public class MyIdleGame : MonoBehaviour
{
[Header("Databases")]
public ScriptableObject[] resourceDatabases;
public ScriptableObject[] generatorDatabases;
public ScriptableObject[] upgradeDatabases;
public ScriptableObject[] prestigeDatabases;
public ScriptableObject[] achievementDatabases;
IdleResourcePool resourcePool;
IdleGeneratorManager generatorManager;
IdleUpgradeTracker upgradeTracker;
IdlePrestigeManager prestigeManager;
IdleMilestoneTracker milestoneTracker;
List<IdleBonus> bonusCache = new List<IdleBonus>();
void Start()
{
// Cast databases to interfaces
var resSources = new List<IIdleResourceDataSource>();
foreach (var db in resourceDatabases)
if (db is IIdleResourceDataSource src) resSources.Add(src);
var genSources = new List<IIdleGeneratorDataSource>();
foreach (var db in generatorDatabases)
if (db is IIdleGeneratorDataSource src) genSources.Add(src);
var upgSources = new List<IIdleUpgradeDataSource>();
foreach (var db in upgradeDatabases)
if (db is IIdleUpgradeDataSource src) upgSources.Add(src);
var presSources = new List<IIdlePrestigeDataSource>();
foreach (var db in prestigeDatabases)
if (db is IIdlePrestigeDataSource src) presSources.Add(src);
var mileSources = new List<IIdleMilestoneDataSource>();
foreach (var db in achievementDatabases)
if (db is IIdleMilestoneDataSource src) mileSources.Add(src);
// Initialize trackers
resourcePool = new IdleResourcePool();
resourcePool.Initialize(resSources);
generatorManager = new IdleGeneratorManager();
generatorManager.Initialize(genSources);
upgradeTracker = new IdleUpgradeTracker();
upgradeTracker.Initialize(upgSources);
prestigeManager = new IdlePrestigeManager();
prestigeManager.Initialize(presSources);
milestoneTracker = new IdleMilestoneTracker();
milestoneTracker.Initialize(mileSources);
// Apply starting amounts
foreach (var code in resourcePool.GetAllCodes())
{
var def = resourcePool.GetDefinition(code);
if (def.HasValue && def.Value.startingAmount > 0)
resourcePool.SetAmount(code, def.Value.startingAmount);
}
}
void Update()
{
float dt = Time.deltaTime;
// Collect global bonuses from all sources
bonusCache.Clear();
upgradeTracker.CollectBonusesForTarget("", bonusCache);
prestigeManager.CollectBonusesForTarget("", bonusCache);
milestoneTracker.CollectBonusesForTarget("", bonusCache);
// Tick generators (produces resources)
generatorManager.Tick(dt, resourcePool, bonusCache);
// Auto-collect timer generators
foreach (var code in generatorManager.GetAllCodes())
{
if (generatorManager.IsTimerReady(code))
generatorManager.CollectTimer(code, resourcePool, bonusCache);
}
// Periodically check conditions (every 0.5s, not every frame)
generatorManager.CheckUnlockConditions(GetValueProvider());
milestoneTracker.CheckAll(GetValueProvider(), resourcePool);
}
Func<IdleConditionType, string, double> GetValueProvider()
{
return (type, code) =>
{
switch (type)
{
case IdleConditionType.ResourceAmount:
return resourcePool.GetAmount(code);
case IdleConditionType.GeneratorLevel:
return generatorManager.GetLevel(code);
case IdleConditionType.UpgradePurchased:
return upgradeTracker.GetPurchaseCount(code);
case IdleConditionType.PrestigeCount:
return prestigeManager.GetPrestigeCount(code);
default: return 0;
}
};
}
}
The rest of this page documents every class, method, and event in detail.
IdleResourcePool
Tracks current resource amounts at runtime. Initialize with one or more databases, then add, remove, or set amounts. Respects caps and fires events on changes. This is the foundation — every other tracker interacts with it.
Namespace: SimpleIdleForge
Type: class (pure C#, not a MonoBehaviour)
Events
| Event | Signature | Description |
|---|---|---|
OnResourceChanged |
Action<string, double, double> |
Fired whenever a resource amount changes. Args: resourceCode, oldAmount, newAmount. |
OnCapReached |
Action<string, double> |
Fired when a resource reaches its cap. Args: resourceCode, capAmount. |
OnResourceDepleted |
Action<string> |
Fired when a resource reaches zero. Args: resourceCode. |
Methods
| Method | Returns | Description |
|---|---|---|
Initialize(IIdleResourceDataSource dataSource) |
void |
Initialize from a single database. All resources are added with their startingAmount. |
Initialize(IEnumerable<IIdleResourceDataSource> dataSources) |
void |
Initialize from multiple databases (merged, deduped by code). |
GetAmount(string resourceCode) |
double |
Current amount. Returns 0 if the resource code is unknown. |
HasResource(string resourceCode) |
bool |
Whether a resource exists in the pool. |
GetDefinition(string resourceCode) |
IdleResourceDefinition? |
The full definition struct, or null if not found. |
GetAllCodes() |
IEnumerable<string> |
All resource codes in the pool. |
GetCap(string resourceCode) |
double |
Cap value for a resource. Returns 0 if unlimited (no cap). |
GetFillRatio(string resourceCode) |
float |
Fill ratio 0.0–1.0. Returns 0 if no cap is set. |
CanAfford(string resourceCode, double cost) |
bool |
Single-resource affordability check. |
CanAffordAll(IEnumerable<KeyValuePair<string, double>> costs) |
bool |
Multi-resource affordability check. All costs must be affordable. |
GetCapEfficiency(string resourceCode) |
float |
Production efficiency at current fill level (from IdleCapBehavior). Returns 1.0 if no cap behavior. |
Add(string resourceCode, double amount) |
double |
Add to a resource. Returns actual amount added (may be less due to cap). |
Remove(string resourceCode, double amount) |
double |
Remove from a resource. Cannot go below 0. Returns actual amount removed. |
SetAmount(string resourceCode, double amount) |
void |
Set an exact amount. Respects cap and 0 floor. |
TrySpend(string resourceCode, double cost) |
bool |
Spend if affordable. Returns false if not enough. |
TrySpendAll(IEnumerable<KeyValuePair<string, double>> costs) |
bool |
Atomic multi-resource spend. Checks all first, then deducts all. Returns false if any cost cannot be met. |
ResetToStarting(string resourceCode) |
void |
Reset a single resource to its definition's startingAmount. |
ResetAll() |
void |
Reset all resources to their starting amounts. |
CreateSnapshot() |
IdleResourcePoolSnapshot |
Create a serializable snapshot of all resource amounts for save/load. |
RestoreFromSnapshot(IdleResourcePoolSnapshot snapshot) |
void |
Restore resource amounts from a snapshot. Unknown codes are ignored. |
Usage Example
// Check if the player can afford a custom purchase
if (resourcePool.CanAfford("GOLD", 5000) && resourcePool.CanAfford("GEMS", 10))
{
resourcePool.Remove("GOLD", 5000);
resourcePool.Remove("GEMS", 10);
// Grant the item...
}
// Or use the atomic multi-resource spend
var costs = new Dictionary<string, double> { { "GOLD", 5000 }, { "GEMS", 10 } };
if (resourcePool.TrySpendAll(costs))
{
// Both resources deducted atomically
}
// Listen for events
resourcePool.OnResourceChanged += (code, oldVal, newVal) =>
{
Debug.Log($"{code}: {oldVal} -> {newVal}");
UpdateResourceUI(code, newVal);
};
resourcePool.OnCapReached += (code, cap) =>
{
ShowNotification($"{code} is full! ({cap})");
};
// Cap efficiency query for UI
float efficiency = resourcePool.GetCapEfficiency("ENERGY");
// 1.0 = full production, 0.5 = half production, 0.0 = stopped
IdleGeneratorManager
Tracks generator levels, handles purchases, evaluates prerequisites, and ticks production. Supports both Continuous (per-frame) and Timer (batch) production modes. The workhorse of any idle game.
Namespace: SimpleIdleForge
Type: class (pure C#, not a MonoBehaviour)
Events
| Event | Signature | Description |
|---|---|---|
OnGeneratorLevelChanged |
Action<string, int, int> |
Fired when a generator is purchased or leveled. Args: generatorCode, oldLevel, newLevel. |
OnGeneratorUnlocked |
Action<string> |
Fired when a generator is unlocked. Args: generatorCode. |
OnMilestoneReached |
Action<string, IdleGeneratorMilestone> |
Fired when a generator milestone threshold is crossed. Args: generatorCode, milestone. |
OnGeneratorAutomated |
Action<string> |
Fired when a generator becomes automated. Args: generatorCode. |
OnTimerCompleted |
Action<string> |
Fired when a Timer-mode generator completes a cycle. Args: generatorCode. |
Methods
| Method | Returns | Description |
|---|---|---|
Initialize(IIdleGeneratorDataSource dataSource) |
void |
Initialize from a single generator database. |
Initialize(IEnumerable<IIdleGeneratorDataSource> dataSources) |
void |
Initialize from multiple databases (merged, deduped by code). |
GetLevel(string generatorCode) |
int |
Current level. Returns 0 if unknown. |
IsUnlocked(string generatorCode) |
bool |
Whether the generator is unlocked (prerequisites met). |
IsAutomated(string generatorCode) |
bool |
Whether the generator is automated. |
GetDefinition(string generatorCode) |
IdleGeneratorDefinition? |
The full definition struct, or null if not found. |
GetAllCodes() |
IReadOnlyList<string> |
All generator codes. |
HasGenerator(string generatorCode) |
bool |
Whether a generator exists in the manager. |
GetBaseProduction(string generatorCode, string resourceCode) |
double |
Base production per second for a generator at its current level for a specific resource. Does NOT include bonuses. |
GetNextLevelCost(string generatorCode, string costResourceCode) |
double |
Cost to buy the next level for a specific resource cost entry. |
CanAfford(string generatorCode, Func<string, double> getResourceAmount) |
bool |
Check if a single level purchase is affordable across all cost entries. |
GetMaxAffordableLevels(string generatorCode, Func<string, double> getResourceAmount) |
int |
Maximum levels the player can buy right now. |
GetNextMilestoneLevel(string generatorCode) |
int |
Next milestone level threshold, or -1 if no more milestones. |
Buy(string generatorCode, IdleResourcePool resourcePool) |
bool |
Buy one level. Deducts costs from resource pool. Returns true if successful. |
BuyLevels(string generatorCode, int count, IdleResourcePool resourcePool) |
bool |
Buy multiple levels. Deducts total costs atomically. Returns true if successful. |
BuyMax(string generatorCode, IdleResourcePool resourcePool) |
int |
Buy the maximum affordable levels. Returns number of levels purchased. |
SetLevel(string generatorCode, int level) |
void |
Set a generator's level directly (for save/load or cheats). |
Unlock(string generatorCode) |
void |
Unlock a generator manually (bypassing prerequisites). |
SetAutomated(string generatorCode, bool value = true) |
void |
Set a generator as automated (or remove automation). |
CheckUnlockConditions(Func<IdleConditionType, string, double> valueProvider) |
void |
Evaluate prerequisites against current game state and unlock qualifying generators. |
Tick(float deltaSeconds, IdleResourcePool resourcePool, List<IdleBonus> bonusCache = null) |
void |
Tick all generators: add production to the resource pool for a time delta. Handles both Continuous and Timer modes. |
GetTimerProgress(string code) |
float |
Timer progress 0.0–1.0 for UI progress bars. Returns 0 for Continuous generators. |
IsTimerReady(string code) |
bool |
Whether a Timer-mode generator has completed its cycle and is ready for manual collection. |
CollectTimer(string code, IdleResourcePool resourcePool, List<IdleBonus> bonusCache = null) |
bool |
Manually collect a completed timer cycle. Returns true if collection happened. |
GetEffectiveInterval(string code) |
double |
Effective interval in seconds for a Timer-mode generator, accounting for Speed bonuses. |
ResetGenerator(string generatorCode) |
void |
Reset a single generator to level 0 and re-evaluate its locked state. |
ResetAll() |
void |
Reset all generators to level 0 and locked state. |
CreateSnapshot() |
IdleGeneratorManagerSnapshot |
Create a snapshot for save/load. |
RestoreFromSnapshot(IdleGeneratorManagerSnapshot snapshot) |
void |
Restore from a snapshot. |
Important: CanAfford Signature
CanAfford takes a Func<string, double> delegate, not an
IdleResourcePool directly. This keeps the generator manager decoupled from any
specific resource source. The standard usage pattern is:
// Standard usage with IdleResourcePool
bool canBuy = generatorManager.CanAfford("MINE", code => resourcePool.GetAmount(code));
// Or with a shorthand lambda
bool canBuy = generatorManager.CanAfford("MINE", resourcePool.GetAmount);
Timer Production Example
Timer-mode generators (Adventure Capitalist style) produce in batches instead of continuously. Automated timers collect automatically. Non-automated timers wait for manual collection.
// Update timer progress bar UI
float progress = generatorManager.GetTimerProgress("STEEL_FOUNDRY");
mySlider.value = progress; // 0.0 to 1.0
// Check if timer batch is ready
if (generatorManager.IsTimerReady("STEEL_FOUNDRY"))
generatorManager.CollectTimer("STEEL_FOUNDRY", resourcePool, bonusCache);
// Get effective interval (accounts for Speed bonuses)
double interval = generatorManager.GetEffectiveInterval("STEEL_FOUNDRY");
// Display: "12.0s per cycle" or faster with speed upgrades
Buy Max Example
// Buy as many levels as the player can afford
int levelsBought = generatorManager.BuyMax("MINE", resourcePool);
if (levelsBought > 0)
Debug.Log($"Bought {levelsBought} levels of MINE");
// Or buy a specific number of levels
if (generatorManager.BuyLevels("MINE", 10, resourcePool))
Debug.Log("Bought 10 levels of MINE");
IdleUpgradeTracker
Tracks upgrade purchase counts, evaluates prerequisites, handles cost deduction, and fires special events for Automate, Unlock, and ResourceGrant effect types. Supports both one-time and repeatable upgrades.
Namespace: SimpleIdleForge
Type: class (pure C#, not a MonoBehaviour)
Events
| Event | Signature | Description |
|---|---|---|
OnUpgradePurchased |
Action<string, int> |
Fired when an upgrade is purchased. Args: upgradeCode, newPurchaseCount. |
OnAutomatorPurchased |
Action<string, string> |
Fired when an Automate-type upgrade is purchased. Args: upgradeCode, targetCode. |
OnUnlockPurchased |
Action<string, string> |
Fired when an Unlock-type upgrade is purchased. Args: upgradeCode, targetCode. |
Methods
| Method | Returns | Description |
|---|---|---|
Initialize(IIdleUpgradeDataSource dataSource) |
void |
Initialize from a single upgrade database. |
Initialize(IEnumerable<IIdleUpgradeDataSource> dataSources) |
void |
Initialize from multiple databases (merged, deduped by code). |
GetPurchaseCount(string upgradeCode) |
int |
Number of times this upgrade has been purchased. 0 if unknown. |
IsPurchased(string upgradeCode) |
bool |
Whether this upgrade has been purchased at least once. |
IsMaxed(string upgradeCode) |
bool |
Whether this upgrade has reached its maxPurchases limit. False if unlimited (maxPurchases <= 0). |
CanPurchase(string upgradeCode) |
bool |
Whether this upgrade can be purchased (not maxed out). |
GetDefinition(string upgradeCode) |
IdleUpgradeDefinition? |
The full definition struct, or null if not found. |
GetAllCodes() |
IReadOnlyList<string> |
All upgrade codes. |
HasUpgrade(string upgradeCode) |
bool |
Whether an upgrade exists in the tracker. |
ArePrerequisitesMet(string upgradeCode, Func<IdleConditionType, string, double> valueProvider) |
bool |
Check if all prerequisites for an upgrade are met. |
CanAfford(string upgradeCode, Func<string, double> getResourceAmount) |
bool |
Check if an upgrade is affordable (all cost entries). |
Purchase(string upgradeCode, IdleResourcePool resourcePool) |
bool |
Purchase an upgrade. Deducts costs, increments count, fires events, handles special effects (Automate/Unlock/ResourceGrant). Returns true if successful. |
SetPurchaseCount(string upgradeCode, int count) |
void |
Set purchase count directly (for save/load). |
ResetAll() |
void |
Reset all purchase counts to 0. |
ResetWhere(Func<IdleUpgradeDefinition, bool> shouldReset) |
void |
Reset only upgrades matching a predicate. Useful for selective prestige resets. |
CollectBonusesForTarget(string targetCode, List<IdleBonus> output) |
void |
Collect all active bonuses from purchased upgrades for a target. Feed into IdleBonusResolver. |
CreateSnapshot() |
IdleUpgradeTrackerSnapshot |
Create a snapshot for save/load. |
RestoreFromSnapshot(IdleUpgradeTrackerSnapshot snapshot) |
void |
Restore from a snapshot. |
Purchase Example
string code = "MINE_BOOST";
if (!upgradeTracker.IsMaxed(code)
&& upgradeTracker.CanAfford(code, c => resourcePool.GetAmount(c))
&& upgradeTracker.ArePrerequisitesMet(code, GetValueProvider()))
{
upgradeTracker.Purchase(code, resourcePool);
// Costs deducted, effect applied, events fired
}
Selective Prestige Reset
// Reset all upgrades EXCEPT those targeting "PRESTIGE_POINTS"
upgradeTracker.ResetWhere(def => def.targetCode != "PRESTIGE_POINTS");
// Reset all upgrades except those with the Automate effect type
upgradeTracker.ResetWhere(def => def.effectType != IdleBonusType.Automate);
IdlePrestigeManager
Manages prestige layers: counts, total currency earned, reward calculations, reset rule execution, and permanent bonus collection. The prestige loop — reset progress in exchange for permanent bonuses — is the core progression mechanic of most idle games.
Namespace: SimpleIdleForge
Type: class (pure C#, not a MonoBehaviour)
Events
| Event | Signature | Description |
|---|---|---|
OnPrestigeExecuted |
Action<string, double> |
Fired when a prestige is executed. Args: prestigeCode, pointsEarned. |
OnPrestigeAvailable |
Action<string> |
Fired when a prestige becomes available (minimum met). Args: prestigeCode. |
Methods
| Method | Returns | Description |
|---|---|---|
Initialize(IIdlePrestigeDataSource dataSource) |
void |
Initialize from a single prestige database. |
Initialize(IEnumerable<IIdlePrestigeDataSource> dataSources) |
void |
Initialize from multiple databases (merged, deduped by code). |
GetPrestigeCount(string prestigeCode) |
int |
Number of times this prestige has been executed. |
GetTotalCurrencyEarned(string prestigeCode) |
double |
Total prestige currency earned across all resets for this layer. |
HasPrestiged(string prestigeCode) |
bool |
Whether the player has prestiged at least once. |
GetDefinition(string prestigeCode) |
IdlePrestigeDefinition? |
The full definition struct, or null if not found. |
GetAllCodes() |
IReadOnlyList<string> |
All prestige layer codes. |
HasPrestige(string prestigeCode) |
bool |
Whether a prestige layer exists in the manager. |
CanPrestige(string prestigeCode, double currentSourceAmount) |
bool |
Check if prestige is available (source amount meets minimum and would earn points). |
GetPotentialReward(string prestigeCode, double currentSourceAmount) |
double |
Get the potential reward without executing prestige. |
GetNetGain(string prestigeCode, double currentSourceAmount) |
double |
Get the net gain (potential minus already earned total). |
CheckAvailability(Func<string, double> getResourceAmount) |
void |
Check all prestige layers and fire OnPrestigeAvailable events for those that qualify. |
ExecutePrestige(string prestigeCode, IdleResourcePool resources, IdleGeneratorManager generators, IdleUpgradeTracker upgrades) |
double |
Execute prestige: calculate reward, apply reset rules, grant prestige currency. Returns points earned, or 0 if prestige failed. |
CollectBonusesForTarget(string targetCode, List<IdleBonus> output) |
void |
Collect all permanent bonuses from all prestige layers for a target. Bonuses scale with total currency earned. |
GetPermanentBonus(string targetCode, IdleBonusType type) |
double |
Convenience: get the total permanent bonus value for a target and bonus type, summed across all prestige layers. |
SetPrestigeCount(string prestigeCode, int count) |
void |
Set prestige count directly (for save/load). |
SetTotalCurrencyEarned(string prestigeCode, double amount) |
void |
Set total currency earned directly (for save/load). |
ResetAll() |
void |
Reset all prestige state to 0. |
CreateSnapshot() |
IdlePrestigeManagerSnapshot |
Create a snapshot for save/load. |
RestoreFromSnapshot(IdlePrestigeManagerSnapshot snapshot) |
void |
Restore from a snapshot. |
Prestige Example
double sourceAmount = resourcePool.GetAmount("GOLD");
if (prestigeManager.CanPrestige("REBIRTH", sourceAmount))
{
double preview = prestigeManager.GetPotentialReward("REBIRTH", sourceAmount);
// Show confirmation dialog: "Prestige for {preview} points?"
double earned = prestigeManager.ExecutePrestige(
"REBIRTH", resourcePool, generatorManager, upgradeTracker);
// Resets applied, currency granted, permanent bonuses active
Debug.Log($"Prestiged for {earned} points!");
}
// Check permanent bonus values
double prodBonus = prestigeManager.GetPermanentBonus("MINE", IdleBonusType.AddPercent);
Debug.Log($"MINE has +{prodBonus * 100}% permanent production bonus");
IdleMilestoneTracker
Tracks achievement and milestone completion. Evaluates conditions each tick, fires completion events, auto-grants ResourceGrant rewards, and collects bonus rewards for the bonus resolution pipeline. Supports both one-time and repeatable milestones, plus hidden (secret) achievements.
Namespace: SimpleIdleForge
Type: class (pure C#, not a MonoBehaviour)
Events
| Event | Signature | Description |
|---|---|---|
OnMilestoneCompleted |
Action<string, IdleMilestoneDefinition> |
Fired when a milestone is completed for the first time. Args: milestoneCode, definition. |
OnMilestoneRepeated |
Action<string, int> |
Fired when a repeatable milestone is completed again. Args: milestoneCode, completionCount. |
OnMilestoneProgress |
Action<string, double, double> |
Fired to report progress toward an incomplete milestone. Args: milestoneCode, currentValue, targetValue. |
Methods
| Method | Returns | Description |
|---|---|---|
Initialize(IIdleMilestoneDataSource dataSource) |
void |
Initialize from a single milestone database. |
Initialize(IEnumerable<IIdleMilestoneDataSource> dataSources) |
void |
Initialize from multiple databases (merged, deduped by code). |
IsCompleted(string milestoneCode) |
bool |
Whether this milestone has been completed at least once. |
GetCompletionCount(string milestoneCode) |
int |
Number of times this milestone has been completed (for repeatables). |
GetCompletedCount() |
int |
Total number of completed milestones. |
GetTotalCount() |
int |
Total number of milestones in the tracker. |
GetDefinition(string milestoneCode) |
IdleMilestoneDefinition? |
The full definition struct, or null if not found. |
GetAllCodes() |
IReadOnlyList<string> |
All milestone codes. |
HasMilestone(string milestoneCode) |
bool |
Whether a milestone exists in the tracker. |
GetCompletedMilestones() |
List<IdleMilestoneDefinition> |
All completed milestone definitions. |
GetInProgressMilestones() |
List<IdleMilestoneDefinition> |
All incomplete milestone definitions. |
GetCurrentProgress(string milestoneCode, Func<IdleConditionType, string, double> valueProvider) |
double |
Get the current progress value for a milestone's condition. |
CheckAll(Func<IdleConditionType, string, double> valueProvider, IdleResourcePool resourcePool = null) |
void |
Check all milestones against current game state. The optional resourcePool is used for auto-granting ResourceGrant rewards. |
CollectBonusesForTarget(string targetCode, List<IdleBonus> output) |
void |
Collect all non-ResourceGrant bonuses from completed milestones for a target. Feed into IdleBonusResolver. |
SetCompleted(string milestoneCode, int count = 1) |
void |
Mark a milestone as completed directly (for save/load). |
ResetAll() |
void |
Reset all milestones to incomplete. |
ResetWhere(Func<IdleMilestoneDefinition, bool> shouldReset) |
void |
Reset milestones matching a predicate. |
CreateSnapshot() |
IdleMilestoneTrackerSnapshot |
Create a snapshot for save/load. |
RestoreFromSnapshot(IdleMilestoneTrackerSnapshot snapshot) |
void |
Restore from a snapshot. |
Achievement UI Example
// Display achievement progress in a UI panel
foreach (var code in milestoneTracker.GetAllCodes())
{
var def = milestoneTracker.GetDefinition(code);
if (!def.HasValue) continue;
bool completed = milestoneTracker.IsCompleted(code);
bool hidden = def.Value.isHidden && !completed;
if (hidden)
{
// Show as "???" in the UI
ShowHiddenAchievement();
}
else
{
double current = milestoneTracker.GetCurrentProgress(code, GetValueProvider());
double target = def.Value.conditionValue;
float ratio = target > 0 ? (float)(current / target) : 0f;
ShowAchievement(def.Value.displayName, def.Value.description, ratio, completed);
}
}
// Display completion stats
int done = milestoneTracker.GetCompletedCount();
int total = milestoneTracker.GetTotalCount();
achievementCountLabel.text = $"{done} / {total}";
IdleBuffManager
Manages temporary boosts with durations — ad rewards, consumable potions, event
bonuses, and similar timed multipliers. Call Tick() each frame to expire
buffs. Use CollectBonusesForTarget() to feed active buffs into the bonus
resolution pipeline.
Namespace: SimpleIdleForge
Type: class (pure C#, not a MonoBehaviour)
Events
| Event | Signature | Description |
|---|---|---|
OnBuffApplied |
Action<string, IdleActiveBuff> |
Fired when a buff is applied. Args: buffId, buff. |
OnBuffExpired |
Action<string> |
Fired when a buff expires naturally (timer ran out). Args: buffId. |
OnBuffRemoved |
Action<string> |
Fired when a buff is manually removed. Args: buffId. |
Methods
| Method | Returns | Description |
|---|---|---|
ApplyBuff(string buffId, IdleTargetType targetType, string targetCode, IdleBonusType bonusType, double value, float duration) |
void |
Apply a temporary buff. Duration 0 = permanent (must be removed manually). If a buff with the same ID exists, it is replaced. |
ApplyGlobalBuff(string buffId, IdleBonusType bonusType, double value, float duration) |
void |
Convenience: apply a global buff (affects all production). |
RemoveBuff(string buffId) |
void |
Remove a buff manually (before it expires). Fires OnBuffRemoved. |
RemoveAll() |
void |
Remove all active buffs. Fires OnBuffRemoved for each. |
RefreshBuff(string buffId, float newDuration) |
void |
Restart a buff's timer with a new duration. |
Tick(float deltaTime) |
void |
Update all buff timers. Call each frame with Time.deltaTime. Expired buffs are automatically removed. |
IsActive(string buffId) |
bool |
Check if a buff is currently active. |
GetBuff(string buffId) |
IdleActiveBuff |
Get an active buff by ID, or null if not active. |
GetAllActiveBuffs() |
IReadOnlyCollection<IdleActiveBuff> |
Get all currently active buffs. |
ActiveCount |
int |
Property: number of active buffs. |
GetBuffsForTarget(string targetCode) |
List<IdleActiveBuff> |
Get all active buffs affecting a specific target (includes global buffs). |
CollectBonusesForTarget(string targetCode, List<IdleBonus> output) |
void |
Collect all active buff bonuses for a target. Feed into IdleBonusResolver. |
CreateSnapshot() |
IdleBuffManagerSnapshot |
Create a snapshot for save/load. |
RestoreFromSnapshot(IdleBuffManagerSnapshot snapshot) |
void |
Restore from a snapshot. Expired buffs in the snapshot are skipped. |
Buff Examples
// Apply 2x global production for 120 seconds (ad reward)
buffManager.ApplyBuff("ad_reward_2x", IdleTargetType.Global, "",
IdleBonusType.Multiply, 2.0, 120f);
// Apply permanent +10% to a specific generator
buffManager.ApplyBuff("passive_mine_boost", IdleTargetType.Generator, "MINE",
IdleBonusType.AddPercent, 0.1, 0f); // duration 0 = permanent
// Apply a Speed buff (makes Timer generators faster)
buffManager.ApplyBuff("speed_potion", IdleTargetType.Global, "",
IdleBonusType.Speed, 0.5, 60f); // +50% speed for 60 seconds
// In Update loop
buffManager.Tick(Time.deltaTime);
// Include buffs in bonus collection
bonusCache.Clear();
upgradeTracker.CollectBonusesForTarget("MINE", bonusCache);
prestigeManager.CollectBonusesForTarget("MINE", bonusCache);
milestoneTracker.CollectBonusesForTarget("MINE", bonusCache);
buffManager.CollectBonusesForTarget("MINE", bonusCache);
// Display active buff timers in UI
foreach (var buff in buffManager.GetAllActiveBuffs())
{
if (!buff.IsPermanent)
ShowBuffTimer(buff.buffId, buff.remainingDuration, buff.Progress);
}
IdleProgressCalculator
This is THE killer feature. No other Asset Store package provides reusable offline progress calculation as a static utility.
Calculates offline earnings, current production rates, and time-to-afford estimates. All methods are static — no state, no initialization needed. Call on demand when the game loads, when the player returns from the background, or when the UI needs production rate data.
Namespace: SimpleIdleForge
Type: static class
Methods
| Method | Returns | Description |
|---|---|---|
GetProductionSnapshot(IIdleGeneratorDataSource, IdleGeneratorManager, params Action<string, List<IdleBonus>>[]) |
IdleProductionSnapshot |
Calculate current production rates across all generators from a single database. Pass CollectBonusesForTarget methods as bonus collectors. |
GetProductionSnapshot(IEnumerable<IIdleGeneratorDataSource>, IdleGeneratorManager, params Action<string, List<IdleBonus>>[]) |
IdleProductionSnapshot |
Multi-database overload. Merges production rates across all databases. |
CalculateOfflineProgress(IdleProductionSnapshot, float offlineSeconds, IdleOfflineEfficiency, float maxOfflineSeconds = 0) |
IdleProgressReport |
Calculate offline earnings from a production snapshot. Applies efficiency config and optional max-time cap. |
CalculateOfflineProgress(IIdleGeneratorDataSource, IdleGeneratorManager, float offlineSeconds, IdleOfflineEfficiency, float maxOfflineSeconds = 0, params Action<string, List<IdleBonus>>[]) |
IdleProgressReport |
Convenience overload: builds production snapshot internally. |
ApplyOfflineProgress(IdleProgressReport, IdleResourcePool) |
void |
Grant earned resources from a progress report to the resource pool. |
GetTimeToAfford(string resourceCode, double amount, IdleResourcePool, IdleProductionSnapshot) |
float |
Seconds until the player can afford an amount. Returns 0 if affordable now, float.PositiveInfinity if no production. |
GetTimeToAffordFormatted(string resourceCode, double amount, IdleResourcePool, IdleProductionSnapshot) |
string |
Formatted time-to-afford: "Now", "Never", or formatted time string. |
GetTimeToAffordCosts(IdleCostEntry[], int currentLevel, IdleResourcePool, IdleProductionSnapshot) |
float |
Worst-case time across all cost entries for a multi-resource purchase. |
Complete Offline Progress Example
// On game load — calculate what happened while away
float offlineSeconds = 3600f; // 1 hour away
// Get current production rates with all bonuses
var snapshot = IdleProgressCalculator.GetProductionSnapshot(
generatorDatabase, generatorManager,
upgradeTracker.CollectBonusesForTarget,
prestigeManager.CollectBonusesForTarget,
milestoneTracker.CollectBonusesForTarget);
// Configure offline efficiency (step-based diminishing returns)
var offlineConfig = new IdleOfflineEfficiency
{
mode = IdleOfflineMode.Step,
steps = new IdleOfflineStep[]
{
new IdleOfflineStep { hoursThreshold = 0, efficiency = 0.8f }, // First hour: 80%
new IdleOfflineStep { hoursThreshold = 1, efficiency = 0.5f }, // 1-4 hours: 50%
new IdleOfflineStep { hoursThreshold = 4, efficiency = 0.25f }, // 4-12 hours: 25%
new IdleOfflineStep { hoursThreshold = 12, efficiency = 0.1f } // 12+ hours: 10%
}
};
// Calculate and apply
var report = IdleProgressCalculator.CalculateOfflineProgress(
snapshot, offlineSeconds, offlineConfig, maxOfflineSeconds: 86400f);
if (report.HasProgress)
{
IdleProgressCalculator.ApplyOfflineProgress(report, resourcePool);
Debug.Log(report.formattedSummary);
// "You were away for 1h 0m:\n+5.4K Gold\n+230 Wood"
}
// Time-to-afford for UI countdowns
string timeText = IdleProgressCalculator.GetTimeToAffordFormatted(
"GOLD", 10000, resourcePool, snapshot);
// "2m 34s" or "Now" or "Never"
IdleProductionSnapshot
The snapshot returned by GetProductionSnapshot() contains production rates
you can use directly in your UI:
// Display production rates
foreach (var kvp in snapshot.productionPerSecond)
{
string rate = IdleNumberFormatter.FormatRate(kvp.Value);
Debug.Log($"{kvp.Key}: {rate}"); // "GOLD: 1.5M/s"
}
// Get rate for a specific resource
double goldRate = snapshot.GetTotalRate("GOLD");
IdleProgressReport
The report returned by CalculateOfflineProgress() contains everything needed
for the "welcome back" screen:
| Property | Type | Description |
|---|---|---|
totalSecondsOffline |
float |
Total seconds the player was offline. |
efficiencyApplied |
float |
Efficiency multiplier that was applied (0.0–1.0). |
earnedResources |
Dictionary<string, double> |
Resources earned during offline time, keyed by resource code. |
formattedSummary |
string |
Pre-formatted summary for UI ("You were away for 4h 23m:..."). |
formattedTime |
string |
Formatted offline time ("4h 23m"). |
HasProgress |
bool |
Whether any meaningful progress was made. |
GetEarned(string resourceCode) |
double |
Get earned amount for a specific resource, or 0. |
IdleAutoPurchaser
Handles automatic purchasing of generators and upgrades. Tracks which generators/upgrades are automated (from Automate-type upgrades), and supports a configurable global auto-buy strategy for generators. Processes purchases on a configurable tick interval, not every frame.
Namespace: SimpleIdleForge
Type: class (pure C#, not a MonoBehaviour)
Events
| Event | Signature | Description |
|---|---|---|
OnAutoPurchased |
Action<string, string> |
Fired when an auto-purchase occurs. Args: type ("generator" or "upgrade"), code. |
Methods
| Method | Returns | Description |
|---|---|---|
SetGeneratorStrategy(IdleAutoBuyStrategy strategy) |
void |
Set the global auto-buy strategy for generators (Cheapest, MostExpensive, RoundRobin, or Disabled). |
SetTickInterval(float interval) |
void |
Set how often auto-buy checks run, in seconds. Default 1s. Minimum 0.1s. |
AutomateGenerator(string generatorCode) |
void |
Enable auto-buy for a specific generator. |
RemoveGeneratorAutomation(string generatorCode) |
void |
Disable auto-buy for a specific generator. |
AutomateUpgrade(string upgradeCode) |
void |
Enable auto-buy for a specific upgrade. |
RemoveUpgradeAutomation(string upgradeCode) |
void |
Disable auto-buy for a specific upgrade. |
IsGeneratorAutomated(string generatorCode) |
bool |
Check if a generator is automated. |
IsUpgradeAutomated(string upgradeCode) |
bool |
Check if an upgrade is automated. |
GetAutomatedGenerators() |
IReadOnlyCollection<string> |
Get all automated generator codes. |
GetAutomatedUpgrades() |
IReadOnlyCollection<string> |
Get all automated upgrade codes. |
WireToUpgradeTracker(IdleUpgradeTracker upgrades) |
void |
Subscribe to upgrade tracker events to auto-enable generator automation when Automate-type upgrades are purchased. |
Tick(float deltaTime, IdleGeneratorManager generators, IdleUpgradeTracker upgrades, IdleResourcePool resources) |
void |
Process auto-purchases. Call each frame with deltaTime. Purchases happen at the configured tick interval. |
ClearAll() |
void |
Clear all automation settings. Does not change the strategy. |
ResetForPrestige() |
void |
Reset for prestige — clears automation but keeps the strategy setting. |
Auto-Purchaser Example
// Setup
var autoPurchaser = new IdleAutoPurchaser();
autoPurchaser.SetGeneratorStrategy(IdleAutoBuyStrategy.Cheapest);
autoPurchaser.SetTickInterval(0.5f); // check every 0.5 seconds
// Wire to upgrade tracker so Automate upgrades take effect automatically
autoPurchaser.WireToUpgradeTracker(upgradeTracker);
// In Update loop
autoPurchaser.Tick(Time.deltaTime, generatorManager, upgradeTracker, resourcePool);
// Listen for auto-purchases (for UI notifications)
autoPurchaser.OnAutoPurchased += (type, code) =>
{
Debug.Log($"Auto-purchased {type}: {code}");
};
// On prestige: reset automation but keep strategy
autoPurchaser.ResetForPrestige();
IdleStatisticsTracker
Tracks cumulative lifetime statistics: total earned, total spent, peak amounts,
purchase counts, prestige count, milestones completed, play time, and more. Wire it
to all other trackers for automatic recording, or call Record methods
manually. Useful for achievement conditions, stats UI, and analytics.
Namespace: SimpleIdleForge
Type: class (pure C#, not a MonoBehaviour)
Events
| Event | Signature | Description |
|---|---|---|
OnStatChanged |
Action<string, double> |
Fired when any stat changes. Args: statName (e.g., "earned:GOLD", "prestige"), newValue. |
Methods
| Method | Returns | Description |
|---|---|---|
WireToTrackers(IdleResourcePool, IdleGeneratorManager, IdleUpgradeTracker, IdlePrestigeManager, IdleMilestoneTracker, IdleBuffManager) |
void |
Subscribe to all tracker events for automatic stat recording. All parameters are optional (pass null to skip). Call once after initializing all trackers. |
Tick(float deltaTime) |
void |
Call each frame to track total play time. |
RecordEarned(string resourceCode, double amount) |
void |
Record resources earned (production, offline, rewards). |
RecordSpent(string resourceCode, double amount) |
void |
Record resources spent (purchases, costs). |
RecordPeak(string resourceCode, double currentAmount) |
void |
Record current amount to track peak (highest ever) values. |
RecordGeneratorPurchase(string generatorCode) |
void |
Record a generator level-up. |
RecordUpgradePurchase(string upgradeCode) |
void |
Record an upgrade purchase. |
RecordPrestige() |
void |
Record a prestige execution. |
RecordMilestoneCompleted() |
void |
Record a milestone/achievement completion. |
RecordBuffUsed() |
void |
Record a buff being applied. |
RecordOfflineTime(float seconds) |
void |
Record offline time (from IdleProgressCalculator). |
GetTotalEarned(string resourceCode) |
double |
Lifetime total earned for a resource. |
GetTotalSpent(string resourceCode) |
double |
Lifetime total spent for a resource. |
GetPeakAmount(string resourceCode) |
double |
Highest amount ever held for a resource. |
GetGeneratorPurchases(string generatorCode) |
int |
Total level-ups for a generator. |
GetUpgradePurchases(string upgradeCode) |
int |
Total purchases for an upgrade. |
GetTrackedResourceCodes() |
IEnumerable<string> |
All resource codes that have earned stats. |
GetTrackedGeneratorCodes() |
IEnumerable<string> |
All generator codes that have purchase stats. |
Properties
| Property | Type | Description |
|---|---|---|
TotalPrestigeCount |
int |
Lifetime prestige count. |
TotalMilestonesCompleted |
int |
Lifetime milestones completed. |
TotalBuffsUsed |
int |
Lifetime buffs applied. |
TotalPlayTimeSeconds |
double |
Total play time in seconds. |
TotalOfflineSeconds |
double |
Total offline time in seconds. |
FormattedPlayTime |
string |
Total play time as formatted string ("4d 12h 35m"). |
Additional Query Methods
| Method | Returns | Description |
|---|---|---|
GetTotalGeneratorsPurchased() |
int |
Total generators purchased across all types. |
GetTotalUpgradesPurchased() |
int |
Total upgrades purchased across all types. |
ResetAll() |
void |
Reset all statistics to zero. Use with caution. |
CreateSnapshot() |
IdleStatisticsSnapshot |
Create a snapshot for save/load. |
RestoreFromSnapshot(IdleStatisticsSnapshot snapshot) |
void |
Restore from a snapshot. |
Statistics Tracker Example
// Setup — wire to all trackers for automatic recording
var stats = new IdleStatisticsTracker();
stats.WireToTrackers(resourcePool, generatorManager, upgradeTracker,
prestigeManager, milestoneTracker, buffManager);
// In Update loop
stats.Tick(Time.deltaTime);
// After offline progress is applied
stats.RecordOfflineTime(offlineSeconds);
// Display in Stats UI
statsText.text = $@"Play Time: {stats.FormattedPlayTime}
Total Gold Earned: {IdleNumberFormatter.Format(stats.GetTotalEarned("GOLD"))}
Peak Gold: {IdleNumberFormatter.Format(stats.GetPeakAmount("GOLD"))}
Generators Purchased: {stats.GetTotalGeneratorsPurchased()}
Upgrades Purchased: {stats.GetTotalUpgradesPurchased()}
Prestiges: {stats.TotalPrestigeCount}
Achievements: {stats.TotalMilestonesCompleted}";
Static Utilities
These static classes provide core calculations used throughout the system. You can also call them directly in your own code for UI formatting, cost previews, bonus resolution, and condition evaluation.
IdleNumberFormatter
Formats large numbers for idle game UI. Supports suffix notation (K/M/B/T/Qa/Qi/...), scientific notation, engineering notation, and full numbers with commas. Also formats time durations and per-second rates.
| Method | Returns | Description |
|---|---|---|
Format(double value, IdleNumberFormat format = Short, int precision = 2) |
string |
Format a number. Examples: "1.50K", "1.50e3", "1.50x10^3", "1,500". |
FormatTime(float seconds) |
string |
Format seconds as readable time. "45s", "2m 34s", "1h 23m", "4d 12h". |
FormatRate(double perSecond, IdleNumberFormat format = Short, int precision = 2) |
string |
Format a per-second rate. "1.50M/s". |
// Number formatting
IdleNumberFormatter.Format(1500) // "1.50K"
IdleNumberFormatter.Format(1500000) // "1.50M"
IdleNumberFormatter.Format(1.5e12) // "1.50T"
IdleNumberFormatter.Format(1.5e15) // "1.50Qa"
IdleNumberFormatter.Format(1500, IdleNumberFormat.Scientific) // "1.50e3"
IdleNumberFormatter.Format(1500, IdleNumberFormat.Full) // "1,500"
// Time formatting
IdleNumberFormatter.FormatTime(45) // "45s"
IdleNumberFormatter.FormatTime(154) // "2m 34s"
IdleNumberFormatter.FormatTime(5000) // "1h 23m"
IdleNumberFormatter.FormatTime(90000) // "1d 1h"
// Rate formatting
IdleNumberFormatter.FormatRate(1500000) // "1.50M/s"
IdleCostCalculator
Computes purchase costs with scaling formulas. Handles single-level costs, bulk costs (geometric series for Exponential), and max-affordable calculations (O(1) for Exponential via logarithm).
| Method | Returns | Description |
|---|---|---|
GetCostAtLevel(double baseCost, int level, IdleCostScaling scaling, double factor) |
double |
Cost to purchase at a given level (0-based). |
GetTotalCost(double baseCost, int fromLevel, int toLevel, IdleCostScaling scaling, double factor) |
double |
Total cost to purchase from fromLevel to toLevel (exclusive). Uses closed-form for Exponential/Linear. |
GetMaxAffordableLevels(double baseCost, int currentLevel, double budget, IdleCostScaling scaling, double factor) |
int |
Maximum levels affordable with a budget. O(1) for Exponential via logarithm. |
CanAffordGenerator(IdleGeneratorDefinition gen, int currentLevel, Func<string, double> getResourceAmount) |
bool |
Check if a generator purchase is affordable across all cost entries. |
CanAffordUpgrade(IdleUpgradeDefinition upgrade, int purchaseCount, Func<string, double> getResourceAmount) |
bool |
Check if an upgrade purchase is affordable across all cost entries. |
// Cost at level 50 with exponential scaling (Cookie Clicker uses 1.15)
double cost = IdleCostCalculator.GetCostAtLevel(10, 50, IdleCostScaling.Exponential, 1.15);
// 10 * 1.15^50 = ~10,836
// Total cost to buy levels 0 through 49 (50 levels total)
double totalCost = IdleCostCalculator.GetTotalCost(10, 0, 50, IdleCostScaling.Exponential, 1.15);
// How many levels can I afford with 100,000 gold?
int maxLevels = IdleCostCalculator.GetMaxAffordableLevels(10, 0, 100000,
IdleCostScaling.Exponential, 1.15);
IdleBonusResolver
Resolves stacked bonuses in a strict order. This is the single source of truth for how bonuses combine. The pipeline is:
finalValue = (base + flatSum) * (1 + percentSum) * multiply1 * multiply2 * ...
| Method | Returns | Description |
|---|---|---|
Resolve(double baseValue, List<IdleBonus> bonuses) |
IdleBonusResult |
Resolve a base value with a list of bonuses. Returns a result struct with baseValue, flatTotal, percentTotal, multiplyTotal, finalValue, and bonusCount. |
// Example: base production of 100/s with upgrades
var bonuses = new List<IdleBonus>
{
new IdleBonus(IdleBonusType.AddFlat, 50, "upgrade:EXTRA_MINERS"), // +50 flat
new IdleBonus(IdleBonusType.AddPercent, 0.5, "upgrade:BETTER_PICKS"), // +50%
new IdleBonus(IdleBonusType.Multiply, 2.0, "prestige:REBIRTH"), // x2
};
var result = IdleBonusResolver.Resolve(100, bonuses);
// (100 + 50) * (1 + 0.5) * 2.0 = 450
Debug.Log($"Base: {result.baseValue}, Final: {result.finalValue}"); // "Base: 100, Final: 450"
IdleConditionEvaluator
Shared utility for evaluating comparison conditions. Used internally by IdleMilestoneTracker, IdleUpgradeTracker, and IdleGeneratorManager, but you can call it directly too.
| Method | Returns | Description |
|---|---|---|
Evaluate(double currentValue, IdleComparison comparison, double targetValue) |
bool |
Evaluate a comparison between two values. |
EvaluateAllPrerequisites(IdlePrerequisite[] prerequisites, Func<IdleConditionType, string, double> valueProvider) |
bool |
Evaluate all prerequisites in an array. Returns true if ALL are met (empty = true). |
IdlePrestigeCalculator
Computes prestige rewards and inverse lookups. Supports four formula types: Linear, Sqrt (most common), Log, and Polynomial.
| Method | Returns | Description |
|---|---|---|
CalculatePrestigeReward(IdlePrestigeDefinition prestige, double sourceAmount) |
double |
Calculate how many prestige points would be earned from a source amount. |
GetSourceAmountForReward(IdlePrestigeDefinition prestige, double desiredReward) |
double |
Inverse lookup: how much source amount is needed to earn N points. |
CanPrestige(IdlePrestigeDefinition prestige, double currentSourceAmount) |
bool |
Check if prestige is available (meets minimum and would earn points). |
Snapshot Serialization
Every runtime tracker has CreateSnapshot() and RestoreFromSnapshot()
methods. The IdleGameSnapshot class bundles all tracker snapshots into a
single serializable object. All snapshot classes are [Serializable] and
compatible with JsonUtility.ToJson().
Complete Save/Load Pattern
// ── SAVE ──
var gameSnapshot = new IdleGameSnapshot
{
timestampTicks = DateTime.UtcNow.Ticks,
resources = resourcePool.CreateSnapshot(),
generators = generatorManager.CreateSnapshot(),
upgrades = upgradeTracker.CreateSnapshot(),
prestige = prestigeManager.CreateSnapshot(),
milestones = milestoneTracker.CreateSnapshot(),
buffs = buffManager.CreateSnapshot()
};
string json = JsonUtility.ToJson(gameSnapshot);
PlayerPrefs.SetString("idle_save", json);
// ── LOAD ──
string savedJson = PlayerPrefs.GetString("idle_save", "");
if (!string.IsNullOrEmpty(savedJson))
{
var snapshot = JsonUtility.FromJson<IdleGameSnapshot>(savedJson);
resourcePool.RestoreFromSnapshot(snapshot.resources);
generatorManager.RestoreFromSnapshot(snapshot.generators);
upgradeTracker.RestoreFromSnapshot(snapshot.upgrades);
prestigeManager.RestoreFromSnapshot(snapshot.prestige);
milestoneTracker.RestoreFromSnapshot(snapshot.milestones);
buffManager.RestoreFromSnapshot(snapshot.buffs);
// Calculate offline progress
long savedTicks = snapshot.timestampTicks;
float offlineSeconds = (float)(DateTime.UtcNow - new DateTime(savedTicks)).TotalSeconds;
// ... use IdleProgressCalculator to calculate and apply offline earnings ...
}
Snapshot Types
| Type | Created By | Contents |
|---|---|---|
IdleGameSnapshot |
You (manual construction) | timestampTicks + all per-component snapshots |
IdleResourcePoolSnapshot |
IdleResourcePool.CreateSnapshot() |
List of ResourceAmountEntry (resourceCode + amount) |
IdleGeneratorManagerSnapshot |
IdleGeneratorManager.CreateSnapshot() |
List of GeneratorLevelEntry (generatorCode + level + timerElapsed) |
IdleUpgradeTrackerSnapshot |
IdleUpgradeTracker.CreateSnapshot() |
List of UpgradePurchaseEntry (upgradeCode + purchaseCount) |
IdlePrestigeManagerSnapshot |
IdlePrestigeManager.CreateSnapshot() |
List of PrestigeStateEntry (prestigeCode + prestigeCount + totalCurrencyEarned) |
IdleMilestoneTrackerSnapshot |
IdleMilestoneTracker.CreateSnapshot() |
List of completed codes + MilestoneRepeatEntry for repeatables |
IdleBuffManagerSnapshot |
IdleBuffManager.CreateSnapshot() |
List of IdleBuffSnapshot (full buff state). Expired buffs skipped on restore. |
IdleStatisticsSnapshot |
IdleStatisticsTracker.CreateSnapshot() |
Earned/spent/peaks per resource, purchases per generator/upgrade, lifetime counters |
API Hook Interfaces
Simple Idle Forge provides three interfaces that you implement in your own code. We never touch ads, IAP, analytics, or platform-specific systems. Your implementation decides where rewards come from, how data is stored, and how notifications are displayed.
IIdleSaveHandler
Implement this to handle persistence. Your implementation decides the storage backend (PlayerPrefs, JSON file, cloud save, etc.).
| Method | Returns | Description |
|---|---|---|
SaveState(IdleGameSnapshot snapshot) |
void |
Save the complete game state. |
LoadState() |
IdleGameSnapshot |
Load the complete game state. Returns null if no save exists. |
GetOfflineSeconds() |
float |
Get the number of seconds since the last save (for offline progress). |
HasSave() |
bool |
Check if a save exists. |
DeleteSave() |
void |
Delete the saved state. |
public class MyPlayerPrefsSaveHandler : IIdleSaveHandler
{
private const string SaveKey = "idle_game_save";
private const string TimestampKey = "idle_game_timestamp";
public void SaveState(IdleGameSnapshot snapshot)
{
string json = JsonUtility.ToJson(snapshot);
PlayerPrefs.SetString(SaveKey, json);
PlayerPrefs.SetString(TimestampKey, DateTime.UtcNow.Ticks.ToString());
PlayerPrefs.Save();
}
public IdleGameSnapshot LoadState()
{
string json = PlayerPrefs.GetString(SaveKey, "");
if (string.IsNullOrEmpty(json)) return null;
return JsonUtility.FromJson<IdleGameSnapshot>(json);
}
public float GetOfflineSeconds()
{
string ticksStr = PlayerPrefs.GetString(TimestampKey, "");
if (string.IsNullOrEmpty(ticksStr)) return 0;
long ticks = long.Parse(ticksStr);
return (float)(DateTime.UtcNow - new DateTime(ticks)).TotalSeconds;
}
public bool HasSave() => PlayerPrefs.HasKey(SaveKey);
public void DeleteSave() { PlayerPrefs.DeleteKey(SaveKey); PlayerPrefs.DeleteKey(TimestampKey); }
}
IIdleRewardHandler
Implement this to handle ad rewards, IAP boosts, daily logins, or any external reward source. The idle system calls your implementation when rewards are processed.
| Method | Returns | Description |
|---|---|---|
OnRewardClaimed(string rewardType, string resourceCode, double amount) |
void |
Called when a reward is claimed and applied to the game state. |
IsRewardAvailable(string rewardType) |
bool |
Check if a reward type is currently available (e.g., ad loaded, IAP owned). |
GetRewardMultiplier(string rewardType) |
double |
Get the multiplier for a reward type (e.g., 2.0 for "double production" ad). |
GetRewardDuration(string rewardType) |
float |
Get the duration in seconds for a timed reward (e.g., 120 for "2 minute boost"). |
IIdleNotificationHandler
Implement this to handle game notifications and UI updates. Your implementation decides how notifications are displayed (toast, popup, log, HUD, etc.).
| Method | Returns | Description |
|---|---|---|
OnMilestoneReached(string milestoneCode, string displayName, string description) |
void |
Called when a milestone is completed. |
OnPrestigeAvailable(string prestigeCode, double potentialReward) |
void |
Called when prestige becomes available for the first time. |
OnOfflineProgressReady(IdleProgressReport report) |
void |
Called when offline progress has been calculated and is ready to display. |
OnGeneratorMilestoneReached(string generatorCode, int level, string milestoneDescription) |
void |
Called when a generator milestone threshold is reached. |
OnBuffExpired(string buffId) |
void |
Called when a temporary buff expires. |
Events Reference
Complete table of every event across all runtime trackers. Subscribe to these in your game controller for UI updates, sound effects, analytics, and state synchronization.
| Class | Event | Signature | When Fired |
|---|---|---|---|
IdleResourcePool |
OnResourceChanged |
Action<string, double, double> |
Any resource amount changes (resourceCode, oldAmount, newAmount) |
IdleResourcePool |
OnCapReached |
Action<string, double> |
A resource hits its cap (resourceCode, capAmount) |
IdleResourcePool |
OnResourceDepleted |
Action<string> |
A resource reaches zero (resourceCode) |
IdleGeneratorManager |
OnGeneratorLevelChanged |
Action<string, int, int> |
A generator is purchased or leveled (code, oldLevel, newLevel) |
IdleGeneratorManager |
OnGeneratorUnlocked |
Action<string> |
A generator is unlocked (code) |
IdleGeneratorManager |
OnMilestoneReached |
Action<string, IdleGeneratorMilestone> |
A generator milestone threshold is crossed (code, milestone) |
IdleGeneratorManager |
OnGeneratorAutomated |
Action<string> |
A generator becomes automated (code) |
IdleGeneratorManager |
OnTimerCompleted |
Action<string> |
A Timer-mode generator completes a cycle (code) |
IdleUpgradeTracker |
OnUpgradePurchased |
Action<string, int> |
An upgrade is purchased (code, newPurchaseCount) |
IdleUpgradeTracker |
OnAutomatorPurchased |
Action<string, string> |
An Automate-type upgrade is purchased (upgradeCode, targetCode) |
IdleUpgradeTracker |
OnUnlockPurchased |
Action<string, string> |
An Unlock-type upgrade is purchased (upgradeCode, targetCode) |
IdlePrestigeManager |
OnPrestigeExecuted |
Action<string, double> |
A prestige is executed (prestigeCode, pointsEarned) |
IdlePrestigeManager |
OnPrestigeAvailable |
Action<string> |
A prestige becomes available (prestigeCode) |
IdleMilestoneTracker |
OnMilestoneCompleted |
Action<string, IdleMilestoneDefinition> |
A milestone is completed for the first time (code, definition) |
IdleMilestoneTracker |
OnMilestoneRepeated |
Action<string, int> |
A repeatable milestone is completed again (code, completionCount) |
IdleMilestoneTracker |
OnMilestoneProgress |
Action<string, double, double> |
Progress reported for an incomplete milestone (code, currentValue, targetValue) |
IdleBuffManager |
OnBuffApplied |
Action<string, IdleActiveBuff> |
A buff is applied (buffId, buff) |
IdleBuffManager |
OnBuffExpired |
Action<string> |
A buff expires naturally (buffId) |
IdleBuffManager |
OnBuffRemoved |
Action<string> |
A buff is manually removed (buffId) |
IdleAutoPurchaser |
OnAutoPurchased |
Action<string, string> |
An auto-purchase occurs (type "generator"/"upgrade", code) |
IdleStatisticsTracker |
OnStatChanged |
Action<string, double> |
Any stat changes (statName, newValue) |
Enums Reference
All enums in the SimpleIdleForge namespace with every value and its meaning.
IdleCostScaling
Cost scaling formula type for generators and repeatable upgrades.
| Value | Formula | Description |
|---|---|---|
None |
cost(n) = baseCost |
No scaling — cost stays constant. |
Linear |
cost(n) = baseCost + (n * factor) |
Linear increase per level. |
Exponential |
cost(n) = baseCost * factor^n |
The most common idle game formula. Cookie Clicker uses factor = 1.15. |
Polynomial |
cost(n) = baseCost * n^factor |
Power curve. Factor 2 = quadratic, factor 0.5 = sub-quadratic. |
Logarithmic |
cost(n) = baseCost * (1 + factor * ln(n+1)) |
Gentle late-game curve. Costs grow very slowly at high levels. |
IdleBonusType
Bonus and effect type for upgrades, milestones, prestige bonuses, and buffs.
| Value | Description |
|---|---|
Multiply |
Multiply the target value. Applied last in the bonus pipeline. Each multiplier stacks multiplicatively. |
AddFlat |
Add a flat amount to the base value. Applied first in the bonus pipeline. |
AddPercent |
Add a percentage of the base value. All percents are summed before multiplying. 0.5 = +50%. |
Unlock |
Unlock a previously locked feature, generator, or upgrade. Not a numeric bonus. |
Automate |
Automate purchasing of a target generator. Not a numeric bonus. |
ResourceGrant |
Grant a one-time resource amount (from milestones or prestige rewards). Not a production bonus. |
Speed |
Modify timer interval for Timer-mode generators. Formula: interval / (1 + totalSpeed). Positive = faster. |
IdleProductionMode
Production mode for generators.
| Value | Description |
|---|---|
Continuous |
Produces resources every frame at a continuous rate (X per second). Classic Cookie Clicker style. |
Timer |
Produces resources in batches every N seconds. Adventure Capitalist style. Requires manual collection unless automated. |
IdleTargetType
Target type for upgrades, bonuses, and buffs.
| Value | Description |
|---|---|
Generator |
Affects a specific generator (identified by targetCode). |
Resource |
Affects a specific resource (identified by targetCode). |
Global |
Affects everything globally. targetCode is ignored or empty. |
IdleResetTarget
Target type for prestige reset rules.
| Value | Description |
|---|---|
Resource |
Reset a resource amount. targetCode = specific code or "ALL". |
Generator |
Reset generator levels. targetCode = specific code or "ALL". |
Upgrade |
Reset upgrade purchase counts. targetCode = specific code or "ALL". |
IdleConditionType
Condition type for prerequisites and milestone conditions.
| Value | Description |
|---|---|
ResourceAmount |
Check a resource's current amount. |
GeneratorLevel |
Check a generator's current level. |
TotalGenerators |
Check total generators owned across all types. |
UpgradePurchased |
Check if an upgrade has been purchased (count > 0). |
PrestigeCount |
Check prestige count for a layer. |
TotalProduction |
Check total production rate for a resource. |
MilestoneCompleted |
Check if a milestone/achievement has been completed. |
IdleComparison
Comparison operator for prerequisites and milestone conditions.
| Value | Operator | Description |
|---|---|---|
Equals |
== |
Current value equals target (with epsilon tolerance). |
NotEquals |
!= |
Current value does not equal target. |
GreaterThan |
> |
Current value is strictly greater than target. |
LessThan |
< |
Current value is strictly less than target. |
GreaterOrEqual |
>= |
Current value is greater than or equal to target. The most common operator for milestones. |
LessOrEqual |
<= |
Current value is less than or equal to target. |
IdlePrestigeFormula
Prestige reward formula type. Controls how prestige points scale with source amount.
| Value | Formula | Description |
|---|---|---|
Linear |
floor(scale * (source / base)) |
Points grow linearly. Simple but rarely used — too generous at high amounts. |
Sqrt |
floor(scale * sqrt(source / base)) |
The most common formula. Diminishing returns encourage repeated prestiges. |
Log |
floor(scale * log10(source / base + 1)) |
Very aggressive diminishing returns. Points grow extremely slowly. |
Polynomial |
floor(scale * (source / base)^exponent) |
Configurable curve. exponent < 1 for diminishing, > 1 for accelerating. |
IdleNumberFormat
Number display format for idle game UI.
| Value | Example Output | Description |
|---|---|---|
Short |
1.50K, 1.50M, 1.50B, 1.50T, 1.50Qa | Suffix notation. The most common format for idle games. |
Scientific |
1.50e3, 1.50e6, 1.50e9 | Scientific notation. Compact for extremely large numbers. |
Engineering |
1.50x10^3, 1.50x10^6 | Engineering notation. Exponent always a multiple of 3. |
Full |
1,500 / 1,500,000 | Full number with comma separators. Falls back to scientific for very large values. |
IdleAutoBuyStrategy
Strategy for the IdleAutoPurchaser's global generator auto-buy behavior.
| Value | Description |
|---|---|
Cheapest |
Buy the cheapest affordable generator first. Efficient for steady growth. |
MostExpensive |
Buy the most expensive affordable generator first. Best value per purchase. |
RoundRobin |
Buy generators in round-robin order. Keeps levels balanced. |
Disabled |
No global auto-buying. Individual generators can still be automated via Automate upgrades. |
IdleOfflineMode
How offline efficiency scales with time away. Used by IdleOfflineEfficiency.
| Value | Description |
|---|---|
Flat |
Constant efficiency regardless of time (e.g., always 50%). Simplest option. |
Curve |
Efficiency follows a Unity AnimationCurve over hours offline. Full designer control. |
Step |
Efficiency changes at defined hour breakpoints. Easy to configure with clear tiers. |