PONY λ M2 Modula-2

C#.CodeCompared.To/GDScript

An interactive executable cheatsheet comparing C# and GDScript

C# 13 GDScript 4.5 (Godot 4.5.2)
Hello World & Output
Hello, World
Console.WriteLine("Hello, World!");
print("Hello, World!")
GDScript uses the global print() function rather than the Console class. Both append a newline automatically.
String interpolation
var name = "Godot"; Console.WriteLine($"Hello, {name}!");
var name := "Godot" print("Hello, %s!" % name)
GDScript uses Python-style % formatting instead of $"..." interpolation. The %s placeholder converts any value to a string.
Print multiple values
Console.WriteLine(string.Join(", ", new[] { 1, 2, 3 }));
print(1, ", ", 2, ", ", 3)
GDScript's print() accepts any number of arguments and joins them without a separator. Use prints() for automatic space-separated output, or str() to build the string yourself.
Formatted numbers
double ratio = 3.14159; Console.WriteLine($"Pi ≈ {ratio:F2}");
var ratio := 3.14159 print("Pi ≈ %.2f" % ratio)
GDScript uses C-style format specifiers: %.2f for two decimal places, %d for integers, %s for strings. The snprintf-style % operator also accepts an array of values for multi-placeholder strings.
Variables & Types
Explicit type annotation
string name = "Alice"; int count = 42; bool active = true;
var name: String = "Alice" var count: int = 42 var active: bool = true print(name, " ", count, " ", active)
GDScript type annotations go after the variable name, separated by a colon — the reverse of C#'s type name order. GDScript's String is capitalized (Godot convention); int and bool are lowercase.
Type inference with <code>:=</code>
var count = 42; // inferred as int var greeting = "hi"; // inferred as string Console.WriteLine($"{count} {greeting}");
var count := 42 # inferred as int var greeting := "hi" # inferred as String print(count, " ", greeting)
GDScript's := (walrus assignment) infers the type from the right-hand side and locks the variable to that type — the variable becomes statically typed. Plain var count = 42 (without :=) leaves the variable as a dynamically typed Variant.
Dynamic typing (Variant)
object anything = 42; anything = "now a string"; // requires object/dynamic Console.WriteLine(anything);
var anything = 42 anything = "now a string" print(anything)
Variables declared with plain var x = value (no :=, no type annotation) are Variant — GDScript's equivalent of object or dynamic. They can hold any value and change type freely. This is the default mode; typing is entirely opt-in.
Null values
string? name = null; Console.WriteLine(name ?? "unknown");
var name = null print(name if name != null else "unknown")
GDScript has no null safety features — any variable can be null without a ? annotation. The ?? operator does not exist; use an inline if expression instead. Reference types accessed on null crash with a nil reference error at runtime.
Multiple assignments
int x = 1, y = 2; (x, y) = (y, x); // swap Console.WriteLine($"{x} {y}");
var x := 1 var y := 2 var temp := x x = y y = temp print(x, " ", y)
GDScript does not support C#'s tuple destructuring assignment for variable swaps — a temporary variable is the idiomatic approach. The language does support returning multiple values from a function via an Array or Dictionary.
Boolean operators
bool ready = true; bool loaded = false; Console.WriteLine(ready && !loaded); Console.WriteLine(ready || loaded);
var ready := true var loaded := false print(ready and not loaded) print(ready or loaded)
GDScript uses English words and, or, not instead of C#'s &&, ||, !. The symbolic operators (&&, ||, !) are also accepted as aliases but are less idiomatic.
Constants
Constant declaration
const double GRAVITY = 9.8; const int MAX_SCORE = 9999; Console.WriteLine($"Gravity: {GRAVITY}, Max: {MAX_SCORE}");
const GRAVITY := 9.8 const MAX_SCORE := 9999 print("Gravity: ", GRAVITY, ", Max: ", MAX_SCORE)
GDScript const works identically to C#'s const: the value must be a compile-time literal. The := syntax infers the type. Constants declared at the class body level are accessible throughout the script.
Built-in math constants
Console.WriteLine(Math.PI); Console.WriteLine(Math.Tau); Console.WriteLine(double.PositiveInfinity);
print(PI) print(TAU) print(INF)
GDScript exposes PI, TAU (2π), and INF as built-in global constants — no namespace or class prefix required. These are the same values as Math.PI, Math.Tau, and double.PositiveInfinity.
Enumerations
enum Direction { North, South, East, West } var heading = Direction.North; Console.WriteLine(heading);
# enum declarations require class-body scope and cannot # be called from within this snippet runner # enum Direction { NORTH, SOUTH, EAST, WEST } # var heading = Direction.NORTH # print(heading) # prints 0 (integer value) print("enum: see note")
GDScript enum declarations must appear at the class body level — not inside a method. The runner executes code as method-level statements, so enums are marked norun here. In a full Godot script, enum Direction { NORTH, SOUTH, EAST, WEST } creates named integer constants; Direction.NORTH evaluates to 0.
Strings
Concatenation
string first = "Hello"; string second = "World"; Console.WriteLine(first + ", " + second + "!");
var first := "Hello" var second := "World" print(first + ", " + second + "!")
The + operator concatenates strings in both languages. GDScript also supports the % format operator and the global str() function for converting values before joining.
Format with multiple placeholders
string name = "Alice"; int score = 95; Console.WriteLine($"{name} scored {score} points");
var name := "Alice" var score := 95 print("%s scored %d points" % [name, score])
When a GDScript format string has more than one placeholder, the values go in an Array on the right side of %. The specifiers are C-style: %s (string), %d (integer), %f (float).
String length
string message = "Hello, World!"; Console.WriteLine(message.Length);
var message := "Hello, World!" print(message.length())
GDScript uses .length() (a method call) rather than C#'s .Length property. Both return the number of characters as an integer.
Upper / lower case
string greeting = "Hello, World!"; Console.WriteLine(greeting.ToUpper()); Console.WriteLine(greeting.ToLower());
var greeting := "Hello, World!" print(greeting.to_upper()) print(greeting.to_lower())
GDScript follows a snake_case naming convention for all method names, in contrast to C#'s PascalCase. The behaviour is identical: a new string is returned; the original is unchanged.
Substring search
string sentence = "The quick brown fox"; Console.WriteLine(sentence.Contains("quick")); Console.WriteLine(sentence.StartsWith("The")); Console.WriteLine(sentence.EndsWith("fox"));
var sentence := "The quick brown fox" print(sentence.contains("quick")) print(sentence.begins_with("The")) print(sentence.ends_with("fox"))
GDScript names the prefix/suffix checks begins_with() and ends_with() rather than StartsWith/EndsWith. All three methods return a bool.
Replace & trim
string text = " hello world "; Console.WriteLine(text.Trim()); Console.WriteLine(text.Trim().Replace("world", "GDScript"));
var text := " hello world " print(text.strip_edges()) print(text.strip_edges().replace("world", "GDScript"))
GDScript's strip_edges() removes leading and trailing whitespace, equivalent to C#'s Trim(). The replace() method replaces all occurrences by default — there is no equivalent of Replace with a count limit in the base String API.
Split & join
string csv = "alpha,beta,gamma"; string[] parts = csv.Split(','); Console.WriteLine(string.Join(" | ", parts));
var csv := "alpha,beta,gamma" var parts := csv.split(",") print(", ".join(parts))
GDScript's split() returns a PackedStringArray. Joining uses the separator string as the receiver — separator.join(array) — rather than C#'s static string.Join(separator, array) call.
Multiline strings
string poem = """ Roses are red, Violets are blue. """; Console.WriteLine(poem.Trim());
var poem := """ Roses are red, Violets are blue. """ print(poem.strip_edges())
Both languages use triple-quote syntax for multiline string literals. GDScript 4 added the """...""" form in Godot 4.0, with the same semantics as C# raw string literals: the content is taken verbatim, including newlines.
Numbers & Math
Integer arithmetic
int total = 10 + 3; int difference = 10 - 3; int product = 10 * 3; int quotient = 10 / 3; // integer division: 3 int remainder = 10 % 3; Console.WriteLine($"{total} {difference} {product} {quotient} {remainder}");
var total := 10 + 3 var difference := 10 - 3 var product := 10 * 3 var quotient := 10 / 3 # integer division: 3 var remainder := 10 % 3 print(total, " ", difference, " ", product, " ", quotient, " ", remainder)
GDScript performs integer division when both operands are integers — 10 / 3 yields 3, not 3.333.... This matches C#'s integer division. To get a float result, use a float literal on either side: 10.0 / 3.
Float arithmetic
double temperature = 98.6; double half = temperature / 2.0; Console.WriteLine(half);
var temperature := 98.6 var half := temperature / 2.0 print(half)
GDScript's float is a 64-bit double-precision number, equivalent to C#'s double. There is no separate single-precision float type; GDScript does not have f literal suffixes.
Math functions
Console.WriteLine(Math.Abs(-7)); Console.WriteLine(Math.Min(3, 8)); Console.WriteLine(Math.Max(3, 8)); Console.WriteLine(Math.Sqrt(16.0)); Console.WriteLine(Math.Floor(3.9)); Console.WriteLine(Math.Ceiling(3.1));
print(abs(-7)) print(min(3, 8)) print(max(3, 8)) print(sqrt(16.0)) print(floor(3.9)) print(ceil(3.1))
GDScript exposes math functions as global built-ins — no Math. prefix required. The function names are lower-case and mostly match their C standard library counterparts: abs, min, max, sqrt, floor, ceil, pow, log, etc.
Type conversion
int count = int.Parse("42"); double ratio = double.Parse("3.14"); string label = count.ToString(); Console.WriteLine($"{count} {ratio} {label}");
var count := int("42") var ratio := float("3.14") var label := str(count) print(count, " ", ratio, " ", label)
GDScript uses constructor-style type conversion: int(x), float(x), str(x), bool(x). These are built-in global functions, not static methods on a class.
Clamping and linear interpolation
double value = Math.Clamp(150.0, 0.0, 100.0); Console.WriteLine(value); // 100 // Linear interpolation (no built-in lerp in base C#) double lerped = 0.0 + (100.0 - 0.0) * 0.25; Console.WriteLine(lerped); // 25
var value := clamp(150.0, 0.0, 100.0) print(value) # 100 var lerped := lerp(0.0, 100.0, 0.25) print(lerped) # 25
GDScript includes clamp(value, min, max) and lerp(from, to, weight) as global functions. These are essential in game development and are more convenient than C#'s Math.Clamp (which was added in .NET Core 2.0) and the absence of a standard lerp in the base class library.
Arrays & Collections
Array creation
var numbers = new List<int> { 10, 20, 30 }; Console.WriteLine(numbers[0]); Console.WriteLine(numbers.Count);
var numbers := [10, 20, 30] print(numbers[0]) print(numbers.size())
GDScript arrays use square-bracket literals and are dynamically sized by default. The size method is .size() rather than C#'s .Count property on List<T>.
Typed array
var scores = new List<int>(); scores.Add(85); scores.Add(92); Console.WriteLine(scores.Count);
var scores: Array[int] = [] scores.append(85) scores.append(92) print(scores.size())
GDScript 4 introduced typed arrays with the Array[Type] syntax. Typed arrays reject values of the wrong type at runtime, giving some of the safety of List<T>. The append method is .append() (or the alias .push_back()).
Access and modify elements
var items = new List<string> { "alpha", "beta", "gamma" }; Console.WriteLine(items[1]); items[1] = "BETA"; Console.WriteLine(items[items.Count - 1]);
var items := ["alpha", "beta", "gamma"] print(items[1]) items[1] = "BETA" print(items[-1])
GDScript supports negative indexing: items[-1] accesses the last element, items[-2] the second-to-last, and so on. C# lists require items[items.Count - 1] or the index operator with the range syntax (items[^1] in C# 8+).
Membership test
var fruits = new List<string> { "apple", "banana", "cherry" }; Console.WriteLine(fruits.Contains("banana")); Console.WriteLine(fruits.Contains("mango"));
var fruits := ["apple", "banana", "cherry"] print(fruits.has("banana")) print(fruits.has("mango"))
GDScript uses .has(value) for membership tests, while C#'s List<T> uses .Contains(value). The in operator also works: "banana" in fruits returns true.
Add and remove elements
var items = new List<string> { "a", "b", "c" }; items.Add("d"); items.Remove("b"); Console.WriteLine(string.Join(", ", items));
var items := ["a", "b", "c"] items.append("d") items.erase("b") print(", ".join(items))
GDScript's .erase(value) removes the first occurrence of a value, equivalent to List<T>.Remove(value). To remove by index use .remove_at(index). To add at a specific position use .insert(index, value).
Filter and map
var numbers = new List<int> { 1, 2, 3, 4, 5, 6 }; var evens = numbers.Where(n => n % 2 == 0).ToList(); var doubled = numbers.Select(n => n * 2).ToList(); Console.WriteLine(string.Join(", ", evens)); Console.WriteLine(string.Join(", ", doubled));
var numbers := [1, 2, 3, 4, 5, 6] var evens := numbers.filter(func(number): return number % 2 == 0) var doubled := numbers.map(func(number): return number * 2) print(", ".join(evens.map(func(number): return str(number)))) print(", ".join(doubled.map(func(number): return str(number))))
GDScript 4 added .filter(callable) and .map(callable) to Array. The callables are anonymous functions created with the func(args): body syntax. Unlike LINQ's lazy evaluation, GDScript's filter/map return new arrays eagerly.
Sorting
var words = new List<string> { "banana", "apple", "cherry" }; words.Sort(); Console.WriteLine(string.Join(", ", words));
var words := ["banana", "apple", "cherry"] words.sort() print(", ".join(words))
GDScript's .sort() sorts in place using the natural order of the element type. To sort with a custom comparator, use .sort_custom(func(a, b): return a < b) — the comparator returns true if the first argument should come first.
Dictionaries
Dictionary creation
var scores = new Dictionary<string, int> { { "Alice", 95 }, { "Bob", 87 }, }; Console.WriteLine(scores["Alice"]);
var scores := {"Alice": 95, "Bob": 87} print(scores["Alice"])
GDScript dictionaries use {key: value} literal syntax with a colon separator, similar to Python. Keys can be any hashable type. GDScript Dictionary is untyped by default; GDScript 4.4 introduced typed dictionaries (Dictionary[String, int]).
Access with default
var config = new Dictionary<string, string> { { "theme", "dark" } }; Console.WriteLine(config.GetValueOrDefault("theme", "light")); Console.WriteLine(config.GetValueOrDefault("volume", "50"));
var config := {"theme": "dark"} print(config.get("theme", "light")) print(config.get("volume", "50"))
GDScript's .get(key, default) returns the value if the key exists, or the default value if it does not — equivalent to C#'s GetValueOrDefault. Direct index access (config["missing"]) throws a key error.
Key existence check
var options = new Dictionary<string, bool> { { "fullscreen", true } }; Console.WriteLine(options.ContainsKey("fullscreen")); Console.WriteLine(options.ContainsKey("vsync"));
var options := {"fullscreen": true} print(options.has("fullscreen")) print(options.has("vsync"))
GDScript uses .has(key) for both arrays and dictionaries — the same method name for membership tests in both collection types. C# uses .ContainsKey() for dictionaries and .Contains() for lists.
Iterating entries
var population = new Dictionary<string, int> { { "Paris", 2161000 }, { "Tokyo", 13960000 } }; foreach (var (city, count) in population) Console.WriteLine($"{city}: {count}");
var population := {"Paris": 2161000, "Tokyo": 13960000} for city in population: print(city, ": ", population[city])
Iterating a GDScript dictionary with for key in dict yields the keys. Access the corresponding value with dict[key]. There is no direct equivalent to C#'s destructuring foreach (var (key, value) in dict), but dict.keys() and dict.values() return arrays of each side independently.
Keys and values
var colors = new Dictionary<string, string> { { "red", "#FF0000" }, { "green", "#00FF00" } }; Console.WriteLine(string.Join(", ", colors.Keys)); Console.WriteLine(string.Join(", ", colors.Values));
var colors := {"red": "#FF0000", "green": "#00FF00"} print(", ".join(colors.keys())) print(", ".join(colors.values()))
GDScript's .keys() and .values() each return an Array. The order of entries follows insertion order in Godot 4 (consistent with Python's dict behaviour).
Control Flow
if / else if / else
int score = 75; if (score >= 90) Console.WriteLine("A"); else if (score >= 70) Console.WriteLine("B"); else Console.WriteLine("C");
var score := 75 if score >= 90: print("A") elif score >= 70: print("B") else: print("C")
GDScript uses elif (like Python) where C# uses else if. Blocks are delimited by indentation rather than braces. Condition parentheses are optional but permitted.
Inline conditional expression
int temperature = 22; string label = temperature >= 20 ? "warm" : "cold"; Console.WriteLine(label);
var temperature := 22 var label := "warm" if temperature >= 20 else "cold" print(label)
GDScript's inline conditional reads value_if_true if condition else value_if_false — the opposite order from C#'s condition ? true_value : false_value. The GDScript form reads more like natural language.
Logical operators
bool admin = true; bool active = true; bool guest = false; Console.WriteLine(admin && active); Console.WriteLine(admin || guest); Console.WriteLine(!guest);
var admin := true var active := true var guest := false print(admin and active) print(admin or guest) print(not guest)
GDScript prefers English keywords (and, or, not) over symbolic operators. The symbolic forms (&&, ||, !) are valid aliases. Short-circuit evaluation applies: or stops at the first truthy operand.
Null / fallback checks
string? name = null; // null coalescing Console.WriteLine(name ?? "anonymous"); // null conditional int? length = name?.Length; Console.WriteLine(length);
var name = null # inline-if as null coalescing print(name if name != null else "anonymous") # length check with guard var length = name.length() if name != null else 0 print(length)
GDScript has no ?? or ?. operators — null checks must be written explicitly with an inline if expression. Accessing a method on null without checking will crash with a nil reference error, so guard checks are important.
Loops
<code>for</code> with a numeric range
for (int index = 0; index < 5; index++) Console.WriteLine(index);
for index in range(5): print(index)
GDScript uses range(stop), range(start, stop), or range(start, stop, step) to generate a sequence of integers — equivalent to Python's range(). The traditional C-style three-clause for loop does not exist in GDScript.
<code>for</code> over a collection
var fruits = new List<string> { "apple", "banana", "cherry" }; foreach (var fruit in fruits) Console.WriteLine(fruit);
var fruits := ["apple", "banana", "cherry"] for fruit in fruits: print(fruit)
GDScript's for item in collection mirrors C#'s foreach exactly. It works on arrays, packed arrays, dictionaries (yields keys), strings (yields characters), and ranges.
<code>while</code> loop
int counter = 0; while (counter < 5) { Console.WriteLine(counter); counter++; }
var counter := 0 while counter < 5: print(counter) counter += 1
GDScript has no ++ or -- operators — use += 1 and -= 1. The while loop otherwise behaves identically to C#'s.
<code>break</code> and <code>continue</code>
for (int number = 0; number < 10; number++) { if (number == 7) break; if (number % 2 == 0) continue; Console.WriteLine(number); }
for number in range(10): if number == 7: break if number % 2 == 0: continue print(number)
GDScript's break and continue work identically to C#'s. There is no labeled break for exiting outer loops — use a flag variable or restructure the logic.
Loop with index
var colors = new List<string> { "red", "green", "blue" }; foreach (var (index, color) in colors.Select((c, i) => (i, c))) Console.WriteLine($"{index}: {color}");
var colors := ["red", "green", "blue"] for index in range(colors.size()): print(index, ": ", colors[index])
GDScript does not have a built-in enumerate() function — the idiomatic approach is to iterate a range(array.size()) and index into the array. This is more verbose than Python's enumerate() or C#'s LINQ-based equivalent.
Pattern Matching
Basic <code>match</code> / <code>switch</code>
int day = 3; string name = day switch { 1 => "Monday", 2 => "Tuesday", 3 => "Wednesday", _ => "Other", }; Console.WriteLine(name);
var day := 3 var name: String match day: 1: name = "Monday" 2: name = "Tuesday" 3: name = "Wednesday" _: name = "Other" print(name)
GDScript's match uses an indented block per arm without case or break keywords. The wildcard arm is _:. Unlike C# switch statements, there is no fall-through behaviour — each arm is independent.
Match multiple patterns per arm
int code = 404; string message = code switch { 200 or 201 or 204 => "Success", 400 or 422 => "Bad request", 401 or 403 => "Auth error", 404 => "Not found", _ => "Unknown", }; Console.WriteLine(message);
var code := 404 var message: String match code: 200, 201, 204: message = "Success" 400, 422: message = "Bad request" 401, 403: message = "Auth error" 404: message = "Not found" _: message = "Unknown" print(message)
GDScript separates multiple patterns in one arm with a comma: 200, 201, 204:. C# 9+ uses the or pattern combinator: 200 or 201 or 204 =>. Both achieve the same grouping effect.
Match against a range
int score = 82; string grade = score switch { >= 90 => "A", >= 80 => "B", >= 70 => "C", _ => "F", }; Console.WriteLine(grade);
var score := 82 var grade: String match score: var n when n >= 90: grade = "A" var n when n >= 80: grade = "B" var n when n >= 70: grade = "C" _: grade = "F" print(grade)
GDScript does not have relational pattern matching in match arms. The workaround is a var binding when guard arm: var n when n >= 90: binds the value to n and then tests a guard condition. For simple range checks, a plain if/elif chain is often clearer.
Match array patterns
// C# can match array/tuple shapes via switch expressions with patterns object[] point = { 0, 5 }; string description = point switch { [0, 0] => "origin", [0, _] => "on Y axis", [_, 0] => "on X axis", [var x, var y] => $"at ({x}, {y})", _ => "unknown", }; Console.WriteLine(description);
var point := [0, 5] var description: String match point: [0, 0]: description = "origin" [0, var y]: description = "on Y axis at y=" + str(y) [var x, 0]: description = "on X axis at x=" + str(x) [var x, var y]: description = "at (" + str(x) + ", " + str(y) + ")" print(description)
GDScript's match can destructure arrays: each element position can be a literal, a wildcard (_), or a var binding. This is more expressive than C#'s switch, which requires additional syntax to match list patterns.
Lambdas & Callables
Anonymous function (lambda)
Func<int, int> double_it = x => x * 2; Console.WriteLine(double_it(5));
var double_it := func(number): return number * 2 print(double_it.call(5))
GDScript lambdas are created with the func(params): body syntax and stored as Callable values. Unlike C# where a lambda can be called directly (double_it(5)), a GDScript callable must be explicitly invoked with .call(args).
Typed lambda
Func<int, int, int> add = (x, y) => x + y; Console.WriteLine(add(3, 4));
var add := func(left: int, right: int) -> int: return left + right print(add.call(3, 4))
GDScript lambdas can carry full type annotations on parameters and the return type (-> int), making them statically checked when type hints are in use. The body can span multiple lines with indentation.
Closure (capturing outer variables)
int base_value = 10; Func<int, int> add_base = x => x + base_value; Console.WriteLine(add_base(5));
var base_value := 10 var add_base := func(number): return number + base_value print(add_base.call(5))
GDScript lambdas close over variables in the enclosing scope exactly as C# lambdas do. The captured variable is referenced by name; if the outer variable changes after the lambda is created, the lambda sees the updated value.
Passing lambdas to higher-order functions
var numbers = new List<int> { 1, 2, 3, 4, 5 }; var evens = numbers.Where(n => n % 2 == 0).ToList(); var squares = numbers.Select(n => n * n).ToList(); Console.WriteLine(string.Join(", ", evens)); Console.WriteLine(string.Join(", ", squares));
var numbers := [1, 2, 3, 4, 5] var evens := numbers.filter(func(number): return number % 2 == 0) var squares := numbers.map(func(number): return number * number) print(", ".join(evens.map(func(n): return str(n)))) print(", ".join(squares.map(func(n): return str(n))))
GDScript 4's Array.filter(callable) and Array.map(callable) accept any Callable — including lambdas. The pattern mirrors LINQ's Where and Select, though GDScript evaluates eagerly rather than lazily.
Named function definition
string Greet(string person_name) => $"Hello, {person_name}!"; Console.WriteLine(Greet("World"));
func greet(person_name: String) -> String: return "Hello, " + person_name + "!" # NOTE: calling greet() here fails in the snippet runner because # named functions are class-level members and cannot be called from # the implicit initialization context. In a real Godot script, # call greet("World") from _ready() or another method. print("named func: see note")
GDScript named functions are defined with func name(params) -> ReturnType: at the class body level. The snippet runner executes code as method-level statements and cannot call functions defined in the same snippet — a real Godot script's _ready() would call them normally.
Type System
Getting the type of a value
object value = 42; Console.WriteLine(value.GetType()); Console.WriteLine(value.GetType() == typeof(int));
var value := 42 print(typeof(value)) # prints 2 (TYPE_INT constant) print(typeof(value) == TYPE_INT)
GDScript's typeof(value) returns an integer constant from the Variant.Type enum: TYPE_INT = 2, TYPE_FLOAT = 3, TYPE_STRING = 4, TYPE_BOOL = 1, etc. The type_string(typeof(value)) built-in returns a human-readable name.
<code>is</code> type test
object value = "hello"; Console.WriteLine(value is string); Console.WriteLine(value is int);
var value = "hello" # Variant — untyped so 'is' tests both types cleanly print(value is String) print(value is int)
GDScript's is operator tests whether a value is of a given type or inherits from it — identical to C#'s is. It works on both built-in types (int, String, float) and Godot object classes (Node, Resource).
Type casting
double ratio = 3.9; int truncated = (int)ratio; Console.WriteLine(truncated); // 3
var ratio := 3.9 var truncated := int(ratio) print(truncated) # 3
GDScript uses constructor-style casting — int(value), float(value), str(value) — rather than C-style casts. Like C#'s explicit cast, int() truncates toward zero rather than rounding.
Safe cast with <code>as</code>
object value = "hello"; string? text = value as string; Console.WriteLine(text ?? "not a string");
var value := "hello" var text = value as String print(text if text != null else "not a string")
GDScript supports the as operator for safe casts, mirroring C#. If the cast fails, as returns null rather than throwing. This is most useful when casting between Godot object types (e.g., node as RigidBody2D).
Error Handling
Assertions
int age = -1; Debug.Assert(age >= 0, "Age must be non-negative"); // (throws in debug; skipped in release)
var age := -1 assert(age >= 0, "Age must be non-negative") # crashes in debug build; skipped in release
GDScript's assert(condition, message) halts execution in debug builds and prints the message. It is stripped entirely in release exports, matching Debug.Assert semantics. Use assertions to verify internal invariants, not to validate user input.
Logging errors and warnings
Console.Error.WriteLine("Something went wrong"); // or with structured logging: // logger.LogError("Database unreachable");
push_error("Something went wrong") push_warning("Low memory")
GDScript does not use exceptions for recoverable errors. Instead, push_error(message) logs a red error in the Godot console and push_warning(message) logs an orange warning — both continue execution. For fatal unrecoverable states, use assert(false, message).
Try / catch (exception handling)
try { int result = int.Parse("not a number"); Console.WriteLine(result); } catch (FormatException exception) { Console.WriteLine($"Parse failed: {exception.Message}"); }
# GDScript has no try/catch or exceptions. # Errors are communicated via return codes, push_error(), # or by checking for null/invalid results. var text := "not a number" if text.is_valid_int(): print(text.to_int()) else: print("Parse failed: not a valid integer")
GDScript has no exception system. The idiomatic alternative is defensive checking — is_valid_int(), is_valid_float() — before converting. For operations that might fail (file I/O, network), Godot functions return an error code (Error enum) that callers check.
Classes & Nodes
Class definition
class Animal { public string Name { get; } public Animal(string name) { Name = name; } public virtual string Speak() => "..."; } var animal = new Animal("Cat"); Console.WriteLine(animal.Name);
class Animal: var name: String func _init(animal_name: String) -> void: name = animal_name func speak() -> String: return "..." # var animal = Animal.new("Cat") # print(animal.name) print("class: see note")
GDScript classes are defined with class Name: and must appear at the script's top level (class body scope). The constructor is always named _init(). Fields are declared with var inside the class block. The snippet runner cannot execute class definitions — use them in full Godot scripts.
Inheritance
class Dog : Animal { public Dog(string name) : base(name) {} public override string Speak() => $"{Name} says: Woof!"; } var dog = new Dog("Rex"); Console.WriteLine(dog.Speak());
class Dog extends Animal: func speak() -> String: return name + " says: Woof!" # Extends a built-in Godot type: # class Player extends CharacterBody2D: # pass print("inheritance: see class note")
GDScript uses extends ClassName for inheritance — either a user-defined class or a Godot built-in type (Node, CharacterBody2D, etc.). Overriding a method just redefines it; there is no override keyword. Call the parent method with super().
Properties and exported variables
class Player { private int _health = 100; public int Health { get => _health; set => _health = Math.Max(0, value); } }
class Player: var _health := 100 var health: get: return _health set(value): _health = max(0, value) # @export var speed: float = 200.0 # @export exposes the variable in the Godot editor inspector
GDScript 4 supports computed properties with inline get and set blocks, mirroring C# properties. The @export annotation additionally exposes the variable in the Godot editor inspector, replacing C#'s [Export] attribute.
Static members
class Counter { public static int Total { get; private set; } = 0; public static void Increment() => Total++; } Counter.Increment(); Console.WriteLine(Counter.Total);
class Counter: static var total := 0 static func increment() -> void: total += 1 # Counter.increment() # print(Counter.total) print("static: see class note")
GDScript 4 supports static var and static func at the class level. Static members are accessed via the class name (Counter.increment()), not via an instance — identical to C#.
Signals & Events
Declaring a signal / event
class Button { public event Action<string>? Clicked; protected virtual void OnClicked(string label) => Clicked?.Invoke(label); }
class Button: signal clicked(label: String) # emitting: emit_signal("clicked", "Start") # or: clicked.emit("Start") print("signal: see note")
GDScript's signal name(params) declaration is the built-in observer pattern — no delegate types, event handlers, or invocation list required. Signals are first-class objects in Godot; they can be connected in the editor by dragging, or in code with connect().
Connecting a signal
var button = new Button(); button.Clicked += label => Console.WriteLine($"Clicked: {label}");
# In a Godot scene script: # func _ready() -> void: # $Button.clicked.connect(_on_button_clicked) # # func _on_button_clicked(label: String) -> void: # print("Clicked: ", label) print("connect: see note")
Connecting a GDScript signal calls signal_name.connect(callable) on the emitting node. The callable is typically a method on the receiving node (_on_button_clicked). C# uses the += operator on the event field; GDScript uses an explicit .connect() call.
Emitting a signal
class HealthBar { public event Action<int>? HealthChanged; public void TakeDamage(int amount) { int newHealth = 80 - amount; HealthChanged?.Invoke(newHealth); } }
class HealthBar: signal health_changed(new_health: int) var current_health := 80 func take_damage(amount: int) -> void: current_health -= amount health_changed.emit(current_health) print("emit: see note")
A GDScript signal is emitted with signal_name.emit(args). The older emit_signal("name", args) string-based form is still valid but less safe. All connected callables are invoked synchronously in the order they were connected.