Как стать автором
Обновить

An incursion under C#. Протаскиваем F# в Godot. Исправления

Уровень сложностиПростой
Время на прочтение8 мин
Количество просмотров693

Этой статьёй я хочу внести некоторые правки в алгоритм развёртывания, который я описывал год назад вот здесь. Статья тогда получилась объёмная, и я искал способы её сократить, в результате чего опрометчиво пренебрёг несколькими сценариями, которые пригодились мне в дальнейшей разработке. Они образовали техдолг, который мне необходимо закрыть ввиду грядущего продолжения.

Кто мы, где мы, откуда и куда идём

Год назад я с аккаунта «издателя» опубликовал статью, где описал процедуру подключения 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

Я перенял её от старших коллег по цеху в пору своего студенчества и воспринимаю её как стандарт. У неё есть альтернативы, некоторым из которых я следую на правах гостя, но разбирать мы их не будем, так как все они соблюдают два ключевых правила:

  1. Все проекты лежат по отдельным непересекающимся папкам.

  2. .sln тоже лежит отдельно, но в корневой директории.

В случае с Godot имеются дополнения:

  1. project.godot и вообще всё что относится к «классическому» Godot-овскому проекту должно лежать в папке с «исходным» GodotFSharp2.csproj.

  2. Имя .sln должно совпадать с именем исходного .cproj, иначе движок не сможет собрать проект.

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

Создание нового проекта

Если кому-то хочется воспроизвести подобную структуру через интерфейс редактора, то ему надо:

  1. Создать Godot-проект в папке ProjectName/src/ProjectName.

  2. Поправить Dotnet -> Проект -> Имя сборки, если вдруг оно содержит пробелы, camelCase и прочую некондицию.

  3. Изменить Dotnet -> Проект -> Каталог решения на ../..:

    [dotnet]
    
    project/assembly_name="ProjectName.WithoutSpaces"
    project/solution_directory="../.."
  4. Создать C#-проект через Проект -> Инструменты -> C# -> Create C# Solution.

  5. Открыть IDE и добавить F#-проект в папку src.

  6. Поменять во всех проектах SDK и TargetFramework:

    <Project Sdk="Godot.NET.Sdk/4.4.1">
        <PropertyGroup>
            <TargetFramework>net9.0</TargetFramework>
            <GenerateDocumentationFile>true</GenerateDocumentationFile>
        </PropertyGroup>

На мой взгляд, в этом алгоритме слишком много телодвижений, так что мне проще начинать работу с предварительной заготовки. Сейчас я стартую с 20+ стартовых .fs-файлов (для F# это очень много), которые постоянно дописываю или кодогеню на месте, после чего растаскиваю «обновления» по остальным репозиториям. Я понятия не имею, когда и чем эта эволюция закончится, но пластичность для неё куда важнее, чем побочные эффекты кустарщины, поэтому шаблоны, библиотеки и прочие проявления стандартизации подождут.

Желающие могут повторить этот путь с минимального проекта, что лежит в прилагающейся репе. Это обновлённая версия проекта из оригинальной статьи — никаких излишеств от меня там не будет. Вам достаточно найти и заменить все вхождения GodotFSharp2 на MyProjectName в тексте и названиях файлов и папок.

Обновление старого проекта

По идее, для обновления старого проекта, созданного по схеме из оригинальной статьи, надо:

  1. Выпилить все бинарники, папки .godot и т. д.

  2. Завести папку src.

  3. Перекинуть папку с F#-проектом.

  4. Перекинуть всё остальное (за исключением MyProjectName.sln) в папку src/MyProjectName.

  5. Подправить путь к F#-проекту в .csproj (../MyProjectName.Core/MyProjectName.Core.fsproj).

  6. Подправить пути к проектам в MyProjectName.sln.

  7. Подправить путь к решению в src/MyProjectName/project.godot (project/solution_directory="../..").

  8. Опционально обновить .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 я так и не подобрал.

Заключение

На этом правки к исходной статье закончились. Обновлённая версия проекта лежит здесь.

Продолжение цикла «Шестидесятилетний заключённый и лабораторная крыса» выйдет совсем скоро.

Теги:
Хабы:
+5
Комментарии2

Публикации

Работа

.NET разработчик
44 вакансии

Ближайшие события