跳转到内容

Godot SDK

本文介绍如何使用 Framedash Godot SDK(C# 插件)自动收集性能遥测数据。

  • Godot 4.3 或更高版本的 .NET(C#)版本。标准的 GDScript-only 版本无法编译 C# 插件。
  • .NET 8 SDK 或更高版本(任何能以 net8.0 为目标的 SDK 均可;该插件会构建进你以 net8.0 为目标的游戏程序集)
  • 不支持 Web(HTML5)导出,因为 Godot 4 暂不支持 .NET 项目的 Web 导出。

SDK 既可从 Godot Asset Library(编辑器 GUI)安装,也可从 GitHub 手动安装(适合脚本 / CI)。对于 CI 或脚本化的设置,请使用下面的手动安装,因为 Asset Library 流程需要编辑器 GUI。

SDK 已发布到 Godot Asset Library。此方式仅限 GUI。在 Godot 编辑器中:

  1. 打开 AssetLib 标签页
  2. 搜索 Framedash Telemetry SDK,并选择版本 0.1.8
  3. 点击 Download,然后点击 Install。保持选中 addons/framedash/,以便将插件安装到 res://addons/framedash/

如需固定发布版本或用于 CI,请克隆 Framedash Godot SDK 仓库

Terminal window
git clone --branch v0.1.8 --depth 1 https://github.com/crane-valley/framedash-godot-sdk.git

为了让检出可复现,请固定到发布标签(v0.1.8),而不要跟踪默认分支。克隆后,如果项目中没有 addons/ 目录,请先创建该目录,然后将仓库的 addons/framedash/ 文件夹复制到 res://addons/

通过任一方式安装后:

  1. 构建一次 C# 解决方案。这会生成 .csproj / .sln 并编译插件,使其 autoload 类型可用。
  2. 在 Godot 编辑器中打开 Project > Project Settings > Plugins
  3. 启用 Framedash Telemetry SDK

启用插件后会注册 Framedash autoload singleton。在启动附近的 _Ready() 中初始化 SDK 一次:

using Framedash;
using Godot;
public partial class GameBootstrap : Node
{
public override void _Ready()
{
TelemetrySDK.Initialize(
apiKey: "your-api-key",
buildId: "1.0.0");
}
}

endpointUrlbuildId 是可选项。省略 endpointUrl 时,SDK 使用 https://ingest.framedash.dev/v1/events

SDK 会自动收集:

  • FPS / 帧时间:根据真实帧间隔计算
  • 内存:Godot static memory
  • GPU / 渲染 CPU 时间RenderingServer 报告的最近一帧计时
  • 游戏线程时间_process 花费的时间

session_start 在初始化时发送,perf_heartbeat 每 10 秒发送一次。

TelemetrySDK.Instance.Track(
eventName: "player_death",
mapId: "map_01",
position: playerNode.GlobalPosition);

如果需要附加分类属性或数值指标:

using System.Collections.Generic;
TelemetrySDK.Instance.Track(
eventName: "player_death",
mapId: "map_01",
position: playerNode.GlobalPosition,
attributes: new Dictionary<string, string> { { "cause", "fall_damage" } },
metrics: new Dictionary<string, float> { { "health", 0f } });

已跟踪的事件会缓冲在内存中,并按 30 秒的间隔,或当一个批次达到 100 个事件或 100 KB(以先到者为准)自动刷新。长时间运行的游戏无需手动刷新。

短暂运行或无头运行可能在下一次自动刷新之前退出。此时请显式调用 Flush(),并让进程保持存活直到确认送达(详细日志会打印 HTTP 结果)。Flush() 只是异步启动一次发送,最后的排空会随进程退出一并完成,因此请调用 Flush() 后稍等片刻再退出,而不要在同一行紧接着退出。Godot SDK 0.1.5 及更高版本会在正常关闭时同步排空缓冲(上限 2.5 秒)。仍然没有离线队列,因此在该预算内发送失败的事件,或进程被强制终止时残留的事件,仍会丢失。见故障排查

TelemetrySDK.Instance.Flush();

默认情况下事件以匿名方式发送。玩家登录后调用 SetPlayerId,后续事件即可关联到该玩家:

TelemetrySDK.Instance.SetPlayerId(playerId);

SDK 会应用一个全局采样率(默认 1.0 = 全部保留)。高频事件可以在运行时选用按事件名的采样率,覆盖全局采样率:

Framedash.TelemetrySDK.Instance.SetEventSamplingRate("ai_pathfind_step", 0.05f); // 约 5%
Framedash.TelemetrySDK.Instance.RemoveEventSamplingRate("ai_pathfind_step"); // 回到全局采样率

SetEventSamplingRate(string eventName, float rate) 设置采样率并钳制到 [0, 1],RemoveEventSamplingRate(string eventName) 移除该覆盖。自动收集的事件(session_startperf_heartbeat)不受采样影响。

自 Godot SDK 0.1.4 起可用。SDK 可以测量场景或关卡加载所需的时间,并将其作为 map_load 事件上报,进入构建比对(perf-diff)回归门禁以及仪表盘的加载耗时图表。

BeginMapLoad / EndMapLoad 包裹一次你自行控制的加载:

TelemetrySDK.Instance.BeginMapLoad("Level_01");
// ... 加载场景 ...
TelemetrySDK.Instance.EndMapLoad();

BeginMapLoad 会在不受暂停和时间缩放影响的单调时钟上启动计时,EndMapLoad 则停止计时并发出事件。在 EndMapLoad 之前再次调用 BeginMapLoad,会替换掉当前挂起的测量。

如果自定义加载器或流式加载器已经自行测量了耗时,可以直接上报:

TelemetrySDK.Instance.ReportMapLoad("Level_01", 1234f); // 加载耗时(毫秒)

loadTimeMs 为 NaN、无穷大或负值时,ReportMapLoad 会整个丢弃该样本(不会钳制)。

两条路径都会发出携带 metrics["load_time_ms"]attributes["map_name"]map_load 事件。该事件有意将 map_id 留空,因此不会进入空间热力图和激活门禁。这些调用可以从任意线程安全地进行,不会抛出异常,并且在 Initialize 之前为空操作。

自 Godot SDK 0.1.4 起可用。SDK 可以把磁盘读取计数器以 io.read_bytesio.read_time_msio.read_ops 这几个指标键附加到 perf_heartbeat 事件上。每个值都是相对上一次 heartbeat 的增量,并且只有在真正采集到样本之后这些键才会出现(不会填零)。与其他性能指标一样,io.* 会进入 perf-diff / builds-compare 回归门禁以及仪表盘图表。io.* 没有阈值告警。

Godot 没有自动的磁盘 I/O 来源,因此需要用 ReportIoSample 自行供给样本:

TelemetrySDK.Instance.ReportIoSample(bytes: 1048576, readTimeMs: 3.2f, ops: 12);

Godot SDK 0.1.5 及更高版本可用。SDK 会按 heartbeat 周期采样各内存分类的用量,并作为 mem.* 指标自动附加到 perf_heartbeat 事件上。这些值来自 Godot 的 Performance 监视器,无需任何 opt-in。

  • mem.vram:使用中的显存(字节)。
  • mem.textures:纹理占用的显存(字节)。
  • mem.buffers:缓冲区占用的显存(字节)。

读数为 0 或更低的采样会被省略:键缺失表示该值未收集,SDK 绝不会发送捏造的 0

perf_heartbeat 外,这些键还会附加到带位置(map_id 非空)的事件上。由于 perf_heartbeatmap_id 为空、不会进入空间热力图网格,SDK 会把同一采样也放到带位置的事件上,从而支持逐单元格的内存热力图。带位置的事件携带的是按 heartbeat 周期刷新的缓存采样。

调用方传入的指标键始终优先,无论是键冲突还是容量:mem.* 只填充 50 个指标摄取上限以下剩余的槽位,且优先填入 mem.vram

插件注册的 autoload 使用 TelemetrySDK.cs 的代码默认值创建,因此不能通过 Project Settings 编辑 [Export] 字段。常规设置应调用 TelemetrySDK.Initialize(...)

如果你将 TelemetrySDK 脚本添加到自己控制的 scene node(例如自己的 autoload scene),则可以在 Inspector 中编辑 API key、endpoint、sampling rate 和 camera rotation 等 [Export] 字段。

首次集成时,请启用详细日志以确认发送:

TelemetrySDK.Instance.VerboseLogging = true;

传输失败会记录重试阶梯,例如 [Framedash] Retry 1/3 in 1.0s (HTTP 0)HTTP 0 表示传输层失败(DNS、TLS 或超时),而非 API 拒绝。若事件未到达,见故障排查

Godot 编辑器的 Build 会为 C# 项目生成 .csproj / .slngodot --headless --build-solutions --quit 只会构建已存在的解决方案,无法在从未构建过的项目上生成初始的 .csproj / .sln:在全新的 CI-only 项目上,它可能以退出码 0 且无错误的方式结束,却仍未写出任何项目文件(在 4.6.3 mono 上观察到)。因此首次构建不能是无头构建。

对于从未在编辑器中打开过的 CI-only 项目,请先自行创建项目文件。在项目根目录手动编写 <project>.csproj,指定 Sdk="Godot.NET.Sdk/<engine version>" 并以 net8.0 为目标,同时让 Godot.NET.Sdk 版本与你构建所用的 Godot 版本一致:

<Project Sdk="Godot.NET.Sdk/4.6.3">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<EnableDynamicLoading>true</EnableDynamicLoading>
</PropertyGroup>
</Project>

如果你至少能在编辑器中打开项目一次,另一种做法是在那里点击一次 Build,并提交生成的 .csproj / .sln

项目文件就位后,用 .NET SDK 构建:

Terminal window
dotnet build

编译后的程序集会生成在 .godot/mono/temp/bin/Debug/。由于插件只是注册一个 Initialize 能自行创建的 autoload,无头运行无需通过编辑器界面启用插件。

若事件未到达,见故障排查

在自动化测试或性能剖析运行中,请为整个会话打标签,使每个事件都携带 CI 构建及其 branch/commit/scenario。在测试入口处调用一次自动会话 API:

using Framedash;
// 所有参数均为可选。
TelemetrySDK.Instance.BeginAutomatedSession(
buildId: "build-123",
branch: "main",
commit: "abc1234",
scenario: "nightly");
// ... 运行自动化场景 ...
TelemetrySDK.Instance.EndAutomatedSession();

完整签名为 void BeginAutomatedSession(string buildId = null, string branch = null, string commit = null, string scenario = null)。在 CI 中,推荐使用 BeginAutomatedSessionFromEnvironment(),它会读取 FRAMEDASH_BUILD_IDFRAMEDASH_GIT_BRANCHFRAMEDASH_GIT_COMMITFRAMEDASH_TEST_SCENARIO(这些正是 framedash run-profile-test 导出的变量):

TelemetrySDK.Instance.BeginAutomatedSessionFromEnvironment();
// ... 运行自动化场景 ...
TelemetrySDK.Instance.EndAutomatedSession();

自动会话会为会话内的每个事件打上 build_id 覆盖和 ci.branch / ci.commit / ci.scenario 属性,这正是进入构建比对(perf-diff)CI 门禁的依据。它不会改变任何事件的 source:SDK 自身的自动事件(session_startperf_heartbeat)始终是 source=automated,而你的 Track 事件始终是 source=player,在 CI 与正常游玩中都一样。完整流水线见 CI 剖析