LoadSceneAsync 完成后,加载画面可能还没有消失。
存档恢复、玩家生成或输入初始化可能仍在进行。
要缩短加载时间,先定义 什么时候停止计时。
本文把加载请求到可以操作的时间作为整体指标,再用中间时点帮助定位等待。 计测设计依据 Unity 6.4 API 和 Framedash Unity SDK 0.1.8 的行为。
进度 0.9 不等于可以操作
当 allowSceneActivation = false 时,Unity 将进度停在 0.9,并保持 isDone 为 false。
这是场景激活前的等待点,不代表字节数或 CPU 工作量已完成 90%。
阻止激活还可能让其他 AsyncOperation 操作等待。
参阅 Unity 激活文档,不要仅为计测而给原本不等待的加载器增加这一步。
sceneLoaded 也是另一个独立信号。
Unity 在 OnEnable 之后、Start 之前调用它。
如果把它当作可以操作的时点,Start 和后续异步初始化就会落在计测区间之外。
应将 引擎回调顺序与游戏的准备完成条件分开。
用同一个时钟记录四个时点
- T0:加载请求。即将调用加载器的时点
- T1:允许激活。仅当现有加载器阻止激活时,在将
allowSceneActivation设为 true 之前记录 - T2:观察到场景操作完成。确认
LoadSceneAsync.isDone为 true 的时点 - T3:可以操作。游戏定义的条件成立,例如必要初始化结束、加载遮罩移除且开始接受输入
T0 到 T1 可能包含加载过程,也可能包含过场表现或等待“继续”按钮的时间。 T1 到 T2 同样是经过时间,而不是纯粹的激活 CPU 时间。 主线程中的其他工作,以及协程下次检查前的间隔,都可能包含在内。 需要定位到具体处理时,应结合 Unity Profiler。
如果加载器没有手动阻止激活,就不必记录 T1。
只记录 T0 到 T2、T2 到 T3,也能分出场景操作之后的等待。
如果准备完成信号早于 T2,调用方需要保存该状态,并在观察到 T2 后再次调用 TryReportPlayable。
辅助类不会保存提前到达的信号。T3 指观察到两个条件均已成立并进行报告的时点。
记录一次总时间,另用事件记录分段
比较构建时,保持 map_load 的起止定义一致。
这里将 T0 到 T3 记录为一次加载。
如果基准构建在 T2 结束、候选构建在 T3 结束,即使工作相同,计测结果也会改变。
Framedash 加载计时 API 提供 BeginMapLoad / EndMapLoad 和 ReportMapLoad。
让 SDK 负责计时可使用前者;加载器已有毫秒结果时使用后者。
再次调用 BeginMapLoad 会替换尚未结束的测量,因此并发加载需要各自的时钟和 ReportMapLoad。
下面的辅助类使用后者。
传入已初始化的 SDK,在 T0 创建实例;在 T1 可选调用 MarkActivationRequested,T2 调用 MarkOperationDone,T3 调用 TryReportPlayable。
实例应由不会随场景切换而销毁的加载器持有。
SDK 调用已与 0.1.8 源码核对。 在 Unity 主线程执行所有调用;后台加载器完成后,先切回主线程再报告。
using System;
using System.Collections.Generic;
using System.Diagnostics;
using Framedash;
// One instance per load. Call every method on Unity's main thread.
public sealed class LoadProbe
{
private readonly TelemetrySDK sdk;
private readonly string mapName;
private readonly Stopwatch clock = Stopwatch.StartNew();
private double? activationRequestedMs;
private double? operationDoneMs;
private bool reported;
public LoadProbe(TelemetrySDK initializedSdk, string mapName)
{
if (initializedSdk == null)
throw new ArgumentNullException(nameof(initializedSdk));
if (string.IsNullOrWhiteSpace(mapName))
throw new ArgumentException("A stable map name is required.", nameof(mapName));
sdk = initializedSdk;
this.mapName = mapName;
}
// Optional: only if the existing loader controls activation.
public void MarkActivationRequested()
{
if (!reported && !activationRequestedMs.HasValue && !operationDoneMs.HasValue)
activationRequestedMs = clock.Elapsed.TotalMilliseconds;
}
public void MarkOperationDone()
{
if (!reported && !operationDoneMs.HasValue)
operationDoneMs = clock.Elapsed.TotalMilliseconds;
}
// Call when both readiness and isDone have been observed.
// An early readiness call is not cached; call again after MarkOperationDone.
public bool TryReportPlayable()
{
if (reported || !operationDoneMs.HasValue) return false;
double totalMs = clock.Elapsed.TotalMilliseconds;
double doneMs = operationDoneMs.Value;
var metrics = new Dictionary<string, float>
{
{ "scene_operation_ms", (float)doneMs },
{ "after_operation_ms", (float)(totalMs - doneMs) }
};
if (activationRequestedMs.HasValue)
{
double activationMs = activationRequestedMs.Value;
metrics["before_activation_ms"] = (float)activationMs;
metrics["activation_window_ms"] = (float)(doneMs - activationMs);
}
reported = true;
sdk.ReportMapLoad(mapName, totalMs);
sdk.Track("load_phases", attributes: new Dictionary<string, string>
{ { "map_name", mapName } }, metrics: metrics);
return true;
}
}
TryReportPlayable 返回 true,只表示辅助类执行了一次报告流程,并不确认服务器已收到事件。
失败或取消的尝试不要调用它。
如需记录失败,使用单独的事件,避免混入成功加载统计。
load_phases 及其指标名称是本例定义的自定义事件,不会自动生成专门的分段图表或回归门禁。
标准 map_load 的 map_id 为空,地图名称存于 attributes["map_name"],时间存于 metrics["load_time_ms"]。
它本身不会生成空间热力图上的点。
这两个事件都会受采样影响。 若验证时需要在采样阶段保留所有事件,可在初始化后设置以下覆盖值。 采样率为 1 仍不能避免传输故障或缓冲区限制导致的缺失。
TelemetrySDK.Instance.SetEventSamplingRate("map_load", 1f);
TelemetrySDK.Instance.SetEventSamplingRate("load_phases", 1f);
比较前检查结束条件和失败路径
集成到自己的加载器时,请进行以下检查。
- 在相同场景和构建设置下确认 T0、T2、T3 的顺序与单位
- 对现有激活等待流程,确认主动等待计入 T0 到 T1
- 刻意延迟
MarkOperationDone之后的异步准备完成,确认 T2 到 T3 增长;延迟Awake等更早的工作可能使 T2 本身后移 - 检查失败、取消、重复完成和并发加载,避免错误增加成功数或混淆不同尝试
- 区分冷启动和重新加载,记录设备、存储、缓存条件与构建 ID
示例的验证范围
这个辅助类已使用 Unity 6000.3.25f1(6.3 LTS)和 6000.6.4f1、Framedash Unity SDK 0.1.8 验证。
在同一 Windows x64 主机上,Mono 和 IL2CPP 均分别以非 batchmode 的 D3D12 隐藏窗口和 -batchmode -nographics 运行。
SDK 和 LoadProbe 未作修改,文章示例的测试场景检查通过,Player 在 SDK 关闭处理完成后正常退出。
这次版本验证在本地解码并确认接收了 map_load 和 load_phases 的实际发送数据。
失败和取消的尝试未作为成功加载发送。
另一次 Framedash 生产环境连通性测试通过回读查询,确认两种事件均已存储。
这些结果只确认了被测试的尝试,不保证所有事件都能送达。
验证范围是本文示例,不涵盖 SDK 的完整测试套件或客户游戏的性能。 可见交互式窗口以及 Android / iOS 真机仍未验证。 集成到自己的游戏时,除上述检查外,还应在目标设备上确认实际行为。
先固定 T0 到 T3 的定义,再调查较长的区间。 这样就能用相同指标判断,改动缩短的是场景加载工作,还是玩家开始操作前的等待。