godot-genre-simulation

Expert blueprint for simulation and tycoon games (SimCity, RollerCoaster Tycoon, Factorio, Two Point Hospital) covering economy management, time progression, interconnected systems, NPC simulation, and feedback loops. Use when building management sims, tycoon games, city builders, or resource optimization games. Keywords tycoon, economy system, resource management, time scale, feedback loop, progression unlock, simulation tick.

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "godot-genre-simulation" with this command: npx skills add thedivergentai/gd-agentic-skills/thedivergentai-gd-agentic-skills-godot-genre-simulation

Genre: Simulation / Tycoon

Optimization, systems mastery, and satisfying feedback loops define management games.

Available Scripts

sim_tick_manager.gd

Expert tick-based simulation with variable speed control and batch processing.

Core Loop

Invest → Build/Manage → Generate Income → Optimize → Expand

NEVER Do in Simulation Games

  • NEVER process every simulated entity individually in _process() — Batch updates in fixed ticks (e.g., once per second). Simulating 1000 businesses individually = 60k calls/second. Batch = 1k calls/second.
  • NEVER use floating-point for currencyfloat accumulates rounding errors. $1.10 * 3 = $3.2999999. Use int cents: 1100 * 3 = 3300 cents = $33.00 exact.
  • NEVER let early game be dull waiting — Front-load decision points. First 5 minutes must hook players. Don't make them wait 10 real minutes for first unlock.
  • NEVER use linear cost scaling — Buildings that cost 10 * level break at level 100 (1000 cost). Use exponential: BASE * pow(1.5, level) creates meaningful trade-offs.
  • NEVER hide critical numbers from players — Show income/expense breakdown, production rates, efficiency %. Optimization games require transparency.
  • NEVER allow infinite resource stacking without consequence — Storage caps create interesting decisions. Unlimited resources remove strategy.
  • NEVER update UI labels every frame — Updating 50 UI labels @60fps = 3000 updates/sec. Use signals: update only when value changes.

Economy Design

The heart of any tycoon game is its economy. Key principle: multiple interconnected resources that force trade-offs.

Multi-Resource System

class_name TycoonEconomy
extends Node

signal resource_changed(resource_type: String, amount: float)
signal went_bankrupt

var resources: Dictionary = {
    "money": 10000.0,
    "reputation": 50.0,  # 0-100
    "workers": 0,
    "materials": 100.0,
    "energy": 100.0
}

var resource_caps: Dictionary = {
    "reputation": 100.0,
    "workers": 50,
    "energy": 1000.0
}

func modify_resource(type: String, amount: float) -> bool:
    if amount < 0 and resources[type] + amount < 0:
        if type == "money":
            went_bankrupt.emit()
        return false  # Can't go negative
    
    resources[type] = clamp(
        resources[type] + amount,
        0,
        resource_caps.get(type, INF)
    )
    resource_changed.emit(type, resources[type])
    return true

Income/Expense Tracking

class_name FinancialTracker
extends Node

var income_sources: Dictionary = {}  # source_name: amount_per_tick
var expense_sources: Dictionary = {}

signal financial_update(profit: float, income: float, expenses: float)

func calculate_tick() -> float:
    var total_income := 0.0
    var total_expenses := 0.0
    
    for source in income_sources.values():
        total_income += source
    
    for source in expense_sources.values():
        total_expenses += source
    
    var profit := total_income - total_expenses
    financial_update.emit(profit, total_income, total_expenses)
    return profit

Time System

Simulation games need controllable time:

class_name SimulationTime
extends Node

signal time_tick(delta_game_hours: float)
signal day_changed(day: int)
signal speed_changed(new_speed: int)

enum Speed { PAUSED, NORMAL, FAST, ULTRA }

@export var seconds_per_game_hour := 30.0  # Real seconds

var current_speed := Speed.NORMAL
var speed_multipliers := {
    Speed.PAUSED: 0.0,
    Speed.NORMAL: 1.0,
    Speed.FAST: 3.0,
    Speed.ULTRA: 10.0
}

var current_hour := 8.0  # Start at 8 AM
var current_day := 1

func _process(delta: float) -> void:
    if current_speed == Speed.PAUSED:
        return
    
    var game_delta := (delta / seconds_per_game_hour) * speed_multipliers[current_speed]
    current_hour += game_delta
    
    if current_hour >= 24.0:
        current_hour -= 24.0
        current_day += 1
        day_changed.emit(current_day)
    
    time_tick.emit(game_delta)

func set_speed(speed: Speed) -> void:
    current_speed = speed
    speed_changed.emit(speed)

Entity Management

Workers/NPCs

class_name Worker
extends Node

enum State { IDLE, WORKING, RESTING, COMMUTING }

@export var wage_per_hour: float = 10.0
@export var skill_level: float = 1.0  # Productivity multiplier
@export var morale: float = 80.0  # 0-100

var current_state := State.IDLE
var assigned_workstation: Workstation

func update(game_hours: float) -> void:
    match current_state:
        State.WORKING:
            if assigned_workstation:
                var productivity := skill_level * (morale / 100.0)
                assigned_workstation.work(game_hours * productivity)
                morale -= game_hours * 0.5  # Working tires workers
        State.RESTING:
            morale = min(100.0, morale + game_hours * 2.0)

func calculate_hourly_cost() -> float:
    return wage_per_hour

Buildings/Facilities

class_name Facility
extends Node3D

@export var build_cost: Dictionary  # resource_type: amount
@export var operating_cost_per_hour: float = 5.0
@export var capacity: int = 5
@export var output_per_hour: Dictionary  # resource_type: amount

var assigned_workers: Array[Worker] = []
var is_operational := true
var efficiency := 1.0

func calculate_output(game_hours: float) -> Dictionary:
    if not is_operational or assigned_workers.is_empty():
        return {}
    
    var worker_efficiency := 0.0
    for worker in assigned_workers:
        worker_efficiency += worker.skill_level * (worker.morale / 100.0)
    worker_efficiency /= capacity  # Normalize to 0-1
    
    var result := {}
    for resource in output_per_hour:
        result[resource] = output_per_hour[resource] * game_hours * worker_efficiency * efficiency
    return result

Customer/Demand System

class_name CustomerSimulation
extends Node

@export var base_customers_per_hour := 10.0
@export var demand_curve: Curve  # Hour of day vs demand multiplier

var customer_queue: Array[Customer] = []

func generate_customers(game_hour: float, delta_hours: float) -> void:
    var demand_mult := demand_curve.sample(game_hour / 24.0)
    var reputation_mult := Economy.resources["reputation"] / 50.0  # 100 rep = 2x customers
    
    var customers_to_spawn := base_customers_per_hour * delta_hours * demand_mult * reputation_mult
    
    for i in int(customers_to_spawn):
        spawn_customer()

func spawn_customer() -> void:
    var customer := Customer.new()
    customer.patience = randf_range(30.0, 120.0)  # Seconds before leaving
    customer.spending_budget = randf_range(10.0, 100.0)
    customer_queue.append(customer)

Feedback Systems

Visual Feedback

# Money flying to bank, resources flowing, etc.
class_name ResourceFlowVisualizer
extends Node

func show_income(amount: float, from: Vector2, to: Vector2) -> void:
    var coin := coin_scene.instantiate()
    coin.position = from
    add_child(coin)
    
    var tween := create_tween()
    tween.tween_property(coin, "position", to, 0.5)
    tween.tween_callback(coin.queue_free)
    
    var label := Label.new()
    label.text = "+$" + str(int(amount))
    label.position = from
    add_child(label)
    
    var label_tween := create_tween()
    label_tween.tween_property(label, "position:y", label.position.y - 30, 0.5)
    label_tween.parallel().tween_property(label, "modulate:a", 0.0, 0.5)
    label_tween.tween_callback(label.queue_free)

Statistics Dashboard

class_name StatsDashboard
extends Control

@export var graph_history_hours := 24
var income_history: Array[float] = []
var expense_history: Array[float] = []

func record_financial_tick(income: float, expenses: float) -> void:
    income_history.append(income)
    expense_history.append(expenses)
    
    # Keep last N entries
    while income_history.size() > graph_history_hours:
        income_history.pop_front()
        expense_history.pop_front()
    
    queue_redraw()

func _draw() -> void:
    # Draw income/expense graph
    draw_line_graph(income_history, Color.GREEN)
    draw_line_graph(expense_history, Color.RED)

Progression & Unlocks

class_name UnlockSystem
extends Node

var unlocks: Dictionary = {
    "basic_facility": true,
    "advanced_facility": false,
    "marketing": false,
    "automation": false
}

var unlock_conditions: Dictionary = {
    "advanced_facility": {"money_earned": 50000},
    "marketing": {"reputation": 70},
    "automation": {"workers_hired": 20}
}

var progress: Dictionary = {
    "money_earned": 0.0,
    "workers_hired": 0
}

func check_unlocks() -> Array[String]:
    var newly_unlocked: Array[String] = []
    
    for unlock in unlock_conditions:
        if unlocks[unlock]:
            continue  # Already unlocked
        
        var conditions := unlock_conditions[unlock]
        var all_met := true
        
        for condition in conditions:
            if progress.get(condition, 0) < conditions[condition]:
                all_met = false
                break
        
        if all_met:
            unlocks[unlock] = true
            newly_unlocked.append(unlock)
    
    return newly_unlocked

Common Pitfalls

PitfallSolution
Economy too easy to breakExtensive balancing, soft caps, diminishing returns
Boring early gameFront-load interesting decisions, quick early progression
Information overloadProgressive disclosure, collapsible UI panels
No clear goalsMilestones, achievements, scenarios
Tedious micromanagementAutomation unlocks, batch operations

Godot-Specific Tips

  1. UI: Use Control nodes extensively, Tree for lists, GraphEdit for connections
  2. Performance: Process entities in batches, not every frame
  3. Save/Load: Convert all game state to Dictionary for JSON serialization
  4. Isometric view: Use Camera2D with orthographic projection

Reference

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

Automation

godot-master

No summary provided by upstream source.

Repository SourceNeeds Review
Automation

godot-shaders-basics

No summary provided by upstream source.

Repository SourceNeeds Review
Automation

godot-ui-theming

No summary provided by upstream source.

Repository SourceNeeds Review
Automation

godot-particles

No summary provided by upstream source.

Repository SourceNeeds Review