Skip to content
Search

Godot:CSharp

lastmod 2025-02-14created 2020-09-2245 edits

About C# scripts in godot.

Project setup

The following development environment is used:

  • Godot
  • C#
    • .NET Core SDK - Godot bundles the parts of Mono needed to run already compiled games, but Godot does not include the tools needed to build and compile games, such as MSBuild. For this, you need to install .NET Core SDK.
    • Godot 4.0 uses .NET6 (.NET7 is not supported, so .NET6 must be installed separately)
    • Mono SDK - As described below, on Linux it is needed to make the C# tools plugin for VSCode work. It is not needed for C# compilation in Godot.
  • VSCode

For reference, the .NET version can be checked as follows.

$ dotnet --list-sdks
6.0.404 [/usr/share/dotnet/sdk]

VCS Settings

Some of the folders Godot creates should be ignored by the version control system:

  • .import/: This folder stores all the files imported according to the source assets and their import flags.
  • *.translation: These files are translations generated from CSV files, imported as binary files.
  • export_presets.cfg: This file contains all the project's export presets, including sensitive information such as Android keystore credentials.
  • .mono/: This folder holds automatically generated Mono files. It exists only in projects that use the Mono version of Godot.

If you have plugin-related problems, see OmniSharp#Troubleshooting.

Install Godot

Download the Mono version (C# support) from here and extract it to a suitable folder.

In the Godot editor, in the Editor -> Editor Settings menu, set Mono -> Editor -> External Editor to Visual Studio Code.

In the project settings, check Mono -> Wait For Debugger. Adjust Wait Timeout as needed.

INFORMATION

When running with the C# Tools for Godot extension in VSCode, the Wait For Debugger setting may cause problems. In that case, turn the option off.

Install Visual Studio Code

Install the following VSCode extensions:

INFORMATION

If you use VSCode on Linux, you need to install Mono SDK for the C# tools plugin to work.

Open the launch.json and tasks.json files and set the path to the Godot executable.

On Linux, choose C# Mono for the debug configuration.

Install .NET Core SDK

See the .NET Core#SDK page. On Ubuntu 20.04, simply:

wget https://packages.microsoft.com/config/ubuntu/20.04/packages-microsoft-prod.deb -O packages-microsoft-prod.deb
sudo dpkg -i packages-microsoft-prod.deb
rm packages-microsoft-prod.deb

sudo apt-get update
sudo apt-get install -y apt-transport-https
sudo apt-get update
sudo apt-get install -y dotnet-sdk-6.0

Install Mono

Install by referring to Mono#Ubuntu 20.04 (amd64, armhf, arm64, ppc64el). Simply:

sudo apt install gnupg ca-certificates
sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys 3FA7E0328081BFF6A14DA29AA6A19B38D3D831EF
echo "deb https://download.mono-project.com/repo/ubuntu stable-focal main" | sudo tee /etc/apt/sources.list.d/mono-official-stable.list
sudo apt update
sudo apt install mono-devel

Creating a C# script

When you create a new C# script in Godot, a solution file (.sln), a project file (.csproj), Properties/AssemblyInfo.cs, and the .mono folder are generated automatically.

Add all files except the .mono folder to VCS.

If problems occur, removing the .mono folder may fix them.

Example

using Godot;
using System;

public class YourCustomClass : Node
{
    // Member variables here, example:
    private int a = 2;
    private string b = "textvar";

    public override void _Ready()
    {
        // Called every time the node is added to the scene.
        // Initialization here.
        GD.Print("Hello from C# to Godot :)");
    }

    public override void _Process(float delta)
    {
        // Called every frame. Delta is time since the last frame.
        // Update game logic here.
    }
}

Functions such as GDScript's print can be used by referring to the GD class in the Godot Namespace.

Full class reference:

INFORMATION

The class you want to attach to a node must have the same name as the file. Otherwise, an error like Cannot find class XXX for scriptres://XXX.cs is printed.

Debugging

After running in Godot, within the Wait Timeout time (set above), run it with Attach from the RUN AND DEBUG tab of VSCode. (Not Launch!)

Reference video:

Building directly from the command line

Check the log in the MSBuild tab at the bottom.

"/usr/bin/msbuild" \
    "/home/your/Project/minidun.godot/minidun.sln" \
    /restore \
    /t:Build \
    "/p:Configuration=Debug" \
    /v:normal \
    "/l:GodotTools.BuildLogger.GodotBuildLogger,/home/your/bin/Godot_v3.4.3-stable_mono_x11_64/GodotSharp/Tools/GodotTools.BuildLogger.dll;/home/your/.local/share/godot/mono/build_logs/38b36c25cbee2623e1dc61c31e05423d_Debug" \
    /p:GodotTargetPlatform=x11

Currently known issues

  • Writing editor plugins is possible, but currently quite complicated.
  • State is currently not saved and restored on hot reloading, except for exported variables.
  • Attached C# scripts must refer to a Class with a class name that matches the file name.
  • There are some methods, such as Get()/Set(), Call()/CallDeferred(), and the signal connection method Connect(), that rely on Godot's snake_case API naming conventions.
    • For example, using CallDeferred("AddChild") does not work for AddChild, because the API expects the original snake_case version, add_child.
    • However, you can use any custom properties or methods without this limitation.

Exporting Mono projects is supported on desktop platforms (Linux, Windows, and macOS), Android, HTML5, and iOS. The only platform not yet supported is UWP.

Prerequisites

C# API differences to GDScript is important!

Loading resources

C# does not have GDScript's load and preload keywords. Instead, use the ResourceLoader class.

var texture = ResourceLoader.Load<Texture>("res://icon.png");

In particular, preload is a feature specific to GDScript. C# does not have it. (Godot can load resources when a script is loaded, but this is not possible in C#)

Type conversion and casting

Cast and Type Check

Throws InvalidCastException if the returned node cannot be cast to Sprite. You would use it instead of the as operator if you are sure it won't fail.

Sprite mySprite = (Sprite)GetNode("MySprite");
mySprite.SetFrame(0);

Using the AS operator

The as operator returns null if the node cannot be cast to Sprite, and for this reason it cannot be used with value types.

Sprite mySprite = GetNode("MySprite") as Sprite;
// Only call SetFrame() if mySprite is not null
mySprite?.SetFrame(0);

Using generic methods

Generic methods are provided to make this type conversion transparent.

GetNode<T>() casts the node before returning it. If the node cannot be cast to the desired type, it throws InvalidCastException.

Sprite mySprite = GetNode<Sprite>("MySprite");
mySprite.SetFrame(0);

GetNodeOrNull<T>() uses the as operator and returns null if the node cannot be cast to the desired type.

Sprite mySprite = GetNodeOrNull<Sprite>("MySprite");
// Only call SetFrame() if mySprite is not null
mySprite?.SetFrame(0);

Type checking using the IS operator

To check whether the node can be cast to Sprite, you can use the is operator. If it cannot be cast to Sprite, the is operator returns false; otherwise it returns true.

if (GetNode("MySprite") is Sprite) {
    // Yup, it's a sprite!
}

Property export (Export)

You can use [Export], the equivalent of GDScript's export. This attribute can also be given optional PropertyHint and hintString parameters. Default values can be set by assigning a value.

Example:

using Godot;

public class MyNode : Node
{
    [Export]
    private NodePath _nodePath;

    [Export]
    private string _name = "default";

    [Export(PropertyHint.Range, "0,100000,1000,or_greater")]
    private int _income;

    [Export(PropertyHint.File, "*.png,*.jpg")]
    private string _icon;
}

Signals

For a complete C# example, see the section on handling signals in the step-by-step Scripting tutorial.

In C#, a signal is declared with the [Signal] attribute on a delegate.

[Signal]
delegate void MySignal();

[Signal]
delegate void MySignalWithArguments(string foo, int bar);

These signals can be connected in the editor or from code with Connect. To connect a signal in the editor, you need to rebuild the project assemblies to see the new signal. This build can be triggered manually by clicking the "Build" button at the top right of the editor window.

public void MyCallback()
{
    GD.Print("My callback!");
}

public void MyCallbackWithArguments(string foo, int bar)
{
    GD.Print("My callback with: ", foo, " and ", bar, "!");
}

public void SomeFunction()
{
    instance.Connect("MySignal", this, "MyCallback");
    instance.Connect(nameof(MySignalWithArguments), this, "MyCallbackWithArguments");
}

You can emit signals with the EmitSignal method.

public void SomeFunction()
{
    EmitSignal(nameof(MySignal));
    EmitSignal("MySignalWithArguments", "hello there", 28);
}

Keep in mind that you can always reference a signal name with the nameof keyword applied to the delegate itself).

You can bind values when establishing a connection by passing an object array.

public int Value { get; private set; } = 0;

private void ModifyValue(int modifier)
{
    Value += modifier;
}

public void SomeFunction()
{
    var plusButton = (Button)GetNode("PlusButton");
    var minusButton = (Button)GetNode("MinusButton");

    plusButton.Connect("pressed", this, "ModifyValue", new object[] { 1 });
    minusButton.Connect("pressed", this, "ModifyValue", new object[] { -1 });
}

Signals support parameters and bind values of all built-in types and of classes derived from Godot.Object. Consequently, any Node or Reference will be compatible automatically, but custom data objects will need to extend from Godot.Object or one of its subclasses.

public class DataObject : Godot.Object
{
    public string Field1 { get; set; }
    public string Field2 { get; set; }
}

Finally, signals can be created by calling AddUserSignal, but keep in mind that this must be executed before the signals are used (with Connect or EmitSignal).

public void SomeFunction()
{
    AddUserSignal("MyOtherSignal");
    EmitSignal("MyOtherSignal");
}

Preprocessor defines

Godot has a set of defines that let you change your C# code depending on the environment you are compiling for.

WARNING

If you created your project before Godot 3.2, you need to modify or regenerate your csproj file to use this feature

Examples

For example, you can change code depending on the platform.

    public override void _Ready()
    {
#if GODOT_SERVER
        // Don't try to load meshes or anything, this is a server!
        LaunchServer();
#elif GODOT_32 || GODOT_MOBILE || GODOT_WEB
        // Use simple objects when running on less powerful systems.
        SpawnSimpleObjects();
#else
        SpawnComplexObjects();
#endif
    }

Or you can detect which engine your code is in, which is useful for making cross-engine libraries.

    public void MyPlatformPrinter()
    {
#if GODOT
        GD.Print("This is Godot.");
#elif UNITY_5_3_OR_NEWER
        print("This is Unity.");
#else
        throw new InvalidWorkflowException("Only Godot and Unity are supported.");
#endif
    }

Full list of defines

  • GODOT is always defined for Godot projects.
  • One of GODOT_64 or GODOT_32 is defined depending on if the architecture is 64-bit or 32-bit.
  • One of GODOT_X11, GODOT_WINDOWS, GODOT_OSX, GODOT_ANDROID, GODOT_IOS, GODOT_HTML5, or GODOT_SERVER depending on the OS. These names may change in the future. These are created from the get_name() method of the OS singleton, but not every possible OS the method returns is an OS that Godot with Mono runs on.

When exporting, the following may also be defined depending on the export features.

  • One of GODOT_PC, GODOT_MOBILE, or GODOT_WEB depending on the platform type.
  • One of GODOT_ARM64_V8A or GODOT_ARMEABI_V7A on Android only depending on the architecture.
  • One of GODOT_ARM64 or GODOT_ARMV7 on iOS only depending on the architecture.
  • Any of GODOT_S3TC, GODOT_ETC, and GODOT_ETC2 depending on the texture compression type.
  • Any custom features added in the export menu will be capitalized and prefixed: foo -> GODOT_FOO.

To see an example project, see the OS testing demo: https://github.com/godotengine/godot-demo-projects/tree/master/misc/os_test

Using NuGet packages in Godot

NuGet packages can be installed and used with Godot, as with any project. Many IDEs can add packages directly. You can also manually add a package reference to the .csproj file at the project root:

<Project>
    ...
    <ItemGroup>
        <PackageReference Include="Newtonsoft.Json">
          <Version>11.0.2</Version>
        </PackageReference>
    </ItemGroup>
    ...
</Project>

Calling GDScript functions from C#

var Node = GetNode<NodeClassName>("NodeName")

Node.Call("function", "parameters");
// or
string funcResult = (string)Node.Call("function", "parameters);

How to run code in the editor

Add the [Tool] Attributes to the class. Then you can tell whether it is running in the editor with the Engine.EditorHint variable.

using Godot;
using System;

[Tool]
public class MySprite : Sprite
{
    public override void _Process(float delta)
    {
        if (Engine.EditorHint)
        {
            // Code to execute in editor.
        }

        if (!Engine.EditorHint)
        {
            // Code to execute in game.
        }

        // Code to execute both in editor and in game.
    }
}

How to show dynamically added nodes in the editor

using Godot;
using System;

[Tool]
public class HexagonTileMap : Node2D
{
    private static PackedScene HEXAGON_TILE = GD.Load<PackedScene>("res://Components/HexagonTile.tscn");

    private int _width = 24;
    private int _height = 24;
    private float _radius = 48f;
    private float _lineWidth = 1f;

    public override void _Ready()
    {
        UpdateTiles();
    }

    public void ClearChildren()
    {
        var children = GetChildren();
        if (children.Count > 0)
        {
            foreach (Node node in children)
            {
                RemoveChild(node);
                node.QueueFree();
            }
        }
    }

    public void UpdateTiles()
    {
        ClearChildren();

        for (var x = 0; x < this._width; ++x)
        {
            for (var y = 0; y < this._height; ++y)
            {
                var tile = HEXAGON_TILE.Instance<HexagonTile>();
                tile.radius = this._radius;
                tile.lineWidth = this._lineWidth;
                tile.Position = new Vector2(x * this._radius, y * this._radius);
                if (Engine.EditorHint)
                {
                    tile.Owner = GetTree().EditedSceneRoot;
                }
                AddChild(tile);
            }
        }
    }
}

csproj example

Used as follows when developing EudPiano with Godot version 3.2.3.

<?xml version="1.0" encoding="utf-8"?>
<Project Sdk="Godot.NET.Sdk/3.2.3">
  <PropertyGroup>
    <ProjectGuid>{3D2D5DB4-B5C8-49AB-9671-40CCF377C9D2}</ProjectGuid>
    <OutputType>Library</OutputType>
    <RootNamespace>EduPiano</RootNamespace>
    <AssemblyName>EduPiano</AssemblyName>
    <GodotProjectGeneratorVersion>1.0.0.0</GodotProjectGeneratorVersion>
    <GodotUseNETFrameworkRefAssemblies>true</GodotUseNETFrameworkRefAssemblies>
    <TargetFramework>net472</TargetFramework>
    <!-- <TargetFramework>netcoreapp3.0</TargetFramework> -->
    <!--The following properties were overriden during migration to prevent errors.
    Enabling them may require other manual changes to the project and its files.-->
    <EnableDefaultCompileItems>false</EnableDefaultCompileItems>
    <GenerateAssemblyInfo>false</GenerateAssemblyInfo>
    <Deterministic>false</Deterministic>
  </PropertyGroup>
  <ItemGroup>
    <Reference Include="System" />
    <Reference Include="System.IO.Compression" />
  </ItemGroup>
  <ItemGroup>
    <PackageReference Include="System.ComponentModel.Annotations" Version="5.0.0" />
    <!-- <PackageReference Include="MusicXml.NET" Version="2.0.1" /> -->
  </ItemGroup>
  <ItemGroup>
    <Compile Include="Properties\AssemblyInfo.cs" />
    <Compile Include="Scene\AnimatedSprite.cs" />
    <Compile Include="Scene\Component\Board\Board.cs" />
    <Compile Include="Scene\Component\Board\MyProgressBoard.cs" />
    <Compile Include="Scene\Component\Board\ProgressBar.cs" />
    <Compile Include="Scene\Component\Character.cs" />
    <Compile Include="Scene\Component\Board\MonthlyReportCrayonBoard.cs" />
    <Compile Include="Scene\Component\Board\MonthlyReportDetailBoard.cs" />
    <Compile Include="Scene\Component\HomeMap.cs" />
    <Compile Include="Scene\Component\Score\ScoreCanvas.cs" />
    <Compile Include="Scene\Component\Score\EighthNote.cs" />
    <Compile Include="Scene\Component\Score\HalfNote.cs" />
    <Compile Include="Scene\Component\Score\QuaterNote.cs" />
    <Compile Include="Scene\Component\Score\Rhythm.cs" />
    <Compile Include="Scene\Component\Score\SixteenthNote.cs" />
    <Compile Include="Scene\Component\Score\Staff.cs" />
    <Compile Include="Scene\Component\Score\WholeNote.cs" />
    <Compile Include="Scene\Component\Score\XmlReader.cs" />
    <Compile Include="Scene\Component\Score\MusicXmlReader.cs" />
    <Compile Include="Scene\Component\NavigationLinks.cs" />
    <Compile Include="Scene\Component\NavigationLinksMap.cs" />
    <Compile Include="Scene\Component\MyPageLinks.cs" />
    <Compile Include="Scene\Component\PianoOctave.cs" />
    <Compile Include="Scene\Component\TitleBar.cs" />
    <Compile Include="Scene\Component\Pitch\PitchDetection.cs" />
    <Compile Include="Scene\Component\Pitch\Complex.cs" />
    <Compile Include="Scene\Component\Pitch\FourierTransform.cs" />
    <Compile Include="Scene\Component\Pitch\Tools.cs" />
    <Compile Include="Scene\Component\Pitch\PitchRange.cs" />
    <Compile Include="Scene\Component\VirtualKeyboardEdit.cs" />
    <Compile Include="Scene\CustomDialog.cs" />
    <Compile Include="Scene\EPFactoryCollectionUI.cs" />
    <Compile Include="Scene\EPMagazineDetailUI.cs" />
    <Compile Include="Scene\EPMagazineUI.cs" />
    <Compile Include="Scene\FindIdResultUI.cs" />
    <Compile Include="Scene\FindIdUI.cs" />
    <Compile Include="Scene\FindPwResultUI.cs" />
    <Compile Include="Scene\FindPwUI.cs" />
    <Compile Include="Scene\GameUI.cs" />
    <Compile Include="Scene\HomeUI.cs" />
    <Compile Include="Scene\ItemShopUI.cs" />
    <Compile Include="Scene\LoginUI.cs" />
    <Compile Include="Scene\LoginUI2.cs" />
    <Compile Include="Scene\MenuUI.cs" />
    <Compile Include="Scene\MonthlyReportCrayonUI.cs" />
    <Compile Include="Scene\MonthlyReportDetailUI.cs" />
    <Compile Include="Scene\MonthlyReportUI.cs" />
    <Compile Include="Scene\MyCharacterUI.cs" />
    <Compile Include="Scene\MyInfoUI.cs" />
    <Compile Include="Scene\MyItemUI.cs" />
    <Compile Include="Scene\MyProgressUI.cs" />
    <Compile Include="Scene\MyRepertoryUI.cs" />
    <Compile Include="Scene\NoticeUI.cs" />
    <Compile Include="Scene\OnePointLessonUI.cs" />
    <Compile Include="Scene\PlayAndPlay.cs" />
    <Compile Include="Scene\PlayPlayUI.cs" />
    <Compile Include="Scene\PlayPlayVideoPlayerUI.cs" />
    <Compile Include="Scene\ProgressRateUI.cs" />
    <Compile Include="Scene\ScheduleUI.cs" />
    <Compile Include="Scene\VersionUI.cs" />
  </ItemGroup>
</Project>

See also

Favorite site

author Zer0 sitemap