--- name: godot-genre-idle-clicker description: "Expert blueprint for idle/clicker games including big number handling (mantissa + exponent system), exponential growth curves (cost_growth_factor 1.15x), generator systems (auto-producers), offline progress calculation, prestige systems (reset for permanent multipliers), number formatting (K/M/B suffixes, scientific notation). Use for incremental games, idle games, or cookie clicker derivatives. Trigger keywords: idle_game, big_number, exponential_growth, generator_system, offline_progress, prestige_system, number_formatting." --- # Genre: Idle / Clicker Expert blueprint for idle/clicker games with exponential progression and prestige mechanics. ## NEVER Do (Expert Anti-Patterns) ### Economics & Math - NEVER use standard floats for currency; strictly implement a **BigNumber** (Mantissa/Exponent) system (e.g., `1.5e300`) to prevent `INF` crashes at 1e308. - NEVER use `Timer` nodes for revenue generation; strictly use a manual accumulator in `_process(delta)` to prevent drift during frame fluctuations. - NEVER hardcode generator costs or growth; strictly use an exponential formula: `Cost = BasePrice * pow(GrowthFactor, OwnedCount)` (industry standard **1.15x**). - NEVER evaluate exact float equality (`==`); strictly use `is_equal_approx()` or `>=` to prevent "stuck" progress due to precision loss. - NEVER parse scientific notation strings with `to_int()`; strictly use `to_float()` or a dedicated BigNumber parser. ### Performance & Optimization - NEVER update all UI labels every frame; strictly use **Signals** to update labels ONLY when values change, or throttle updates to 10 FPS. - NEVER ignore **Low Processor Usage Mode** for mobile; strictly enable `OS.low_processor_usage_mode = true` to preserve battery life. - NEVER instantiate/delete hundreds of text nodes per second; strictly use **Object Pooling** or `MultiMeshInstance` for click-feedback. - NEVER update massive logs by modifying the `text` property; strictly use `append_text()` to prevent main thread blocking. ### Player Experience & Persistence - NEVER ignore **Offline Progress**; strictly calculate `seconds_offline * total_revenue` using system UNIX timestamps (`Time.get_unix_time_from_system()`). - NEVER make the "Prestige" reset feel like a loss; strictly provide a global multiplier that makes the next run **significantly** faster (2-5x). - NEVER calculate offline time using `Time.get_ticks_msec()`; strictly use **Persistent UNIX timestamps** as ticks reset on app restart. - NEVER use Node hierarchies for raw data; strictly use `RefCounted` or `Resource` objects for lightweight, serializable logic. --- ## 🛠 Expert Components (scripts/) ### Original Expert Patterns - [big_number.gd](scripts/idle_performance_setup.gd) - The foundation for handling e308+ scales using Mantissa + Exponent math. - [generator.gd](scripts/precision_cost_validator.gd) - Generic template for exponential cost units and rate calculation. - [scientific_notation_formatter.gd](scripts/scientific_notation_math.gd) - readable formatting for K, M, B, T suffixes and scientific notation. ### Modular Components - [offline_progress_calculator.gd](scripts/offline_progress_calculator.gd) - Real-world delta tracking using UNIX timestamps. - [functional_income_reducer.gd](scripts/functional_income_reducer.gd) - C++ optimized array reduction for fast income summation. - [threaded_catchup_simulator.gd](scripts/threaded_catchup_simulator.gd) - WorkerThreadPool background simulation patterns. --- ## Core Loop 1. **Click**: Player performs manual action to gain currency. 2. **Buy**: Player purchases "generators" (auto-clickers). 3. **Wait**: Game plays itself, numbers go up. 4. **Upgrade**: Player buys multipliers to increase efficiency. 5. **Prestige**: Player resets progress for a permanent global multiplier. ## Skill Chain | Phase | Skills | Purpose | |-------|--------|---------| | 1. Math | `godot-gdscript-mastery` | Handling numbers larger than 64-bit float | | 2. UI | `godot-ui-containers`, `labels` | Displaying "1.5e12" or "1.5T" cleanly | | 3. Data | `godot-save-load-systems` | Saving progress, offline time calculation | | 4. Logic | `signals` | Decoupling UI from the economic simulation | | 5. Meta | `json-serialization` | Balancing hundreds of upgrades via data | ## Architecture Overview ### 1. Big Number System Standard `float` goes to `INF` around 1.8e308. Idle games often go beyond. You need a custom `BigNumber` class (Mantissa + Exponent). ```gdscript # big_number.gd class_name BigNumber var mantissa: float = 0.0 # 1.0 to 10.0 var exponent: int = 0 # Power of 10 func _init(m: float, e: int) -> void: mantissa = m exponent = e normalize() func normalize() -> void: if mantissa >= 10.0: mantissa /= 10.0 exponent += 1 elif mantissa < 1.0 and mantissa != 0.0: mantissa *= 10.0 exponent -= 1 ``` ### 2. Generator System The core entities that produce currency. ```gdscript # generator.gd class_name Generator extends Resource @export var id: String @export var base_cost: BigNumber @export var base_revenue: BigNumber @export var cost_growth_factor: float = 1.15 var count: int = 0 func get_cost() -> BigNumber: # Cost = Base * (Growth ^ Count) return base_cost.multiply(pow(cost_growth_factor, count)) ``` ### 3. Simulation Manager (Offline Progress) Calculating gains while the game was closed. ```gdscript # game_manager.gd func _ready() -> void: var last_save_time = save_data.timestamp var current_time = Time.get_unix_time_from_system() var seconds_offline = current_time - last_save_time if seconds_offline > 60: var revenue = calculate_revenue_per_second().multiply(seconds_offline) add_currency(revenue) show_welcome_back_popup(revenue) ``` ## Key Mechanics Implementation ### Prestige System (Reset) Resetting `generators` but keeping `prestige_currency`. ```gdscript func prestige() -> void: if current_money.less_than(prestige_threshold): return # Formula: Cube root of money / 1 million # (Just an example, depends on balance) var gained_keys = calculate_prestige_gain() save_data.prestige_currency += gained_keys save_data.global_multiplier = 1.0 + (save_data.prestige_currency * 0.10) # Reset save_data.money = BigNumber.new(0, 0) save_data.generators = ResetGenerators() save_game() reload_scene() ``` ### Formatting Numbers Displaying `1234567` as `1.23M`. ```gdscript static func format(bn: BigNumber) -> String: if bn.exponent < 3: return str(int(bn.mantissa * pow(10, bn.exponent))) var suffixes = ["", "K", "M", "B", "T", "Qa", "Qi"] var suffix_idx = bn.exponent / 3 if suffix_idx < suffixes.size(): return "%.2f%s" % [bn.mantissa * pow(10, bn.exponent % 3), suffixes[suffix_idx]] else: return "%.2fe%d" % [bn.mantissa, bn.exponent] ``` ## Godot-Specific Tips * **Timers**: Do NOT use `Timer` nodes for revenue generation (drifting). Use `_process(delta)` and accumulate time. * **GridContainer**: Perfect for the "Generators" list. * **Resources**: Use `.tres` files to define every generator (Farm, Mine, Factory) so you can tweak balance without touching code. ## Common Pitfalls 1. **Floating Point Errors**: Using standard `float` for money. **Fix**: Use BigNumber implementation immediately. 2. **Boring Prestige**: Resetting feels like a punishment. **Fix**: Ensure the post-prestige run is *significantly* faster (2x-5x speed). 3. **UI Lag**: Updating 50 text labels every frame. **Fix**: Only update labels when values actually change (Signal-based), or throttling updates to 10fps. --- ## 🚀 Elite Technical Implementations (Batch 09) ### 1. BigReal-Math-Structure (Handling > 1e308) Idle games often exceed the limits of 64-bit floats (~1.8e308). Use a custom `RefCounted` class to store numbers in scientific notation (mantissa + exponent), allowing for virtually infinite growth. ```gdscript class_name BigReal extends RefCounted @export var mantissa: float = 0.0 @export var exponent: int = 0 func _init(m: float = 0.0, e: int = 0) -> void: mantissa = m exponent = e _normalize() func _normalize() -> void: if mantissa == 0.0: exponent = 0 return while abs(mantissa) >= 10.0: mantissa /= 10.0 exponent += 1 while abs(mantissa) < 1.0 and mantissa != 0.0: mantissa *= 10.0 exponent -= 1 func multiply(other: BigReal) -> BigReal: return BigReal.new(mantissa * other.mantissa, exponent + other.exponent) ``` ### 2. Multi-Offline-Progression Pattern Calculate retroactively what the player earned while the game was closed using `Time.get_unix_time_from_system()`. Save the timestamp to `user://` and compare it upon relaunch. ```gdscript class_name OfflineProgressionManager extends Node signal offline_earnings_calculated(seconds_offline: float) const SAVE_PATH: String = "user://offline_save.json" func _ready() -> void: _process_offline_time() func _process_offline_time() -> void: var current_time = Time.get_unix_time_from_system() var last_time = load_timestamp() # From FileAccess var delta = current_time - last_time if delta > 60.0: offline_earnings_calculated.emit(delta) func save_timestamp() -> void: var file = FileAccess.open(SAVE_PATH, FileAccess.WRITE) file.store_string(JSON.stringify({"last_time": Time.get_unix_time_from_system()})) ``` ### 3. Particle-Batch-Juice (Manual Emission) Spawning new nodes for click-juice is expensive. Use a single `GPUParticles2D` and call `emit_particle()` manually on every click to batch spawn particles directly at the mouse coordinates. ```gdscript class_name ClickJuiceManager extends Node2D @export var click_particles: GPUParticles2D func _input(event: InputEvent) -> void: if event.is_action_pressed(&"click"): var click_pos = get_global_mouse_position() _burst_particles(click_pos) func _burst_particles(pos: Vector2) -> void: for i in range(15): var xform = Transform2D(0.0, pos) var velocity = Vector2(randf_range(-200.0, 200.0), randf_range(-200.0, 200.0)) # Direct GPU emission bypasses SceneTree overhead click_particles.emit_particle(xform, velocity, Color.WHITE, Color.WHITE, 0) ``` - Master Skill: [godot-master](../godot-master/SKILL.md)