Unity SDK
本文介绍如何使用 Framedash Unity SDK 自动收集性能遥测数据。
- Unity 2022.3 及以上
- .NET Standard 2.1 / .NET Framework 4.x
Unity Package Manager(推荐)
Section titled “Unity Package Manager(推荐)”- 打开 Window > Package Manager。Unity 6.5 将此路径改名为 Window > Package Management > Package Manager;旧版本仍保留较短的 Window > Package Manager。
- 选择「+」>「Add package from git URL…」
- 输入以下 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,等同于之后调用 SetPlayerId。enableOfflineQueue 默认为 true;传入 false 可禁用磁盘离线队列。完整签名为 Initialize(string apiKey = null, string endpointUrl = null, string buildId = null, string playerId = null, bool enableOfflineQueue = true)。
性能数据自动收集
Section titled “性能数据自动收集”SDK 会自动收集以下数据:
- FPS: 帧率
- Frame Time: 每帧处理时间
- Memory: Unity Profiler 的总分配内存
发送自定义事件
Section titled “发送自定义事件”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 } });标识玩家(可选)
Section titled “标识玩家(可选)”默认情况下事件以匿名方式发送。玩家登录后调用 SetPlayerId,即可将后续事件与该玩家关联:
TelemetrySDK.Instance.SetPlayerId(playerId);DAU 仅统计非空玩家 ID。匿名会话仍会计入事件数和会话数,但不会增加 DAU 或 MAU。请在希望计入玩家类 KPI 的活动发生前调用 SetPlayerId。
运行时采样覆盖
Section titled “运行时采样覆盖”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_start、perf_heartbeat)不受采样影响。
地图加载耗时采集
Section titled “地图加载耗时采集”自 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 之前为空操作。当自定义加载器或流式加载器在工作线程上完成时,请在调用 EndMapLoad 或 ReportMapLoad 之前先切回主线程(例如借助播放器循环的更新,或捕获的 SynchronizationContext)。SDK 不会替你做线程编组,从其他线程发起的调用会静默丢弃该事件。
磁盘 I/O 指标
Section titled “磁盘 I/O 指标”自 Unity SDK 0.1.3 起可用。SDK 可以把磁盘读取计数器以 io.read_bytes、io.read_time_ms、io.read_ops 这几个指标键附加到 perf_heartbeat 事件上。每个值都是相对上一次 heartbeat 的增量,并且只有在真正采集到样本之后这些键才会出现(不会填零)。与其他性能指标一样,io.* 会进入 perf-diff / builds-compare 回归门禁以及仪表盘图表。io.* 没有阈值告警。
在 Unity Editor 和 Development Build 中,SDK 会自动采样 AsyncReadManagerMetrics。发布版播放器不会采集任何自动的 io.* 样本。
内存分类指标
Section titled “内存分类指标”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_heartbeat 的 map_id 为空、不会进入空间热力图网格,SDK 会把同一采样也放到带位置的事件上,从而支持逐单元格的内存热力图。带位置的事件携带的是按 heartbeat 周期刷新的缓存采样,因此事件路径不会读取引擎。
调用方传入的指标键始终优先,无论是键冲突还是容量:mem.* 只填充 50 个指标摄取上限以下剩余的槽位,且优先填入 mem.vram。
编辑器内 SceneView 热力图
Section titled “编辑器内 SceneView 热力图”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 可能比你跟踪的数量多,这是预期行为,而非重复。若事件未到达,见故障排查。
无头 / CI
Section titled “无头 / CI”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 启动,并从结束的进程读取退出码:
$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 / 自动会话
Section titled “CI / 自动会话”在自动化测试或性能剖析运行中,请为整个会话打标签,使每个事件都携带 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_ID、FRAMEDASH_GIT_BRANCH、FRAMEDASH_GIT_COMMIT 和 FRAMEDASH_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_start、perf_heartbeat)始终是 source=automated,而你的 Track 事件始终是 source=player,在 CI 与正常游玩中都一样。完整流水线见 CI 剖析。