Attention: Here be dragons

This is the latest (unstable) version of this documentation, which may document features not available in or compatible with released stable versions of Godot.

创建应用程序

Godot 拥有强大的内置 UI(用户界面)系统,而且它的体积非常小巧,这让它成为 Electron 或 Qt 等框架的理想替代品。

本页面提供了使用 Godot 开发非游戏类应用的指南,以及执行常见任务以优化桌面集成的操作说明。

备注

Godot 首先且本质上是一个游戏引擎。这意味着,用 Godot 来开发普通应用只是其功能带来的一个‘副产品’,而并不是它开发团队的首要重心。

参见

请查看 Material MakerPixelorama,以了解用 Godot 制作的开源应用程序的例子。

执行常见任务

创建多窗口

此功能仅在 Windows、 macOS 和 Linux 系统上受支持(仅限 X11/XWayland,不支持原生 Wayland 模式)。

可以使用 Window 节点来创建额外的窗口。这些窗口能够独立于主应用程序窗口进行移动、调整大小、最小化和关闭。

不过,如果你关闭了主窗口,所有其他窗口也会随之关闭,因为关闭主窗口意味着整个程序进程会被结束。为了避免这种情况,你可以将主窗口最小化,并将其 无法获取焦点 属性设置为 true (这样它就会从任务栏和任务切换器中隐藏),然后在程序启动时立即创建额外的 Window 节点。在这种情况下,记得要为用户提供一种退出应用的替代方式,比如添加一个 托盘图标

限制窗口大小

大多数应用只有在达到某个最小窗口尺寸时,才能正常渲染(显示)内容。而对于更具体的使用场景,可能还需要强制限制一个最大窗口尺寸。

可以通过 Window 节点上的 min_sizemax_size 属性来强制限制窗口大小。不过要记得,这些尺寸限制需要根据应用程序的缩放比例进行相应的倍增(具体细节请参阅关于 针对高 DPI 显示屏进行缩放 )。

小技巧

提醒一下,你可以在任意 Node(节点)上使用 get_window() 方法来获取根 Window 节点,从而对它进行属性设置。

使用原生文件对话框

此功能仅支持 Windows、 macOS 、Linux 和 Android 系统。

默认情况下,Godot 会使用它自己实现的 FileDialog 作为文件对话框。不过,你也可以选择改用操作系统原生的文件对话框。一般来说,用户会更喜欢后者,因为原生文件对话框能更好地融入桌面环境,带来更熟悉的操作体验。

你可以通过启用 FileDialog 节点上的 use_native_dialog 属性,来切换使用原生的文件对话框。需要注意的是,你必须在项目中使用到的每一个 FileDialog 节点上单独进行设置,因为目前并没有一个全局的项目设置可以统一控制这个行为。

macOS 上标准 FileDialog(左)与原生文件对话框(右)的对比

macOS 上标准 FileDialog(左)与原生文件对话框(右)的对比

备注

关于(各)平台的支持情况,请参见 property description 了解详细信息。

另外,在 macOS 上,如果编辑器中启用了游戏内嵌(game embedding)功能,将不支持调用系统原生的文件对话框。如果你想在运行项目时测试这个功能,请务必先关闭游戏内嵌。具体操作是:切换到顶部的 Game 面板,点击顶部工具栏最右侧的图标,然后取消勾选 Embed Game on Next Play

在系统托盘中创建图标

此功能仅支持 Windows 和 macOS 系统。

你可以通过 StatusIndicator 节点,在系统托盘(也就是大家常说的通知区域)里创建一个或多个图标。除了可以设置鼠标悬停时显示的提示文本(tooltip)外,这个节点还能绑定一个 PopupMenu 节点,这样当你点击托盘图标时,就会弹出一个下拉菜单供你操作。

StatusIndicator 还有一个 pressed 信号,当用户点击这个图标时,该信号就会被触发。你可以利用它来执行某些操作,而不会弹出下拉菜单;或者根据用户按下的是鼠标的哪个按键(比如左键或右键),来执行不同的动作。

在创建了托盘图标之后,你可能还想实现 "点击关闭时最小化"(minimize on close)的行为。这意味着当用户尝试通过窗口管理器的 X 按钮关闭应用程序时,程序不会真正退出,而是会最小化隐藏到系统托盘里。要实现这个功能,请将以下脚本附加到一个 Autoload (自动加载)的 场景 上,并且该场景的根节点必须是一个 StatusIndicator。

extends StatusIndicator

# Disable this behavior when running from the editor with game embedding,
# as it doesn't cooperate well.
var tray_icon_supported = (
        DisplayServer.has_feature(DisplayServer.FEATURE_STATUS_INDICATOR)
        and not Engine.is_embedded_in_editor()
    )


func _ready():
    visible = false

    if tray_icon_supported:
        get_tree().auto_accept_quit = false
        get_window().focus_entered.connect(
                func():
                    # Hide the tray icon when the window gains focus,
                    # which means it was restored from its minimized state.
                    visible = false
            )
        pressed.connect(
                func(_mouse_button, _position):
                    # Restore the application when the tray icon is clicked.
                    get_window().mode = Window.MODE_WINDOWED
            )


func _notification(what):
    if not tray_icon_supported:
        return

    match what:
        NOTIFICATION_WM_CLOSE_REQUEST:
            get_window().mode = Window.MODE_MINIMIZED
            # Show the tray icon.
            visible = true

关于如何覆盖用户尝试关闭应用程序时的默认行为,详情请参见 处理退出请求 (处理退出请求)。这一点非常重要,尤其是当用户有未保存的修改时,妥善处理可以避免数据丢失。

备注

当存在多个 StatusIndicator 节点时,它们在系统托盘中的排列顺序,取决于它们被添加到场景树中的先后顺序。

使用客户端装饰

此功能仅支持 macOS 。

在 macOS 系统上,应用程序可以选择使用系统顶部的全局菜单栏,而不需要在自己的应用程序窗口内部显示菜单栏。在 Godot 中,这种形式也被称为 原生菜单 (native menu)。

macOS 上标准 MenuBar(左)、带有原生弹窗的 MenuBar(中)和原生菜单(右)的对比

macOS 上标准 MenuBar(左)、带有原生弹窗的 MenuBar(中)和原生菜单(右)的对比

Godot 支持通过 MenuBar 节点来创建菜单,它会将自身的 PopupMenu 子节点直接显示为一个个菜单项。你可以在检查器(Inspector)中,开启指定 MenuBar 节点的 prefer_global_menu 属性,来启用全局菜单支持。在 macOS 系统上,开启该属性后,MenuBar 节点本身会从游戏画面中消失且不再占用任何空间,而它的菜单内容则会直接显示在系统的全局菜单栏(屏幕顶端)中。如果关闭这个属性,MenuBar 节点就会像往常一样,在应用程序窗口内部显示菜单,不过在操作系统支持的情况下,弹出的下拉菜单依然会使用系统的原生样式。

备注

应用菜单(也就是那个以粗体显示项目名的菜单),以及 Window (窗口)和 Help (帮助)菜单,在 macOS 上是始终存在的。因此,你不应该手动把这些菜单添加到全局菜单栏中。

在 Godot 4.6 及更高版本中,你可以通过修改 PopupMenu 节点上的 system_menu_id 属性,来向这些系统菜单中添加新的菜单项。你可以选择将菜单项添加到 Application Menu(应用程序菜单,即第一个菜单,菜单标题是加粗的应用名称)Window Menu(窗口菜单)Help Menu(帮助菜单) 以及 Dock(当在 Dock 栏的应用图标上右键点击时显示的菜单) 。这些系统菜单中原本就存在的标准菜单项会被保留,不会受到影响。

在 macOS 系统窗口菜单中添加的自定义选项

在 macOS 系统窗口菜单中添加的自定义选项

一个项目可以包含多个 MenuBar 节点。如果有多个 MenuBar 节点都开启了 Prefer Global Menu(优先使用全局菜单) 属性,那么当这些 MenuBar 节点被添加到场景树时,它们的菜单选项就会按照各自 Start Index(起始索引) 属性所定义的索引位置依次添加。这样做的好处是,你可以把那些特定情境下的菜单放在菜单栏的末尾,这样一来,即使后面添加或移除了额外的菜单栏,最前面的那些基础菜单选项也能保持位置不变。

对于更进阶的使用场景,你也可以直接调用 NativeMenu 单例,而不必非得依赖 MenuBar 节点。

备注

当编辑器中启用了游戏内嵌(game embedding)功能时,将不支持全局菜单的整合。如果你想在运行项目时测试这个功能,请务必先关闭游戏内嵌。具体操作是:切换到顶部的 Game 面板,点击顶部工具栏最右侧的图标,然后取消勾选 Embed Game on Next Play

使用客户端窗口装饰

此功能仅支持 macOS 。

许多现代应用程序都采用 客户端装饰(CSD) ,而不再依赖操作系统的窗口管理器来绘制标题栏和窗口边框(即传统的服务器端装饰)。这样做不仅能让应用的外观更加个性化、可定制,还能让窗口与应用程序自身的 UI 界面实现更完美的融合。

目前,Godot 仅在 macOS 系统上支持客户端装饰(CSD)。你可以通过开启项目设置中的 display/window/size/extend_to_title (将窗口延伸至标题栏)选项来启用该功能。

macOS 上标准窗口装饰(上)与客户端装饰(下)的对比

macOS 上标准窗口装饰(上)与客户端装饰(下)的对比

启用客户端装饰后,原本的窗口边框将不再显示,而最小化、最大化和关闭按钮则会以悬浮叠加层的形式出现在应用程序的上方。你需要确保应用程序在顶部留出足够的边距(margin),以便让这些按钮能舒适地显示出来,同时还需要使用一个 Label 节点(或类似的控件)来手动显示窗口标题。

若要根据是否启用了客户端装饰来动态调整你的 UI,请使用 DisplayServer.has_feature 方法,并同时检查 Window.extend_to_title 的当前值(这也是项目设置所修改的内容):

func _ready():
    if DisplayServer.has_feature(FEATURE_EXTEND_TO_TITLE) and get_window().extend_to_title:
        # Adjust UI for client-side decorations (a MarginContainer node
        # can be useful here). Also set the window title to be displayed
        # according to the native window title.
        $WindowTitle.visible = true
        $WindowTitle.text = get_window().title
        if OS.is_debug_build():
            $WindowTitle.text += " (DEBUG)"

为了正确定位窗口标题,建议考虑使用 DisplayServer.window_get_safe_title_margins() ,它会返回一个 Vector3,其中 x 代表左边距, y 代表右边距(当系统使用从右向左的排版时会增加),而 z 代表高度。此外,你可以调用 DisplayServer.window_set_window_buttons_offset() 来调整关闭/最小化/最大化按钮的位置(通常是为了让它们垂直居中)。

macOS 上使用客户端装饰时的安全标题边距

macOS 上使用客户端装饰时的安全标题边距

备注

在 macOS 上,如果编辑器中启用了游戏内嵌(game embedding)功能,将不支持客户端装饰(client-side decorations)。如果你想在运行项目时测试这个功能,请务必先关闭游戏内嵌。具体操作是:切换到顶部的 Game 面板,点击顶部工具栏最右侧的图标,然后取消勾选 Embed Game on Next Play

在任务栏或 Dock 栏上显示进度状态

此功能仅支持 Windows 和 macOS 系统。

应用程序可以向操作系统汇报进度状态,这个状态会直接显示在任务栏或 Dock 栏的图标上。这个状态主要由两部分组成:当前的状态(比如进行中、已暂停、出错)以及完成的百分比。这样,即使用户当前没有聚焦在这个应用上(比如正在看别的窗口),也能直观地看到任务的进展啦。

在 macOS 的 Dock 栏上显示进度

在 macOS 的 Dock 栏上显示进度

这通常是通过将 ProgressBar 节点的进度,与汇报给操作系统的进度保持同步来实现的。

func set_progress(value, indeterminate = false):
    $ProgressBar.value = value
    $ProgressBar.indeterminate = indeterminate

    if $ProgressBar.indeterminate:
        get_window().set_taskbar_progress_state(DisplayServer.PROGRESS_STATE_INDETERMINATE)
    else:
        get_window().set_taskbar_progress_state(DisplayServer.PROGRESS_STATE_NORMAL)

    # The taskbar progress value must be between `0.0` and `1.0`
    # (values outside this range are clamped).
    # However, ProgressBar can have minimum/maximum values that differ.
    # We use the `remap()` method to convert the value to the range
    # expected by taskbar progress reporting.
    get_window().set_taskbar_progress_value(remap($ProgressBar.value, $ProgressBar.min_value, $ProgressBar.max_value, 0.0, 1.0))

有几种进度状态可供选择:无进度(会直接隐藏进度条)、不确定、正常、已暂停、出错。具体的细节可以查看类参考文档哦。

你还可以使用 Window.request_attention() 来让窗口在任务栏上闪烁(或者在 macOS 的 Dock 栏上跳动)。举个例子,当某个耗时较长的操作完成后,就可以用这个功能来吸引用户的注意。

备注

当编辑器中启用了游戏内嵌(game embedding)功能时,将不支持进度报告。如果你想在运行项目时测试这个功能,请务必先关闭游戏内嵌。具体操作是:切换到顶部的 Game 面板,点击顶部工具栏最右侧的图标,然后取消勾选 Embed Game on Next Play

发送桌面通知

Godot 目前不支持发送桌面通知的原生功能。

不过,在 macOS 和 Linux 上,你可以分别使用 osascriptnotify-send 命令行工具来发送桌面通知:

func send_notification(title, message):
    var app_name = ProjectSettings.get_setting("application/config/name")
    if app_name.is_empty():
        app_name = "Unnamed Project"

    if OS.has_feature("macos") and not OS.is_sandboxed():
        # Note that this will not work if the project is exported in sandbox mode
        # (e.g. for the Mac App Store).
        OS.execute("osascript", [
                "-e",
                'display notification \\"%s\\" with title \\"%s\\" subtitle \\"%s\\"' % [
                    message,
                    app_name,
                    title,
                ]
            ])
    elif OS.has_feature("linuxbsd"):
        OS.execute("notify-send", ["--app-name", app_name, title, message])

func _ready():
    send_notification("Success", "Operation completed successfully.")

遗憾的是,Windows 上没有开箱即用的等效工具。

跨会话记住窗口位置和大小

Godot 没有内置的跨会话记忆窗口位置和大小功能,但可以通过脚本手动实现。一个支持多显示器设置的基础示例是使用包含此脚本的 Autoload

extends Node

# Use a dedicated configuration file for the window state.
# This way, the application's other configuration files are left
# untouched and can be put in version control without unnecessary diffs
# being produced.
const CONFIG_WINDOW_PATH = "user://window.ini"

var config_file = ConfigFile.new()


func _enter_tree():
    config_file.load(CONFIG_WINDOW_PATH)

    # Do not restore previous window state if running from the editor
    # with game embedding enabled.
    if not Engine.is_embedded_in_editor():
        var window_screen = config_file.get_value("main", "screen", -1)
        if window_screen is int:
            get_window().current_screen = window_screen

        var window_mode = config_file.get_value("main", "mode", -1)
        if window_mode is Window.Mode:
            get_window().mode = window_mode

        var window_position = config_file.get_value("main", "position", -1)
        if window_position is Vector2i:
            get_window().position = window_position

        var window_size = config_file.get_value("main", "size", -1)
        if window_size is Vector2i:
            get_window().size = window_size


func _exit_tree():
    # Save the current window state when the application is quit normally.
    # In a real world scenario, it's recommended to also save this information
    # regularly (e.g. with a Timer node), so that the window state can be
    # restored after a crash or when terminated externally.
    config_file.set_value("main", "screen", get_window().current_screen)
    config_file.set_value("main", "mode", get_window().mode)
    config_file.set_value("main", "position", get_window().position)
    config_file.set_value("main", "size", get_window().size)
    config_file.save(CONFIG_WINDOW_PATH)

备注

上面的示例仅追踪主窗口的位置。在生成多个窗口的应用程序中,你需要分别保存和加载每个窗口的位置和大小。

在启动画面(Splash Screen)期间隐藏窗口

对于某些应用程序,可能更倾向于隐藏默认启动画面,转而绘制一个带有进度条的自定义启动画面(或者,如果应用程序启动很快,甚至完全可以不要启动画面)。

Godot 缺乏在启动画面期间隐藏窗口的原生支持,但你可以通过在项目设置中使用一个非常小的透明窗口来实现这一点,然后在主场景加载完成后调整窗口大小并禁用透明度。

为此,项目设置应按照如下方式进行配置:

这个脚本可以作为一个 Autoload (自动加载单例)来使用,以便在启动画面显示完毕后,恢复原始的(项目)设置:

extends Node


func _enter_tree():
    # Wait a frame to be rendered before restoring the window properties.
    # Otherwise, properties will be restored too early and the window border
    # will show up around a transparent window.
    await get_tree().process_frame

    get_viewport().transparent_bg = false
    get_window().transparent = false
    get_window().borderless = false
    get_window().size = Vector2i(1152, 648)

将应用程序显示为覆盖层

可以将应用程序窗口显示为悬浮在其他窗口之上的覆盖层。这对于小部件或系统监视器之类的应用非常有用。

要做到这一点,请启用以下 所有 项目设置:

记得使用脚本来设置窗口的位置和大小,因为无边框窗口通常无法由用户直接拖动。

若要允许鼠标输入穿透到背景应用程序,请在作为覆盖层绘制的 Window 上将 mouse_passthrough (鼠标穿透)属性设置为 true 。你也可以在 mouse_passthrough_polygon (鼠标穿透多边形)中定义一个多边形,以便覆盖层上的某些区域仍然可以拦截鼠标输入。

此外,你可能还想将 exclude_from_capture (从捕获中排除)属性设置为 true ,以防止叠加层出现在截图或录屏中。此提示仅在 Windows 和 macOS 上实现,且属于“尽力而为”(best-effort)的机制,因此不应将其用作绝对的安全措施或数字版权管理(DRM)手段。

备注

当编辑器中启用了游戏内嵌(game embedding)功能时,将不支持以覆盖层(overlay)的形式显示。如果你想在运行项目时测试这个功能,请务必先关闭游戏内嵌。具体操作是:切换到顶部的 Game 面板,点击顶部工具栏最右侧的图标,然后取消勾选 Embed Game on Next Play

此外,请记住,如果目标应用程序使用了独占全屏模式,叠加层将无法显示在其上方。必须改用无边框全屏模式,叠加层才能可见。

此外,在 Windows 上使用混合 GPU 设置(例如 NVIDIA Optimus)时,透明窗口显示也存在一些 known issues 。切换渲染器可能有助于解决此问题。

在 Linux (X11) 系统上,如果用户在窗口管理器设置中禁用了合成器,那么透明效果将无法显示。

针对高 DPI 显示屏进行缩放

现代显示屏的像素密度差异很大,这通常意味着需要采用不同的缩放比例,以确保用户界面元素清晰可读。此外,也可以将此缩放比例作为手动调节选项提供给用户,以便他们能根据个人习惯调整,从而保持舒适的使用体验。

Godot 的多分辨率支持功能在配置得当的情况下,非常适合用于应用程序的缩放适配。请按照 non-game application section of the Multiple resolutions documentation 中的说明进行操作。

备注

Godot 目前仅在 macOS、Android 和 Linux(仅限 Wayland 协议)系统上支持从操作系统设置中读取屏幕缩放比例。在 Linux(X11 协议)和 Windows 系统上,你需要提供一个手动缩放选项,以便用户根据需要调整用户界面的缩放比例。

屏幕阅读器集成

屏幕阅读器通过朗读用户界面元素并提供导航控制,使视障人士能够使用应用程序。盲文显示器则是另一种方法,它们同样依赖辅助功能信息来正常工作。

Godot 会自动检测是否有屏幕阅读器在运行,如果检测到,就会自动启用辅助功能支持。你可以在 项目设置 中通过 accessibility/general/accessibility_support 来配置此项:如果不需要,可以将其禁用。你也可以强制启用它,这在使用的辅助功能调试工具未被 Godot 识别为屏幕阅读器时非常有用。

Godot 使用 AccessKit 库来实现与屏幕阅读器的集成。

小技巧

由于屏幕阅读器支持是直接使用屏幕阅读器应用程序本身来播放音频(而不是通过 Godot 项目),因此即使如在下文中所述将音频驱动程序设置为 Dummy ,它也能正常工作。

强烈建议在你的目标平台上,使用主流的屏幕阅读器来测试你的应用程序,以确保视障用户也能获得良好的使用体验。常见的例子包括 Windows 上的 NVDA、macOS 上的`VoiceOver <https://www.apple.com/accessibility/features/?vision>`__ 以及 Linux 上的 Orca

要让屏幕阅读器的支持达到良好的可用性水平,需要做大量的工作。你需要使用 Control.accessibility_nameControl.accessibility_description 属性来定义无障碍标签,并确保用户界面在被屏幕阅读器读取时,其流程符合逻辑顺序。

参见

另请参阅 文本转语音 ,了解独立于屏幕阅读器之外的文本转语音功能。

添加单元测试

在一个应用程序中,拥有 单元测试 通常比在游戏中更具价值。这可用于以自动化方式捕获回归错误,在逻辑能够清晰分离的应用程序场景中,这往往更容易实现。

GDScript 本身并没有内置单元测试框架,但社区维护了一些用于单元测试的插件:

对于 C# 和 GDExtension(C++、Rust 等),你可以使用标准测试框架,例如 NUnit 或 doctest

优化发布包的体积

由于非游戏应用程序通常避免使用引擎的大部分功能,例如音频或 3D 功能,你可以编译一个优化的导出模板以减小其文件大小。这也会改善启动时间,尤其是在 Web 平台上,因为可执行文件的大小与初始化速度直接相关。

文件大小的减小通常很显著(相对于项目的大小而言),因为与游戏相比,应用程序包含的大型资产更少。请参阅 为尺寸优化构建 以了解执行此操作的更多信息。

打包成一个单独的可执行文件

默认情况下,Godot 会生成一个包含项目数据的 PCK 文件,并把它放在可执行程序(.exe)的旁边。这就意味着,如果你只移动了可执行程序,而没有同时把 PCK 文件一起移走,程序就没法运行了。这对于那些越来越倾向于‘单文件分发’的应用程序来说,显然不太理想。

如果你想让应用完全独立,变成一个单文件的可执行程序,可以在导出预设选项中开启 Embed PCK (嵌入 PCK)。这样会把 PCK 资源数据直接打包塞进可执行程序(.exe)内部,以后你就可以随意移动这个程序,而不用担心它因为找不到文件而损坏。另外,这也让你可以直接在 ZIP 压缩包里运行程序,完全不需要先把它解压出来。

备注

PCK 嵌入功能会根据平台的不同,存在一定的体积限制。对于非常庞大的应用(比如好几个 GB 的大型游戏),可能无法在所有平台上使用这个功能。具体的细节,建议去查看对应目标平台的导出文档哦。

创建便携式应用

当一个应用程序无需安装就能直接运行,并且它的所有配置信息都完全独立、只保存在它被解压的那个文件夹里时,这个应用就被称为 便携版(Portable) 。这样一来,你就可以把应用程序的文件直接放在 U 盘或类似的移动设备里,带到不同的电脑上随时使用,完全不需要经历繁琐的安装过程。

Godot 编辑器自带的 self-contained mode 目前还无法直接在项目内部使用。不过,你依然可以通过以下方法,选择将你自己的配置文件保存到包含可执行程序的那个文件夹里:

var config_path = OS.get_executable_path().get_base_dir().path_join("config.ini")
# Then use `config_path` to save/load configuration files using ConfigFile or similar.

你可能希望把便携模式做成可选的,因为并不是所有玩家都希望这样。通常的做法是,检测可执行程序所在的文件夹里是否存在某个特定的文件(比如一个命名为 portable.txt 的文件),只有当这个文件存在时,才把配置文件保存到可执行程序所在的文件夹里。

警告

请记住,这个功能(便携模式)只有在应用程序被解压到一个‘可写入’的位置时才会生效。如果可执行程序是在一个‘只读’的位置运行的,比如在 Windows 上的 C:\Program Files 目录里,就会导致权限报错。

制作安装包

虽然游戏通常通过 Steam 等启动器安装或以 ZIP 格式下载,但应用程序通常以安装程序的形式分发,以便更好地与桌面集成。安装程序可以执行诸如向开始菜单或桌面添加快捷方式、设置文件关联等操作。安装程序也可以通过命令行自动运行,这使其更适合企业环境。

Godot 没有对为导出项目创建安装程序的内置支持。不过,你仍然可以使用第三方工具创建自己的安装程序。

以下是一些可以用来制作安装程序的工具(不完全列表):

资源

这些页面涵盖了一些常在非游戏应用之中执行的任务: