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
Download .NET Godot
Download the Godot .NET version from godotengine.org . The standard version doesn’t include C# support.
Install .NET SDK
Install the .NET SDK version 6.0 or later. Verify installation:
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
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 ()
using Godot ;
public partial class Player : CharacterBody2D
{
public const float Speed = 300.0f ;
public const float JumpVelocity = - 400.0f ;
public override void _PhysicsProcess ( double delta )
{
Vector2 velocity = Velocity ;
if ( ! IsOnFloor ())
{
velocity += GetGravity () * ( float ) delta ;
}
if ( Input . IsActionJustPressed ( "ui_accept" ) && IsOnFloor ())
{
velocity . Y = JumpVelocity ;
}
Vector2 direction = Input . GetVector ( "ui_left" , "ui_right" , "ui_up" , "ui_down" );
if ( direction != Vector2 . Zero )
{
velocity . X = direction . X * Speed ;
}
else
{
velocity . X = Mathf . MoveToward ( Velocity . X , 0 , Speed );
}
Velocity = velocity ;
MoveAndSlide ();
}
}
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 );
Properties vs setters/getters
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
Method connection
Lambda connection
Connect method
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.
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:
Build the project
In Godot, click Build in the top-right corner or press Alt+B .
Configure export templates
Download the .NET export templates for your target platform from the Godot download page.
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
Visual Studio Code
Visual Studio
JetBrains Rider
Install the C# extension:
Install C# Dev Kit
Open your Godot project folder
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
Install Visual Studio 2022 with .NET workload
Open the .sln file generated by Godot
Set Godot as external editor in Visual Studio
External Editor Settings:
Go to Editor → Editor Settings → Dotnet → Editor
Set External Editor to Visual Studio
Install JetBrains Rider
Install the Godot support plugin
Open the .sln file
External Editor Settings:
Go to Editor → Editor Settings → Dotnet → Editor
Set External Editor to JetBrains Rider
Common Patterns
Singleton Pattern
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