Skip to content

Godot:CSharp

godot의 C# 스크립트에 대한 설명.

프로젝트 셋업

다음의 개발환경을 사용한다:

  • Godot
  • C#
    • .NET Core SDK - Godot은 이미 컴파일된 게임을 실행하는 데 필요한 Mono의 일부를 번들로 제공하지만, Godot에는 MSBuild와 같은 게임을 빌드하고 컴파일하는 데 필요한 도구가 포함되어 있지 않습니다. 이를 위해 .NET Core SDK를 설치해야 한다.
    • Godot 4.0 은 .NET6 (.NET7은 지원하지 않으므로 .NET6을 별도로 설치해야 한다)
    • Mono SDK - 하술하겠지만 Linux를 사용할 경우 VSCode의 C# 도구 플러그인이 작동되도록 하기 위해 필요하다. Godot의 C# 컴파일을 위해 필요한게 아니다.
  • VSCode

참고로 .NET 은 버전은 다음과 같이 할 수 있다.

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

VCS Settings

Godot이 생성하는 폴더 중 버전 관리 시스템이 무시하도록 설정해야 할 폴더가 몇 가지 있습니다:

  • .import/: 이 폴더는 소스 에셋과 에셋의 가져오기 플래그에 따라 가져온 모든 파일이 저장되는 곳입니다.
  • *.translation: 이 파일은 CSV 파일에서 생성된 번역을 이진 파일로 가져온 파일입니다.
  • export_presets.cfg: 이 파일에는 Android keystore 보증서 같이 민감한 정보를 포함한 프로젝트의 모든 내보내기 프리셋이 적혀있습니다.
  • .mono/: 이 폴더는 자동 생성된 Mono 파일입니다. Mono 버전의 Godot을 사용한 프로젝트에만 존재합니다.

플러그인 관련 문제가 있다면 OmniSharp#Troubleshooting 항목 참조.

Install Godot

여기에서 Mono version (C# support)을 다운로드 하고 적당한 폴더에 푼다.

고도 에디터에서 Editor -> Editor Settings 메뉴의, Mono -> Editor -> External EditorVisual Studio Code로 설정.

프로젝트 설정에서 Mono -> Wait For Debugger를 체크한다. 필요에 따라 Wait Timeout도 조절하자.

INFORMATION

VSCode에서 C# Tools for Godot 확장 플러그인으로 실행할 경우 Wait For Debugger 설정이 문제될 수 있다. 이 때는 해당 옵션을 끄자.

Install Visual Studio Code

VSCode 확장은 다음을 설치한다:

INFORMATION

Linux를 VSCode를 사용하는 경우 C# 도구 플러그인이 작동하려면 Mono SDK를 설치해야 합니다.

launch.jsontasks.json 파일을 열고 Godot 실행파일 경로를 설정한다.

리눅스에서 디버깅 설정은 C# Mono를 선택해야 한다.

Install .NET Core SDK

.NET Core#SDK페이지 참조. Ubuntu 20.04 에서는 간단히:

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

Mono#Ubuntu 20.04 (amd64, armhf, arm64, ppc64el) 를 참고하여 설치. 간단히:

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

Godot에서 새로운 C# 스크립트를 생성하면 솔루션 파일(.sln), 프로젝트 파일(.csproj), Properties/AssemblyInfo.cs.mono 폴더가 자동 생성된다.

.mono 폴더를 제외한 모든 파일을 VCS에 추가한다.

문제가 생기면 .mono 폴더를 제거하면 해결될 수 있다.

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.
    }
}

GDScript의 print와 같은 함수는 Godot Namespace 에 있는 GD 클래스를 참조하면 사용할 수 있다.

전체 클래스 참조:

INFORMATION

노드에 연결하려는 클래스는 파일과 이름이 같아야 합니다. 안그럼 Cannot find class XXX for scriptres://XXX.cs 같은 에러가 출력된다.

Debugging

Godot에서 실행 후, (위에서 설정한) Wait Timeout 시간안에 VSCode의 RUN AND DEBUG 탭에서 Attach 로 실행한다. (Launch 가 아니다!)

참조 영상:

명령행에서 직접 빌드

하단, MSBuild 탭의 로그를 확인하면 된다.

"/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

현재 확인된 알려진 문제들

  • 편집기 플러그인 작성이 가능하지만 현재 상당히 복잡합니다.
  • 현재 상태는 내보낸 변수를 제외하고 핫 리로딩 시 저장 및 복원되지 않습니다.
  • Attached C# 스크립트는 파일 이름과 일치하는 클래스 이름이 있는 Class를 참조해야 합니다.
  • Godot의 snake_case API 명명 규칙에 의존하는 Get()/Set(),Call()/CallDeferred() 및 신호 연결 방법 Connect()와 같은 몇 가지 방법이 있습니다.
    • 예를 들면, CallDeferred("AddChild")를 사용하면 AddChild가 작동하지 않는데, API가 원래 snake_case 버전의 add_child를 예상하기 때문이다.
    • 그러나 이 제한 없이 모든 사용자 지정 속성이나 메서드를 사용할 수 있습니다.

Mono 프로젝트 내보내기는 데스크탑 플랫폼(Linux, Windows 및 macOS), Android, HTML5 및 iOS에서 지원됩니다. 아직 지원되지 않는 유일한 플랫폼은 UWP입니다.

전제 지식

C# API differences to GDScript 가 중요!

리소스 로드

C# 에는 GDScript의 loadpreload 키워드가 없다. 대신 ResourceLoader 클래스를 사용해야 한다.

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

특히 preloadGDScript에만 해당하는 기능이다. C#에는 없다. (Godot은 스크립트가로드 될 때 리소스를 로드할 수 있지만 C# 에서는 불가능 하다)

형 변환(Type conversion)과 캐스팅(casting)

캐스트(Cast)와 타입 체크(Type Check)하기

반환된 노드를 Sprite로 캐스트 할 수 없는 경우 InvalidCastException을 발생시킵니다. 실패하지 않는다고 확신하면 as 연산자 대신에 그것을 사용할 것입니다.

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

AS 연산자 사용하기

as 연산자는 노드를 Sprite로 캐스트 할 수없는 경우 null을 반환하고 이러한 이유로 값 유형과 함께 사용할 수 없습니다.

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

일반 메서드 사용하기

일반 메서드는 이 타입 변환을 투명하게 만들기 위해 제공됩니다.

GetNode<T>()은 반환하기 전에 노드를 캐스트 합니다. 노드가 원하는 타입으로 캐스트 되지 않는다면 InvalidCastException을 발생시킵니다.

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

GetNodeOrNull<T>()as 연산자를 사용하고 노드가 원하는 타입으로 캐스트 되지 않는다면 null을 반환합니다.

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

IS 연산자를 사용하여 타입 체크하기

노드가 Sprite로 캐스트 되도록 체크하기 위해, is 연산자를 사용할 수 있습니다. Sprite가 캐스트 되지 않는다면 is 연산자는 거짓을 반환하고, 그렇지 않으면 참을 반환합니다.

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

속성 수출 (Export)

GDScript의 export와 동일한 [Export]을 사용할 수 있다. 이 속성은 선택적 PropertyHinthintString 매개 변수와 함께 제공 될 수도 있습니다. 값을 할당하여 기본값을 설정할 수 있습니다.

예시:

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;
}

시그널

완전한 C# 예제를 위해, 단계별 스크립팅(Scripting) 튜토리얼에서 시그널 다루기를 참고하세요.

C#에서 시그널 선언은 델리게이트(delegate)에서 [Signal] 속성으로 됩니다.

[Signal]
delegate void MySignal();

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

이러한 신호는 편집기에서 또는 Connect를 사용하여 코드에서 연결할 수 있습니다. 편집기에서 신호를 연결하려면 새 신호를보기 위해 프로젝트 어셈블리를 재빌드해야합니다. 이 빌드는 편집기 창의 오른쪽 상단에있는 "Build"버튼을 클릭하여 수동으로 트리거 할 수 있습니다.

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");
}

EmitSignal 메서드로 시그널을 방출할 수 있습니다.

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

nameof 키워드로 항상 시그널 이름을 참조할 수 있다는 것을 명심하세요 델리게이트 자체에 적용됨).

객체 배열을 전달함으로써 연결을 지을 때 값들을 묶을 수 있습니다.

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 });
}

시그널은 매개변수를 지원하고, 모든 내장 타입의 값들과 Godot.Object에서 파생된 클래스들을 묶습니다. 결과적으로, 모든 NodeReference는 자동으로 호환될 것이지만, 맞춤 데이터 객체는 Godot.Object나 그 하위 클래스에서 확장해야 할 것입니다.

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

마침내, 시그널은 AddUserSignal로 호출하는 것으로 생성될 수 있지만, 시그널을 사용하기 전에 실행되어야 한다는 것을 명심하세요 (ConnectEmitSignal로 말이죠).

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

Preprocessor defines

Godot에는 컴파일하는 환경에 따라 C# 코드를 변경할 수있는 정의 세트가 있습니다.

WARNING

Godot 3.2 이전에 프로젝트를 만든 경우이 기능을 사용하려면 csproj 파일을 수정하거나 재생성해야합니다

예제

예를 들어 플랫폼에 따라 코드를 변경할 수 있습니다.

    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
    }

또는 크로스 엔진 라이브러리를 만드는 데 유용한 코드가있는 엔진을 감지 할 수 있습니다.

    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
    }

전체 정의 목록

  • 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.

내보낼 때 내보내기 기능에 따라 다음을 정의 할 수도 있습니다.

  • 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

Godot에서 NuGet 패키지 사용하기

NuGet 패키지를 설치하여 프로젝트처럼, Godot와 사용할 수 있습니다. 많은 IDE는 직접 패키지를 추가할 수 있습니다. 또한 프로젝트 루트에 있는 .csproj 파일에 패키지 참조를 수동으로 추가할 수 있습니다:

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

C# 에서 GDScript의 함수 호출

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

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

에디터에서 코드 재생하는 방법

클레스에 [Tool] Attributes 를 추가하면 된다. 이후, Engine.EditorHint 변수로 에디터에서 실행되는지 여부를 분간할 수 있다.

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.
    }
}

동적으로 추가된 노드를 에디터에 출력하는 방법

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

Godot 3.2.3 버전에서 EudPiano 개발시 다음과 같이 사용했다.

<?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