SDK 开发文档
面向内容 / 业务工程接入 Slhx 大空间内容端 SDK。 Framework 仅作简要说明;房间网络、事件、自定义消息、物体同步、体验区、内容推进、管控桥接等业务能力展开说明。 当前对应 v1.0.2,变更见 更新说明; 接口一览见 API 文档。
1. 概述
Slhx SDK 以 UPM 交付:核心包 com.slhx.sdk(必装)、示例包
com.slhx.samples(可选)。业务玩法、关卡与品牌资源放在接入工程(推荐
Assets/Gameplay),通过公开 API 组合平台能力。
2. 环境要求
| 项 | 要求 |
|---|---|
| Unity | 2022.3.x LTS |
| 设备 | Android XR(产线以 PICO 为主) |
| 渲染 / 后端 | 建议 URP;IL2CPP + ARM64 |
| PICO Integration SDK | 接入工程自备,zip 不含 |
| OpenUPM | LiteNetLib 1.3.3、YooAsset 3.0.4(以 package.json 为准) |
| 名称 | 固定? | 说明 |
|---|---|---|
| applicationId | 可变 | 产品包名 com.xxx.xxx |
| Android 桥 Java 包 | 固定 | com.slhx.android |
| lycj 目录 | 固定 | /sdcard/Download/lycj |
3. 安装 SDK
- URP 管线:须安装并在 Graphics/Quality 指定 URP Asset,否则粉红材质、Shader 报错。
- XR 官方 Samples:Package 装上 ≠ Sample 已导入。须在 Package Manager 对 XRI / XR Hands 点 Import,否则 Missing Script、预制体丢失。
- Layers / Tags:工程
TagManager不会随 UPM 包迁移。导入后执行 Tools → Slhx → 安装/修复 Layers 与 Tags(空槽会自动写入;有冲突时按菜单提示处理)。缺层会导致相机 Culling Mask、Boundary/Avatar 显示全错。
推荐顺序: URP → 安装 Slhx 包 → OpenUPM → PICO → 导入 XR Samples → 配置 XR Plug-in。
3.1 配置 URP(必做)
制作工程与交互资源按 Universal Render Pipeline 制作。接入工程仍用 Built-in 时,常见粉红材质与 URP Shader 报错。
- Package Manager 安装 Universal RP(Unity 2022.3 对应 14.x)。
Create → Rendering → URP Asset (with Universal Renderer)(或使用工程内已有 URP 资源)。- Edit → Project Settings → Graphics:指定 Scriptable Render Pipeline Settings = 上述 URP Asset。
- Project Settings → Quality:各等级 Render Pipeline Asset 一并指定(勿留空)。
- 打开场景确认无大面积粉红、无大量 unsupported shader 报错。
3.2 安装 Slhx 包
- 解压
slhx-upm-{version}-release.zip - 将
com.slhx.sdk(及可选com.slhx.samples)拷入工程Packages/ - 配置 OpenUPM scopedRegistry(LiteNetLib、YooAsset)
- 导入 PICO Unity Integration SDK,XR Plug-in Management 启用 PICO
- Package Manager 中确认 Slhx SDK、XRI 2.6.5、XR Hands 1.4.3
"scopedRegistries": [{
"name": "package.openupm.com",
"url": "https://package.openupm.com",
"scopes": ["com.revenantx.litenetlib", "com.tuyoogame.yooasset"]
}]
3.3 导入 XR 官方 Samples(必做)
路径:Window → Package Manager → Packages: In Project。 选中包 → 右侧 Samples → 点 Import。 仅安装包体不会自动带上这些资源。
XR Interaction Toolkit(2.6.5)
| Sample | 用途 | 不导入的后果 |
|---|---|---|
| Starter Assets | 默认 Input Actions、XR Origin / 交互预设 | 输入与交互预制 Missing、控制器绑定异常 |
| Hands Interaction Demo | 手部交互与 XRI 集成资源 | 手部交互预制/脚本 Missing |
| XR Device Simulator | Editor 模拟头显与手柄 | 编辑器难模拟 XR 输入(联调强烈建议导入) |
导入后目录示例:
Assets/Samples/XR Interaction Toolkit/2.6.5/
Starter Assets/
Hands Interaction Demo/
XR Device Simulator/
XR Hands(1.4.3)
| Sample | 用途 | 不导入的后果 |
|---|---|---|
| Gestures | 手势检测/调试示例 | 手势相关资源 Missing |
| HandVisualizer | 手部可视化 | 手部网格/可视化预制 Missing |
Assets/Samples/XR Hands/1.4.3/
Gestures/
HandVisualizer/
Assets/Samples 中已导入内容,否则场景引用会再次丢失。
3.4 安装验收
- URP 已指定;场景无大面积粉红
- 已 Import:Starter Assets、Hands Interaction Demo、XR Device Simulator、Gestures、HandVisualizer
- Entry / 交互场景无成批 Missing Script
- PICO 已导入且 XR Plug-in 已勾选
- Layers:26–30 为 Overlay / Underlay / Boundary / OutBoundary / Avatar;或 Tools → Slhx → 检查 Layers 与 Tags
3.5 Layers 与 Tags(新工程必做)
Unity 的 Layer/Tag 存在接入工程的 ProjectSettings/TagManager.asset,
不会随 UPM / 解压示例自动带上。预制体里写的是 Layer 索引(如 Avatar=30),
新工程缺层或索引错位会导致相机 Culling Mask、边界与 Avatar 全错。
- 菜单 Tools → Slhx → 安装/修复 Layers 与 Tags(只写 SDK 约定槽位,其它层不动)
- 若提示槽位冲突,确认后强制覆盖,或手工改到约定索引
- 可用 Tools → Slhx → 检查 Layers 与 Tags 验收
| Index | Name | 用途(简述) |
|---|---|---|
| 26 | Overlay | 叠加层 |
| 27 | Underlay | 底层 |
| 28 | Boundary | 体验区内边界 |
| 29 | OutBoundary | 区外/出区表现 |
| 30 | Avatar | 角色 / 示例内容区 |
Tags:XRSetup、ContentArea、ContentScene、Portal。其余 User Layer 由工程自用。
4. 工程结构建议
Assets/
Gameplay/ # 业务内容
Samples/ # XR 官方 Samples(§3.3 Import 后)
XR Interaction Toolkit/2.6.5/...
XR Hands/1.4.3/...
Packages/
com.slhx.sdk/ # 必装
com.slhx.samples/ # 可选(Slhx 业务示例,≠ 上面 XR Samples)
5. 快速接入
5.1 启动
- 场景放置
BootSystem(名建议 BootSystem) - 需接管控指令时放置
AndroidBridge - 可选:
Framework.Core.FrameworkBootstrap.InitFramework()
5.2 进房
using Slhx.Game.LiteNet;
RoomNetClient.Instance.Configure("10.0.0.5", 12000);
RoomNetClient.Instance.Connect();
未配置 IP/端口不会连接。真机端口 11001 有保护性跳过,正式端口由业务/管控下发。
5.3 地图工具
Tools → Slhx → 地图体验区配置工具(随 com.slhx.sdk 交付,接入工程可用)。
5.4 编辑器多人调试(ParrelSync + 房间服务)
无需打 APK 即可在 PC 上验证双端进房、同步、体验区切换: 用 ParrelSync 开第二份编辑器,用本机已安装的 Slhx 房间服务 提供 LiteNet 房间。
- ParrelSync:同一工程两个 Unity Editor(主工程 + Clone),各自 Play,模拟两个客户端。
- 房间服务安装器:客户从文档站下载安装;安装后启动服务,两端都连到它。
下载房间服务安装器
请前往 下载页 获取安装包。 不同安装器版本对应不同 SDK 版本,请选择「适用 SDK」包含当前文档版本(本页为 1.0.2)的安装器,例如:
SlhxRoomServer-1.0.2-Setup.exe → 适用于 SDK v1.0.2
安装包请从本站 下载页 获取(选择与当前文档版本对应的房间服务)。
联调网络参数(约定)
| 项 | 值 | 说明 |
|---|---|---|
| IP | 本机局域网 IP | 如 192.168.88.5(ipconfig 查看);主工程与 Clone 填同一地址 |
| 端口 | 11001 | 与房间调试服务约定一致 |
// 将 192.168.x.x 换成你的本机局域网 IP
RoomNetClient.Instance.Configure("192.168.x.x", 11001);
RoomNetClient.Instance.Connect();
步骤
-
安装并启动房间服务
从 下载页 获取与当前 SDK 匹配的安装器 → 安装 → 启动房间服务(默认监听 11001,以安装包说明为准)。 -
主工程(Player A)
打开联调场景;进房前Configure(局域网IP, 11001)后 Play。 -
Clone(Player B)——必须改 DeviceSn
ParrelSync → Clones Manager → Create / Open in New Editor → 同场景、同样Configure(局域网IP, 11001)。【接入方必改】DeviceSn 必然冲突 ParrelSync 克隆的是同一场景资源,主工程与 Clone 默认共用 Editor 设备号(改完 DeviceSn 后再 Play。BootSystem.testDeviceSn, 常见默认Test Device)——肯定会冲突。 房间成员身份依赖 DeviceSn 唯一;不改则两客户端被当成同一设备, 出现成员顶替、本端/远端 Avatar 错乱、同步异常等。
接入方必须在 Clone 窗口选中带BootSystem的物体, 将 Inspector 中Test Device Sn改为与主工程 不同的字符串(如Clone-B/Editor-2), 再进 Play。主工程保持原值即可。 - 验证:两端 Connected、成员列表为两个不同 DeviceSn;再测事件广播、体验区、物体同步等。
6. 模块能力
6.1 Framework(简要)
命名空间 Framework.* / 程序集 Slhx.Framework。
提供服务定位、事件总线、配置/存档、场景服务、输入、定时器、下载、日志、
等基础设施。
入口(须在 App 入口 Awake 中调用一次):
Framework.Core.FrameworkBootstrap.InitFramework();
InitFramework 会向 ServiceLocator 注册包括下列服务(节选):
| 接口 | 实现 | 说明 |
|---|---|---|
ILogService | DebugLogService | 日志 |
IEventBus | EventDispatcher | 事件总线 |
ICoroutineRunner | CoroutineRunner | 协程 |
ITimeService / ITimerService | Unity 时间 / Timer | 时间与定时 |
IResourceService | Resources 实现 | 资源 |
ISceneService | SceneService | 场景 |
IConfigService / ISaveService | 配置 / 存档 | — |
IInputService | 默认实现 | 可被 Game 输入覆盖 |
UI(Slhx.UI)、Yoo 热更(Slhx.Yoo)、HandGesture 同属通用层,本文不展开内部实现。
枚举标注(Framework.Extension):
[Description] 常用于场景名或文案;
[ExperienceAreaId] 用于空间体验区 id(如 Boot placeItems[].id)。
读取:GetDescByEnum / GetAreaIdByEnum。
内容步骤表示例见 §14。
6.2 业务相关模块一览
| 模块 | 命名空间 / 入口 | 用途 |
|---|---|---|
| 房间连接 | RoomNetClient | 配置、连接、收发底层包 |
| 房间门面 | RoomMgr | 聚合 Handler、生成物体、断线清理 |
| 事件广播 | EventBroadcastHandler | ushort 事件 + payload,业务推进首选 |
| 自定义消息 | CustomMessageHandler | 频道 channelId 可靠/不可靠字节流 |
| 物体同步 | ObjectSyncHandler / NetworkedObject | Transform、所有权、生成销毁 |
| 内容播放同步 | ContentSyncHandler / IContentController | 多媒体播控状态 |
| 成员 / 位姿 | MemberHandler / TransformHandler | 房间成员与头手位姿 |
| 体验区 | ExperienceAreaHandler | 场地加载、切换、预览、出区 |
| Boot / 路径 | BootSystem / ProductPaths | 配置、设备 SN、ContentMode |
| 管控 | AndroidBridge / UnityToServiceNative | 收指令 / 回传 JSON |
| Avatar | AvatarMgr / Catalog | 多人虚拟形象 |
7. 房间与网络总览
房间通信基于 LiteNetLib。客户端连接房间服务器后,消息首字节为
RoomMsgType,由 RoomMessageReceiver 分发给各 Handler。
7.1 架构关系
RoomNetClient 连接 / Send / LocalPlayerId
│
RoomMessageReceiver 按 RoomMsgType 分发
│
├── MemberHandler 成员列表
├── TransformHandler 玩家头手位姿
├── EventBroadcastHandler 业务事件(推荐)
├── CustomMessageHandler 自定义频道
├── ObjectSyncHandler 物体同步 / 生成
├── ContentSyncHandler 内容播控
├── AnimationSyncHandler 动画
├── HeartbeatHandler 心跳
└── TimeSyncHandler 时间同步
RoomMgr 引用上述组件,提供 SpawnObject / RegisterPrefab / 断线 Cleanup
7.2 连接生命周期
| API | 说明 |
|---|---|
Configure(ip, port, roomKey?) | 写入地址;不自动连 |
Connect() | 发起连接(需已 Configure) |
Disconnect() | 断开 |
ConnectStatus / IsConnected | 状态查询 |
LocalPlayerId | 进房后由服务分配;未就绪为 -1 |
OnRoomDisconnected | 断线回调;RoomMgr 会 CleanupRoomState |
// 推荐顺序
RoomNetClient.Instance.Configure(host, port);
RoomNetClient.Instance.Connect();
// 业务发送前请判断 ConnectStatus == RoomNetStatus.Connected
7.3 消息类型一览(RoomMsgType)
| 类型 | 值 | 用途 |
|---|---|---|
| JoinRoom / LeaveRoom / RoomMembers | 1–3 | 进房与成员列表 |
| MemberStatus / Heartbeat / MemberTimeout | 4–6 | 成员状态与保活 |
| PlayerTransform | 10 | 头 + 双手位姿 |
| AnimationSync / HandGesture / HandPose | 11–13 | 动画与手势 |
| ObjectSync / Ownership / Spawn / Destroy | 20–24 | 网络物体 |
| TimeSync | 30–31 | 时钟对齐 |
| ContentSync / ContentControl | 40–41 | 媒体内容播控 |
| EventBroadcast / EventTrigger | 50–51 | 业务事件广播 |
| CustomReliable / CustomUnreliable | 60–61 | 自定义频道消息 |
8. 事件广播(业务推荐)
组件:EventBroadcastHandler(通常挂在与 RoomMgr 相同的网络对象上,经
RoomMgr.EventBroadcastHandler 访问)。
客户端调用 Trigger 向服务器发 EventTrigger;
服务器(或房间逻辑)确认后以 EventBroadcast 下发全房。
本地 Handler 按 eventId 回调已注册监听器。
8.1 注册与触发
using Slhx.Game.LiteNet;
using UnityEngine.Events;
var eb = RoomMgr.Instance.EventBroadcastHandler;
// 监听:senderId = 触发方玩家 Id,payload 可为 null
eb.RegisterEvent(1001, (senderId, payload) => {
Debug.Log($"evt from {senderId}, bytes={payload?.Length ?? 0}");
});
// 字符串 / JSON 便捷监听
eb.RegisterStringEvent(1002, (senderId, text) => { /* ... */ });
eb.RegisterJsonEvent<MyDto>(1003, (senderId, dto) => { /* ... */ });
eb.RegisterSimpleEvent(1004, senderId => { /* 无 payload */ });
// 全局监听所有事件
eb.RegisterGlobalListener((senderId, eventId, payload) => { });
// 触发(需已进房)
eb.TriggerEventSimple(1004);
eb.TriggerEventString(1002, "hello");
eb.TriggerEventJson(1003, new MyDto { step = 2 });
eb.TriggerEvent(1001, myBytes);
8.2 取消注册
eb.UnregisterEvent(1001, myListener);
eb.UnregisterGlobalListener(myGlobal);
eb.ClearAllEvents(); // 慎用
8.3 约定与注意
eventId为ushort。SDK 示例占用部分号段(见 §14),业务请规划自有区段,避免冲突。- 发送使用
ReliableOrdered,适合状态推进,不适合每帧高频。 - 未连接时 Trigger 会打 Warning 并丢弃。
- payload 由业务定义(UTF-8 字符串、JSON、
BitConverter等),全房编解码必须一致。
8.4 典型业务用法
| 场景 | 做法 |
|---|---|
| 全房切内容步骤 | 客户端上报 C* 事件 → 服务确认 S* → 各端监听 S* 后加载 |
| 全房切体验区 | 同上,payload 带 areaId |
| 机关触发 / 过关 | 自定义 eventId + 可选 JSON |
| 只通知、无数据 | TriggerEventSimple + RegisterSimpleEvent |
9. 自定义消息(频道)
组件:CustomMessageHandler。
按 channelId(ushort) 收发原始 byte[],
支持可靠(CustomReliable)与不可靠(CustomUnreliable)两种投递。
9.1 注册频道与收发
var cm = FindObjectOfType<CustomMessageHandler>(); // 或自行缓存引用
cm.RegisterChannel(10, (senderId, data) => {
// 处理频道 10
});
// 可靠:指令、配置、关键状态
cm.SendReliable(10, bytes);
cm.SendStringReliable(10, "ping");
cm.SendJsonReliable(10, myObj);
// 不可靠:高频、可丢(如非关键预览状态)
cm.SendUnreliable(10, bytes);
cm.SendStringUnreliable(10, "tick");
9.2 与事件广播的区别
| 事件广播 | 自定义频道 | |
|---|---|---|
| 标识 | eventId | channelId |
| 语义 | 离散业务事件 | 任意字节流管道 |
| 可靠性 | 可靠有序 | 可选可靠 / 不可靠 |
| API 便利 | String/JSON/Simple 封装更全 | 同样有 String/JSON 发送 |
| 适用 | 玩法阶段、全房确认 | 自定义协议、高频弱状态 |
RegisterChannel 的频道收到消息时仅 Debug 警告,不会自动处理。
同一 channelId 重复注册会覆盖旧 handler。
10. 网络物体与所有权
场景或运行时需要同步的物体挂 NetworkedObject,
由 ObjectSyncHandler 管理注册、脏标记同步、所有权与生成销毁。
10.1 objectId 规则
| 类型 | id 范围 | 如何产生 |
|---|---|---|
| 场景静态物体 | ≥ 1000(通常 < 1_000_000) | 编辑器添加/复制时无感自动分配并写入序列化;须保存场景 |
| 运行时生成 | ≥ 1_000_000 | RoomMgr.SpawnObject → NetworkedObjectId.AllocateRuntimeId + 网络 Spawn |
- 全房对同一逻辑物体必须同一 id(静态靠同场景资源;动态靠 Spawn 消息)。
- 无效 id 不会正常注册。
10.2 同步与所有权
Current Owner = -1 表示无人持有。此时拖位置只改本端,不会同步到其他端。
默认 Auto Request Ownership 为关;调试时请先拿到所有权。
Inspector 组件右键(Play 且已进房):
- 申请所有权 — 无人占用时获取(
RequestOwnership(false)) - 强制申请所有权 — 抢占(
RequestOwnership(true)) - 立即同步 — 本端已是 Owner 时立刻推一帧(无所有权会失败)
成功后 Current Owner 应变为本端 LocalPlayerId(非 -1),再移动物体,对端才会跟着动。
// 场景物体:挂 NetworkedObject,进 Play 后自动注册(需有效 id)
// 仅所有者会根据位移 MarkDirty 并周期同步 Transform
// 代码申请所有权
netObj.RequestOwnership(force: false);
// 或
RoomMgr.Instance.RequestOwnership(objectId, force: false);
// 立即推一次(须已是 Owner)
netObj.SyncNow();
// 事件
objectSync.OnOwnershipChanged += (id, oldOwner, newOwner) => { };
objectSync.OnObjectSpawned += (id, go) => { };
objectSync.OnObjectDestroyed += id => { };
同步字段由组件勾选:位置 / 旋转 / 缩放;可选 customData。 远端使用插值缓冲回放(Handler 上可调 backTime、snap 距离等)。
Editor 双开测物体同步: 房间服务已启动 → 两端 Connected → Clone 改过 DeviceSn → 主端对物体右键申请所有权 → 确认 Owner ≠ -1 → 再拖 Transform。
10.3 运行时生成 / 销毁
// 注册预制体名(与 Spawn 时 goName / prefabName 对应)
RoomMgr.Instance.RegisterPrefab("MyProp", prefab);
uint id = RoomMgr.Instance.SpawnObject(
"MyProp",
pos, rot, scale,
customData: "{}" // UTF-8 字符串 → byte[]
);
// 销毁(需所有权)
objectSync.DestroyNetworkedObject(id);
11. 内容播放同步
ContentSyncHandler 用于视频/音频等播放状态跨端对齐
(Idle / Loading / Playing / Paused / Stopped / Finished)。
11.1 实现 IContentController
public class MyVideo : MonoBehaviour, IContentController
{
public void ApplySyncState(ContentSyncMsg msg) { /* 按 msg.State / PlaybackTime 驱动播放器 */ }
public float GetCurrentTime() => ...;
public float GetDuration() => ...;
public ContentState GetCurrentState() => ...;
}
11.2 注册与控制
var cs = FindObjectOfType<ContentSyncHandler>();
cs.RegisterContent("intro_video", myVideo);
cs.Play("intro_video", seekTime: 0f);
cs.Pause("intro_video");
cs.Stop("intro_video");
cs.Seek("intro_video", 12.5f);
cs.OnContentSynced += msg => { };
cs.OnContentStateChanged += (id, state) => { };
控制消息类型为 ContentControl;远端同步为 ContentSync。
示例实现见 samples:SimpleContentController。
12. 成员、位姿、时间同步
12.1 成员 MemberHandler
- 接收
RoomMembers列表(PlayerId + DeviceSn)。 - 匹配本地
BootSystem.DeviceSn后SetSelf,隐藏本端房间 Avatar 重复体。 - 为其他成员
AvatarMgr.GetOrCreateRoom并绑定 PlayerId。
业务侧一般监听成员变化即可;进房 Avatar 创建已由 Handler 串联。
12.2 位姿 TransformHandler
- 上报本地头显与双手(可选,受 Hand 跟踪开关影响)。
- 远端写入对应
AvatarMono;支持 pending 缓存(成员晚到时补刷)。
12.3 时间 TimeSyncHandler
RequestTimeSync()与服务器对齐,提供 RTT / offset 统计。- 用于需要统一时间轴的玩法(可与内容进度配合)。
12.4 心跳与成员状态
HeartbeatHandler:保活。MemberStatusHandler:Joined / Loading / Ready / Playing 等状态(见MemberState枚举)。
13. 体验区业务
门面组件:Slhx.Game.ToB.ExperienceAreaHandler
(Catalog 放置 + Switcher 切换 + Aligner 对齐 + Safety 出区)。
不含 Unity Scene 加载状态机;切场景请业务或 Samples 自行处理。
SwitchExperienceArea / 房间 area 事件使用的是
Boot placeItems[].id(与模板 areaId 一致,如 area_01),
不是 Unity 内容场景名。场景加载与体验区切换应分开处理(示例见 §14)。
13.1 数据从哪来
- 真机 / 管控:Boot 配置中
placeOperation.placeItems(id、位姿、缩放等)。 - 模板:Inspector 上
ExperienceAreaTemplate[](areaId + Prefab)。 - 硬约束:凡要落地的区,
placeItems[].id与模板areaId必须字符串一致。 ContentMode.Origin时可不实例化场地(原点模式)。
13.2 核心 API
var h = experienceAreaHandler; // 场景引用
h.LoadBoundaryPlace(); // 按 Boot 实例化全部区(只做一次)
h.SetContentRoot(contentRootGo); // 若需运行时指定内容根
// 唯一推荐切换入口(会清预览)
h.SwitchExperienceArea("area_01");
// 预览下一区边界(不改 Active)
h.ShowNextExperienceArea(loop: false);
h.ShowExperienceAreaPreview("area_02");
h.ClearExperienceAreaPreview();
// 切到顺序上的下一区
h.SwitchToNextExperienceArea();
// 查询
string cur = h.ActiveAreaId;
string next = h.GetNextExperienceAreaId();
// 事件
h.OnAreaChanged += e => { /* from/to */ };
h.OnPreviewChanged += e => { };
13.3 出区与安全表现
链路:Detecting → ExperienceAreaSafetyPresenter → 边界 Presentation / 相机 cullingMask。
出区线显示与「预览另一区」是不同概念:
- 出区线:当前 Active 区安全相关视觉
ShowExperienceAreaPreview:预览另一区边界,不切换 Active
13.4 Prefab 标记
使用 Marker 组件,不要依赖节点名:
| 组件 | 用途 |
|---|---|
ExperienceObstacleMarker 等 Detect | 识别 / 触发 |
| Near/Out/Obstacle 线 Marker | 边界视觉根 |
ExperienceBoundaryVisualMarker | 子视觉类型 |
ExperienceAreaItem | 实例上的区数据与表现转发 |
地图配置工具:Tools → Slhx → 地图体验区配置工具,可导入 placeOperation、预览线框、导出 BoundaryConfig。
14. 内容推进(Samples 协议)
RoomContentSceneFlow 通过 EventBroadcastHandler 做「客户端请求 → 服务确认 → 全房执行」。
本地加载由 ContentSceneDirectorSample 执行。
14.1 步骤表:场景名与体验区 id 分离
示例枚举 ContentSceneSearcher 使用 Framework 双标注
(勿把两套字符串绑成同一个):
| 步骤 | [Description] 场景名 |
[ExperienceAreaId] |
|---|---|---|
| 佩戴区 | Level_A | area_01 |
| 内容1 | Level_B | area_02 |
| 内容2 | Level_C | area_03 |
| 归还区 | (空,不加载场景) | area_04 |
- Boot / Handler 只认
area_*(placeItems.id 与模板 areaId),不要改成Level_*。 - Build Settings 内容场景与 Description 一致(示例为
Level_A/B/C)。 Director.GoTo:GetDescByEnum→ 加载场景;GetAreaIdByEnum→ 切体验区。- 房间
Request Area/ 事件 104·204 仍传 UTF-8 areaId(如area_02),不传场景名。 Level_*仅为 Sample 命名;接入工程可自定场景名,只要与 Description、Build Settings 对齐,且 areaId 仍对齐 Boot。
// 示意(Samples)
[Description("Level_A")]
[ExperienceAreaId("area_01")]
佩戴区 = 0,
// GoTo 内部分开:
// LoadScene(GetDescByEnum(step));
// SwitchExperienceArea(GetAreaIdByEnum(step));
14.2 房间事件
| 事件 | Id | 方向 | Payload |
|---|---|---|---|
| CArrivedNextContent | 101 | 客户端 → | uint 内容步骤(ContentSceneSearcher) |
| SConfirmNextContent | 201 | ← 服务确认 | 步骤,全房执行 GoTo |
| CArrivedNextExperienceArea | 104 | 客户端 → | UTF-8 areaId(非场景名) |
| SConfirmNextExperienceArea | 204 | ← 服务确认 | areaId,全房只切区 |
Offline 无房间时,示例会本地直接确认,便于单机调试。 业务若接真房间服务器,需在服务侧处理 C* 并广播 S*。 4 字节 uint 兼容路径按步骤解析为 ExperienceAreaId,不是 Description。
15. Avatar
- 默认 Catalog:
Resources/AvatarTemplate/DefaultAvatarCatalog - 自定义:创建 Catalog 资产,配置 avatarId + 带
AvatarMono的 Prefab,赋给AvatarMgr或SetCatalog - 进房后由 MemberHandler 创建/显隐房间 Avatar;大厅 Avatar 可淡出
- 远端位姿由 TransformHandler 驱动
AvatarMgr.Instance.SetCatalog(myCatalog);
16. Android 与管控
16.1 收:管控 → Unity
- 包内
ServiceCmdReceiver接收广播,回调到场景AndroidBridge.OnPcCommand(json) - 业务注册:
AndroidBridge.Instance.RegisterCommandReceiver(json => {
// 解析业务指令
});
16.2 发:Unity → 管控
using Slhx.Game.Net;
// 默认双发 agent + server
UnityToServiceNative.Send("{\"type\":\"yourCmd\"}");
// 仅某一管控包
UnityToServiceNative.SetBroadcastPackages("com.lanyuxujie.server");
UnityToServiceNative.Send(json);
// 恢复默认双包
UnityToServiceNative.SetBroadcastPackages(null);
| 方向 | Action | Extra |
|---|---|---|
| 管控 → Unity | …ACTION_PC_CMD | cmd_json(按 applicationId 投递) |
| Unity → 管控 | …ACTION_UNITY_MSG | unity_json |
AIDL 主通道规划在后续版本;当前 Runtime 为系统广播。
16.3 Boot 与权限
BootSystem:读 boot 配置、设备 SN、ContentMode、存储权限流程- GameObject 名保持
BootSystem/AndroidBridge便于原生回调
17. 配置与路径
# 真机运营 / boot
/sdcard/Download/lycj/boot/config.json
# Editor 唯一(Slhx 包内样例,不回退 BoundaryConfig)
Assets/Slhx/Samples/Map/config.json
# 内容热更外置(与 lycj 不同)
/sdcard/Download/{Application.identifier}/
Editor 下 ProductPaths.BootConfigPath 固定 为 Samples/Map;
工程根 BoundaryConfig 不在包内,运行时不读。
placeOperation.placeItems 含 id、index、name、pos/rot/sle 等,供体验区 Catalog 使用。
18. 常见问题
TriggerEvent 无反应?
检查是否 Connected;是否在接收端 Register 了同一 eventId;房间服务是否转发 EventBroadcast。
自定义消息收不到?
是否 RegisterChannel;可靠/不可靠类型是否与发送一致;channelId 是否一致。
物体不同步?
优先看 Inspector Current Owner:为 -1 时本端不发包。
Play 后对 NetworkedObject 右键 申请所有权(或强制申请),
待 Owner 变为本端 PlayerId 后再移动。
另查:有效 objectId(两端相同)是否进房 Connected、对端是否 Register 同一 id、Spawn 预制体名是否一致。
体验区 area not found?
LoadBoundaryPlace 是否已执行;Boot placeItems 与模板 areaId 是否匹配;Origin 模式是否故意不生成区。ParrelSync 双开时确认两端都能读到 Samples/Map/config.json。
编辑器如何测多人?
见 §5.4:安装并启动与 SDK 匹配的房间服务安装器,ParrelSync 双开,两端 Configure(局域网IP, 11001)。下载见 下载页。
Clone 工程的 DeviceSn 必然与主工程冲突——接入方必须修改 Clone 侧
BootSystem.testDeviceSn,与主工程不同后再 Play。
ParrelSync 双开后成员/Avatar 异常?
几乎都是 DeviceSn 相同(克隆场景默认共用 Test Device)。
在 Clone 的 BootSystem 上把 testDeviceSn 改成与主工程不同的唯一值后再 Play。
换 applicationId 后广播异常?
Java 桥固定 com.slhx.android。检查管控是否按应用包名投递,以及 Bridge 物体是否存在。
Package 解析失败,找不到 LiteNetLib / YooAsset?
检查 scopedRegistries、网络与代理;版本与 SDK package.json 对齐。
场景粉红、Shader 报错?
未使用 URP,或未在 Graphics / Quality 指定 URP Asset。见 §3.1。只装 URP 包不够。
Missing Script、交互/手部预制体丢失?
仅安装 XR Interaction Toolkit / XR Hands 包不够。
须在 Package Manager → 各包 Samples 页 Import
Starter Assets、Hands Interaction Demo、XR Device Simulator、Gestures、HandVisualizer(见 §3.3)。
勿删除 Assets/Samples/XR Interaction Toolkit 与 Assets/Samples/XR Hands。