Skip to main content

Overview

Godot supports C# as a scripting language through .NET integration. C# offers strong typing, powerful language features, and access to the entire .NET ecosystem.
C# support requires a special build of Godot with Mono/.NET enabled. Download the .NET version from the official Godot website.

Getting Started

Prerequisites

1

Download .NET Godot

Download the Godot .NET version from godotengine.org. The standard version doesn’t include C# support.
2

Install .NET SDK

Install the .NET SDK version 6.0 or later.Verify installation:
dotnet --version
3

Create a C# project

In Godot, go to Project → Project Settings → Dotnet and click Create C# Solution.This generates the necessary .csproj and .sln files.

Your First C# Script

Player.cs
using Godot;
using System;

public partial class Player : CharacterBody2D
{
    // Called when the node enters the scene tree for the first time
    public override void _Ready()
    {
        GD.Print("Player is ready!");
    }

    // Called every frame. 'delta' is the elapsed time since the previous frame
    public override void _Process(double delta)
    {
        // Update logic here
    }
}
All C# classes that extend Godot nodes must be marked as partial. This is required for Godot’s source generators.

C# vs GDScript

Syntax Comparison

extends CharacterBody2D

const SPEED = 300.0
const JUMP_VELOCITY = -400.0

func _physics_process(delta: float) -> void:
	if not is_on_floor():
		velocity += get_gravity() * delta
	
	if Input.is_action_just_pressed("ui_accept") and is_on_floor():
		velocity.y = JUMP_VELOCITY
	
	var direction := Input.get_axis("ui_left", "ui_right")
	if direction:
		velocity.x = direction * SPEED
	else:
		velocity.x = move_toward(velocity.x, 0, SPEED)
	
	move_and_slide()

Key Differences

GDScript: snake_case for methods and variablesC#: PascalCase for methods, properties, and public members; camelCase for private fields
// C# naming
public void MovePlayer() { }  // Method
public int Health { get; set; }  // Property
private float speed = 10.0f;  // Private field
C# is statically typed by default, while GDScript supports gradual typing:
// C# - must specify types
int health = 100;
string playerName = "Hero";
Vector2 position = new Vector2(0, 0);
C# uses properties instead of GDScript’s set and get:
private int _health = 100;

public int Health
{
    get => _health;
    set
    {
        _health = value;
        EmitSignal(SignalName.HealthChanged, _health);
    }
}
C# uses generic collections:
// Godot arrays
Godot.Collections.Array items = new();
Godot.Collections.Array<string> names = new();

// .NET collections
List<string> dotnetList = new();
Dictionary<string, int> scores = new();

Exported Variables

Use the [Export] attribute to expose variables in the Inspector:
public partial class Player : CharacterBody2D
{
    [Export]
    public float Speed { get; set; } = 100.0f;
    
    [Export]
    public int MaxHealth { get; set; } = 100;
    
    [Export]
    public string PlayerName { get; set; } = "Hero";
    
    // Export with range
    [Export(PropertyHint.Range, "0,100,")]
    public int Volume { get; set; } = 50;
    
    // Export node paths
    [Export]
    public Node2D Target { get; set; }
    
    [Export]
    public NodePath TargetPath { get; set; }
    
    // Export file paths
    [Export(PropertyHint.File)]
    public string ConfigFile { get; set; }
    
    // Export enums
    public enum CharacterClass { Warrior, Mage, Rogue }
    
    [Export]
    public CharacterClass Class { get; set; } = CharacterClass.Warrior;
}

Signals in C#

Declaring Signals

public partial class Player : Node
{
    // Declare signals
    [Signal]
    public delegate void HealthChangedEventHandler(int newHealth);
    
    [Signal]
    public delegate void PlayerDiedEventHandler();
    
    [Signal]
    public delegate void ItemCollectedEventHandler(string itemName, int quantity);
    
    private int _health = 100;
    
    public int Health
    {
        get => _health;
        set
        {
            _health = value;
            EmitSignal(SignalName.HealthChanged, _health);
            
            if (_health <= 0)
            {
                EmitSignal(SignalName.PlayerDied);
            }
        }
    }
}

Connecting Signals

public override void _Ready()
{
    // Connect to signal
    HealthChanged += OnHealthChanged;
    PlayerDied += OnPlayerDied;
}

private void OnHealthChanged(int newHealth)
{
    GD.Print($"Health is now: {newHealth}");
}

private void OnPlayerDied()
{
    GD.Print("Game Over");
}

Node References

Getting Nodes

public partial class Player : Node2D
{
    // Get nodes in _Ready()
    private Sprite2D _sprite;
    private AnimationPlayer _animation;
    private ProgressBar _healthBar;
    
    public override void _Ready()
    {
        _sprite = GetNode<Sprite2D>("Sprite2D");
        _animation = GetNode<AnimationPlayer>("AnimationPlayer");
        _healthBar = GetNode<ProgressBar>("UI/HealthBar");
        
        // Using NodePath
        var enemy = GetNode<CharacterBody2D>(new NodePath("../Enemy"));
        
        // Safer alternative with null check
        if (HasNode("Sprite2D"))
        {
            _sprite = GetNode<Sprite2D>("Sprite2D");
        }
    }
}

OnReady Pattern

C# doesn’t have @onready, but you can use lazy initialization:
public partial class Player : Node2D
{
    private Sprite2D _sprite;
    private Sprite2D Sprite => _sprite ??= GetNode<Sprite2D>("Sprite2D");
    
    public override void _Process(double delta)
    {
        Sprite.Visible = true;  // Automatically initialized on first access
    }
}

Using .NET Libraries

One major advantage of C# is access to the entire .NET ecosystem:
using Godot;
using System;
using System.Linq;
using System.Collections.Generic;
using System.Text.Json;
using System.IO;
using System.Threading.Tasks;

public partial class GameManager : Node
{
    // LINQ queries
    public void ProcessScores(List<int> scores)
    {
        var topScores = scores
            .Where(s => s > 1000)
            .OrderByDescending(s => s)
            .Take(10)
            .ToList();
        
        var average = scores.Average();
        GD.Print($"Average score: {average}");
    }
    
    // JSON serialization
    public void SaveGameData()
    {
        var data = new GameData
        {
            PlayerName = "Hero",
            Level = 10,
            Score = 5000
        };
        
        string json = JsonSerializer.Serialize(data);
        File.WriteAllText("user://savegame.json", json);
    }
    
    // Async/await
    public async Task LoadDataAsync()
    {
        await Task.Delay(1000);  // Simulate loading
        GD.Print("Data loaded!");
    }
}

public class GameData
{
    public string PlayerName { get; set; }
    public int Level { get; set; }
    public int Score { get; set; }
}
Be cautious with blocking async operations in Godot’s main thread. Use await properly or consider using Godot’s built-in threading.

Performance Considerations

C# is compiled

C# is compiled to native code, offering better performance for CPU-intensive tasks.

Startup overhead

C# has slightly longer initial startup time due to .NET runtime initialization.

Strong typing benefits

Static typing enables better compiler optimizations and catches errors at compile time.

Memory management

C# uses garbage collection, which can cause occasional frame hitches if not managed carefully.

Building and Exporting

Build Configuration

C# projects require compilation before running:
1

Build the project

In Godot, click Build in the top-right corner or press Alt+B.
2

Configure export templates

Download the .NET export templates for your target platform from the Godot download page.
3

Export settings

In Project → Export, ensure Embed .NET Runtime is enabled for standalone executables.

NuGet Packages

You can use NuGet packages in your Godot project:
dotnet add package Newtonsoft.Json
Or edit the .csproj file directly:
<ItemGroup>
  <PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
</ItemGroup>

IDE Setup

Install the C# extension:
  1. Install C# Dev Kit
  2. Open your Godot project folder
  3. VS Code will detect the .sln file automatically
External Editor Settings:
  • Go to Editor → Editor Settings → Dotnet → Editor
  • Set External Editor to Visual Studio Code

Common Patterns

Singleton Pattern

GameManager.cs
public partial class GameManager : Node
{
    private static GameManager _instance;
    
    public static GameManager Instance
    {
        get => _instance;
    }
    
    public int Score { get; set; }
    
    public override void _EnterTree()
    {
        if (_instance != null)
        {
            QueueFree();
            return;
        }
        
        _instance = this;
    }
    
    public override void _ExitTree()
    {
        if (_instance == this)
        {
            _instance = null;
        }
    }
}

// Usage
GameManager.Instance.Score += 100;

Resource Management

public partial class Player : Node2D
{
    private AudioStreamPlayer _audioPlayer;
    
    public override void _Ready()
    {
        // Load resources
        var texture = GD.Load<Texture2D>("res://player.png");
        var scene = GD.Load<PackedScene>("res://bullet.tscn");
        
        // Preload in C# (loaded at class initialization)
        // Not available - use GD.Load instead
    }
    
    public void PlaySound()
    {
        var audioStream = GD.Load<AudioStream>("res://sounds/jump.ogg");
        _audioPlayer.Stream = audioStream;
        _audioPlayer.Play();
    }
}

Debugging

public partial class Player : CharacterBody2D
{
    public override void _Ready()
    {
        // Print to console
        GD.Print("Player ready");
        GD.PrintErr("Error message");
        GD.PrintRaw("No newline");
        
        // Formatted output
        GD.Print($"Player health: {Health}");
        
        // Assertions
        System.Diagnostics.Debug.Assert(Health > 0, "Health must be positive");
        
        // Debugger breakpoint (when attached)
        System.Diagnostics.Debugger.Break();
    }
}
Attach the Visual Studio or VS Code debugger to Godot for breakpoint debugging. Run Godot, then attach to the Godot process.

Next Steps

GDScript

Learn Godot’s built-in scripting language

GDExtension

Create native C++ extensions

Visual Scripting

Explore node-based programming

Build docs developers (and LLMs) love