Этой статьёй я хочу внести некоторые правки в алгоритм развёртывания, который я описывал год назад вот здесь. Статья тогда получилась объёмная, и я искал способы её сократить, в результате чего опрометчиво пренебрёг несколькими сценариями, которые пригодились мне в дальнейшей разработке. Они образовали техдолг, который мне необходимо закрыть ввиду грядущего продолжения.
Кто мы, где мы, откуда и куда идём
Год назад я с аккаунта «издателя» опубликовал статью, где описал процедуру подключения F# к Godot. Она получилась аномально канительной, потому что преодолевала сразу две проблемы. Нам надо было следовать неидеоматичной для F# архитектуре приложения, которую предлагал Godot, и при этом бороться с довольно кривым интеропом между Godot и C# (ну или F#, тут как посмотреть).
На практике меня эти проблемы не особенно волновали, так как для своих пет-проектов я предпочёл использовать более характерные для F# подходы, о чём предупредил в введении и заключении. Постепенно я набил опыт и захотел рассказать о нём в отдельном большом цикле. Предполагалось обычное руководство по идеоматичному подходу F# в Godot, но ввиду своих размеров и разбросу тем, цикл превратился в этакий гайд по языку в декорациях движка.

Тут налицо косяк в планировании, но, судя по имеющейся у меня обратной связи, моей целевой аудиторией оказались люди, сочувствующие F# и малознакомые с движком, так что ни у кого из них не было горящих проектов на Godot. Я позволил себе медлить, поэтому после первых 3 глав цикла, что вышли полгода назад, образовалась длинная пауза. Причины её описаны в 4-й главе, которая сейчас находится на низком старте и ждёт выхода данной статьи. За ней пойдут ещё, как минимум, 6 глав, что пока лежат в черновиках. И это не конец!
Текст статьи, которую вы сейчас читаете, должен был затеряться где-то в перечисленных главах, но он постоянно отовсюду вываливался, так как являлся скорее работой над недочётами An incursion under C#
, чем руководством по языку или движку. Лишь для заключительных глав, он становился «блокирующей задачей», но и там он был не к месту. Одновременно с этим я оказался перед необходимостью «разблокировать» собственный профиль на Хабре, чтобы потом публиковать статьи от лица одного намечающегося проекта (подробностей не будет). Для этого мне нужно было пройти песочницу с какой-нибудь средней по размеру статьёй, и этот «лишний» текст показался крайне удачной кандидатурой для первой попытки.
Так что, если вы задаётесь вопросом «Когда в идеале надо прочесть этот текст?», то наилучший ответ — «Сразу после An incursion under C#. Протаскиваем F# в Godot», но в целом это некритично. В любом случае, когда большой цикл упрётся в сегодняшние правки, я явно обозначу в нём этот момент.
Обновление
Месяц назад Godot релизнул версию 4.4.1
, которую можно скачать с официального сайта. По каким-то причинам доступ к сайту периодически отваливается. Я длительное время думал, что дело в неизбирательном банхамере РКН, но похоже, что проблема не в нём. Разгадывать эту загадку я не буду и вместо этого порекомендую скачивать нужную версию Godot со страницы релизов на GitHub. У неё и онлайн стабильнее, и devops-скрипты на работу с github уже давно заточены.
В апреле 2024 в ходу была версия Godot за номером 4.2.1
. Формально она ориентировалась на 6-ю версию dotnet, но мобильные платформы требовали 7-ю (для Android) и 8-ю (для iOS). Эти опциональные «апгрейды» были включены в шаблон проектов и срабатывали автоматически, но Visual Studio на них реагировала очень странно. Лично у меня встроенный в редактор деплой приложения на телефоне приводил к тому, что вся подсветка, подсказки и т. д. в IDE напрочь отваливались до следующей сборки под desktop. Избавиться от этого можно было только переехав на 8-ю версию dotnet <TargetFramework>net8.0</TargetFramework>
. Понятия не имею, почему это не было сделано сразу, но начиная с 4.4
Godot сразу подвязывается на восьмёрку, так что проблем с отсутствующими подсказками больше возникать не должно. В целом же я склоняюсь к обновлению до максимума, что на апрель 2025 означает 9-ю версию dotnet.
Исправление структуры решения
Напомню, что схема, которую я предложил год назад, предполагала следующее расположение файлов и папок:
Assets/
...
GodotFSharp.Core/
*.fs
GodotFSharp.Core.fsproj
PrepareLaunchSettings.fsx
MyScene/
MyScene.tscn
MyScene.cs
GodotFSharp.csproj
GodotFSharp.sln
project.godot
Она вытекает естественным образом из того проекта, что создаёт Godot по умолчанию, поэтому её легко объяснить и воспроизвести. Это её очевидный, но, к сожалению, единственный плюс. С ростом кодовой базы её всё тяжелее поддерживать, так как все новые проекты оказываются вложениями исходного. С точки зрения логики это не совсем верно, поэтому некоторые дефолты начинают сбоить. Например, при наличии второго C#-проекта .cs
-файлы подхватываются обоими проектами, поэтому приходится вспоминать о <EnableDefaultCompileItems>false</EnableDefaultCompileItems>
. А при совпадении имён вложенного проекта и сцены, возникает риск смешения папок.
Не сказать, что быстро, но я вернулся к стандартной для себя структуре решений. Обычно она выглядит так:
libs/
MyPackage.0.1.0.nupkg
docs/
README.md
src/
Project1/
*.cs
Project1.csproj
Project2/
*.fs
Project1.fsproj
tests/
Project1.Tests/
*.fs
Project1.Tests.fsproj
Solution.sln
.gitignore
nuget.config
Я перенял её от старших коллег по цеху в пору своего студенчества и воспринимаю её как стандарт. У неё есть альтернативы, некоторым из которых я следую на правах гостя, но разбирать мы их не будем, так как все они соблюдают два ключевых правила:
Все проекты лежат по отдельным непересекающимся папкам.
.sln
тоже лежит отдельно, но в корневой директории.
В случае с Godot имеются дополнения:
project.godot
и вообще всё что относится к «классическому» Godot-овскому проекту должно лежать в папке с «исходным»GodotFSharp2.csproj
.Имя
.sln
должно совпадать с именем исходного.cproj
, иначе движок не сможет собрать проект.PrepareLaunchSettings.fsx
и прочие инфраструктурно значимые скрипты будут лежать в корневой директории.
src/
GodotFSharp2/
MyScene/
MyScene.tscn
MyScene.cs
*.cs
Project1.csproj
project.godot
GodotFSharp2.Core/
*.fs
GodotFSharp2.Core.fsproj
GodotFSharp.sln
.gitignore
PrepareLaunchSettings.fsx
Создание нового проекта
Если кому-то хочется воспроизвести подобную структуру через интерфейс редактора, то ему надо:
Создать Godot-проект в папке
ProjectName/src/ProjectName
.Поправить
Dotnet -> Проект -> Имя сборки
, если вдруг оно содержит пробелы,camelCase
и прочую некондицию.Изменить
Dotnet -> Проект -> Каталог решения
на../..
:[dotnet] project/assembly_name="ProjectName.WithoutSpaces" project/solution_directory="../.."
Создать C#-проект через
Проект -> Инструменты -> C# -> Create C# Solution
.Открыть IDE и добавить F#-проект в папку
src
.Поменять во всех проектах
SDK
иTargetFramework
:<Project Sdk="Godot.NET.Sdk/4.4.1"> <PropertyGroup> <TargetFramework>net9.0</TargetFramework> <GenerateDocumentationFile>true</GenerateDocumentationFile> </PropertyGroup>
На мой взгляд, в этом алгоритме слишком много телодвижений, так что мне проще начинать работу с предварительной заготовки. Сейчас я стартую с 20+ стартовых .fs
-файлов (для F# это очень много), которые постоянно дописываю или кодогеню на месте, после чего растаскиваю «обновления» по остальным репозиториям. Я понятия не имею, когда и чем эта эволюция закончится, но пластичность для неё куда важнее, чем побочные эффекты кустарщины, поэтому шаблоны, библиотеки и прочие проявления стандартизации подождут.

Желающие могут повторить этот путь с минимального проекта, что лежит в прилагающейся репе. Это обновлённая версия проекта из оригинальной статьи — никаких излишеств от меня там не будет. Вам достаточно найти и заменить все вхождения GodotFSharp2
на MyProjectName
в тексте и названиях файлов и папок.
Обновление старого проекта
По идее, для обновления старого проекта, созданного по схеме из оригинальной статьи, надо:
Выпилить все бинарники, папки
.godot
и т. д.Завести папку
src
.Перекинуть папку с F#-проектом.
Перекинуть всё остальное (за исключением
MyProjectName.sln
) в папкуsrc/MyProjectName
.Подправить путь к F#-проекту в
.csproj
(../MyProjectName.Core/MyProjectName.Core.fsproj
).Подправить пути к проектам в
MyProjectName.sln
.Подправить путь к решению в
src/MyProjectName/project.godot
(project/solution_directory="../.."
).Опционально обновить
.gitignore
.
Формально всё должно заработать, но тут сохраняется некоторая неопределённость. Дело в том, что Godot умеет стабильно ронять Visual Studio. Я не знаю, что происходит в их недрах, но бывают ситуации, когда скачиваешь обновления репозитория, пробуешь собрать проект, и IDE падает, после чего все попытки открыть солюшен тоже оканчиваются падением. Я не знаю, в каком именно кеше проблема, но мне надоело его искать, так что сейчас мне проще удалить локальную копию репозитория и скачать его заново. Помните об этой подлянке и будьте готовы зачистить солюшен до основания.
Профили запуска
Из-за смены расположения я слегка обновил скрипт PrepareLaunchSettings.fsx
, который генерирует профили запуска. Также он теперь самостоятельно находит директорию с project.godot
, но для совмещения нескольких godot-проектов в одном решении его надо будет слегка дополнить. Расположение движков я не менял, так что скрипт будет ждать свою версию Godot в папке ..\..\Engines\Godot_v4.4.1-stable_mono_win64\Godot_v4.4.1-stable_mono_win64.exe
. Но в целом все эти вещи надо уметь считывать из кода скрипта, а не спрашивать у меня в личке. Там принципиально нет ничего сложного, что могло бы вызывать трудности у рядового разработчика.
Фильтрация сцен
Мой опыт общения с другими Godot-разрабами говорит о том, что количество сцен в проекте может перевалить за сотню, что в текущей конфигурации приводит к сотне с лишним профилей запуска, большинство из которых никогда не будут использованы. В силу специфики моих проектов, мне это не грозит, но мне тоже иногда хочется выкинуть служебные сцены из списка. Для этого можно завести словарь в стиле .gitignore
с перечислением заблокированных или включённых сцен, но в случае Godot я предпочитаю использовать метадату. Если добавить в какую-нибудь из нод булевое свойство IgnoreMe
и поставить галочку:

То оно сохранится в виде следующей строки в коде .tscn
:
metadata/IgnoreMe = true
Эту строчку можно примитивным образом отыскать в найденных сценах и использовать как метку для игнора (или наоборот). Я знаю, что метадату придумали не для этого, но она вшивается в исходник сцены, и поэтому не может потеряться при переносе или переименовании. Она также исчезает вместе с удаленным .tscn
, что избавляет нас от необходимости чистить список фильтров. Кроме того, в метадату можно класть не только bool
, но и любой Variant
, одним из кейсов которого является строка. А уже в строку можно сериализовать вообще что угодно, и это «что угодно» потом можно будет найти из любого devops-скрипта.
Аргументы командной строки
Я добавил в скрипт вариант профиля Run.CustomScene
с аргументами командной строки, для тех, кто, как и я, пришёл из мира настольных приложений и нуждается в привычных подходах к быстрым запускам. Экземпляры Run.CustomScene
надо будет собирать индивидуально, но фича для тех, кто «шарит», так что из кода разберётесь что и куда совать. Я же укажу лишь на метод в API:
OS.GetCmdlineUserArgs() // : string array
И на странное решение в редакторе Godot. У них почему-то отсутствует понятие профилей запуска, поэтому настройка аргументов задаётся сразу на весь project.godot
:
[editor]
run/main_run_args="-- PathToLevel"
То есть редактор будет передавать одни и те же аргументы во все запускаемые .tscn
. Это, мягко говоря, неудобно, но я пока не понял, что с этим можно сделать, так что аргументы лучше оставить для разработки из-под VS. В противном случае к сцене придётся прикручивать альтернативный способ передачи аргументов, чтобы обойти ограничения редактора Godot. Технологически это не сложно, но удобных источников string array
я так и не подобрал.
Заключение
На этом правки к исходной статье закончились. Обновлённая версия проекта лежит здесь.
Продолжение цикла «Шестидесятилетний заключённый и лабораторная крыса» выйдет совсем скоро.