开发文档 / SDK Guide
v1.0.2 v1.0.2

SDK 开发文档

面向内容 / 业务工程接入 Slhx 大空间内容端 SDK。 Framework 仅作简要说明;房间网络、事件、自定义消息、物体同步、体验区、内容推进、管控桥接等业务能力展开说明。 当前对应 v1.0.2,变更见 更新说明; 接口一览见 API 文档

1. 概述

Slhx SDK 以 UPM 交付:核心包 com.slhx.sdk(必装)、示例包 com.slhx.samples(可选)。业务玩法、关卡与品牌资源放在接入工程(推荐 Assets/Gameplay),通过公开 API 组合平台能力。

分层 只引用 SDK,不改包内源码。场景表、玩法状态机、业务事件号建议放在接入工程或参考 Samples 后自建。

2. 环境要求

要求
Unity2022.3.x LTS
设备Android XR(产线以 PICO 为主)
渲染 / 后端建议 URP;IL2CPP + ARM64
PICO Integration SDK接入工程自备,zip 不含
OpenUPMLiteNetLib 1.3.3、YooAsset 3.0.4(以 package.json 为准)
名称固定?说明
applicationId可变产品包名 com.xxx.xxx
Android 桥 Java 包固定com.slhx.android
lycj 目录固定/sdcard/Download/lycj

3. 安装 SDK

易漏三项(必读)
  1. URP 管线:须安装并在 Graphics/Quality 指定 URP Asset,否则粉红材质、Shader 报错。
  2. XR 官方 Samples:Package 装上 ≠ Sample 已导入。须在 Package Manager 对 XRI / XR Hands 点 Import,否则 Missing Script、预制体丢失。
  3. 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 报错。

  1. Package Manager 安装 Universal RP(Unity 2022.3 对应 14.x)。
  2. Create → Rendering → URP Asset (with Universal Renderer)(或使用工程内已有 URP 资源)。
  3. Edit → Project Settings → Graphics:指定 Scriptable Render Pipeline Settings = 上述 URP Asset。
  4. Project Settings → Quality:各等级 Render Pipeline Asset 一并指定(勿留空)。
  5. 打开场景确认无大面积粉红、无大量 unsupported shader 报错。
注意 只安装 URP 包、不在 Graphics/Quality 里指定 Asset,渲染仍走 Built-in。须在本工程内创建并指定 URP Asset。

3.2 安装 Slhx 包

  1. 解压 slhx-upm-{version}-release.zip
  2. com.slhx.sdk(及可选 com.slhx.samples)拷入工程 Packages/
  3. 配置 OpenUPM scopedRegistry(LiteNetLib、YooAsset)
  4. 导入 PICO Unity Integration SDK,XR Plug-in Management 启用 PICO
  5. 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/
版本 Sample 目录版本须与包版本一致;升级 XRI/Hands 后请重新 Import。 Hands Interaction Demo 依赖 XR Hands、Shader Graph 等,按 Package Manager 提示满足依赖。 勿随意删除或移动 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 全错。

  1. 菜单 Tools → Slhx → 安装/修复 Layers 与 Tags(只写 SDK 约定槽位,其它层不动)
  2. 若提示槽位冲突,确认后强制覆盖,或手工改到约定索引
  3. 可用 Tools → Slhx → 检查 Layers 与 Tags 验收
IndexName用途(简述)
26Overlay叠加层
27Underlay底层
28Boundary体验区内边界
29OutBoundary区外/出区表现
30Avatar角色 / 示例内容区

Tags:XRSetupContentAreaContentScenePortal。其余 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.5ipconfig 查看);主工程与 Clone 填同一地址
端口 11001 与房间调试服务约定一致
// 将 192.168.x.x 换成你的本机局域网 IP
RoomNetClient.Instance.Configure("192.168.x.x", 11001);
RoomNetClient.Instance.Connect();

步骤

  1. 安装并启动房间服务
    下载页 获取与当前 SDK 匹配的安装器 → 安装 → 启动房间服务(默认监听 11001,以安装包说明为准)。
  2. 主工程(Player A)
    打开联调场景;进房前 Configure(局域网IP, 11001) 后 Play。
  3. Clone(Player B)——必须改 DeviceSn
    ParrelSync → Clones Manager → Create / Open in New Editor → 同场景、同样 Configure(局域网IP, 11001)
    【接入方必改】DeviceSn 必然冲突 ParrelSync 克隆的是同一场景资源,主工程与 Clone 默认共用 Editor 设备号(BootSystem.testDeviceSn, 常见默认 Test Device)——肯定会冲突。 房间成员身份依赖 DeviceSn 唯一;不改则两客户端被当成同一设备, 出现成员顶替、本端/远端 Avatar 错乱、同步异常等。

    接入方必须在 Clone 窗口选中带 BootSystem 的物体, 将 Inspector 中 Test Device Sn 改为与主工程 不同的字符串(如 Clone-B / Editor-2), 再进 Play。主工程保持原值即可。
    改完 DeviceSn 后再 Play。
  4. 验证:两端 Connected、成员列表为两个不同 DeviceSn;再测事件广播、体验区、物体同步等。

6. 模块能力

6.1 Framework(简要)

命名空间 Framework.* / 程序集 Slhx.Framework。 提供服务定位、事件总线、配置/存档、场景服务、输入、定时器、下载、日志、 等基础设施。

入口(须在 App 入口 Awake 中调用一次):

Framework.Core.FrameworkBootstrap.InitFramework();

InitFramework 会向 ServiceLocator 注册包括下列服务(节选):

接口实现说明
ILogServiceDebugLogService日志
IEventBusEventDispatcher事件总线
ICoroutineRunnerCoroutineRunner协程
ITimeService / ITimerServiceUnity 时间 / Timer时间与定时
IResourceServiceResources 实现资源
ISceneServiceSceneService场景
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、生成物体、断线清理
事件广播EventBroadcastHandlerushort 事件 + payload,业务推进首选
自定义消息CustomMessageHandler频道 channelId 可靠/不可靠字节流
物体同步ObjectSyncHandler / NetworkedObjectTransform、所有权、生成销毁
内容播放同步ContentSyncHandler / IContentController多媒体播控状态
成员 / 位姿MemberHandler / TransformHandler房间成员与头手位姿
体验区ExperienceAreaHandler场地加载、切换、预览、出区
Boot / 路径BootSystem / ProductPaths配置、设备 SN、ContentMode
管控AndroidBridge / UnityToServiceNative收指令 / 回传 JSON
AvatarAvatarMgr / 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 / RoomMembers1–3进房与成员列表
MemberStatus / Heartbeat / MemberTimeout4–6成员状态与保活
PlayerTransform10头 + 双手位姿
AnimationSync / HandGesture / HandPose11–13动画与手势
ObjectSync / Ownership / Spawn / Destroy20–24网络物体
TimeSync30–31时钟对齐
ContentSync / ContentControl40–41媒体内容播控
EventBroadcast / EventTrigger50–51业务事件广播
CustomReliable / CustomUnreliable60–61自定义频道消息
选型建议 跨端「做了什么事」(切场景、切体验区、玩法阶段)→ 用 事件广播(固定 eventId + 小 payload)。 自建二进制/高频状态 → 用 自定义频道。 场景里可交互道具位姿 → 用 NetworkedObject

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 约定与注意

  • eventIdushort。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 与事件广播的区别

事件广播自定义频道
标识eventIdchannelId
语义离散业务事件任意字节流管道
可靠性可靠有序可选可靠 / 不可靠
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.SpawnObjectNetworkedObjectId.AllocateRuntimeId + 网络 Spawn
  • 全房对同一逻辑物体必须同一 id(静态靠同场景资源;动态靠 Spawn 消息)。
  • 无效 id 不会正常注册。

10.2 同步与所有权

只有 Owner 才会发 Transform 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);
流程 本地 Spawn → 绑定 objectId → 注册 → 发 ObjectSpawn → 其他端实例化并 BindObjectId。 各端预制体注册表名称必须一致。

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.DeviceSnSetSelf,隐藏本端房间 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 自行处理。

切区 id ≠ 内容场景名 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_Aarea_01
内容1Level_Barea_02
内容2Level_Carea_03
归还区(空,不加载场景)area_04
  • Boot / Handler 只认 area_*(placeItems.id 与模板 areaId),不要改成 Level_*
  • Build Settings 内容场景与 Description 一致(示例为 Level_A/B/C)。
  • Director.GoToGetDescByEnum → 加载场景;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
CArrivedNextContent101客户端 →uint 内容步骤(ContentSceneSearcher)
SConfirmNextContent201← 服务确认步骤,全房执行 GoTo
CArrivedNextExperienceArea104客户端 →UTF-8 areaId(非场景名)
SConfirmNextExperienceArea204← 服务确认areaId,全房只切区

Offline 无房间时,示例会本地直接确认,便于单机调试。 业务若接真房间服务器,需在服务侧处理 C* 并广播 S*。 4 字节 uint 兼容路径按步骤解析为 ExperienceAreaId,不是 Description。

15. Avatar

  • 默认 Catalog:Resources/AvatarTemplate/DefaultAvatarCatalog
  • 自定义:创建 Catalog 资产,配置 avatarId + 带 AvatarMono 的 Prefab,赋给 AvatarMgrSetCatalog
  • 进房后由 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);
方向ActionExtra
管控 → Unity…ACTION_PC_CMDcmd_json(按 applicationId 投递)
Unity → 管控…ACTION_UNITY_MSGunity_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)。 在 CloneBootSystem 上把 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 → 各包 SamplesImport Starter Assets、Hands Interaction Demo、XR Device Simulator、Gestures、HandVisualizer(见 §3.3)。 勿删除 Assets/Samples/XR Interaction ToolkitAssets/Samples/XR Hands