godot-csharp

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Godot C# / .NET (4.x)

Godot C# / .NET (4.x)

Write Godot game code in C#: node subclasses, the engine lifecycle, exports, signals as events, and GDScript interop. Targets Godot 4.3+ (.NET / C#); install the .NET SDK that matches your Godot version — .NET 6.0 for 4.3/4.4, .NET 8 for 4.5+.
使用C#编写Godot游戏代码:节点子类、引擎生命周期、导出属性、将信号作为事件处理,以及与GDScript的互操作。适用于Godot 4.3+(.NET / C#);请安装与你的Godot版本匹配的.NET SDK——Godot 4.3/4.4对应**.NET 6.0**,Godot 4.5+对应**.NET 8**。

When to use

适用场景

  • Use when scripting a Godot game in C# (
    .cs
    +
    .csproj
    ), translating GDScript idioms to C#, exposing
    [Export]
    fields, or wiring
    [Signal]
    delegates and GetNode<T>.
When not to use: GDScript-specific syntax →
godot-gdscript
; engine concepts that are language-neutral (scenes, physics, animation) → the relevant
godot-*
skill. You need the Godot .NET build + the .NET SDK installed; the standard build can't run C#.
  • 适用于使用C#编写Godot游戏脚本(
    .cs
    +
    .csproj
    )、将GDScript惯用写法转换为C#、暴露
    [Export]
    字段,或是配置
    [Signal]
    委托与GetNode<T>的场景。
不适用场景:仅涉及GDScript特定语法的内容→参考
godot-gdscript
;与语言无关的引擎概念(场景、物理、动画)→参考对应的
godot-*
技能。你需要安装Godot .NET版本以及.NET SDK;标准版本的Godot无法运行C#代码。

Core workflow

核心工作流程

  1. Use the Godot .NET editor build and install the matching .NET SDK (.NET 6.0 for Godot 4.3/4.4, .NET 8 for 4.5+). Creating the first C# script generates a
    .csproj
    /
    .sln
    . Build with the editor or
    dotnet build
    .
  2. Every node script is a
    partial
    class
    extending a Godot type (the source generator relies on
    partial
    ). The file/class name should match the node script.
  3. Override lifecycle methods in PascalCase with
    double
    delta:
    _Ready()
    ,
    _Process(double delta)
    ,
    _PhysicsProcess(double delta)
    .
  4. Expose tunables with
    [Export]
    ; they show in the Inspector like GDScript
    @export
    .
  5. Declare signals as
    [Signal]
    delegates
    named
    XxxEventHandler
    ; emit with
    EmitSignal(SignalName.Xxx, ...)
    and subscribe with the generated C#
    event
    .
  6. Get nodes with
    GetNode<T>("Path")
    (or
    %Unique
    ), and call into GDScript with
    Call
    /
    Get
    /
    Set
    when needed.
  1. 使用Godot .NET编辑器版本并安装匹配的.NET SDK(Godot 4.3/4.4对应.NET 6.0,Godot 4.5+对应.NET 8)。创建第一个C#脚本时会生成
    .csproj
    /
    .sln
    文件。可通过编辑器或
    dotnet build
    命令进行构建。
  2. 每个节点脚本都是继承自Godot类型的
    partial
    (源代码生成器依赖
    partial
    关键字)。文件/类名应与节点脚本名称一致。
  3. 以PascalCase命名重写生命周期方法,参数为
    double
    类型的delta:
    _Ready()
    _Process(double delta)
    _PhysicsProcess(double delta)
  4. 使用
    [Export]
    暴露可调节参数
    ;这些参数会像GDScript的
    @export
    一样显示在检视面板中。
  5. 将信号声明为
    [Signal]
    委托
    ,命名需以
    XxxEventHandler
    结尾;使用
    EmitSignal(SignalName.Xxx, ...)
    发送信号,并通过生成的C#
    event
    订阅信号。
  6. 使用
    GetNode<T>("Path")
    (或
    %Unique
    )获取节点
    ,必要时通过
    Call
    /
    Get
    /
    Set
    调用GDScript代码。

Patterns

模式示例

1. A node script: lifecycle, [Export], GetNode<T>

1. 节点脚本:生命周期、[Export]、GetNode<T>

csharp
using Godot;

public partial class Player : CharacterBody2D
{
    [Export] public float Speed = 200.0f;          // editable in the Inspector
    [Export] public float JumpVelocity = -400.0f;

    private const float Gravity = 1200.0f;
    private AnimatedSprite2D _sprite;

    public override void _Ready()
    {
        _sprite = GetNode<AnimatedSprite2D>("AnimatedSprite2D");
    }

    public override void _PhysicsProcess(double delta)
    {
        Vector2 v = Velocity;                       // Velocity is a property here
        if (!IsOnFloor())
            v.Y += Gravity * (float)delta;          // delta is double; cast for float math
        if (Input.IsActionJustPressed("jump") && IsOnFloor())
            v.Y = JumpVelocity;

        float dir = Input.GetAxis("move_left", "move_right");
        v.X = dir != 0 ? dir * Speed : Mathf.MoveToward(v.X, 0, Speed);

        Velocity = v;
        MoveAndSlide();                             // no args, like GDScript 4.x
    }
}
csharp
using Godot;

public partial class Player : CharacterBody2D
{
    [Export] public float Speed = 200.0f;          // editable in the Inspector
    [Export] public float JumpVelocity = -400.0f;

    private const float Gravity = 1200.0f;
    private AnimatedSprite2D _sprite;

    public override void _Ready()
    {
        _sprite = GetNode<AnimatedSprite2D>("AnimatedSprite2D");
    }

    public override void _PhysicsProcess(double delta)
    {
        Vector2 v = Velocity;                       // Velocity is a property here
        if (!IsOnFloor())
            v.Y += Gravity * (float)delta;          // delta is double; cast for float math
        if (Input.IsActionJustPressed("jump") && IsOnFloor())
            v.Y = JumpVelocity;

        float dir = Input.GetAxis("move_left", "move_right");
        v.X = dir != 0 ? dir * Speed : Mathf.MoveToward(v.X, 0, Speed);

        Velocity = v;
        MoveAndSlide();                             // no args, like GDScript 4.x
    }
}

2. Signals as C# events

2. 将信号作为C#事件使用

csharp
using Godot;

public partial class Health : Node
{
    // Delegate name MUST end with "EventHandler"; generator creates the event + SignalName.
    [Signal] public delegate void HealthChangedEventHandler(int current, int max);

    private int _hp = 100;

    public void TakeDamage(int amount)
    {
        _hp = Mathf.Max(_hp - amount, 0);
        EmitSignal(SignalName.HealthChanged, _hp, 100);   // type-safe signal name
    }

    public override void _Ready()
    {
        HealthChanged += OnHealthChanged;            // subscribe like a normal C# event
    }

    private void OnHealthChanged(int current, int max) => GD.Print($"HP {current}/{max}");
}
csharp
using Godot;

public partial class Health : Node
{
    // Delegate name MUST end with "EventHandler"; generator creates the event + SignalName.
    [Signal] public delegate void HealthChangedEventHandler(int current, int max);

    private int _hp = 100;

    public void TakeDamage(int amount)
    {
        _hp = Mathf.Max(_hp - amount, 0);
        EmitSignal(SignalName.HealthChanged, _hp, 100);   // type-safe signal name
    }

    public override void _Ready()
    {
        HealthChanged += OnHealthChanged;            // subscribe like a normal C# event
    }

    private void OnHealthChanged(int current, int max) => GD.Print($"HP {current}/{max}");
}

3. Instancing a scene in C#

3. 在C#中实例化场景

csharp
public partial class Spawner : Node2D
{
    // Load once; PackedScene is the C# equivalent of preload's result.
    private readonly PackedScene _bullet = GD.Load<PackedScene>("res://bullet.tscn");

    public void Shoot(Vector2 at)
    {
        var b = _bullet.Instantiate<Node2D>();       // typed instantiate
        b.GlobalPosition = at;
        AddChild(b);
    }
}
csharp
public partial class Spawner : Node2D
{
    // Load once; PackedScene is the C# equivalent of preload's result.
    private readonly PackedScene _bullet = GD.Load<PackedScene>("res://bullet.tscn");

    public void Shoot(Vector2 at)
    {
        var b = _bullet.Instantiate<Node2D>();       // typed instantiate
        b.GlobalPosition = at;
        AddChild(b);
    }
}

4. Interop with GDScript nodes

4. 与GDScript节点互操作

csharp
public override void _Ready()
{
    Node gd = GetNode("GDScriptNode");
    // Call a GDScript method and read/write its properties dynamically.
    gd.Call("take_damage", 10);
    int score = (int)gd.Get("score");
    gd.Set("score", score + 5);
    // Connect to a GDScript signal by name:
    gd.Connect("died", Callable.From(OnDied));
}

private void OnDied() => GD.Print("entity died");
csharp
public override void _Ready()
{
    Node gd = GetNode("GDScriptNode");
    // Call a GDScript method and read/write its properties dynamically.
    gd.Call("take_damage", 10);
    int score = (int)gd.Get("score");
    gd.Set("score", score + 5);
    // Connect to a GDScript signal by name:
    gd.Connect("died", Callable.From(OnDied));
}

private void OnDied() => GD.Print("entity died");

Pitfalls

常见陷阱

  • Forgetting
    partial
    .
    Without
    partial
    , the Godot source generator can't extend the class and
    [Export]
    /
    [Signal]
    break with confusing build errors.
  • Wrong method case/signature. C# overrides are
    _Ready
    ,
    _Process(double)
    ,
    _PhysicsProcess(double)
    — PascalCase and
    double
    delta (GDScript uses snake_case and
    float
    ). A mismatched name just won't be called.
  • [Signal]
    delegate naming.
    It must end with
    EventHandler
    ; the engine exposes the signal as the name without that suffix and generates
    SignalName.X
    and a C#
    event
    .
  • GD.Print
    vs
    Console.WriteLine
    .
    Use
    GD.Print
    /
    GD.PrintErr
    to reach the Godot output panel;
    Console
    output may not appear.
  • Value-type structs.
    Vector2
    ,
    Color
    ,
    Transform2D
    are structs — mutate a local copy (
    var v = Velocity; v.X = ...; Velocity = v;
    ); editing
    Velocity.X
    directly won't compile/persist.
  • Needs the .NET build + SDK. The non-.NET editor can't run C#; mismatched/missing .NET SDK causes build failures. Match the SDK to the Godot version (4.3/4.4 → .NET 6.0, 4.5+ → .NET 8; Android export on 4.5 needs .NET 9).
  • QueueFree()
    vs
    Free()
    — same rules as GDScript; prefer
    QueueFree()
    . Disposed objects throw
    ObjectDisposedException
    if used after freeing.
  • Export to some platforms differs for .NET (e.g. extra steps for web/mobile); check the .NET export notes for your target.
  • 忘记添加
    partial
    关键字
    。如果没有
    partial
    ,Godot源代码生成器无法扩展该类,
    [Export]
    /
    [Signal]
    会出现难以理解的构建错误。
  • 方法命名/签名错误。C#中的重写方法为
    _Ready
    _Process(double)
    _PhysicsProcess(double)
    ——采用PascalCase命名且delta参数为
    double
    类型(GDScript使用蛇形命名法和
    float
    类型)。命名不匹配的方法不会被调用。
  • [Signal]
    委托命名错误
    。委托名称必须以
    EventHandler
    结尾;引擎会将信号暴露为去掉该后缀的名称,并生成
    SignalName.X
    和对应的C#
    event
  • GD.Print
    Console.WriteLine
    的区别
    。使用
    GD.Print
    /
    GD.PrintErr
    输出到Godot的面板;
    Console
    的输出可能无法显示。
  • 值类型结构体
    Vector2
    Color
    Transform2D
    都是结构体——需要修改本地副本(
    var v = Velocity; v.X = ...; Velocity = v;
    );直接修改
    Velocity.X
    无法编译或生效。
  • 需要.NET版本的Godot和对应SDK。非.NET版本的编辑器无法运行C#代码;SDK版本不匹配或缺失会导致构建失败。请确保SDK版本与Godot版本匹配(4.3/4.4→.NET 6.0,4.5+→.NET 8;4.5版本导出Android需要.NET 9)。
  • QueueFree()
    Free()
    的区别
    ——规则与GDScript一致;优先使用
    QueueFree()
    。释放后的对象如果被再次使用会抛出
    ObjectDisposedException
    异常。
  • .NET版本导出部分平台的流程不同(例如Web/移动端需要额外步骤);请查看目标平台的.NET导出说明。

References

参考资料

  • For export attribute variants (
    [ExportGroup]
    , ranges, typed arrays), async with
    await ToSignal(...)
    ,
    Godot.Collections
    vs System collections, custom Resources in C#, and project/build setup, read
    references/csharp-setup-and-interop.md
    .
  • 关于导出属性的变体(
    [ExportGroup]
    、范围、类型化数组)、使用
    await ToSignal(...)
    实现异步、
    Godot.Collections
    与系统集合的区别、C#中的自定义资源,以及项目/构建配置,请阅读
    references/csharp-setup-and-interop.md

Related skills

相关技能

  • godot-gdscript
    — the GDScript equivalents of these patterns.
  • godot-signals-groups
    — signal/event architecture (language-neutral).
  • godot-resources
    — data resources; the C#
    [Export]
    +
    Resource
    pattern.
  • unity-csharp-scripting
    — C# in Unity, for developers coming from there.
  • godot-gdscript
    ——这些模式对应的GDScript实现。
  • godot-signals-groups
    ——信号/事件架构(与语言无关)。
  • godot-resources
    ——数据资源;C#中的
    [Export]
    +
    Resource
    模式。
  • unity-csharp-scripting
    ——Unity中的C#脚本开发,供有Unity开发经验的开发者参考。