Godot's other official language, in the same editor, on the same scene tree. You keep every node, signal and lifecycle callback you already know — _ready becomes _Ready and $Sprite2D becomes GetNode<Sprite2D>("Sprite2D"). What changes is everything around them: types are checked before the game runs, a build step stands between you and pressing play, and the engine's reference counting now has .NET's garbage collector living beside it.
Types stop being optional. GDScript lets you write var speed or var speed := 5.0 and checks the annotated one at runtime; C# has no untyped option, and the error arrives at build time instead of on the frame that touches itTruthiness is gone — a condition must be a bool, so if (health) does not compile where if health: quietly meant "not zero"Signals become two things at once: [Signal] delegate void HealthChangedEventHandler(int amount) declares one, and connecting is HealthChanged += OnHealthChanged — a C# event, checked for arity and type at compile time rather than at emit_signal @export var speed := 200.0 becomes [Export] public float Speed = 200.0f; and still appears in the inspector, because the attribute is what the editor readsTwo lifetimes now overlap. A Node you QueueFree() is destroyed by the engine while its C# wrapper is collected by the GC, so a disposed node reached through a stale reference throws instead of returning nullLINQ replaces the for-loop-into-a-fresh-Array that GDScript makes you write for every filter, map and group await get_tree().create_timer(1.0).timeout is await ToSignal(GetTree().CreateTimer(1.0), "timeout") — the same idea, with the compiler checking that the method returning it is async