mcpField
C
csharp-godot

csharp-godot

github.com/jame581開発889168 回インストール4 日前に更新
リポジトリ

csharp-godot について

Godot 4.3以降の開発者向けC#スキルガイドです。言語の違い、プロジェクト設定、相互運用を例とトラブル対処を交えて説明します。

主な機能

  1. 01C#とGDScriptの構文比較
  2. 02Godot C#プロジェクトとIDEの設定
  3. 03シグナルと非同期処理のパターン
  4. 04C#とGDScriptの相互運用
  5. 05パフォーマンスとよくある問題の解説

活用シーン

  1. 01GDScriptのパターンをC#に置き換える場面
  2. 02Godot C#プロジェクトとIDEの設定
  3. 03Godotのシグナルや非同期処理の実装

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/jame581/godotprompter --skill csharp-godot

インストール後、Claude Code で/csharp-godotを入力して読み込まれたか確認します。

同梱ファイルreferences/gdscript-interop.md

詳細

Godot 4.3+ の C#

このスキルでは、C# 固有の規約、GDScript との API の違い、プロジェクトのセットアップ、相互運用のパターンを扱います。例はすべて C# のみです。Godot 4.3+ と GodotSharp NuGet パッケージを対象としています。

関連スキル: C# のシグナルパターンについては csharp-signals、C# プロジェクトのひな形については godot-project-setup、gdUnit4 を使った C# のテストについては godot-testing、C# では不十分な場合のネイティブ C++ については gdextension、C# の並行処理については multithreading を参照してください。


1. C# と GDScript の構文比較

GDScriptC# 相当備考
var x = 5var x = 5; または型を指定した int x = 5;C# の var はコンパイル時に型を推論します
func MyMethod() -> void:public void MyMethod() { }C# ではメソッド名に PascalCase を使います
signal health_changed(amount: int)[Signal] delegate void HealthChangedEventHandler(int amount);EventHandler サフィックスを付ける必要があります
@export var speed: float = 100.0[Export] public float Speed { get; set; } = 100f;PascalCase を使い、プロパティ構文で記述します
@onready var label = $Labelprivate Label _label; + _label = GetNode<Label>("Label"); を _Ready() 内で実行@onready に相当する機能はありません。_Ready() を使います
match value:switch (value) { case X: break; }C# の switch もパターンマッチングに対応しています
class_name MyClass[GlobalClass] public partial class MyClass : GodotObject { }[GlobalClass] 属性が必要です
extends Nodepublic partial class MyScript : Node { }: を使って継承します
preload("res://scene.tscn")GD.Load<PackedScene>("res://scene.tscn")コンパイル時ではなく、実行時に読み込まれます
push_error("msg")GD.PushError("msg");Godot のエラーログに出力します
print("msg")GD.Print("msg");GD.PrintS()、GD.PrintT() も使用できます
node is CharacterBody2Dnode is CharacterBody2Dキーワードも意味も同じです
node as CharacterBody2Dnode as CharacterBody2Dどちらも失敗すると null を返します
await signal_nameawait ToSignal(source, SignalName.X);ToSignal() ラッパーを使う必要があります
ArrayGodot.Collections.ArraySystem.Collections.Generic.List<T> ではありません
DictionaryGodot.Collections.DictionarySystem.Collections.Generic.Dictionary<K,V> ではありません

2. プロジェクトのセットアップ

.csproj とソリューション

Script > New Script > C# から最初の C# スクリプトを作成すると、Godot が .csproj を自動生成します。生成されたファイル構成は手動で編集しないでください。エディターに管理を任せてください。

MyProject/
├── MyProject.csproj          # Auto-generated, edit only for NuGet packages
├── MyProject.sln             # Auto-generated solution file
├── project.godot
└── scripts/
    └── Player.cs
NuGet パッケージ

<ItemGroup> 内の MyProject.csproj にパッケージを追加します。

<Project Sdk="Godot.NET.Sdk/4.3.0">
  <PropertyGroup>
    <TargetFramework>net8.0</TargetFramework>
    <Nullable>enable</Nullable>
    <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
  </PropertyGroup>
  <ItemGroup>
    
    <PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
  </ItemGroup>
</Project>

dotnet restore を実行するか、編集後に IDE が自動で復元するようにします。

Godot.NET プレビュー バインディング(Godot 4.7+)

Godot 4.7 の Godot.NET.Sdk では、オプトインの MSBuild プロパティを指定すると、プロジェクトのバインディングを従来の GodotSharp からプレビュー版の Godot.NET バインディングに切り替えられます(GodotSharp ではなく Godot.Bindings アセンブリを使用します)。

<PropertyGroup>
  <EnableGodotDotNetPreview>true</EnableGodotDotNetPreview>
</PropertyGroup>

プレビュー版バインディングは実験的な機能です。本番プロジェクトでは、このプロパティを設定しないでください。詳細は GH-118001 を参照してください。

IDE のセットアップ
IDE必要なセットアップ備考
JetBrains RiderGodot プラグインをインストールします(Rider 2023.3+ に同梱)Godot の C# サポートが最も充実しています。デバッガーはそのまま動作します
VS CodeC# Dev Kit と Godot Tools の拡張機能をインストールしますデバッガーをアタッチするには launch.json が必要です
Visual StudioGodot Visual Studio 拡張機能をインストールしますWindows のみ。デバッガーは Tools > Attach to Process から使用します

すべての IDE で、ソリューション全体を正しく解決するため、フォルダーだけでなく .sln ファイルを開いてください。


3. partial class の要件

Godot の型を継承するすべてのクラスは、partial として宣言する必要があります。これは任意ではありません。

理由

Godot は C# ソースジェネレーターを使い、シグナルの登録、プロパティのバインディング、RPC のコードをクラスと並べて生成します。ソースジェネレーターが同じクラス宣言にコードを挿入するには、partial が必要です。

忘れた場合のエラー
error GD0001: Missing partial modifier on declaration of type 'Player'
that derives from 'Godot.GodotObject'

ビルドが失敗するため、partial の記述漏れを見逃すことはありません。

ルール
// CORRECT
public partial class Player : CharacterBody2D { }

// WRONG — will cause source generator errors
public class Player : CharacterBody2D { }

これは、中間の基底クラスも含め、Godot の型を継承する継承チェーン内のすべてのクラスに適用されます。


4. 命名規則

要素規則例
メソッドPascalCasepublic void TakeDamage(int amount)
プロパティPascalCasepublic float MaxHealth { get; set; }
シグナル(デリゲート)PascalCase + EventHandler サフィックスHealthChangedEventHandler
[Export] プロパティPascalCase[Export] public float Speed { get; set; }
プライベートフィールド先頭にアンダースコアを付けた _camelCaseprivate float _currentSpeed;
ローカル変数camelCasevar newPosition = ...
パラメーターcamelCasevoid SetHealth(int newHealth)
Godot API 名Godot の PascalCase に正確に合わせるglobal_position ではなく GlobalPosition
列挙型型名とメンバー名は PascalCaseenum State { Idle, Running, Dead }
定数チームのスタイルに応じて PascalCase または SCREAMING_SNAKEconst float MaxSpeed = 200f;

GodotSharp のプロパティ名とメソッド名は、必ず正確に合わせてください。これらは GDScript の snake_case 名を PascalCase に変換したものです(例:is_on_floor() → IsOnFloor())。


5. C# のシグナル

シグナルには、[Signal] 属性を付けたデリゲート宣言が必要です。デリゲート名は 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() { ... }

ワンショット接続、静的型付きシグナル、言語をまたぐシグナル配線など、シグナルのパターン全般については、csharp-signals スキルを参照してください。


6. Async / Await

GDScript の await は、C# の await ToSignal(...) に対応します。Godot のシグナルは、C# の await と互換性のある SignalAwaiter を返します。

Godot シグナルの待機
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");
}
Task ベースのパターン

CPU 負荷の高い処理には Task.Run を使用します。ただし、メインスレッド以外から Godot オブジェクトにアクセスしてはいけません。

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();
}
GDScript の await に相当する記述
GDScriptC#
await get_tree().create_timer(1.0).timeoutawait ToSignal(GetTree().CreateTimer(1.0), SceneTreeTimer.SignalName.Timeout)
await animation_player.animation_finishedawait ToSignal(animPlayer, AnimationPlayer.SignalName.AnimationFinished)
await signal_nameawait ToSignal(this, SignalName.YourSignal)

7. GDScript との相互運用

C# から GDScript オブジェクトを呼び出すには Call、Get、Set を使用します。また、C# クラスが [GlobalClass] として登録されていれば、GDScript から直接使用できます。境界を越える値は Variant として扱われます。リファレンスにはマーシャリング時の注意点(null、int[]、List<T>)が記載されています。

Call/Get/Set の例、[GlobalClass] パターン、Variant のマーシャリング表については、references/gdscript-interop.md を参照してください。


8. パフォーマンス

C# と GDScript の比較
ワークロード優位な言語理由
数学処理の多いループ(経路探索、シミュレーション)C#(大幅に高速)JIT コンパイルと、インタープリター方式の GDScript の違い
大規模な配列/コレクションの処理C#値型の配列ではボックス化が発生しない
Godot API の呼び出し(move_and_slide など)ほぼ同等どちらも同じ C++ エンジンを介して処理される
シーンツリーの操作ほぼ同等ボトルネックは言語ではなく C++ のオーバーヘッド
迅速なプロトタイピングGDScript定型コードが少なく、再コンパイルせずにホットリロードできる
エディターツール(プラグイン、@tool)GDScriptC# のツールスクリプトはビルド一式が必要
ガイダンス
  • 物理ソルバー、プロシージャル生成、AI の意思決定ツリー、データ処理など、タイトなループが必要なシステムには C# を使用してください。
  • エディタープラグイン、@tool スクリプト、ゲームプレイロジックの迅速な反復には GDScript を使用してください。
  • 同じプロジェクト内での言語の混在がサポートされています。異なる言語間の呼び出しがたまにある程度なら、相互運用のコストは最小限です。
  • ホットパスでは Godot.Collections.Array と Array<T> を避けてください。Array<T> は同じエンジン配列をラップしているため、要素にアクセスするたびに Variant のマーシャリングが発生します。代わりに T[] または List<T> を使用し、境界で変換してください。
  • StringName の検索は O(1) ですが、StringName の構築はそうではありません。頻繁に作成する場合はキャッシュしてください。

9. よくある落とし穴

落とし穴問題修正方法
Variant から C# 型への変換(float)v は v.AsSingle() と同じ呼び出しです。どちらも例外はスローされず、int、bool、数値文字列は変換され、それ以外の型は 0 になります保存されている型が重要な場合は v.VariantType を確認してください
null と default(Variant) の違い値を渡さない Godot シグナルは、C# の null ではなく default(Variant) を渡します.VariantType == Variant.Type.Nil を確認してください
Godot.Collections と System.Collections の違いGodot API メソッドは Godot.Collections.Array を返します。List<T> は Variant に変換されないため、渡してもコンパイルできませんGodot API との境界では常に Godot.Collections.Array/Dictionary を使用してください
ネイティブオブジェクトの破棄解放済みの Godot オブジェクトのメソッドを呼び出すと、ObjectDisposedException がスローされます使用前に IsInstanceValid(obj) を確認してください
ホットループ内での StringName の構築new StringName("my_signal") は呼び出しごとにメモリを割り当てますprivate static readonly StringName _signalName = "my_signal"; としてキャッシュしてください
エクスポート配列の型[Export] public Array Items; は型指定のない配列をエクスポートします[Export] public Godot.Collections.Array<MyResource> Items { get; set; } を使用してください
ノードパス文字列パスが変更された場合、GetNode("../UI/Label") は実行時に Node not found をログに出力し、null を返します一意の名前を使って GetNode<Label>("%Label") を使用するか、型指定された [Export] ノード参照を使用してください
partial class の指定漏れビルドエラー GD0001 が発生しますGodot の型を継承するクラスはすべて partial にする必要があります
async void と async Task の違いasync void は await できません。誰からも await されない async Task は例外を隠します(Godot は async void の例外をログに記録します)Task を返して await してください。async void は Godot が呼び出すトップレベルのイベントハンドラーに限定してください

⚠️ Godot 4.7 での変更点: 4.7 の C# アセンブリには、いくつかの互換性を破る変更が含まれています。バイナリ互換性のみの変更(ソースは引き続きコンパイルでき、再ビルドだけで対応できます):OptimizedTranslation.Generate() の戻り値が void から bool に変更されました(GH-119563)。バイナリとソースの両方に互換性のない変更(コードの修正が必要です):Animation.Length の型メタデータが float から double に変更されました(GH-116394)。C# プロパティも double になったため、float len = anim.Length; はコンパイルできなくなりました。また、Control.AccessibilityLive の列挙型が DisplayServer.AccessibilityLiveMode から AccessibilityServer.AccessibilityLiveMode に変更されました(GH-116839)。宣言を AccessibilityServer の列挙型に更新してください。ソース互換性のない変更(既存のバイナリは引き続き読み込めます):RichTextLabel.ImageUpdateMask.WidthInPercent の名前が WidthUnit に変更されました(GH-112617。GDScript では UPDATE_WIDTH_IN_PERCENT から UPDATE_WIDTH_UNIT に変更)。また、EditorSceneFormatImporter の IMPORT_* 定数が ImportFlags 列挙型に移動しました(GH-115788)。削除されたもの:AudioEffectSpectrumAnalyzer.TapBackPos(GH-114355)。4.7 に対して再ビルドし、該当するシンボルを修正してください。4.7 移行ガイドを参照してください。


10. チェックリスト

  • Godot の型を継承するすべてのクラスが partial として宣言されている
  • メソッド名が GodotSharp の PascalCase に従っている(例:_Ready、_PhysicsProcess、IsOnFloor())
  • [Export] プロパティが PascalCase で、プロパティ構文({ get; set; })を使用している
  • シグナルデリゲート名が EventHandler で終わり、EmitSignal(SignalName.X) を使用している
  • シグナルを不要になった際に += で接続し、-= で切断している
  • Godot のシグナルには await ToSignal(...) を使用し、ゲーム内のタイミング制御に生の Task.Delay を使用していない
  • Godot API の境界では System.Collections 型ではなく、Godot.Collections.Array/Dictionary を使用している
  • 解放された可能性のある Godot オブジェクトを使用する前に、IsInstanceValid(obj) を確認している
  • ループ内または頻繁に呼び出されるメソッド内で生成する場合、StringName インスタンスをキャッシュしている
  • Task.Run(...) のコールバック内で Godot API を呼び出していない — エンジンの呼び出しはすべてメインスレッドで行っている
  • ソリューション全体を正しく解決するため、IDE では .sln ファイル経由で .csproj を開いている
  • 計算負荷の高いシステムには C# を使用し、エディターツールと @tool スクリプトには GDScript を引き続き使用している

コメント