Godot:CSharp
프로젝트 셋업
다음의 개발환경을 사용한다:
- 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# 컴파일을 위해 필요한게 아니다.
참고로 .NET 은 버전은 다음과 같이 할 수 있다.
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 Editor
를 Visual Studio Code
로 설정.
프로젝트 설정에서 Mono -> Wait For Debugger
를 체크한다. 필요에 따라 Wait Timeout
도 조절하자.
INFORMATION |
VSCode에서 |
Install Visual Studio Code
VSCode 확장은 다음을 설치한다:
- Install the C# extension.
- Install the Mono Debug extension.
- Install the C# Tools for Godot extension.
INFORMATION |
Linux를 VSCode를 사용하는 경우 C# 도구 플러그인이 작동하려면 Mono SDK를 설치해야 합니다. |
launch.json 와 tasks.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 |
노드에 연결하려는 클래스는 파일과 이름이 같아야 합니다. 안그럼 |
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의 load
및 preload
키워드가 없다. 대신 ResourceLoader 클래스를 사용해야 한다.
특히 preload
는 GDScript에만 해당하는 기능이다. C#에는 없다. (Godot은 스크립트가로드 될 때 리소스를 로드할 수 있지만 C# 에서는 불가능 하다)
형 변환(Type conversion)과 캐스팅(casting)
캐스트(Cast)와 타입 체크(Type Check)하기
반환된 노드를 Sprite로 캐스트 할 수 없는 경우 InvalidCastException을 발생시킵니다. 실패하지 않는다고 확신하면 as
연산자 대신에 그것을 사용할 것입니다.
AS 연산자 사용하기
as
연산자는 노드를 Sprite로 캐스트 할 수없는 경우 null
을 반환하고 이러한 이유로 값 유형과 함께 사용할 수 없습니다.
Sprite mySprite = GetNode("MySprite") as Sprite;
// Only call SetFrame() if mySprite is not null
mySprite?.SetFrame(0);
일반 메서드 사용하기
일반 메서드는 이 타입 변환을 투명하게 만들기 위해 제공됩니다.
GetNode
<T>()
은 반환하기 전에 노드를 캐스트 합니다. 노드가 원하는 타입으로 캐스트 되지 않는다면 InvalidCastException을 발생시킵니다.
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
연산자는 거짓을 반환하고, 그렇지 않으면 참을 반환합니다.
속성 수출 (Export)
GDScript의 export
와 동일한 [Export]
을 사용할 수 있다. 이 속성은 선택적 PropertyHint 및 hintString
매개 변수와 함께 제공 될 수도 있습니다. 값을 할당하여 기본값을 설정할 수 있습니다.
예시:
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에서 파생된 클래스들을 묶습니다. 결과적으로, 모든 Node
나 Reference
는 자동으로 호환될 것이지만, 맞춤 데이터 객체는 Godot.Object나 그 하위 클래스에서 확장해야 할 것입니다.
public class DataObject : Godot.Object
{
public string Field1 { get; set; }
public string Field2 { get; set; }
}
마침내, 시그널은 AddUserSignal
로 호출하는 것으로 생성될 수 있지만, 시그널을 사용하기 전에 실행되어야 한다는 것을 명심하세요 (Connect
나 EmitSignal
로 말이죠).
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
orGODOT_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
, orGODOT_SERVER
depending on the OS. These names may change in the future. These are created from theget_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
, orGODOT_WEB
depending on the platform type. - One of
GODOT_ARM64_V8A
orGODOT_ARMEABI_V7A
on Android only depending on the architecture. - One of
GODOT_ARM64
orGODOT_ARMV7
on iOS only depending on the architecture. - Any of
GODOT_S3TC
,GODOT_ETC
, andGODOT_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>