跳转到内容

Unity SDK

本文介绍如何使用 Framedash Unity SDK 自动收集性能遥测数据。

  • Unity 2022.3 及以上
  • .NET Standard 2.1 / .NET Framework 4.x
  1. 打开 Window > Package Manager。Unity 6.5 将此路径改名为 Window > Package Management > Package Manager;旧版本仍保留较短的 Window > Package Manager
  2. 选择「+」>「Add package from git URL…」
  3. 输入以下 URL:
https://github.com/crane-valley/framedash-unity-sdk.git

如需锁定到特定版本,请在 URL 后追加版本标签:

https://github.com/crane-valley/framedash-unity-sdk.git#v0.1.7

若要从脚本或 CI 安装,请直接在 Packages/manifest.json 中添加依赖。包名是 com.framedash.sdk

{
"dependencies": {
"com.framedash.sdk": "https://github.com/crane-valley/framedash-unity-sdk.git#v0.1.7"
}
}

在启动时初始化一次 SDK,例如在持久化 GameObject 上的 MonoBehaviour 中初始化:

using System.Collections.Generic;
using Framedash;
using UnityEngine;
public sealed class GameBootstrap : MonoBehaviour
{
// 在 Inspector 中设置 API 密钥,或留空以回退到 FRAMEDASH_API_KEY
// 环境变量。完整的解析顺序和平台注意事项见下方说明。
[SerializeField] private string _apiKey;
private void Awake()
{
TelemetrySDK.Initialize(
apiKey: _apiKey,
buildId: Application.version);
}
}

endpointUrl 为可选项,省略时默认使用 https://ingest.framedash.dev/v1/events;如需指向本地或自托管的 ingest,请显式传入。playerId 会在初始化时设置玩家 ID,等同于之后调用 SetPlayerIdenableOfflineQueue 默认为 true;传入 false 可禁用磁盘离线队列。完整签名为 Initialize(string apiKey = null, string endpointUrl = null, string buildId = null, string playerId = null, bool enableOfflineQueue = true)

SDK 会自动收集以下数据:

  • FPS: 帧率
  • Frame Time: 每帧处理时间
  • Memory: Unity Profiler 的总分配内存
TelemetrySDK.Instance.Track(
eventName: "player_death",
mapId: "map_01",
position: transform.position);

如需附加分类属性或数值指标,可传入可选的字典:

// 请确保在文件顶部添加了 'using System.Collections.Generic;'
TelemetrySDK.Instance.Track(
eventName: "player_death",
mapId: "map_01",
position: transform.position,
attributes: new Dictionary<string, string> { { "cause", "fall_damage" } },
metrics: new Dictionary<string, float> { { "health", 0f } });

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

TelemetrySDK.Instance.SetPlayerId(playerId);

DAU 仅统计非空玩家 ID。匿名会话仍会计入事件数和会话数,但不会增加 DAU 或 MAU。请在希望计入玩家类 KPI 的活动发生前调用 SetPlayerId

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

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

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

自 Unity SDK 0.1.3 起可用。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 之前为空操作。当自定义加载器或流式加载器在工作线程上完成时,请在调用 EndMapLoadReportMapLoad 之前先切回主线程(例如借助播放器循环的更新,或捕获的 SynchronizationContext)。SDK 不会替你做线程编组,从其他线程发起的调用会静默丢弃该事件。

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

在 Unity Editor 和 Development Build 中,SDK 会自动采样 AsyncReadManagerMetrics。发布版播放器不会采集任何自动的 io.* 样本。

Unity SDK 0.1.4 及更高版本可用。SDK 会按 heartbeat 周期采样各内存分类的用量,并作为 mem.* 指标自动附加到 perf_heartbeat 事件上。与 UE5 的 bTrackMemoryDetail 不同,无需任何 opt-in。

  • mem.vram:图形驱动已分配的显存(字节),从 Profiler.GetAllocatedMemoryForGraphicsDriver 读取。
  • mem.heap:托管堆用量(字节),从 Profiler.GetMonoUsedSizeLong 读取。

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

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

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

Unity SDK 0.1.4 及更高版本可用。仅供编辑器使用的 Framedash.Editor 程序集会从 Framedash REST API 获取项目的地图与云端聚合的热力图单元格,并在 Unity 编辑器内绘制。

它需要具备 analytics:read 作用域的读取 API 密钥(Read API Key)以及项目 ID(Project ID),绝不是游戏使用的只写 Ingest 密钥。

在 Unity SDK 0.1.6 及更高版本中,可以将 Read API Key 留空,并在启动 Unity 前设置 FRAMEDASH_ANALYTICS_API_KEY。环境变量的值不会保存到 UserSettings/。如果显式输入 Read API Key,该值优先于环境变量。

获取到的热力图单元格会按记录的世界坐标,在 SceneView 中绘制为半透明四边形。无需打包构建或打开仪表盘,即可在编辑器内查看空间热力图。

设置按项目保存在 UserSettings/ 下,该目录不会打包,也不会被版本控制追踪。

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

TelemetrySDK.Instance.VerboseLogging = true;

刷新成功会记录 [Framedash] Flushed N events (HTTP 202)。在你调用 SetPlayerId 之前,每个会话还会记录警告 [Framedash] No player_id set. Events will be sent as anonymous...;这是信息提示,而非错误。自动收集的事件(初始化时的 session_start 和每 10 秒的 perf_heartbeat)会与你的手动事件合并到同一批次,因此 Flushed N events 可能比你跟踪的数量多,这是预期行为,而非重复。若事件未到达,见故障排查

SDK 在 Unity 播放循环上发送:Flush 通过协程和 UnityWebRequest 发送,二者只在循环运行时推进。裸的 Unity.exe -batchmode -executeMethod ... 调用运行在 Edit 模式,协程不 tick,因此事件被缓冲却从不发送。

要从 CI 发送遥测,请用 PlayMode 测试(Unity Test Framework)驱动播放循环。进入 Play 模式,初始化 SDK,跟踪你的事件,并让测试运行足够长时间以便在退出前完成一次刷新:

using System.Collections;
using Framedash;
using NUnit.Framework;
using UnityEngine;
using UnityEngine.TestTools;
public sealed class TelemetrySmokeTest
{
[UnityTest]
public IEnumerator SendsAMarkerEvent()
{
TelemetrySDK.Instance.VerboseLogging = true;
// 从 FRAMEDASH_API_KEY 环境变量读取密钥,不在源码中硬编码密钥。
TelemetrySDK.Initialize(buildId: "ci-smoke");
// 如果项目的全局 SamplingRate 小于 1.0,标记事件可能被采样丢弃,而自动发送的
// session_start 仍返回 HTTP 2xx,于是日志断言在没有标记的情况下也通过(验证误报)。
// 将该事件的采样率固定为 1.0,确保标记始终被保留。
TelemetrySDK.Instance.SetEventSamplingRate("ci_marker", 1f);
TelemetrySDK.Instance.Track(eventName: "ci_marker", mapId: "ci", position: default);
// 等待超过 10 秒的自动 heartbeat 间隔,让至少一个 perf_heartbeat 触发,然后把
// Flush() 作为最后一个 SDK 调用,其后不再追踪任何事件。默认刷新间隔(30秒)比这个
// 测试长,因此显式强制发送,并在退出前等待 HTTP 请求完成。
yield return new WaitForSeconds(12f);
TelemetrySDK.Instance.Flush(); // 最后一次刷新;单个事件不会触发批量阈值
yield return new WaitForSeconds(3f); // 退出前的最小等待;慢速网络下发送可能仍在进行,因此请通过下面的 HTTP 202 日志行确认送达
}
}

该测试需要自己的程序集定义,Unity Test Framework 才能编译并发现它。请将下面的 .asmdef 放在测试文件旁边:

{
"name": "Framedash.SmokeTests",
"references": [
"UnityEngine.TestRunner",
"Framedash.Runtime"
],
"includePlatforms": [],
"excludePlatforms": [],
"defineConstraints": ["UNITY_INCLUDE_TESTS"],
"precompiledReferences": ["nunit.framework.dll"],
"autoReferenced": false,
"overrideReferences": true
}

推荐的布局是把 .asmdef 和测试文件一起放在 Assets/Tests/PlayMode/

Assets/
Tests/
PlayMode/
Framedash.SmokeTests.asmdef
TelemetrySmokeTest.cs

在 CI 中用以下命令运行测试:

Unity.exe -batchmode -nographics -projectPath <path> -runTests -testPlatform PlayMode -testResults <path>\results.xml -logFile -

在 Windows 上,请让 shell 等待真正退出。Unity.exe -batchmode ... -runTests 会在约 2-3 秒内返回 shell,而编辑器仍在运行,测试大约 20 秒后才结束,因此读取即时退出码的脚本会看到虚假的通过。用 Start-Process -Wait -PassThru 启动,并从结束的进程读取退出码:

Terminal window
$proc = Start-Process -FilePath "Unity.exe" -Wait -PassThru -ArgumentList @(
"-batchmode", "-nographics",
"-projectPath", '"<path>"',
"-runTests", "-testPlatform", "PlayMode",
"-testResults", '"<path>\results.xml"',
"-logFile", '"<path>\unity.log"'
)
if ($proc.ExitCode -ne 0) { throw "Unity tests failed (exit code $($proc.ExitCode))" }
$resultsPath = "<path>\results.xml"
if (-not (Test-Path -LiteralPath $resultsPath)) { throw "Unity did not write test results" }
[xml]$results = Get-Content -Raw -LiteralPath $resultsPath
$testRun = $results.'test-run'
if (-not $testRun) { throw "Invalid test results XML format" }
$testCount = if ($testRun.testcasecount) { [int]$testRun.testcasecount } else { [int]$testRun.total }
if ($testCount -lt 1) { throw "Unity discovered zero PlayMode tests" }

-Wait 会阻塞到 Unity 真正退出,-PassThru 返回进程,因此 $proc.ExitCode 反映测试结果(0 = 通过)。每个路径占位符都用内嵌双引号包裹('"<path>"'),使包含空格的工作区路径(例如 C:\build agent\game)在 Start-Process 拆分参数时不会被截断。请把 -logFile 指向真实文件而非 -,以便事后检查,并解析 <path>\results.xml 获取每个测试的结果。仅检查退出码还不够。Unity 可能在发现 0 个测试时仍成功退出,因此必须在 XML 中确认至少有 1 个结果。

测试通过本身并不能证明遥测已送达:flush 仍在途中或已失败时,测试也可能通过。在把流水线判为通过之前,请确认送达。在 Unity 日志中 grep 携带 HTTP 202 的 flush 成功行:

[Framedash] Flushed N events (HTTP 202)

或者用 REST API 查询你发送的标记事件。只有当其中之一确认事件已落库后,才把流水线判为通过。

CI 中由 FRAMEDASH_API_KEY 提供密钥(上面的示例依赖于此,因此不会硬编码任何密钥)。调用 TelemetrySDK.Initialize(buildId: ...)(如上)或无参数的 TelemetrySDK.Initialize()

启用离线队列时(默认),事件会在优雅退出或临时发送失败时持久化到离线队列,并在下次初始化时发送。硬性 kill 会丢失仍在缓冲中的事件,因此在 CI 中不要依赖队列,请在 kill 进程之前等待日志中的 HTTP 202 行。当 enableOfflineQueue: false 时,未刷新的事件会被直接丢弃。见故障排查

在自动化测试或性能剖析运行中,请为整个会话打标签,使每个事件都携带 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 剖析