【免费下载链接】TEngine
Unity 商用级别开发框架,原生内置 AI 工作流支持,集成 HybridCLR 高性能热更、Obfuz 代码混淆加固、YooAssets 企业级资源管理方案,构建高效、安全、可扩展的工业化开发底座。
项目地址:
https://gitcode.com/gh_mirrors/teng/TEngine
点击查看 免费下载
本指南面向 TEngine 仓库内集成的 com.unity.pipeline 包(源码位于 UnityProject/Packages/com.unity.pipeline)。它讲解如何让同一套基于 HTTP 的命令驱动机制从 Unity 编辑器平滑延伸到打包后的 Player:通过 RuntimePipelineManager 组件在开发构建中启动一个仅限本机回环访问的 Pipeline 服务器,使 CLI、CI 或 Agent 可以像驱动编辑器一样驱动正在运行的游戏进程(查询运行状态、读控制台、执行 eval 代码求值、甚至做热重载)。读完本文,你将掌握运行时服务器的组件配置、Inspector 参数含义、Development Build 双条件门控、构建期校验与热更根目录烘焙原理、端口发现文件与令牌鉴权机制,并能独立完成"构建一个可被远程驱动的开发版 Player"的全流程。
本文核心事实依据为包内文档 runtime-setup.md(本指南主体)、connectivity.md、hot-reload.md 以及对应源码实现。
一、运行时服务器的定位与核心思路
在编辑器中,Pipeline 服务器由编辑器侧的 [InitializeOnLoad] 生命周期驱动,客户端(unity CLI 等)通过本地 HTTP API 执行注册的命令。但打包后的 Player 并没有 EditorApplication.update 这类编辑器心跳,也没有编辑器窗口与进程内反射环境,因此要让客户端"像驱动编辑器一样驱动一个正在运行的游戏",需要满足两个条件:
运行时侧把这两件事统一收敛到一个 MonoBehaviour 上:RuntimePipelineManager(源码见 RuntimePipelineManager.cs)。它同时承担服务器创建、每帧泵送分发器、驱动监听 watchdog、以及热重载方法自动发现与注册的职责,取代了早期设计中的 RuntimePipelineConfig 与 RuntimePipelineProcessor 两个独立组件。
重要前提:运行时服务器只存在于开发构建(Development Build)中。非开发构建会通过编译期条件编译把代码求值、热重载相关代码整体剔除,即使组件上勾选了 enableInBuilds 也无法运行。因此"正确构建"是运行时服务器配置中最重要的部分。
二、RuntimePipelineManager 组件:添加方式与生命周期
2.1 添加组件
在任意场景的 GameObject 上通过组件菜单添加:
Add Component → Pipeline → Runtime Pipeline Manager
对应源码中的 [AddComponentMenu("Pipeline/Runtime Pipeline Manager")]。该组件具备以下生命周期特性(源码在 Awake() 中实现):
- [DisallowMultipleComponent]:同一对象禁止挂多个实例;
- 单例约束:Awake() 中会扫描当前进程中所有 RuntimePipelineManager,发现多于一个时销毁重复实例并输出警告日志;
- 跨场景常驻:Awake() 中调用 DontDestroyOnLoad(gameObject),因此场景切换不会销毁它;
- Update() 三职责:每帧用 Time.unscaledDeltaTime 喂帧率采样器 FrameStatsSampler(真实帧间隔,不受 Time.timeScale 影响)、调用 m_Server?.Dispatcher.ProcessWorkQueue(maxWorkItemsPerFrame) 泵送主线程分发队列、调用 m_Server?.WatchdogTick() 驱动监听 watchdog——这三件事在编辑器中原本由 EditorApplication.update 完成,Player 中全部改由该组件的 Update 驱动;
- 退出清理:OnApplicationQuit() 自动调用 StopServer(),OnDestroy() 释放帧率采样器(仅当该实例真正安装了采样器时,被拒绝的重复实例不会误拆)。
2.2 Inspector 字段一览
| enableInBuilds | false | 总开关。服务器仅在勾选后才会启动。出于安全考虑默认关闭。 |
| autoStart | true | 是否在 Start() 中自动启动服务器(依赖 enableInBuilds)。 |
| port | 0 | 监听端口。0 表示从 7900–7949 段自动分配首个空闲端口。 |
| requestTimeoutMs | 30000 | 单请求超时(毫秒),Inspector 上约束范围为 [1000, 60000]。 |
| enableAuditLogging | true | 是否记录远程请求日志用于安全审计。 |
| maxWorkItemsPerFrame | 10 | 分发器每帧处理的最大工作项数,Inspector 上约束范围为 [1, 50]。 |
从源码可见,port 的 Inspector 约束为 [Range(0, 65535)];配置校验(RuntimePipelineConfig.cs 的 Validate())会额外检查:若 port != 0 且不在 7900–7999 内,会给出 warning——"端口超出推荐运行时段,可能与编辑器实例冲突"。
autoStart 关闭时,可通过脚本手动控制生命周期:
// 手动启动(需要 enableInBuilds = true)
GetComponent<RuntimePipelineManager>().StartServer();
// 手动停止
GetComponent<RuntimePipelineManager>().StopServer();
RuntimePipelineManager 还公开了三个有用的运行时只读属性:Server(当前 RuntimePipelineServer 实例)、IsServerRunning(服务器是否在监听)、ActualPort(实际占用端口,未启动时为 0)。StartServer() 内部会从组件字段构建 RuntimePipelineConfig、执行 Validate() 校验、创建 RuntimePipelineServer 并启动——启动成功后还会把热重载注册表的 dispatcher 与"构建期烘焙的允许热重载根目录"注入全局注册表(详见第五节)。
三、Development Build 门控:理解双条件编译
3.1 编译期守卫
运行时服务器、代码求值(eval)、热重载三者的代码实现统一被一段编译期守卫包住(见 runtime-setup.md 以及 EvalCodeCompiler.cs、HotReloadCompiler.cs、RoslynCompilationService.cs 顶部):
#if UNITY_EDITOR || (UNITY_STANDALONE && DEBUG)
含义拆解:
- UNITY_EDITOR:编辑器内恒成立,因此 Play Mode 下热重载与 eval 照常可用;
- UNITY_STANDALONE && DEBUG:仅当目标是独立平台(Windows/macOS/Linux)且为 Development Build 时,DEBUG 符号才被定义。DEBUG 只在独立平台 Development Build 中存在。
3.2 结论:两个条件缺一不可
要在 Player 中使用运行时服务器,必须同时满足:
非开发构建(Release)中,编译/求值/热重载代码被整体编译剔除,即使 enableInBuilds 为 true 也无法运行——这不是配置问题,而是二进制层面根本没有这些代码。从包内测试 RuntimePipelineBuildTests.cs 可以看到构建测试使用 BuildOptions.Development 来保证运行时服务器可用。
四、逐步实操:在开发构建中启用运行时服务器
按以下步骤操作即可让客户端通过 HTTP 驱动一个打包后的 Player:
4.1 客户端连接方式
通过 unity CLI 的 –runtime(按进程名)或 –runtime-path(按描述符文件所在目录)指定目标为运行中的 Player,选项位于 command 之后、具体命令名之前:
# 按 Player 进程/可执行文件名定位
unity command –runtime MyGame.exe runtime_status
# 按描述符文件所在目录定位
unity command –runtime-path "C:\\Builds\\MyGame" runtime_status # Windows:与 .exe 同级
unity command –runtime-path "/Users/me/Builds/MyGame.app" runtime_status # macOS:指向 .app 包
运行时服务器可执行的命令涵盖:runtime_status(运行状态)、quit(退出应用)、set_target_framerate / set_timescale(帧率与时间缩放)、simulate_key / simulate_pointer(Input System 输入模拟)、log / console(日志写入与读取)、eval / eval_file(Roslyn 代码求值)、reload_file / reload_file_override(两种热重载)等。完整参数表见 commands/runtime.md。
五、构建期校验与热重载根目录烘焙
一个运行中的 Player 无法像编辑器那样解析项目布局(Assets 路径、包目录等),因此"允许热重载哪些源文件"必须在构建期固化进构建产物。这一职责由构建处理器承担(源码见 PipelineRuntimeBuildProcessor.cs,实现 IPreprocessBuildWithReport / IProcessSceneWithReport):
- 构建前校验(OnPreprocessBuild):
- 扫描构建场景中的所有 RuntimePipelineManager;一个都没有或全部未勾选 enableInBuilds 时仅输出警告(构建仍可继续,但运行时 Pipeline 在 Player 中不可用);
- 若有多个勾选的实例,只采用第一个并警告;
- 对采用的实例执行 ValidateConfiguration(),校验失败(IsValid == false)时直接抛出 BuildFailedException 中止构建;
- 额外执行 Roslyn DLL 完整性校验:比对 Runtime/Plugins/CodeAnalysis 目录下 DLL 与 CHECKSUMS 清单的 SHA-256,任何缺失、哈希不匹配或出现未登记 DLL 都会中止构建,防止被替换/注入的编译 DLL 被打进 Player;
- 场景处理(OnProcessScene):把 CollectProjectRoots() 收集到的绝对根目录(项目 Assets 文件夹 + 所有已加载包的实际磁盘位置)通过 SetAllowedReloadRoots 烘焙进每个构建场景副本中的 RuntimePipelineManager,用户保存的场景本身不被改动。服务器启动后,热重载命令据此校验传入的源文件是否位于允许范围内。
这就是 RuntimePipelineManager 上那个 HideInInspector 的 m_AllowedReloadRoots 字段的来源——它是构建期注入的,不是用户在 Inspector 里编辑的。
六、端口、发现文件与鉴权(运行时侧安全模型)
运行时服务器与编辑器服务器共用同一套安全与发现机制,细节见 connectivity.md,此处归纳与 Player 相关的要点。
6.1 端口段
| Editor | 7800–7849 | 7850–7899 |
| Runtime(Player) | 7900–7949 | 7950–7999 |
运行时服务器的端口段在 RuntimePipelineServer.cs 的 GetPortRange() 中硬编码为 (7900, 7949),注释注明 7950–7999 预留给测试运行时服务器。若段内无空闲端口,启动会抛出 No available ports in range …。
6.2 描述符文件 .unity-pipeline-runtime-port
运行时描述符(RuntimeInstanceDescriptor.cs)在服务器启动时写入 Player 可执行文件旁边({Application.dataPath}/..):
- Windows / Linux:与可执行文件同级,如 C:\\Builds\\MyGame\\.unity-pipeline-runtime-port;
- macOS:Application.dataPath 为 <Game>.app/Contents,因此描述符落在 .app 包根目录:MyGame.app/.unity-pipeline-runtime-port——这也是 macOS 上 unity command –runtime-path 必须传 .app 包路径的原因。
JSON 字段(运行时版本与编辑器版本略有差异):
| pid | Player 进程 ID |
| port | 监听端口(7900–7999) |
| platform | Unity 运行时平台(如 WindowsPlayer) |
| unityVersion | Player 的 Unity 版本字符串 |
| buildGuid | 唯一构建标识(Application.buildGUID) |
| workingDirectory | Player 运行的工作目录 |
| startedAt / lastHeartbeat | 启动时间与最近心跳(UTC,状态查询时刷新) |
| evalToken | 请求鉴权用 Bearer 令牌 |
描述符是唯一的发现通道——没有广播或注册中心。文件创建时即通过 FilePermissions.RestrictToCurrentUser 限制为仅当前用户可读(因为携带令牌),服务器每次心跳都会重写以刷新 lastHeartbeat,关闭时删除。测试服务器不写描述符(避免覆盖线上服务器的文件)。
6.3 鉴权与访问控制
每次请求都必须携带 Bearer 令牌:
Authorization: Bearer <evalToken>
- 令牌由 SecurityTokenManager 在启动时生成(256 位 CSPRNG 输出、base64 编码、进程内持有,域重载后重新生成);
- 服务器在路由前对每个请求做常量时间比较校验,缺失或错误令牌返回 401 Unauthorized;
- 服务器仅绑定IPv4 回环(127.0.0.1,另加 localhost 主机名兼容),仅本机可达,绝不暴露到可路由网卡。客户端应显式连接 127.0.0.1——因为 localhost 可能解析为 IPv6 回环 ::1,而 Unity 的 Mono HttpListener 无法服务 IPv6 回环(会因错误解析 [::1] 主机返回 400);
- 服务器拒绝携带 Origin 头的请求(正规 CLI/CI 客户端不会发送),从而阻断浏览器页面访问本地服务器并短路 CORS 预检;
- 源码中(BasePipelineServer.cs 的 ProcessRequest)还有一层回环地址显式校验:非回环来源的连接直接返回 403 Forbidden。
由于服务器暴露代码求值与热重载能力,只应在开发/QA 构建中启用,切勿在无额外防护的生产构建中开启。
七、运行时服务器内部结构速览
从源码结构看,运行时侧的关键类分工如下:
- RuntimePipelineManager.cs:MonoBehaviour 外壳,生命周期、配置入口、热重载方法自动发现(扫描加载的程序集中带 [HotReload] / [HotReloadWithOverrides] 的方法并注册到 HotReloadRegistry,跳过 System、UnityEngine 等引擎/框架程序集);
- RuntimePipelineServer.cs:继承抽象基类 BasePipelineServer,只实现端口段、描述符创建/删除/心跳、状态与令牌获取四个抽象成员——HTTP 监听、鉴权、路由、请求体上限(1 MiB,超限 413)、watchdog 自愈等全部复用基类实现;
- BasePipelineServer.cs:共享 HTTP 服务器实现,每个服务器实例持有自己的 dispatcher(无全局单例),在 Player 中由 RuntimePipelineManager.Update 泵送,主线程命令经 dispatcher 排队执行;
- FrameStatsSampler.cs:进程级帧率采样器,Player 无 Profiler 窗口,由管理器从 Update 喂数据,runtime_status 遥测命令读取它——注意它是在 Awake 中创建的,因此 Agent 连接时 fps 历史已经"暖机"。
包内测试也验证了组件默认值(见 RuntimePipelineManagerTests.cs):enableInBuilds 默认 false(安全默认)、port 默认 0(自动分配)、autoStart 默认 true;运行时端口段与编辑器端口的隔离由 RuntimePipelineServerTests 覆盖。
八、常见问题与排查
- Release 构建中无法连接:属预期行为。DEBUG 符号仅在独立平台 Development Build 中定义,求值/热重载代码在 Release 中根本不存在。请检查 Build Settings 是否勾选 Development Build。
- 端口段耗尽:7900–7949 全部被占用时启动失败并抛 No available ports in range …。可显式指定 port,但注意配置校验会对 7900–7999 之外(非 0)的端口给出警告。
- 找不到描述符文件:先看 Player 日志中 Pipeline: Runtime descriptor written to … 一行,确认实际写入位置;macOS 记得描述符在 .app 包根目录内,–runtime-path 传 .app 路径。
- localhost 连接偶发失败:请改用 127.0.0.1,规避 Mono HttpListener 对 IPv6 回环 ::1 的 400 误判。
- 跨场景丢失服务器:RuntimePipelineManager 自带 DontDestroyOnLoad,请确认重复实例被正确清理(重复者会打警告并自毁)。
延伸阅读
- connectivity.md:端口、描述符文件、鉴权与发现机制的完整说明;
- hot-reload.md:两种热重载口味(in-place 与 override)的用法与 Roslyn/内存程序集架构;
- commands/runtime.md:运行时命令的完整参数与返回值参考;
- PipelineRuntimeBuildProcessor.cs:构建期校验与热重载根目录烘焙的实现;
- 相关测试:RuntimePipelineManagerTests.cs、RuntimePipelineBuildTests.cs。
赞
【免费下载链接】TEngine
Unity 商用级别开发框架,原生内置 AI 工作流支持,集成 HybridCLR 高性能热更、Obfuz 代码混淆加固、YooAssets 企业级资源管理方案,构建高效、安全、可扩展的工业化开发底座。
项目地址:
https://gitcode.com/gh_mirrors/teng/TEngine
点击查看 免费下载
相关推荐
PyWxDump环境配置实战指南:三步跑通环境,兼容性问题一次修好
Caveman Skill 上手指南:AI 编码助手回答 token 直降 65% 的 6 种强度模式
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
网硕互联帮助中心


评论前必须登录!
注册