Я хотел, чтобы Godot писал скриншоты и короткие видео из скрипта на моём Mac, как это делают в CI на Linux. Godot 4.7.1 на macOS 26.5.2 не умеет этого в --headless, но умеет с настоящим рендерером и окном за пределами экрана. Одно я так и не решил: приложение всё равно выходит на передний план, пока работает.

Всё, что ниже, запускалось на тестовом проекте 640 × 360 с одним скриптом:
extends Node2D
# Saves the 30th frame to the path given after "--" and quits.
var frames := 0
func _process(_delta: float) -> void:
$Box.position.x = 80.0 + frames * 4.0
frames += 1
var args := OS.get_cmdline_user_args()
if frames == 30 and args.size() > 0:
await RenderingServer.frame_post_draw
get_viewport().get_texture().get_image().save_png(args[0])
get_tree().quit()Что делает --headless, когда снимаешь кадр
Godot.app/Contents/MacOS/Godot --headless --path . -- "$PWD/out.png"Скрипт дошёл до кадра 30 и навсегда остановился на await RenderingServer.frame_post_draw: через 15 секунд процесс всё ещё работал, а сохранено ничего не было. Если читать текстуру без await, всё падает сразу:
ERROR: Parameter "t" is null.
at: texture_2d_get (./servers/rendering/dummy/storage/texture_storage.h:110)В --headless Godot работает с рендерером-заглушкой, так что у вьюпорта нет текстуры, которую можно прочитать; путь в ошибке ведёт в хранилище текстур этой заглушки. На Linux обычно обходятся виртуальным X-дисплеем под настоящим рендерером. Сборка Godot для macOS рисует через оконный сервер macOS и Metal, так что этот приём на Mac не переносится.
Рисовать по-настоящему, за пределами экрана
Оставьте настоящий рендерер, а окно уберите с дороги. Настройки окна применяются в момент его создания, раньше любого скрипта, поэтому на время прогона они кладутся в override.cfg рядом с project.godot:
[display]
window/size/no_focus=true
window/size/initial_position_type=0
window/size/initial_position=Vector2i(-6000, -6000)
window/size/always_on_top=falseЗатем запустите приложение через open:
open -n -W -g -j -a Godot.app --stdout run.log --stderr run.log \
--args --path "$PWD" -- "$PWD/out.png"
rm override.cfg-n запускает новый экземпляр, даже если Godot уже открыт, -W ждёт, пока он закроется, -g оставляет его на заднем плане, -j запускает его скрытым, а --stdout и --stderr возвращают его вывод в файл. Без них open выбрасывает вывод, потому что Godot не дочерний процесс вашей оболочки. Кадр вернулся в 640 × 360 и в цветах самой сцены: средний RGB 188, 117, 82, оранжевый фон с синим квадратом.
Когда прогон закончится, удалите override.cfg. Godot читает его при каждом запуске, поэтому прогон, прерванный до rm, оставляет следующему обычному запуску окно, которое так и не получает фокус клавиатуры.
Movie Maker: последовательность PNG, потом MP4
Movie Maker рендерит каждый кадр с постоянной частотой, поэтому тайминг видео не зависит от того, насколько быстро рендерит машина:
open -n -W -g -j -a Godot.app --stdout run.log --stderr run.log \
--args --path "$PWD" --write-movie "$PWD/movie/f.png" --fixed-fps 24 --quit-after 4848 frames at 24 FPS (movie length: 00:00:02:00), recorded in 00:00:01 (200% of real-time speed).Он записал файлы с f00000000.png по f00000047.png, а звук положил рядом, в f.wav. ffmpeg собирает из кадров видео:
ffmpeg -framerate 24 -i movie/f%08d.png -c:v libx264 -pix_fmt yuv420p -crf 18 film.mp4Получилось 48 кадров с частотой 24 кадра в секунду, длиной 2,0 секунды.
Не решено: фокус
no_focus не даёт окну забрать фокус клавиатуры, но Godot всё равно делает себя активным приложением, когда открывает окно. Во время двух прогонов я опрашивал, какое приложение впереди. В прогоне с одним кадром Godot был впереди в 1 замере из 21, а в прогоне Movie Maker, который занял около секунды, в 4 замерах подряд; после каждого выхода Godot впереди снова оказывалось приложение, в котором я работал. В сентябре не помогла и копия бандла приложения с LSUIElement, равным true. Сколько Godot держится впереди при записи подольше, я не мерил.
Ограничения
- Godot 4.7.1 на macOS 26.5.2, один Mac, рендерер Forward+. Рендерер Compatibility я не пробовал.
- Положение за пределами экрана проверено с одним дисплеем.