Use when working with 2D-specific systems — TileMaps, parallax scrolling, 2D lights and shadows, canvas layers, particles 2D, custom drawing, and 2D meshes in Godot 4.3+
한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.
Use when working with C# in Godot — conventions, GodotSharp API differences from GDScript, project setup, and interop
한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.
설치 방법 보기설치하기 전에 에이전트에게 전달되는 지시 내용을 확인할 수 있습니다.
This skill covers C#-specific conventions, API differences from GDScript, project setup, and interop patterns. All examples are C# only. Target Godot 4.3+ with the GodotSharp NuGet package.
Related skills: csharp-signals for C# signal patterns, godot-project-setup for C# project scaffolding, godot-testing for C# testing with gdUnit4, gdextension for native C++ when C# is not enough, multithreading for C# concurrency.
| GDScript | C# Equivalent | Notes |
|---|---|---|
var x = 5 | var x = 5; or typed int x = 5; | C# var infers type at compile time |
func MyMethod() -> void: | public void MyMethod() { } | Methods are PascalCase in C# |
signal health_changed(amount: int) | [Signal] delegate void HealthChangedEventHandler(int amount); | Must use EventHandler suffix |
@export var speed: float = 100.0 | [Export] public float Speed { get; set; } = 100f; | PascalCase, property syntax |
@onready var label = $Label | private Label _label; + _label = GetNode<Label>("Label"); in _Ready() | No @onready equivalent; use _Ready() |
match value: | switch (value) { case X: break; } | C# switch also supports pattern matching |
class_name MyClass | [GlobalClass] public partial class MyClass : GodotObject { } | Requires [GlobalClass] attribute |
extends Node | public partial class MyScript : Node { } | Inheritance via : |
preload("res://scene.tscn") | GD.Load<PackedScene>("res://scene.tscn") | Loaded at runtime, not compile time |
push_error("msg") | GD.PushError("msg"); | Prints to Godot error log |
print("msg") | GD.Print("msg"); | Also: GD.PrintS(), GD.PrintT() |
node is CharacterBody2D | node is CharacterBody2D | Same keyword, same semantics |
node as CharacterBody2D | node as CharacterBody2D | Returns null on failure in both |
await signal_name | await ToSignal(source, SignalName.X); | Must use ToSignal() wrapper |
Array | Godot.Collections.Array | Not System.Collections.Generic.List<T> |
Dictionary | Godot.Collections.Dictionary | Not System.Collections.Generic.Dictionary<K,V> |
Godot auto-generates the .csproj when you create the first C# script via Script > New Script > C#. Do not edit the generated file structure manually — let the editor manage it.
MyProject/
├── MyProject.csproj # Auto-generated, edit only for NuGet packages
├── MyProject.sln # Auto-generated solution file
├── project.godot
└── scripts/
└── Player.cs
Add packages in MyProject.csproj inside <ItemGroup>:
<Project Sdk="Godot.NET.Sdk/4.3.0">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<ItemGroup>
<!-- Example: add a third-party NuGet package -->
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
</ItemGroup>
</Project>
Run dotnet restore or let the IDE restore automatically after editing.
Godot 4.7's Godot.NET.Sdk accepts an opt-in MSBuild property that switches the project from the classic GodotSharp bindings to the preview Godot.NET bindings (the Godot.Bindings assembly instead of GodotSharp):
<PropertyGroup>
<EnableGodotDotNetPreview>true</EnableGodotDotNetPreview>
</PropertyGroup>
The preview bindings are experimental — leave the property unset for production projects. See GH-118001.
| IDE | Setup Required | Notes |
|---|---|---|
| JetBrains Rider | Install Godot plugin (bundled in Rider 2023.3+) | Best Godot C# support; debugger works out of the box |
| VS Code | Install C# Dev Kit + Godot Tools extensions | Requires launch.json for debugger attachment |
| Visual Studio | Install Godot Visual Studio extension | Windows only; debugger via Tools > Attach to Process |
For all IDEs, open the .sln file (not just a folder) to get full solution resolution.
partial class RequirementEvery class that extends a Godot type must be declared partial. This is not optional.
Godot uses C# source generators to emit the signal registration, property binding, and RPC code alongside your class. Source generators require partial to inject into the same class declaration.
error GD0001: Missing partial modifier on declaration of type 'Player'
that derives from 'Godot.GodotObject'
The build fails, so a missing partial cannot slip through.
// CORRECT
public partial class Player : CharacterBody2D { }
// WRONG — will cause source generator errors
public class Player : CharacterBody2D { }
This applies to every class in the inheritance chain that extends a Godot type, including intermediate base classes.
| Element | Convention | Example |
|---|---|---|
| Methods | PascalCase | public void TakeDamage(int amount) |
| Properties | PascalCase | public float MaxHealth { get; set; } |
| Signals (delegate) | PascalCase + EventHandler suffix | HealthChangedEventHandler |
[Export] properties | PascalCase | [Export] public float Speed { get; set; } |
| Private fields | _camelCase with underscore prefix | private float _currentSpeed; |
| Local variables | camelCase | var newPosition = ... |
| Parameters | camelCase | void SetHealth(int newHealth) |
| Godot API names | Match Godot's PascalCase exactly | GlobalPosition not global_position |
| Enums | PascalCase type, PascalCase members | enum State { Idle, Running, Dead } |
| Constants | PascalCase or SCREAMING_SNAKE per team style | const float MaxSpeed = 200f; |
Always match GodotSharp property and method names exactly — they are PascalCase translations of the GDScript snake_case names (e.g. is_on_floor() → IsOnFloor()).
Signals require a delegate declaration with the [Signal] attribute. The delegate name must end with EventHandler.
using Godot;
public partial class Player : CharacterBody2D
{
// Declaration
[Signal] public delegate void HealthChangedEventHandler(int newHealth);
[Signal] public delegate void DiedEventHandler();
private int _health = 100;
public void TakeDamage(int amount)
{
_health -= amount;
EmitSignal(SignalName.HealthChanged, _health); // Use SignalName.X, not a string
if (_health <= 0)
EmitSignal(SignalName.Died);
}
}
// Connect with +=
player.HealthChanged += OnHealthChanged;
player.Died += OnPlayerDied;
// Disconnect with -=
player.HealthChanged -= OnHealthChanged;
player.Died -= OnPlayerDied;
// Handler signatures must match the delegate
private void OnHealthChanged(int newHealth) { ... }
private void OnPlayerDied() { ... }
For full signal patterns including one-shot connections, static typed signals, and cross-language signal wiring, see the csharp-signals skill.
GDScript's await maps to C#'s await ToSignal(...). Godot signals return a SignalAwaiter that is compatible with C# await.
public async void StartCutscene()
{
// Wait for a timer to finish
await ToSignal(GetTree().CreateTimer(2.0), SceneTreeTimer.SignalName.Timeout);
// Wait for an animation to finish
var anim = GetNode<AnimationPlayer>("AnimationPlayer");
anim.Play("intro");
await ToSignal(anim, AnimationPlayer.SignalName.AnimationFinished);
GD.Print("Cutscene complete");
}
For CPU-bound work, use Task.Run — but never touch Godot objects from a non-main thread:
public async void LoadHeavyData()
{
// Off main thread: pure C# computation only
var result = await Task.Run(() => ComputeSomethingExpensive());
// Back on main thread: safe to use Godot API
ApplyResult(result);
}
private int ComputeSomethingExpensive()
{
// No Godot API calls here
return Enumerable.Range(0, 1_000_000).Sum();
}
await Equivalents| GDScript | C# |
|---|---|
await get_tree().create_timer(1.0).timeout | await ToSignal(GetTree().CreateTimer(1.0), SceneTreeTimer.SignalName.Timeout) |
await animation_player.animation_finished | await ToSignal(animPlayer, AnimationPlayer.SignalName.AnimationFinished) |
await signal_name | await ToSignal(this, SignalName.YourSignal) |
C# calls into GDScript objects with Call, Get, and Set, and GDScript can use a C# class directly when it is registered as a [GlobalClass]. Values cross the boundary as Variant, and the reference lists the marshalling gotchas (null, int[], List<T>).
See references/gdscript-interop.md for the
Call/Get/Setexamples, the[GlobalClass]pattern, and theVariantmarshalling table.
| Workload | Winner | Reason |
|---|---|---|
| Math-heavy loops (pathfinding, simulation) | C# (significantly faster) | Compiled JIT vs interpreted GDScript |
| Large array/collection processing | C# | Value type arrays avoid boxing |
| Godot API calls (move_and_slide, etc.) | Roughly equal | Both route through the same C++ engine |
| Scene tree operations | Roughly equal | Bottleneck is C++ overhead, not language |
| Rapid prototyping | GDScript | Less boilerplate, hot-reload without recompile |
| Editor tooling (plugins, @tool) | GDScript | C# tool scripts require a full build cycle |
@tool scripts, and rapid iteration on gameplay logic.Godot.Collections.Array and Array<T> in hot paths — Array<T> wraps the same engine array, so every element access still marshals a Variant. Use T[] or List<T> and convert at boundaries.StringName lookups are O(1) but StringName construction is not — cache them if created frequently.| Gotcha | Problem | Fix |
|---|---|---|
Variant to C# type conversion | (float)v is the same call as v.AsSingle(): neither throws — int, bool and numeric strings convert, any other type yields 0 | Check v.VariantType when the stored type matters |
null vs default(Variant) | Godot signals passing no value give default(Variant), not C# null | Check .VariantType == Variant.Type.Nil |
Godot.Collections vs System.Collections | Godot API methods return Godot.Collections.Array; a List<T> does not convert to Variant, so passing one does not compile | Always use Godot.Collections.Array/Dictionary at Godot API boundaries |
| Disposing native objects | Calling methods on a freed Godot object throws ObjectDisposedException | Check IsInstanceValid(obj) before use |
StringName construction in hot loops | new StringName("my_signal") allocates each call | Cache as private static readonly StringName _signalName = "my_signal"; |
| Export array types | [Export] public Array Items; exports untyped array | Use [Export] public Godot.Collections.Array<MyResource> Items { get; set; } |
| Node path strings | GetNode("../UI/Label") logs Node not found and returns null at runtime if the path changes | Use GetNode<Label>("%Label") with unique names or typed [Export] node references |
partial class forgotten | Build error GD0001 | Every class extending a Godot type must be partial |
async void vs async Task | async void cannot be awaited; an async Task nobody awaits hides its exception (Godot logs async void exceptions) | Return Task and await it; keep async void for top-level event handlers that Godot calls |
⚠️ Changed in Godot 4.7: The 4.7 C# assemblies ship several compatibility breaks. Binary-incompatible only (source still compiles — a rebuild suffices):
OptimizedTranslation.Generate()now returnsboolinstead ofvoid(GH-119563). Binary- and source-incompatible (code changes required):Animation.Lengthchanged type metadata fromfloattodouble(GH-116394) — the C# property is nowdouble, sofloat len = anim.Length;no longer compiles — andControl.AccessibilityLivechanged enum type fromDisplayServer.AccessibilityLiveModetoAccessibilityServer.AccessibilityLiveMode(GH-116839) — update declarations to theAccessibilityServerenum. Source-incompatible (existing binaries still load):RichTextLabel.ImageUpdateMask.WidthInPercentwas renamed toWidthUnit(GH-112617;UPDATE_WIDTH_IN_PERCENT→UPDATE_WIDTH_UNITin GDScript), and theEditorSceneFormatImporterIMPORT_*constants moved into theImportFlagsenum (GH-115788). Removed:AudioEffectSpectrumAnalyzer.TapBackPos(GH-114355). Rebuild against 4.7 and fix the affected symbols. See the 4.7 migration guide.
partial_Ready, _PhysicsProcess, IsOnFloor())[Export] properties are PascalCase and use property syntax ({ get; set; })EventHandler and use EmitSignal(SignalName.X)+= and disconnected with -= when no longer neededawait ToSignal(...) used for Godot signals — not raw Task.Delay for game timingGodot.Collections.Array/Dictionary used at Godot API boundaries, not System.Collections typesIsInstanceValid(obj) checked before using any potentially freed Godot objectStringName instances cached if constructed in loops or frequently called methodsTask.Run(...) callbacks — all engine calls happen on the main thread.csproj opened via the .sln file in the IDE for full solution resolution@tool scripts아직 리뷰가 없습니다. 사용해 본 소감을 남겨 주세요.
로그인 없이 쓸 수 있습니다. 사용 후기, 질문, 작동하지 않은 환경 등을 편하게 남겨 주세요.
아직 댓글이 없습니다. 첫 댓글을 남겨 보세요.
Use when working with 2D-specific systems — TileMaps, parallax scrolling, 2D lights and shadows, canvas layers, particles 2D, custom drawing, and 2D meshes in Godot 4.3+
한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.
Use when working with 3D-specific systems — materials, lighting, shadows, environment, global illumination, fog, LOD, occlusion culling, and decals in Godot 4.3+
한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.
Use when building character abilities — Resource-based abilities with cost/cooldown/cast, buffs/debuffs, stat modifiers, gameplay tags, and HUD binding
한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.
Use when creating Godot editor plugins — EditorPlugin, @tool scripts, custom inspectors, and dock panels
한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.
Use when implementing AI movement — NavigationAgent2D/3D, steering behaviors, behavior trees, and patrol patterns
한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.
Use when implementing animations — AnimationPlayer, AnimationTree, blend trees, state machines, sprite animation, and code-driven animation
한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.