绿洲启元、Roblox 与 UGC1.0/2.0:封装粒度与脚本运行时
这组产品不能只按“开放程度”排成一条直线。真正决定创作能力、脚本运行方式和平台成本的,是平台把什么对象交给创作者、把什么能力封装成稳定协议,以及作品最终由什么运行时解释。
本文从封装粒度出发,对比绿洲启元、Roblox、UGC1.0 和 UGC2.0。绿洲启元与 Roblox 的结论来自公开官方文档;UGC1.0/2.0 来自内部事实底座和源码复盘;无法从现有证据确认的实现细节会明确标成架构推断。
一、先给结论:四种系统开放的不是同一层
| 系统 | 主要开放层级 | 创作者直接操作的核心对象 | 脚本主要附着对象 | 底层承载 |
|---|---|---|---|---|
| 绿洲启元 | UE 资产与类型层 | Blueprint、Actor、Component、DataAsset、关卡资源 | Actor/可编程蓝图与模式逻辑 | UE Actor/Component、DS 与 Client |
| Roblox | 平台对象层 | DataModel 中的 Instance、Service、Package | Script、LocalScript、ModuleScript | Roblox Server/Client Runtime |
| UGC1.0 | 业务元件与功能层 | 官方注册的元件、属性、事件和工具能力 | 元件事件、Coding 脚本、平台 API | 平台工厂生成的 UE Actor/Component |
| UGC2.0 | 自定义对象平台层 | Instance 树、Class、Service、Prefab、Script | Edit/Client/Server 三类 Luau 运行域 | 自研对象运行时,经绑定层映射到 UE |
最简洁的判断是:
绿洲启元开放的是一部分 UE 原生创作语义;Roblox 开放的是统一的平台对象语义;UGC1.0 开放的是官方定义好的玩法元件;UGC2.0 则试图在 UE 之上建立自己的 Instance 与脚本平台。
这不是简单的高低关系。绿洲启元在 UE 资产和 Actor/Component 组合上更接近引擎层;Roblox 和 UGC2.0 在对象模型一致性、跨端脚本语义和平台治理上更接近完整平台;UGC1.0 的封装最厚,但确定性、审核与快速交付也最容易控制。
二、比较封装粒度,要拆成四层
“使用 UEEditor”或“基于 UEClient”只能描述宿主,不能单独说明创作者能力。更可靠的比较方式是逐层追问。
| 层次 | 要回答的问题 |
|---|---|
| 创作与存储层 | 创作者保存的是 .uasset、自定义 Schema、Instance 树,还是平台制品? |
| 对象构造层 | 创作者能否创建新对象、挂组件、定义类,还是只能实例化官方元件? |
| 行为与 API 层 | 脚本附着在哪里?生命周期、属性、事件和服务由谁定义? |
| 执行与权威层 | 脚本在哪个 VM、哪个进程、哪个网络角色中运行?怎样同步和发布? |
flowchart LR
subgraph Oasis[绿洲启元]
O1[定制 UEEditor] --> O2[uasset / Blueprint]
O2 --> O3[Actor + Component]
O3 --> O4[Lua / ECA]
O4 --> O5[UE DS / Client]
end
subgraph Roblox[Roblox]
R1[Studio] --> R2[DataModel]
R2 --> R3[Instance / Service]
R3 --> R4[Luau Script]
R4 --> R5[Roblox Server / Client]
end
subgraph UGC1[UGC1.0]
U11[UE Client 编辑器] --> U12[元件 Schema]
U12 --> U13[官方元件实例]
U13 --> U14[事件 / Coding]
U14 --> U15[UE Client / DS]
end
subgraph UGC2[UGC2.0]
U21[UE Client + PixUI] --> U22[自研 DataModel]
U22 --> U23[Instance / Class / Service]
U23 --> U24[Edit / Client / Server VM]
U24 --> U25[绑定层 + UE World]
end三、绿洲启元:开放 UE 资产语义,但不是完整裸露 UE
3.1 创作者能做什么
公开文档能确认,绿洲启元的创作环境建立在 UE4 编辑器工作流上。创作者可以在内容浏览器中管理资源,创建蓝图类和 DataAsset,在 Actor 上组合 Component,并通过动画蓝图、关卡对象和 GamePart 组织内容。其 UAsset Browser 也直接以 .uasset 为资产单位。
这意味着它不是“只能拖官方玩法元件”的纯业务编辑器。创作者接触到的是 UE 的资产、类型、继承和组件组合概念,产物与 UE 的资源系统紧密耦合。
但要避免说成“完整开放 UEEditor”。更准确的表述是:
绿洲启元提供的是经过定制、白名单化和平台 API 约束的 UEEditor 创作面。创作者能使用一部分 UE 原生范式,但原生类型、接口、Cook、发布和运行权限仍由平台控制。
能创建 Actor 或挂 Component,不等于可以任意接入 C++ 模块、修改引擎、加载任意插件或绕过平台发布链。
3.2 脚本怎样运行
绿洲启元同时提供模式编辑器的 ECA 逻辑与 Lua 可编程能力。可编程蓝图会关联脚本文件,脚本通过 BeginPlay、Tick、EndPlay 等生命周期进入 Actor 运行过程。
Blueprint / uasset
↓ 实例化
UE Actor + Components
↓ 绑定
Lua 对象 / ECA 逻辑
↓ 生命周期回调
BeginPlay → Tick / Event → EndPlay
↓
DS 权威逻辑、Client 表现与 UI它的关键特点是:脚本身份天然靠近 UE 对象身份。脚本经常围绕“这个 Actor 实例发生了什么”工作;网络语义也延续 UE 的 DS、Actor 属性复制和 RPC 模型。UI 则主要运行在客户端。
3.3 这种封装的收益与代价
- 收益:UE 开发者迁移成本低,资产表达力强,能复用 Actor、Component、Blueprint、Animation 等成熟工具链。
- 代价:作品与引擎版本、资产格式、Cook 和类型白名单的耦合更强;安全与兼容治理必须深入资产加载和引擎对象层。
- 扩展边界:创作者能自由组合已开放的 UE 类型;一旦缺少新的原生组件或底层能力,仍需要平台侧补 C++ 类型、API 和发布支持。
四、Roblox:统一 DataModel,而不是把底层引擎交给创作者
4.1 创作者操作的是 Instance 世界
Roblox Studio 暴露的核心不是底层引擎对象,而是一个统一的 DataModel。场景对象、逻辑对象、服务、UI 和脚本都以 Instance 进入同一棵树;属性、父子关系、事件和引用遵循一致规则。
因此,Roblox 的可组合性很强,但这种能力来自平台对象模型的完整性,而不是“更接近原生引擎”。创作者不能越过 DataModel 直接操作 Roblox 内部使用的物理、渲染或网络实现。
Package 进一步让一棵对象子树成为可复用、可更新的内容单位。复用的核心仍是 Instance 图,而不是某个底层引擎的原生资产。
4.2 脚本怎样运行
Roblox 的 Script、LocalScript 和 ModuleScript 本身也是 DataModel 中的对象。脚本能否运行、在哪一侧运行,与脚本类型、RunContext 和它在对象树中的位置有关。
Studio 中编辑 DataModel
↓ Publish / Start
Server DataModel ─────复制边界───── Client DataModel
↓ ↓
Server Script LocalScript
└──────── Remote ─────────────┘
ModuleScript 复用逻辑服务端拥有最终权威,客户端负责输入和表现;跨边界通信需要 Remote,并且服务端必须把客户端输入当成不可信数据验证。这里的网络与安全模型是平台语义,而不是要求创作者直接理解底层引擎的 Actor Replication。
4.3 这种封装的收益与代价
- 收益:编辑态和运行态共享同一种对象语言,脚本、场景、UI、服务和复用单位能统一组织。
- 代价:创作者只能使用平台已经抽象出的 Instance 类与 Service,无法触达底层实现。
- 扩展边界:大多数玩法可以在 Luau 和现有 Instance 上组合;真正新增底层能力时,需要 Roblox 平台增加新类或 Service。
五、UGC1.0:官方元件是最主要的能力交付单位
5.1 “封装好了 Actor”是什么意思
UGC1.0 的主要创作单位是平台注册的元件,而不是任意 UE Actor 类。一个新元件通常不只是一个运行时类,还包括:
- 类型注册与可序列化 Schema;
- 编辑器菜单、图标、属性面板和摆放规则;
- 客户端或 DS 中的 Actor/Component 工厂;
- 事件、行为、网络同步与校验规则;
- 版本兼容、迁移和发布审核。
所以“创作者放了一个元件”背后,是平台把一组 UE 实现封装成稳定的业务契约。创作者通常修改暴露属性、连接事件、选择预设行为,而不直接决定 Actor 的内部组件结构。
元件类型 + 属性数据
↓ Schema 校验
元件注册表 / Runtime Factory
↓
官方 Actor / Component 实例
↓
平台事件、行为与网络规则5.2 脚本并非绝对不存在
把 UGC1.0 说成“完全没有脚本”不准确。更稳妥的结论是:早期和主路径以官方元件、属性配置与事件连接为中心,后续也存在 Coding 或脚本能力;但脚本面对的仍主要是平台已经开放的元件和 API,而不是完整 UE 类型系统。
其脚本能力的上限由平台契约决定:有哪些元件、哪些属性可写、哪些事件可订阅、哪些行为可调用,以及某个调用由 Client 还是 DS 执行。网络语义常被封在元件实现里,创作者得到确定性,代价是扩展新玩法原语要等待平台研发。
5.3 为什么扩展成本高
在这种模型里,“新增一个玩法对象”经常是一条纵向需求:编辑器、序列化、运行时、网络、迁移和审核都要一起改。每增加一种官方元件,平台都要维护一份新的专用契约。
这不是单纯的技术落后,而是一种产品取舍:它用较厚的封装换取低门槛、移动端可控性和内容治理效率,适合能力边界明确、模板化程度高的创作阶段。
六、UGC2.0:在 UE 之上建立自己的对象与脚本平台
6.1 从“元件集合”转向“对象模型”
UGC2.0 的关键变化不只是把编辑器 UI 换成 PixUI,也不只是引入 Luau。更本质的变化是建立统一的 DataModel/Instance 抽象:对象具有稳定身份、父子关系、属性、Signal、Class 与 Service,编辑器和运行时围绕这套模型协作。
这里必须区分 Instance 与 Actor:
- Instance 是平台对象,可表示场景对象、Folder、Script、Service、UI 或纯逻辑节点。
- Actor/Component 是 UE World 中的物理承载,只在对象确实需要场景、渲染、碰撞或网络实体时出现。
- 因此不能把每个 Instance 简化为一个 Actor。更合理的架构是按类型和运行域选择是否物化,以及物化成什么 UE 对象。
从现有源码复盘可以确认 UGC2.0 存在对象模型、属性与信号、Service、跨语言绑定和多 VM 边界;“某个具体 Instance 类如何映射为某个 Actor 类”仍应以对应源码为准,不能凭概念图代替实现证据。
6.2 三个 VM 解决的是执行域,不只是性能
UGC2.0 将脚本分到 Edit、Client 和 Server 运行域:
| VM | 主要职责 | 生命周期关注点 |
|---|---|---|
| Edit VM | 编辑器插件、创作工具、属性操作和编辑态逻辑 | 页面刷新、撤销重做、试玩前冻结 |
| Client VM | 输入、UI、镜头、本地表现和客户端逻辑 | 进出玩法、重连、对象流入流出 |
| Server VM | 权威玩法、校验、状态推进和服务端逻辑 | DS 会话、玩家加入离开、异常回收 |
编辑态 Instance 树 + Source
↓ 校验、依赖收集、冻结
Versioned Artifact
├── Client 加载 → Client VM → Client Services
└── Server 加载 → Server VM → Server Services
需要进入场景的 Instance
↓ C++ Binding / Adapter
UE Actor / Component / UObject这种结构要求跨 VM 只传稳定 ID、可序列化纯值、版本号和明确消息,不能把 VM 内对象、闭包或裸 C++ 指针直接跨域共享。试玩切换也不是“继续使用编辑器内存”:应先冻结确定版本,再分别启动 Client/Server 运行态;退出试玩后销毁运行实例并重建或恢复 Edit VM。
6.3 它相对 UGC1.0 改变了什么
UGC1.0 以“平台交付更多元件”扩展能力;UGC2.0 更希望以“少量稳定原语 + Class/Service/Script 组合”扩展能力。平台仍然需要提供底层能力,但不必为每个玩法概念都开发一整套专用元件。
这让创作者更容易定义自己的 Prefab、类和行为,也把复杂度转移到了平台基础设施:对象身份、Schema 演进、脚本沙箱、VM 隔离、制品确定性、网络边界和调试工具必须做得更扎实。
七、六个维度的横向对比
| 维度 | 绿洲启元 | Roblox | UGC1.0 | UGC2.0 |
|---|---|---|---|---|
| 数据模型 | UE 资产、UObject、Actor/Component 语义 | 统一 DataModel/Instance 树 | 官方元件类型与属性 Schema | 自研 DataModel/Instance/Class/Service |
| 编辑器 | 定制 UEEditor,保留较多 UE 工作流 | Studio,围绕对象树和属性组织 | UE Client 内嵌业务编辑器 | UE Client 承载,PixUI/React 等构建工具界面 |
| 脚本 | Lua 与 ECA,靠近 Actor 生命周期 | Luau,Script 本身属于对象树 | 元件事件/行为为主,后续 Coding 面向平台 API | Luau,Edit/Client/Server VM 明确隔离 |
| 运行拓扑 | UE DS/Client,Actor 复制与 RPC | 平台 Server/Client,Remote 跨界 | Client/DS,由官方元件实现决定权威与同步 | Client/Server VM + Service,经绑定层进入 UE World |
| 交付物 | 与 UE 资产、版本、Cook 链紧密相关 | 发布后的 Place/Experience 与 Packages | 元件数据、地图和平台定义的作品格式 | 版本化 Artifact,包含对象树、脚本和依赖 |
| 治理方式 | 类型/API 白名单、资产检查、Cook 和发布审核 | 沙箱、权限、服务端校验、平台发布 | 元件白名单、属性限制、官方行为和内容审核 | Class/Service 能力面、VM 沙箱、Schema/制品验证 |
再从平台扩展成本看:
| 需求 | 绿洲启元 | Roblox | UGC1.0 | UGC2.0 |
|---|---|---|---|---|
| 用现有能力组合新玩法 | 组合 Blueprint、Actor、Component 和 Lua | 组合 Instance、Service 和 Luau | 组合已有元件、属性和事件 | 组合 Instance、Class、Service、Prefab 和 Luau |
| 新增底层原语 | 新 UE 类型/API,并纳入白名单和发布链 | 平台新增 Instance 类或 Service | 新元件全链路接入,通常成本最高 | 新增原生 Class/Service/Binding,供多种上层玩法复用 |
| 创作者自由度 | 资产与类型组合较强 | 平台对象和脚本组合很强 | 受官方元件集合限制 | 目标是接近平台对象级可组合性 |
| 平台治理难度 | 深入 UE 资产和原生对象层 | 由统一沙箱与平台运行时控制 | 最容易形成强约束 | 比 1.0 更难,需要基础设施兜住开放性 |
八、为什么封装粒度会改变脚本运行方式
8.1 脚本绑定的身份不同
- 绿洲启元:脚本通常绑定 Blueprint/Actor 实例,身份来自 UE 对象。
- Roblox:脚本本身是 Instance,位置、类型和 RunContext 决定运行环境。
- UGC1.0:逻辑依附于官方元件、事件或平台 Coding 容器,身份由元件系统分配。
- UGC2.0:脚本和业务对象属于自研 Instance 图,VM 根据运行域加载对应制品。
身份模型会继续影响引用保存、对象销毁、热更、调试堆栈和跨端同步。只比较 Lua 或 Luau 语言本身,无法解释这些差异。
8.2 生命周期来源不同
- 绿洲启元继承 Actor 生命周期,常见入口是
BeginPlay/Tick/EndPlay和 UE 事件。 - Roblox 由 DataModel 装载和脚本上下文启动,逻辑围绕 Instance、Service 和事件运行。
- UGC1.0 由元件工厂与官方行为驱动,创作者拿到的是封装后的创建、触发、交互和销毁语义。
- UGC2.0 需要自行定义 Instance、Service、VM、试玩会话和物化对象的完整生命周期。
8.3 网络权威暴露程度不同
绿洲启元较直接地暴露 DS、Actor 属性同步和 RPC 概念;Roblox 将它抽象成服务端/客户端容器、复制边界和 Remote;UGC1.0 往往把网络策略封装进官方元件;UGC2.0 则需要在自研对象层定义哪些属性复制、哪些 Signal 跨域,以及它们如何映射到 UE 网络层。
因此,从 UGC1.0 演进到 UGC2.0 后,脚本自由度上升的同时,平台必须重新回答三件事:
- 谁是状态权威,客户端输入如何校验;
- 对象身份和引用怎样跨 Client/Server 保持稳定;
- 复制的是状态、事件,还是可重放的命令。
8.4 编辑态与运行态关系不同
绿洲启元依赖 UE 资产与 PIE/运行流程;Roblox Studio 将同一 DataModel 转入模拟的 Server/Client 会话;UGC1.0 通常由编辑模式进入平台定义的试玩流程;UGC2.0 则必须显式处理 Edit VM 到 Client/Server VM 的制品边界。
这也解释了为什么 UGC2.0 不能只做“在编辑器里直接执行当前脚本”:没有冻结版本、依赖收集和运行域隔离,就难以保证本地试玩、DS、发布版本和线上回放使用同一份内容。
九、源码复盘:四条端到端实现链
上一轮对比回答了“创作者面对什么”,但面试官继续追问时,必须能够把一个作品从编辑器一路讲到线上运行。最有效的做法,是始终沿着同一条链分析:
创作源数据
→ 类型注册与对象身份
→ 实例化与场景物化
→ 行为调度与脚本生命周期
→ Client/Server 权威与复制
→ 保存、构建与发布
→ 版本兼容、诊断与治理四套方案的本质差别,是这条链由谁定义、在哪一层闭环,以及增加一种能力时谁承担改动成本。
9.1 绿洲启元:以 UE 类型、资产和 Gameplay Framework 为主链
公开资料能支持的实现链是:
Blueprint / DataAsset / uasset
→ UE 类型系统与平台白名单
→ Actor / Component 在关卡中实例化
→ ECA 或 Lua 接收 BeginPlay、Tick、事件
→ GameMode / GameState / PlayerController 等划分职责
→ DS 权威运行,Actor 属性复制与 RPC 同步
→ 资产检查、Cook 与平台发布这条路线的“源数据”和“运行对象”都贴近 UE。Blueprint 既描述类型,也能承载组件结构和行为;关卡实例进入 World 后,生命周期、网络角色和对象引用继续使用 UE 语义。ECA、Lua 和 GamePart 是在这条主链上补充更低门槛的逻辑表达与模块复用,而不是另起一套完全独立的对象世界。
它最值得强调的三个后果是:
- 资产表达力强,但引擎耦合深。 类型加载、引用修复、Cook、版本升级和白名单都进入平台治理范围。
- 脚本容易找到宿主。 Lua 生命周期靠近 Actor;服务端权威、客户端表现也能沿 UE Gameplay Framework 解释。
- 新增底层能力仍由平台交付。 创作者能自由组合已开放类型,但不能凭 Lua 创造一个引擎从未暴露的物理、渲染或网络原语。
公开文档没有披露 ECA 的内部图格式、编译方式和完整制品结构,因此这些内容不能当成事实回答。面试时应主动说清:这里能确定的是公开对象语义和运行分工,不能确定的是平台私有构建实现。
9.2 Roblox:DataModel 同时承担创作、运行与复制语义
Roblox 的完整链路可以表述为:
Studio 编辑 DataModel
→ Instance 类、属性、父子关系和 Service
→ Publish 后生成服务端运行 DataModel
→ Script 按位置与 RunContext 启动
→ 服务端状态持续复制到各客户端 DataModel
→ RemoteEvent / RemoteFunction 传递跨边界意图
→ Streaming 按空间裁剪客户端可见子集
→ PackageLink 管理可复用子树的版本与更新这里最重要的不是“Roblox 也有树”,而是同一种 Instance 语义连续贯穿 Studio、Server 和 Client。脚本也是 Instance,Package 也是一棵 Instance 子树,复用、版本和运行位置都没有跳出 DataModel。
网络层要区分两类东西:
- 状态复制:服务端 DataModel 的对象和属性由平台向客户端同步,Streaming 可以让客户端只持有世界的一部分。
- 行为通信:客户端意图、一次性通知和请求响应通过 Remote 显式跨界;服务端仍须做类型、上下文、权限、数值和频率校验。
Package 也不只是“Prefab 文件”。它保存一个 Instance 子树与包链接,支持版本更新、嵌套包和实例级属性覆盖;所以它解决的是可演进复用,而不仅是复制粘贴。
这套模型的代价是平台必须把任何能力先表达成 Instance、Service 或安全 API。创作者获得的是高度一致的组合语言,而不是底层引擎访问权。
9.3 UGC1.0:一个元件是一条纵向产品契约
源码复盘后,UGC1.0 不能只概括成“元件 + 事件”。一个可用元件实际上贯穿了以下链路:
类型 ID 与投放配置
→ bpPath、属性映射、缩放/绑定/显隐等规则
→ Actor/Component 工厂创建运行实例
→ 专用属性组件解释编辑数据
→ Signal Item 描述发送方、接收方、属性和步骤
→ 各业务域 Signal Modifier 完成适配
→ 场景、Coding、UI、资源分别保存并由流程编排
→ 地图包与资源包校验、压缩、发布注册表源码说明,元件条目不只保存显示名称,而会关联运行类路径、属性映射、尺寸限制、事件显隐能力、分包信息、绑定限制、版本、下架与白名单规则。运行时还需要从 Actor 类反查元件类型。也就是说,元件 ID 是编辑器、持久化数据和 UE 实例之间的协议键。
属性层存在大量面向具体能力的属性组件;信号层不仅保存“事件名称”,还保存模块类型、场景对象、子类型、步骤、组件、属性、发送/接收方向和值。不同业务域又各自提供 Signal Modifier。新增一个复杂领域后,平台通常要同时增加属性解释、信号适配和运行时执行代码。
保存链路同样说明 UGC1.0 不是一棵统一对象树直接序列化:场景层、脚本/Coding、UI、玩法设置和资源存在不同数据域,由保存管理器收集、校验、落盘,再分别打成地图包与资源包。加载也按地图和图层分阶段恢复。因此它的核心工程问题是跨域一致性:对象删了,信号引用、Coding 引用和资源引用是否一起失效;旧版本字段怎样补默认值;中途失败怎样清理半加载状态。
Coding 加入后又形成一套脚本通信链。现有实现包含调用序列化、批处理、包大小限制、压缩以及 Client→Server、Server→Client/广播路径。这说明脚本没有自动消除旧元件体系,而是作为第二套逻辑运行与通信机制叠加在上面。
所以 UGC1.0 的架构判断应是:
它把平台复杂度分摊在每一种元件的纵向链路里。创作者使用简单,但平台新增一种能力,要同时维护注册、属性、编辑、运行、信号、网络、保存和兼容。
9.4 UGC2.0:把复杂度集中到通用对象内核
UGC2.0 的源码链路更接近一个自建平台运行时:
Class 元数据与类注册
→ ObjectManager 创建 Lua Instance 或原生 Instance
→ ObjectId / UniqueId、Parent、属性、Signal 形成对象图
→ 需要场景能力的对象经 Binding 物化为 UE 对象
→ Edit / Client / Server VM 按角色装载
→ 构造复制发布对象存在与拓扑
→ 属性脏标记发布增量状态,RemoteEvent 传递行为消息
→ SceneSerializer 保存对象图
→ Snapshot → Build → Verify → Publish 生成 Artifact对象内核
Instance 不只是一个字段表。统一的 SetParent 路径会检查对象是否销毁、父对象是否合法、是否形成环、允许的父类型以及同父级单例冲突,并按顺序发出 Child、Descendant 和 Ancestry 信号。对象树因此是受约束的运行时拓扑,而不是 UI 面板临时维护的目录。
Class 的属性描述符还承载只读、服务端可写、是否复制、类型检查、getter/setter 和编辑器元数据。属性写入会驱动变更信号和复制脏标记。这里体现了一个关键设计:属性不是裸 Lua table 字段,而是编辑、脚本、网络和反射共同依赖的协议。
ObjectManager 同时管理纯 Lua 类和 C++ 原生对象。其创建顺序会影响网络绑定、Parent 建立和 OnInit;源码中甚至记录过构造初值没有标脏,导致客户端镜像仍保留默认值的问题。这个案例说明对象构造与复制不能各自设计,必须共享初始化语义。
网络内核
UGC2.0 将“对象存在”与“对象状态”拆成两类协议:
- 构造复制负责创建、销毁、父子拓扑、可靠发布以及晚加入玩家的初始快照。
- 属性复制根据 Class 描述符和脏标记批量发送增量状态,并处理 Owner、全局对象和优先级。
- RemoteEvent承载显式行为消息,区分服务端事件、客户端事件、单播与广播,并校验参数可序列化和调用角色。
这比“把属性通过 RPC 发过去”更完整。晚加入客户端必须先得到一份自洽的对象图,之后才能消费属性增量和事件;否则事件可能先于对象到达,Parent 或对象引用也可能悬空。
还要纠正一个容易答错的点:源码里的 ObjectId 是进程内递增 ID,不应直接宣称它在 Server 和 Client 间天然全局稳定。跨进程引用必须依赖复制协议可重建的标识或路径语义;持久化身份则由另一套稳定 ID 承担。把进程内句柄、网络引用和持久化 GUID 混成一个 ID,会埋下复用、重连和反序列化错误。
序列化与制品
SceneSerializer 保存的不只是属性 JSON,还要记录格式版本、根对象、Class、Parent、属性引用和官方 Prefab 根,并在反序列化时处理目标 VM、延迟引用、父子拓扑和原生对象物化。系统同时保留 JSON 与紧凑二进制路径,并为结构性修改提供可提交或回滚的文件事务。
发布链进一步把“编辑器当前内存”冻结成 Snapshot,再构建 Artifact。Manifest 记录源数据摘要、构建器指纹、处理器集合摘要、制品摘要和包校验值;发布前还能独立复验完成的构建结果,并对中断上传做恢复。这样本地试玩、DS 和线上版本才能指向同一份可追溯输入。
所以 UGC2.0 的架构判断应是:
它没有消灭复杂度,而是把原来散落在每个元件里的对象、属性、网络、保存和发布共性集中进平台内核。上层新增玩法的边际成本下降,代价是底座错误的影响面更大。
十、同一个机关玩法,在四套系统里分别怎么做
用“两个玩家同时踩住两个压力板,门才打开;任一玩家离开后关闭”做统一案例,可以避免对比停留在抽象名词。
10.1 绿洲启元
- 用 Blueprint/Actor 表达压力板和门,以碰撞组件感知进入离开。
- 用 ECA 连接条件与动作,或用 Lua 在 Actor 生命周期和事件中维护占用状态。
- 两名玩家是否满足条件应由 DS 权威判断,门的开关状态通过 Actor 属性复制;客户端只做动画、音效和提示。
- 若做成 GamePart,可以把压力板、门、配置和连接关系封成可复用模块。
关键点:玩法组合直接借助 UE 对象、组件、生命周期和网络语义。
10.2 Roblox
- 用一个 Model 组织两块 Part 和 Door,通过 Attribute、Tag 或子对象保存配置。
- 服务端 ModuleScript/Script 监听接触或区域事件,按 Player 去重并计算两个压力板是否同时满足条件。
- 服务端修改门的权威属性或 Transform,平台把状态复制到客户端;Remote 只用于客户端输入、UI 或非权威表现,不用来代替状态所有权。
- 将整棵 Model 子树发布成 Package,保留实例级配置覆盖,同时接受包版本更新。
关键点:场景、脚本、配置和复用单位都仍是 DataModel 中的 Instance 图。
10.3 UGC1.0
- 创作者放置平台提供的触发器、计数/逻辑元件和门元件。
- 两个触发器发出进入、离开 Signal,逻辑元件聚合条件,再调用门的开启与关闭行为。
- 门是否由 DS 判定、哪些属性复制,主要由官方元件实现预先决定。
- 如果平台没有“按玩家去重的双条件聚合”,创作者只能绕已有元件组合,或等待平台新增/扩展元件;平台侧要补齐配置、属性、信号、运行时、保存与兼容链路。
关键点:创作者组合的是平台已经产品化的玩法句子。
10.4 UGC2.0
- 用 Part/Detector 类 Instance、Door Prefab 和脚本对象组成子树,配置走统一属性系统。
- Server VM 中的 Script/Module 订阅 Signal,以玩家 Instance 为键维护占用集合,并写入门的权威状态。
- 构造复制保证晚加入客户端先获得压力板和门的对象拓扑,属性复制同步门状态;RemoteEvent 只发送明确的跨域意图或瞬时表现。
- 整棵对象子树可以保存为 Prefab;SceneSerializer 保存引用关系,Artifact 固化脚本和依赖版本。
关键点:平台只需提供检测、对象、属性和脚本这些稳定原语,玩法聚合逻辑由创作者编写,不必为“双人门”再增加一类专用官方元件。
这个案例暴露了四者真正的差别:不是谁能做出门,而是新增规则时修改的是作品,还是平台本身。
十一、新增一种能力,平台到底要改几层
假设要加入“带所有权、可插值同步的移动平台”,可以用下面这张表衡量扩展成本。
| 改动面 | 绿洲启元 | Roblox | UGC1.0 | UGC2.0 |
|---|---|---|---|---|
| 类型定义 | 新 Blueprint 基类或原生组件,并纳入开放白名单 | 平台已有原语可组合;缺底层能力时新增 Instance/Service | 注册新的元件类型与运行类 | 新 Class/Service/Binding,或直接由脚本组合已有类 |
| 编辑能力 | 资产、详情面板、组件和关卡工作流多可复用 | 属性面板和对象树天然复用元数据 | 菜单、图标、属性组件、选择/摆放规则往往专门接入 | 元数据驱动对象树、详情面板与通用工具 |
| 运行实现 | Actor/Component + UE 生命周期 | Server Script + 平台物理/对象 API | 专用 Actor/Component 与业务行为 | Instance 行为 + 必要的 UE 原生物化 |
| 网络 | 定义 UE Replication/RPC 和 DS 权威 | 平台状态复制 + Remote,服务端校验 | 多数封进元件,常需专用同步逻辑 | 构造复制、属性描述符、Owner/权限与 RemoteEvent |
| 保存发布 | uasset 引用、Cook、版本兼容 | DataModel 发布与 Package 版本 | 元件 Schema、跨域引用、地图/资源包和迁移 | 通用 SceneSerializer、Prefab 与可验证 Artifact |
| 主要风险 | 引擎版本和资产兼容 | 滥用 Remote、客户端信任和大世界流送 | 纵向接入遗漏、专用分支增长、跨域数据不一致 | 内核协议错误影响所有类,Schema/VM/复制需强约束 |
UGC1.0 的成本曲线更像 新增能力数量 × 每种能力的全链路成本;UGC2.0 更像“先付对象平台的高固定成本,再降低上层能力的边际成本”。这才是从 1.0 演进到 2.0 的商业和工程动机,而不只是“脚本更自由”。
十二、故障模式比功能列表更能说明架构深度
| 故障 | 绿洲启元 | Roblox | UGC1.0 | UGC2.0 |
|---|---|---|---|---|
| 类型不存在或被下架 | 资产类/白名单失效,需替换或迁移 | 未知类通常由平台版本控制,作品侧更多遇到 API 变更 | 元件 ID、bpPath、版本/下架规则必须迁移或降级 | Class 缺失应在预检/反序列化阶段报告,不能静默造错对象 |
| 引用对象尚未创建 | UE 资产/Actor 引用按引擎加载阶段处理 | Streaming 下客户端不能假定完整 DataModel | 分层加载时信号、Coding、资源引用可能悬空 | 延迟引用队列 + 构造复制顺序 + Parent 拓扑恢复 |
| 玩家晚加入 | UE DS 向新客户端同步相关 Actor 状态 | 平台复制当前 DataModel,并受 Streaming 影响 | 取决于各元件是否实现完整初始状态同步 | 先发构造快照,再接增量属性和 Remote 事件 |
| 客户端伪造操作 | DS 校验所有权与 RPC 参数 | Remote 服务端校验类型、权限、数值和频率 | 平台元件隐藏了部分入口,但 Coding RPC 仍需校验 | 属性写权限、VM 角色、Remote 参数和服务端业务校验共同约束 |
| 保存中途失败 | 资产事务和编辑器恢复 | Studio/云端版本链负责主要恢复 | 多数据域必须保证清理、回滚或可重试 | 结构日志、Snapshot、构建目录和发布恢复分别兜底 |
| 旧作品升级 | Blueprint/资产重定向和版本迁移 | 平台维持 Instance/API 兼容,Package 可回退版本 | 每种元件 Schema 与专用数据都可能要迁移 | Class/Property Schema、序列化版本和 Artifact 构建器共同演进 |
| 大世界与带宽 | UE Relevant/Replication/资源流送 | Streaming + 平台复制预算 | 各元件专用优化,容易策略不一致 | 构造/属性分流、优先级、Owner、批处理和场景物化预算 |
面试时能讲出故障模式,说明你理解的不是一张静态架构图,而是系统在加载顺序、丢包、版本变化和恶意输入下如何保持成立。
十三、再从十个角度判断四种 UGC
| 角度 | 真正要问的问题 | 能暴露的架构差异 |
|---|---|---|
| 能力交付单位 | 平台交付的是资产类型、Instance 类、业务元件还是底层 Service? | 决定新增玩法由平台改还是创作者组合 |
| 身份 | 编辑 ID、进程句柄、网络引用和持久化 GUID 是否分离? | 决定重连、复制、反序列化是否可靠 |
| 拓扑 | Parent 只是编辑目录,还是受约束的运行时关系? | 决定生命周期、查询、继承和销毁语义 |
| 物化 | 逻辑对象何时需要 Actor/Component? | 决定对象规模、性能和 UE 耦合程度 |
| 行为调度 | 事件图、脚本、组件 Tick 和 Service 如何协作? | 决定逻辑表达力与调试复杂度 |
| 网络权威 | 谁能写属性,谁能创建对象,状态和事件如何跨端? | 决定作弊面、晚加入和一致性 |
| 编辑/运行边界 | 试玩是否冻结版本,Edit 状态能否泄漏到运行 VM? | 决定本地、DS 与线上能否复现 |
| 保存与制品 | 保存的是活动内存、源数据还是可验证运行制品? | 决定发布可追溯性和失败恢复 |
| 复用与升级 | 复制、Prefab、Package 的版本和局部覆盖怎么处理? | 决定生态能否长期维护共享资产 |
| 治理与可观测 | 配额、沙箱、日志、调用链、性能预算在哪一层执行? | 决定开放性是否能在线上规模化 |
因此,四种方案不能只排成“封闭 → 开放”的单轴。至少要看两条轴:一条是创作者可编程性,另一条是作品对底层引擎语义的耦合度。Roblox 可编程性很强但引擎耦合低;绿洲启元更接近 UE 资产语义;UGC1.0 两者都收得较紧;UGC2.0 试图在保持 UE 宿主能力的同时,用自研对象层降低上层对 UE 的直接耦合。
十四、四个压力测试案例:同一需求怎样暴露架构差异
压力板开门只验证了基础事件组合。下面四个案例分别把系统推向跨领域编排、高频网络、动态拓扑和版本复用,能够更清楚地区分四种方案。
14.1 案例一:PVE 波次关卡
需求如下:服务端按阶段生成多波 NPC;NPC 沿路径进攻目标;玩家技能造成伤害;击杀增加队伍分数;客户端显示波次、倒计时和小地图;中途加入的玩家必须看到当前进度。
绿洲启元
可以按 UE Gameplay Framework 拆分:
- GameMode 在 DS 维护波次规则和生成流程,不直接复制给客户端。
- GameState 保存
Phase/Wave/EndTime等公共状态并复制。 - PlayerState 保存玩家分数和局内结果。
- SpawnPoint、Path、NPC、Crystal 都是 Actor/Component;配置可放 Blueprint 或 DataAsset。
- 客户端 UI 观察 GameState/PlayerState,倒计时以服务端结束时间计算,不让每个客户端各自启动一只权威计时器。
- Lua/ECA 适合编排已开放 Actor 的事件;高频寻路、感知和伤害仍应落在原生组件或平台能力里。
这里的重点是“框架职责已经存在”。创作者做的是 UE 类型组合与规则编排;平台要治理的是哪些原生类型可用、资产如何 Cook、脚本能调用什么 API。
Roblox
可以让 ServerScriptService 中的回合控制器持有权威状态,把 NPC 模板放在 ServerStorage,把共享配置和 Remote 放在 ReplicatedStorage。服务端把 NPC Model 克隆到 Workspace,使用 Tag/Attribute 表达生成点、路径点和队伍,再让客户端 UI 观察复制状态。
晚加入时不能只依赖“WaveStarted”历史事件,因为事件已经过去。服务端必须保留 WaveIndex/Phase/EndTime/Score 等当前状态;新客户端先收到 DataModel 当前状态,再订阅后续变化。Streaming 开启后,UI 逻辑也不能假定远处 NPC 已经存在于本地树中。
UGC1.0
UGC1.0 源码中的 PVE/守点玩法更能说明“领域化元件”的真实形态。保存流程会分别导出怪物波次、路径、怪物配置、出生点、复活点、水晶点、能力限制、小地图、陷阱区域和伤害区域数据;这不是几个通用元件自然组合出来的,而是一组围绕特定玩法建设的编辑模型和运行契约。
其优势是策划拿到的是完整玩法语言:波次、路径、陷阱和水晶已经有明确含义。代价是一次“给波次增加分支胜利条件”的改动,可能同时触及波次编辑模型、属性组件、Signal、保存字段、运行时解释、UI 和旧数据迁移。功能越成熟,领域内效率越高;跨出原设计边界时,纵向改动越明显。
UGC2.0
UGC2.0 可以拆成通用服务与对象:RoundService 管回合,NPCManager 管 NPC,DamageService 管权威伤害,AbilityComponent 管技能,Workspace/Players/RunService 提供世界与生命周期,UI 只消费可复制状态。
源码中的 RoundService 给出了一个很典型的状态设计:服务端以真实世界时钟维护回合,用只读且允许复制的 RoundSnapshot 发布 RoundId/Phase/EndTime/Results;客户端不自行重建权威状态,而从快照派生倒计时和一次性表现。玩家到达终点时先提交结果,再发 Signal,避免回调重入造成同一玩家重复结算。
这不等于“有这些类就已经得到完整 PVE”。平台只是提供可组合原语;波次规则、刷怪策略、掉落、任务和 UI 仍要由作品脚本或更高层框架完成。它把扩展压力从新增专用元件,转成脚本框架设计、性能预算和 API 稳定性。
这个案例得出的结论
PVE 不是“刷怪 + UI”两个功能,而是回合、NPC、技能、伤害、奖励、场景、网络和 UI 的跨域事务。UGC1.0 倾向于把事务做成完整领域产品;UGC2.0/Roblox 倾向于提供通用对象与服务,让上层框架完成事务;绿洲则更多依赖 UE Gameplay Framework 组织职责。
14.2 案例二:高频瞄准与技能释放
需求如下:客户端实时显示范围指示器;玩家移动鼠标或摇杆持续改变瞄准点;点击后释放技能;服务端校验冷却、资源、距离和命中,并同步最终伤害。
先把数据分成四类,才能决定通信方式:
| 数据 | 例子 | 合适语义 |
|---|---|---|
| 权威状态 | 血量、资源、冷却结束时间、技能阶段 | 服务端拥有,状态复制;晚加入可恢复 |
| 离散意图 | 请求释放技能、选择目标、取消施法 | 可靠消息,但服务端必须验证 |
| 高频样本 | 瞄准点、射线方向、拖拽轨迹 | 可丢弃、可限频、以后一个样本覆盖前一个 |
| 纯表现 | 指示器、镜头震动、命中特效预测 | 客户端立即播放,必要时按服务端结果纠正 |
四种方案的落地差异:
- 绿洲启元:客户端本地更新指示器,释放请求走 UE RPC;DS 校验并执行 Ability/伤害,关键状态用属性复制。高频瞄准若要上传,应限频并使用适合的非可靠语义,不能每帧可靠 RPC。
- Roblox:LocalScript 负责预览,RemoteEvent 发送释放意图,UnreliableRemoteEvent 可传非关键高频样本;服务端校验角色存活、施法距离、冷却和资源,再修改权威状态。
- UGC1.0:枪械、近战、辅助瞄准等能力已有专用属性组件时,应走平台原生链路。用通用 Coding RPC 每帧传瞄准点,会绕开元件已有的限频、校验和同步策略,也会放大序列化、批处理和压缩成本。
- UGC2.0:可靠 RemoteEvent 与 UnreliableRemoteEvent 是不同 Instance 类;DamageService 明确只允许服务端结算,Class 属性描述符负责只读/服务端写和复制。客户端脚本负责预测,服务端脚本负责校验,最终 Health/Ability 状态走属性复制。
这个案例说明:Remote 不是网络同步的总称。可靠意图、高频样本、持续状态和本地表现必须分开,否则会出现可靠队头阻塞、晚加入缺状态、重复消息重复扣血或客户端作弊。
14.3 案例三:服务端运行时生成随机房间
需求如下:开局后服务端随机拼接十个房间;每个房间包含门、机关、NPC 和掉落;玩家中途加入时看到同一套房间;一局结束后销毁,不写回创作者工程。
四种方案的关键路径
| 系统 | 运行时创建 | 晚加入 | 是否写回源工程 |
|---|---|---|---|
| 绿洲启元 | DS 从白名单 Blueprint/资产生成 Actor | UE Actor 初始状态与属性复制 | 默认不写回 uasset;局内对象与编辑资产分离 |
| Roblox | 服务端从 ServerStorage 克隆 Model/Package 子树到 Workspace | 当前 DataModel 复制,受 Streaming 可见范围影响 | 不写回 Studio 源 DataModel;长期进度另存 DataStore |
| UGC1.0 | 只能通过平台已开放的运行时生成器或元件工厂创建受支持对象 | 由对应元件的初始化同步保证 | 创作数据与局内实例分离,不能把任意运行实例当新元件保存 |
| UGC2.0 | Server VM 创建 Instance/Prefab 子树,必要对象再物化为 UE 对象 | 先构造快照恢复对象和 Parent,再接属性增量 | 运行时服务和动态节点应由持久化策略排除,不能污染 map 数据 |
UGC2.0 在这个案例中最容易暴露网络时序问题。正确顺序不是“先发十个属性包,客户端缺对象时再等等”,而是:
Server 创建 Room 子树
→ 分配本进程对象身份
→ 建立 Parent 与初始属性
→ 构造复制发布 Class、对象、拓扑和初值
→ Client 创建镜像并恢复 Parent
→ 解析对象引用与延迟依赖
→ 对象可见后再消费属性增量和事件如果门的 TargetRoom 引用指向尚未构造的房间,就必须进入 pending reference;如果先让脚本收到 ChildAdded,随后才补齐初始属性,脚本会观察到不完整对象。构造发布屏障、回调延迟和批次完成通知,解决的正是这种“对象已经存在,但还不能安全使用”的中间态。
这个案例还要求区分三类保存:创作者源工程、局内权威状态、跨局玩家数据。把它们都塞进 SceneSerializer 或 map 文件,是职责混乱;场景序列化只应保存被定义为创作内容的对象,运行时房间局末销毁,长期进度通过专门数据服务保存。
14.4 案例四:可复用“门禁套件”的版本升级
需求如下:作者把门、读卡器、灯光、音效和脚本做成可复用套件。十张地图分别覆盖颜色、权限等级和开门速度。套件发布 v2,增加报警器并修复脚本,旧地图希望升级,同时保留本地覆盖。
- 绿洲启元:可以依赖 Blueprint 类默认值、组件层级和关卡实例覆盖;升级要处理类默认变化、实例覆盖、资产重定向和 Cook 兼容。GamePart 可承担更完整的模块复用,但仍在 UE 资产版本语义内。
- Roblox:PackageLink 明确关联包版本;更新包时平台区分包内容与实例级属性覆盖,也支持嵌套 Package 和回退版本。这里的关键能力不是“能保存 Model”,而是更新传播和覆盖保留。
- UGC1.0:从当前证据能确认元件类型有版本、下架和兼容规则,但不能据此假定它拥有与 Roblox Package 等价的通用嵌套版本传播。若套件由多个元件和 Signal 拼成,复制后往往各自成为地图数据,平台升级单个元件 Schema 不等于能升级整套组合。
- UGC2.0:SceneSerializer 已记录官方 Prefab 根和来源信息,Artifact 能冻结依赖与摘要;但“用户覆盖与新版本默认值冲突怎样合并”仍需要明确协议。至少要区分基线版本、上游新版本、本地覆盖三份数据,才能做三方合并,而不是用 v2 整棵覆盖或完全不更新。
一个成熟复用系统至少要回答:子对象被本地删除算覆盖还是缺失;上游重命名怎样保持引用;嵌套 Prefab 谁先升级;脚本与资产是否原子切版;升级失败能否回退;运行中的服务器是否继续使用旧 Artifact。Prefab 只是数据容器,这些版本语义才是平台能力。
十五、两套内部系统的关键执行时序
15.1 UGC1.0:一次信号属性修改经过什么
以计数触发、定时触发和队伍加分为例,源码中的 Modifier 都执行相似步骤:
SignalItem
├─ scene_id:找到哪个属性宿主
├─ comp_name:定位哪组业务节点
├─ index:定位数组里的哪一项
└─ attr_name/value:修改哪个字段
↓
FindUGCPropertyHost(scene_id)
↓
领域 Modifier 将编辑名映射到真实数据列表
↓
修改索引项 → SettingData()
↓
派发编辑器刷新事件 → 进入撤销/保存链源码中甚至存在显式名称翻译:计数触发把编辑侧节点名映射到计数任务列表,定时触发映射到计时任务列表,队伍加分再映射到另一份节点列表。这个细节揭示了三件事:
- Signal 并非完全反射驱动,领域适配器仍知道宿主结构和字段约定。
scene_id + comp_name + index + attr_name本质上是一种跨模块寻址协议,改名、数组重排和对象删除都可能使引用失效。- 一次属性修改还必须连接撤销重做、面板刷新和保存脏标记,不能只改运行时值。
这就是 UGC1.0 随领域增长容易出现 Modifier 分支和字符串协议膨胀的原因。它并非“事件系统设计差”,而是业务数据没有完全收敛到统一对象/属性协议时,适配复杂度必然落到连接层。
15.2 UGC1.0:一次发布为什么是跨域事务
收集各图层 Actor
→ 导出对话、任务、成就、技能、UI、商店、玩法设置
→ 导出 Coding 文件与资源引用
→ 校验容量、非法对象、配置错误和引用
→ 分别写入场景/脚本/资源数据
→ 生成地图包与资源包
→ 本地保存或发布如果场景保存成功而脚本文件失败,地图可能引用旧脚本;如果资源包成功而地图包失败,会留下不可达资源;如果对象删除但 Signal 仍保留,运行时才会暴露悬空引用。因此保存管理器实际上承担轻量事务协调者角色:它要定义校验顺序、失败清理、重试边界和最终提交点。
更深一层的改进方向不是简单把多个文件合成一个大文件,而是给所有输出建立同一 revision/sourceDigest,在发布清单中原子引用。这样即使物理文件仍分开,也能判断它们是否属于同一次快照。
15.3 UGC2.0:创建对象为什么必须分阶段
ObjectManager 同时面对 Lua 对象和 C++ 原生对象,比较稳妥的创建顺序是:
解析 Class 与构造参数
→ 创建 Lua table 或 C++ userdata,并建立 shadow state
→ 注册 ObjectId,解决可能提前到达的网络绑定
→ 写入初始属性并正确标记复制基线
→ 设置 Parent,进入受约束对象树
→ OnInit / 物化 / 运行时回调
→ 对外发布对象可用源码记录过“构造参数直接写入但没有标脏,客户端镜像保留默认值”的问题。这不是单点漏调用,而是说明构造初值同时属于对象初始化和网络初始状态。如果两套路径不共享同一属性语义,服务器看起来正确,客户端却会在晚加入或重新构造时得到另一份对象。
同样,Parent 不能绕过 SetParent 直接写字段。否则循环检测、允许父类型、单例约束、Child/Descendant/Ancestry 信号和原生层级同步会失去统一入口。
15.4 UGC2.0:一次属性写入经过什么
script: object.Health = newValue
→ Class descriptor 校验类型和写权限
→ setter / 原生 Binding 应用
→ 更新 shadow property store
→ 生成 Changed / PropertyChanged Signal
→ 可复制属性进入 dirty set
→ Replication 批量编码并发送
→ Client 在 replication-write context 应用只读属性
→ 客户端观察者收到一致的属性变化属性描述符里的 readonly、serverWritable、notReplicated 和 replicateReadonly 不能互相替代。例如血量可以对创作者脚本只读,但允许 DamageService 在服务端写,并允许复制器在客户端镜像写入;Signal 类型属性则不应进入脏属性集合,因为它代表订阅端点,不是可序列化状态。
“同值写入”也要定义清楚:是否跳过网络脏标记,是否仍调用 setter,是否仍发 Changed。性能优化若改变可观察语义,会让脚本依赖偶发失效;正确做法是把存储变化、原生副作用、通知和网络增量分别定义。
15.5 UGC2.0:晚加入为什么需要基线与增量栅栏
晚加入不是简单地“把全部对象再发一次”。服务端在发送全量快照期间,世界仍可能继续变化:对象 A 被创建、对象 B 改属性、对象 C 被删除。如果没有边界,新客户端可能收到旧基线覆盖新值,或收到 B 的增量时还没有 B。
稳妥模型是:
确定基线序号 N
→ 发送 N 时刻可见对象的构造与初值
→ 基线期间产生的 N+1...M 增量进入暂存
→ Client 完成对象图恢复并确认
→ 按序应用暂存增量
→ 切换到实时流对象创建/销毁必须与属性流共享顺序语义;引用解析必须允许目标稍后出现;重复包应幂等;旧连接的迟到包要被 session/generation 拒绝。只有这样,“晚加入支持”才不是一个测试用按钮,而是一致性协议。
15.6 UGC2.0:Edit、Server、Client VM 切换什么不能复用
进入试玩时,应冻结源工程与脚本依赖,形成确定 revision,再让 Server/Client VM 分别加载。以下对象不能直接跨 VM 沿用:
- Lua table、closure、coroutine 和 Signal connection;
- 指向 VM 内 userdata 的裸引用;
- Editor 选择集、撤销栈和页面状态;
- 未提交的临时文件与异步回调;
- 旧 VM 的定时器、任务 owner 和网络 handler。
可以跨边界的是稳定 ID、可序列化值、脚本源/字节码、资产引用和明确的 Artifact 版本。源码中的 VM Handle 使用角色加单调递增索引,目的就是让旧句柄不能误命中新建 VM。退出试玩也应完整注销 handler、取消异步任务、销毁运行对象,再恢复或重建 Edit 环境;只把全局变量清空并不够。
十六、Instance 不等于 Actor:物化策略决定平台上限
如果每个 Instance 都生成 Actor,十万个逻辑节点会变成十万个 World 对象;如果所有 Instance 都只是 Lua table,碰撞、渲染、物理和网络又无法落到 UE。UGC2.0 必须是混合模型。
| Instance 类型 | 例子 | 合理承载 | 原因 |
|---|---|---|---|
| 纯组织对象 | Folder、配置节点、Prefab 根 | Lua 对象即可 | 只需要树、属性和引用,不需要 World 身份 |
| 运行服务 | RoundService、DamageService、LogService | Lua/C++ 服务单例 | 按 VM 或 DataModel 存在,不应生成场景 Actor |
| 脚本对象 | Script、ModuleScript | VM 内执行单元 | 需要 Source、上下文和生命周期,不需要 Transform |
| 空间可见对象 | Part、Mesh、NPC、Trigger | Actor/Component 或高效原生代理 | 需要 Transform、渲染、碰撞、物理或场景查询 |
| UI 对象 | ScreenGui、Frame、TextButton | UI/PixUI/Slate 承载 | 属于屏幕或窗口树,不属于 3D World Actor 树 |
| 引用和值对象 | ObjectValue、配置资源引用 | 序列化句柄/代理 | 重点是解析和生命周期,不应复制目标实体 |
物化还要回答四个时机问题:
- 何时创建:反序列化第一遍就创建,还是等 Parent、资产和 World Ready 后创建?
- 何时可见:原生对象存在是否等于脚本可以访问,还是要等属性与引用恢复完成?
- 何时休眠/卸载:Streaming 移出后保留逻辑 Instance,还是连脚本状态一起销毁?
- 何时销毁:先广播 Destroying、断开脚本连接,还是先删原生对象;子树与 pending reference 怎样清理?
这也是 UGC2.0 不能机械照搬 Roblox API 名称的地方。外层可以兼容 Instance 语义,但 UE 对象线程、World 生命周期、GC、物理和网络驱动方式不同,Binding 层必须定义自己的物化契约。
十七、从 UGC1.0 迁到 UGC2.0,不是改一遍类型名
17.1 六类迁移映射
| UGC1.0 内容 | UGC2.0 目标 | 迁移难点 |
|---|---|---|
| typeId + bpPath | ClassName / Prefab + 原生 Binding | 一对一、一对多或退化替代;缺类怎样报告 |
| scene_id | UniqueId/来源 ID 映射 | 不能直接拿进程内 ObjectId 当持久化身份 |
| 属性组件字段 | Class Property/Attribute | 单位、枚举、默认值、只读与复制标记变化 |
| SignalItem | Signal/BindableEvent/Script/RemoteEvent | 保留触发顺序、参数、Client/Server 运行域和一次性语义 |
| 专用 Actor 行为 | 通用原语 + 脚本,或兼容原生类 | 性能敏感能力不能强行全部脚本化 |
| 场景/Coding/资源分域文件 | Scene graph + script files + Artifact manifest | 建立原子 revision、依赖摘要和可回滚发布 |
17.2 计数开门的具体迁移
UGC1.0 中可能是“两个触发元件 → 计数触发元件 → 门元件”的 Signal 链。迁移不能只生成四个对象,还要恢复语义:
- 为原场景对象建立
scene_id → UniqueId对照,先创建所有目标对象,再解析连接。 - 把触发器配置映射成 Detector/Part 属性;无法识别的旧字段保留诊断,不静默丢弃。
- 把计数节点转换成 Server Script 或通用 Counter 类,并明确按玩家去重、进入离开对称、重生和掉线规则。
- 把“开门”Signal 转换为对象引用 + Signal 连接;若跨 VM,只能转为允许的 Remote 或复制属性,不能把本地回调直接序列化。
- 用兼容运行器同时执行旧链和新链,对同一输入比较门状态、事件次数和网络结果。
- 通过后再发布新 Artifact;失败时仍能回退旧作品版本。
迁移真正困难的是隐含语义。旧元件可能已经内置防抖、服务端权威、断线清理和动画时长;如果只看面板字段,转成脚本后“正常路径能跑”,边界行为却已经改变。
17.3 哪些能力不应该迁成普通脚本
- 每帧大规模物理、寻路、动画求值和空间查询;
- 需要严格服务端安全边界的交易、伤害、背包和结算;
- 需要平台统一预算和降级的粒子、音频、网络流送;
- 涉及隐私、支付、账号或持久化数据的能力;
- 必须跨版本保持二进制兼容的底层资产加载。
更合理的 2.0 不是“所有东西都让作者写”,而是把高成本、高风险能力收成 Service/Class,把玩法组合开放给脚本。平台原语越少越好只是表面目标;真正目标是原语之间正交、语义稳定并且可治理。
17.4 用什么指标判断 2.0 是否真的更好
| 指标 | 要验证的问题 |
|---|---|
| 新玩法首个可玩版本耗时 | 创作者能否不等平台新增元件就完成闭环? |
| 新原语全链路接入耗时 | Class/元数据/Binding/复制/保存是否真的可复用? |
| Edit→Play P50/P95 | VM 重建、场景加载和 Artifact 是否影响迭代效率? |
| 同源构建一致率 | 相同 source digest 是否生成相同 Artifact 与依赖清单? |
| 晚加入一致性通过率 | 构造、属性、引用和事件是否在并发变化下收敛? |
| 每千对象 CPU/内存/带宽 | Instance 数增长后,Lua、原生物化和复制成本是否可控? |
| 旧作品自动迁移率 | Schema 演进有多少可自动完成,多少必须人工修复? |
| 脚本错误隔离率 | 一个作品脚本死循环、超配额或异常会影响多大范围? |
| 可诊断率 | 错误能否定位到 Artifact、VM、Instance、Script 和调用链? |
可以把长期成本抽象成:
总成本 = 平台固定成本
+ 新增底层原语数 × 单原语接入成本
+ 作品数量 × 单作品创作与维护成本
+ 线上兼容、安全与故障成本UGC1.0 的平台固定成本相对可控,但新原语和复杂作品容易产生专用链路;UGC2.0 前期固定成本很高,只有当通用内核显著降低后面三项时,架构升级才真正成立。不能只用“类更多、脚本更自由”证明成功。
十八、封装粒度不同,创作者到底要负责什么
“平台是否替创作者处理同步”不能只回答是或否。同步至少有三层:
- 玩法一致性设计:谁是权威,什么是持续状态,什么是一次性事件,晚加入应看到什么。
- 平台同步语义:属性是否复制,脚本运行在哪一侧,何时使用 Remote/RPC,客户端输入怎样校验。
- 底层传输实现:对象构造协议、字段编码、分包、压缩、可靠性、重传、带宽调度和 UE 网络接入。
四种方案对第三层都会有不同程度的封装。真正变化最大的是:第一、二层有多少交给创作者。
好的 UGC 平台不是让创作者“不知道有网络”,而是让他只处理玩法必需的一致性语义,不必重复实现传输基础设施。
18.1 先区分三类创作者
| 创作者类型 | 主要工作 | 应具备的能力 |
|---|---|---|
| 内容/关卡作者 | 摆放对象、调参数、连接事件、配置规则 | 理解产品概念、对象引用、触发条件和基本性能预算 |
| 高级玩法作者 | 写 Lua/Luau/Blueprint,组织系统与跨端逻辑 | 理解生命周期、服务端权威、状态/事件、异步和模块化 |
| 平台原语开发者 | 新增 Class/Service/元件/Binding | 理解 UE、序列化、复制、安全、版本兼容和工具链全链路 |
UGC1.0 主要服务第一类作者;Roblox 和 UGC2.0 希望同时覆盖前两类;绿洲启元允许第一类通过 ECA 工作,也让具备 UE 经验的高级作者进入 Blueprint/Lua 层。第三类严格说不是普通创作者,而是平台研发;但封装越开放,平台越要清楚哪些能力仍然不能下放。
18.2 创作者责任总表
| 责任 | 绿洲启元 | Roblox | UGC1.0 | UGC2.0 |
|---|---|---|---|---|
| 建模基本单位 | UE 资产、Blueprint、Actor、Component | Instance、Service、Package | 官方元件、属性、Signal | Instance、Class、Service、Prefab |
| 需要理解底层引擎 | 中高;高级作者要懂 UE Gameplay Framework | 低;理解平台对象即可 | 很低;主要理解业务元件 | 普通作者低;原语开发者高 |
| 需要写代码 | ECA 可免代码,高级能力用 Blueprint/Lua | 简单内容可少写,复杂玩法通常写 Luau | 主路径可不写,缺少组合能力时用 Coding | Prefab/配置可少写,复杂玩法通常写 Luau |
| 需要选择 Client/Server | 高级作者需要明确选择 | 脚本作者必须明确 | 普通元件作者通常不需要;Coding 作者需要知道执行域 | 脚本作者必须明确 Edit/Client/Server |
| 需要定义权威状态 | Blueprint/Lua 作者需要 | 必须;服务端是最终权威 | 官方元件已定义;自定义 Coding 仍需遵守平台边界 | 必须;Server VM 写共享玩法状态 |
| 需要声明/使用状态复制 | 常需配置复制属性、RPC、Owner/Relevancy | 通常不声明底层复制标记,但必须让服务端修改可复制对象 | 大多由元件内部完成 | 使用 Class 属性复制语义;不直接写底层传输 |
| 需要写跨端消息 | 高级作者使用 RPC | 使用 RemoteEvent/RemoteFunction | 元件路径通常不需要;Coding 通过受控 API | 使用 RemoteEvent/UnreliableRemoteEvent |
| 需要处理晚加入 | 必须把关键事实做成可复制状态 | 必须保留当前状态,不能只发历史 Remote | 官方元件负责;组合者要确认元件是否保存当前态 | 平台恢复对象图;脚本仍要把玩法事实设计成状态/快照 |
| 需要校验客户端 | DS 逻辑必须校验 RPC | 服务端必须验证所有客户端输入 | 官方元件由平台校验;Coding 入口仍不能信任客户端 | Server VM 必须验证 Remote、权限、距离、频率与状态 |
| 需要处理封包/压缩/重传 | 不需要,UE 网络层负责 | 不需要,平台负责 | 不需要,元件/Coding 框架负责 | 不需要,构造/属性复制与传输层负责 |
| 需要关心资源与性能 | Actor/Tick/复制/资产 Cook | Instance/脚本/网络/Streaming 预算 | 元件数量、连接复杂度和平台容量限制 | Instance 数、VM、物化对象、脚本和复制预算 |
| 需要关心发布兼容 | 资产引用、引擎版本、Cook 结果 | Package/API/DataStore 兼容 | 元件版本和平台迁移大多被封装 | Schema、Prefab、脚本依赖与 Artifact 版本 |
一句话概括创作者门槛:
- 绿洲启元要求较强的 UE 心智,创作者自由度来自引擎类型与资产能力。
- Roblox不要求理解底层引擎,但高级作者必须理解完整的 Client/Server 平台语义。
- UGC1.0技术门槛最低,重点是正确理解官方元件契约和 Signal 组合边界。
- UGC2.0把 UE 细节挡在 Binding 后,但把对象建模、VM 分工和分布式玩法语义交给高级创作者。
18.3 绿洲启元:创作者要掌握多少 UE 与网络
内容作者
只使用模式编辑器、官方 GamePart 和 ECA 时,创作者主要负责:
- 选择正确的 Actor/组件或模块,配置公开属性;
- 理解事件何时触发、动作作用于哪个实例;
- 区分局内对象、玩家对象和客户端 UI;
- 避免无意义 Tick、过多 Actor、过大资源和错误资产引用。
如果官方模块已经封装了同步,内容作者不需要自己声明复制属性。例如使用一个平台提供的计分或开门模块,通常只配置触发条件和结果。
Blueprint/Lua 高级作者
一旦自定义玩法,要求明显上升:
- 知道 GameMode 只存在于服务端,GameState 适合公共局内状态,PlayerState 适合玩家公共状态,PlayerController/客户端 UI 有各自边界。
- 决定变量是否复制、谁拥有 Actor、哪个调用是 Server RPC、哪个是 Client/Multicast 表现。
- 理解
BeginPlay/Tick/EndPlay、Actor 销毁、关卡切换和异步资产加载。 - 设计晚加入:当前门是否打开应是状态,不能只依赖五分钟前发过的“开门事件”。
- 控制 Actor 数、Tick 数、复制频率、相关性和资产 Cook;平台只会提供预算与限制,不会替作品自动设计。
因此,绿洲对高级创作者的同步要求是四者中最贴近 UE 原生开发的。创作者不用实现网络驱动和封包,但需要直接使用引擎复制概念。
例子:同步一扇门
错误:Client 碰到按钮 → 本地直接播放门动画
结果:只有自己看到;重连或晚加入也不知道门的真实状态
正确:Client 只提交交互意图
→ DS 校验距离、权限和门当前状态
→ DS 修改权威 bOpen
→ bOpen 复制到 Client
→ Client 根据状态播放或校正动画若门完全由 DS 碰撞触发,甚至不需要客户端 RPC;但 bOpen 仍应是可恢复状态。这里创作者必须做出同步设计,平台只负责执行 UE 复制。
18.4 Roblox:不用懂引擎复制,但必须懂平台分布式语义
Roblox 创作者不需要考虑 Actor Channel、UE RPC 或 Cook,但需要掌握:
- DataModel 中不同容器和 Service 的可见范围;
- Script/LocalScript/ModuleScript 的运行位置与 RunContext;
- 服务端对世界状态的最终权威;
- Instance 属性状态与 Remote 行为消息的区别;
- Streaming 下客户端只拥有世界子集,远处对象可能暂时不存在;
- DataStore、Package 和平台 API 的权限、预算和版本限制。
Roblox 会自动复制大量服务端 Instance 状态,但“自动复制”不等于“不用设计同步”。创作者仍要决定状态放在哪里、由谁修改、客户端缺对象时如何处理,以及 Remote 是否可能被伪造和滥用。
例子:排行榜倒计时
不应让服务端每秒 Remote 一次 10、9、8...,因为晚加入可能漏掉前面的消息,网络抖动也会让 UI 跳变。更稳妥的是服务端保存并复制 Phase 与 EndTime,客户端用同步后的服务器时间计算显示;阶段切换仍由服务端确认。
例子:购买道具
Client: FireServer(itemId)
Server:
1. itemId 是否存在
2. 玩家是否有权限和足够货币
3. 是否超过调用频率
4. 是否已经购买,重复请求是否幂等
5. 扣款与发货怎样原子提交
Client: 只根据服务端结果刷新表现Remote 只是消息通道,不是信任边界。Roblox 创作者不写传输层,但安全验证属于作品逻辑,不能交给“平台自动同步”。
18.5 UGC1.0:普通创作者主要学习契约,不学习网络实现
UGC1.0 的核心价值就是降低普通作者需要理解的技术层级。使用官方元件时,创作者通常只需要:
- 了解某个元件暴露哪些属性、事件和行为;
- 正确连接 Signal 的发送端与接收端;
- 理解计数、定时、队伍、玩家和全局条件;
- 避免事件环、重复触发、对象删除后的失效引用;
- 遵守对象数量、连接数量和资源容量限制。
以下内容一般由元件研发负责:Actor/Component 实现、Client/DS 实例差异、复制字段、RPC、初始同步、属性序列化和旧版本迁移。因此,一个普通作者使用“触发区 → 队伍加分 → 达分结束”时,不应自己写同步。
但这不意味着完全没有一致性要求。创作者仍要理解产品语义,例如:
- 触发条件是“每个玩家一次”还是“每次进入一次”;
- 计数器是全局、队伍还是玩家维度;
- 事件只代表某一时刻发生,还是元件还保存可查询的当前状态;
- 重新加入、玩家死亡或对象重置后,元件是否自动恢复。
这些语义若不在属性面板和文档中明确,创作者虽然看不到网络,却仍会做出错误玩法。
Coding 作者多承担什么
进入 Coding 后,作者至少要知道:
- 当前脚本运行在客户端还是服务端,哪些 API 只允许某一侧调用;
- 参数必须能被框架序列化,不能跨端传 closure、VM 对象或裸原生引用;
- 本地 UI 状态不能当成公共游戏状态;
- 高频输入不能无上限走通用可靠调用;
- 通过 scene_id 等引用对象时,要考虑对象删除、分层加载和版本变化。
不过 Coding 作者仍不应自己处理 LZ4、批大小、广播路由和 Transporter Actor,这些属于框架层。UGC1.0 的责任边界是:创作者组合业务动作,平台为每类元件兑现网络和运行承诺。
例子:队伍加分
普通作者只连接“击杀成功”到“队伍加分”,选择队伍与分数。平台元件应在 DS 修改权威比分并同步 UI 所需状态。作者不需要发 RPC;但如果他又在客户端 Coding 中收到表现事件后手动加一次,就会发生重复计分。厚封装系统最常见的创作错误不是漏写同步,而是绕过或重复平台已经实现的权威逻辑。
18.6 UGC2.0:不要求懂 UE Replication,但要求懂对象与 VM
UGC2.0 对创作者的要求是分层的。
Prefab/配置作者
只组合现有 Class 和 Prefab 时,要求接近 UGC1.0:建立正确 Parent、配置属性、绑定 Signal、控制实例数量。平台负责物化、构造复制、属性编码、场景保存和 Artifact 构建。
但对象模型更通用,作者要更理解树语义:把对象放入 ServerStorage、ReplicatedStorage、Workspace 或客户端 UI 容器,含义不同;Destroy、Reparent 和运行时 Clone 也会影响生命周期与可见性。
玩法脚本作者
脚本作者必须理解:
- VM 角色:Edit VM 用于创作工具,Server VM 负责权威玩法,Client VM 负责输入、UI 和本地表现。
- 属性与事件:持续事实写入可复制属性或快照;一次性意图/通知使用 Remote;同进程解耦可以使用普通 Signal/BindableEvent。
- 对象身份:进程内 ObjectId、持久化 UniqueId 和可重建网络引用不是一个概念,不能把临时 ID 写进长期数据。
- 晚加入:平台恢复 Instance 图和复制属性,但作品脚本仍要把回合、任务、门状态等建模成当前事实。
- 安全:客户端 Remote 参数必须在 Server VM 校验;readonly 或 serverWritable 只能限制写入口,不能替代业务权限判断。
- 性能:避免每帧跨 VM 调用、无界 Remote、为纯逻辑节点物化 Actor,以及让所有对象都进入全局复制。
平台原语开发者
新增原生 Class/Service 时,要求最高:要定义 Lua Class、元数据、C++ Binding、父类型、属性读写权、复制策略、序列化、物化、销毁和版本迁移。还要验证初始构造、增量更新、晚加入、重连和 Edit/Play 切换。
这类工作不能推给作品作者。UGC2.0 的目标不是让每位创作者都成为 UE 网络工程师,而是让高级作者可以表达分布式玩法,同时由平台保证对象协议一致。
例子:同步一扇门
Client Script
→ FireServer("TryOpen", door.UniqueId)
Server Script
→ 校验玩家、距离、钥匙、门状态和频率
→ door.IsOpen = true # 权威持续状态
平台复制层
→ 首次加入:随构造/基线得到 IsOpen
→ 在线变化:发送 IsOpen 增量
Client Script
→ 观察 IsOpen,播放动画
→ 动画可预测,但最终以属性状态校正创作者需要设计前后两段业务语义,不需要实现中间的构造协议、属性编码、可靠传输和 UE 对象映射。
18.7 “需不需要关注同步”的精确答案
| 问题 | 绿洲启元 | Roblox | UGC1.0 | UGC2.0 |
|---|---|---|---|---|
| 谁决定服务端权威 | 高级创作者 | 创作者 | 元件平台;Coding 受平台约束 | 脚本创作者 + Service 契约 |
| 谁决定状态还是事件 | 高级创作者 | 创作者 | 元件研发为主,作者理解使用语义 | 脚本创作者 |
| 谁声明底层复制 | 创作者使用 UE 复制配置 | 平台自动复制支持的 Instance 状态 | 元件研发 | Class/平台研发;作品作者使用属性协议 |
| 谁发送跨端意图 | Blueprint/Lua 作者 | Luau 作者 | 元件自动;Coding 使用受控接口 | Luau 作者使用 Remote |
| 谁保证晚加入对象存在 | UE 网络层 + 创作者 Relevancy 设计 | 平台复制/Streaming | 元件平台 | 构造复制平台 |
| 谁保证晚加入玩法状态正确 | 创作者 | 创作者 | 元件契约;组合者仍需验收 | 创作者的状态建模 + 平台基线 |
| 谁校验客户端业务请求 | 创作者的 DS 逻辑 | 创作者的 Server Script | 元件研发;Coding 服务端逻辑 | 创作者的 Server Script/Service |
| 谁处理序列化、分包和重传 | UE/平台 | 平台 | 元件/Coding 框架 | 复制与 Transport 层 |
所以面试中最准确的回答是:
UGC1.0 普通创作者基本不需要关注同步机制,只要遵守元件契约;绿洲的 Blueprint/Lua 作者需要直接理解 UE 复制;Roblox 和 UGC2.0 的脚本作者不用实现底层网络,但必须设计服务端权威、状态与事件、Remote 校验和晚加入。UGC2.0 平台替作者解决的是 Instance 构造、属性复制和 UE 映射,不是替作者决定玩法一致性。
18.8 五个快速判断例子
| 需求 | 应该是什么 | 不应该是什么 |
|---|---|---|
| 门当前打开 | 权威可复制状态 | 只广播一次“开门”事件 |
| 枪口火光 | 本地或非关键表现事件 | 持久化进场景、晚加入补播全部历史火光 |
| 连续瞄准点 | 限频、可覆盖、允许丢样本 | 每帧可靠 Remote 排队 |
| 玩家确认购买 | 服务端校验的可靠请求 + 幂等事务结果 | 客户端直接修改货币与背包 |
| 本机设置面板展开 | Client VM 本地状态 | 广播给服务器和所有玩家 |
判断方法只有三问:这个数据是否影响胜负或资产;后来加入的人是否需要知道;丢一两次更新后是否能被新状态覆盖。三问回答清楚,才能选择状态复制、可靠事件、非可靠样本或纯本地状态。
十九、从资产、战斗到系统:创作责任的完整展开
网络只是作品运行链的一部分。一个可发布玩法至少经过下面九层:
源美术/音频/脚本
→ 资产导入、审核与身份
→ 依赖、Prefab/Package 与版本
→ 场景对象和角色实例化
→ 战斗/AI/交互运行时
→ UI、任务、商店、回合等系统编排
→ 玩家持久化与跨局数据
→ 性能、安全和异常诊断
→ 构建、校验与发布制品封装粒度改变的是每一层由创作者、作品框架还是平台底座负责。
19.1 先看完整领域责任矩阵
| 领域 | 绿洲启元 | Roblox | UGC1.0 | UGC2.0 |
|---|---|---|---|---|
| 资产导入 | 创作者直接处理较多 UE 资产语义 | 创作者导入云资产并管理 Asset ID/权限 | 以官方资源库和受控自定义资源为主 | 创作者使用逻辑 Asset ID/Prefab;平台异步解析并进入 Artifact |
| 依赖与版本 | uasset 引用、Blueprint 继承、Cook 兼容 | Package、PackageLink、权限和版本 | 元件版本、资源引用表、地图包/资源包 | Prefab 来源、Scene 引用、source/artifact digest |
| 场景建模 | Actor/Component/Blueprint | Instance/DataModel | 官方元件与属性组件 | Instance/Class,按需物化 UE 对象 |
| 战斗能力 | 高级作者通过开放的 UE 类型、Blueprint/Lua 组织 | 作者用 Luau 和平台原语自建较多战斗框架 | 枪械、近战、伤害区、技能等专用元件封装较多 | Humanoid、Ability、DamageService 等原语 + 作者脚本 |
| AI/NPC | 使用开放的 UE AI/导航/Actor 能力 | 作者使用平台服务与脚本组织 | NPC、四足、路径、波次等领域元件 | NPCManager、BehaviorTree、Blackboard 与脚本组合 |
| 剧情/对话 | 用开放的交互、UI、脚本和演出能力自行编排 | Dialog 可做简单选择;复杂剧情通常自建图与服务 | 剧情编辑器、对话组/步骤/选项/立绘、任务联动 | 当前可用 NPC、交互、GUI、Remote 等原语组合,尚未核实成熟剧情服务 |
| 运动/动画 | UE Transform、Movement、导航、物理、动画与时间线 | Pivot/Tween、Humanoid/Pathfinding、物理约束、Animator | MoveState、移动机关、NPC 路径和技能时间线等专用能力 | Tween/Mover、Humanoid MoveTo、行为树路点、AnimationTrack 与物理对象分层 |
| UI | UE UI/客户端逻辑 | Gui Instance + LocalScript | 独立 UI 编辑器和平台控件 | GUI Instance/Client VM;构建链处理 UI 派生资源 |
| 玩法系统 | GameMode/GameState 等框架对象 | Service/ModuleScript 自建系统 | 任务、商店、成就、对话等成品系统 | RoundService/DataStore/Service + ModuleScript |
| 持久化 | 依赖平台开放的后端接口,不由 UE 资产自动解决 | 服务端 DataStore/MemoryStore 等云服务 | 平台定义好的业务存储接口与发布配置 | DataStoreService/平台 IRPC,明确与 SceneSerializer 分层 |
| 性能治理 | Actor、Tick、复制、资产和 Cook 预算 | Instance、脚本、Streaming、网络和云服务预算 | 元件数、连接数、资源容量等产品化限制 | Instance/VM/物化/复制/粒子/资产异步等多层预算 |
| 发布 | UE 资产检查与平台 Cook/审核 | 云端 Place、Asset、Package 发布 | 保存编排后生成地图包和资源包 | Snapshot、Processor、Manifest、Artifact、分片上传与发布 |
这张表的重点不是“谁功能更多”,而是创作者需要理解到哪一层。UGC1.0 把大量领域知识预先固化成产品;Roblox/UGC2.0 把更多领域组合留给脚本;绿洲则让高级创作者更直接地使用 UE 生产范式。
19.2 资产链:不是能拖进场景就算完成
一项资产从源文件到线上运行,至少有七个技术问题:
- 身份:引用本地路径、云 Asset ID、GUID,还是包内逻辑 key?
- 权限:作品是否有权使用,资产被下架后怎样处理?
- 依赖:模型引用哪些材质、贴图、骨骼、动画、音频和脚本?
- 派生处理:纹理压缩、网格处理、UI 图集、平台变体由谁生成?
- 版本:上游资产更新后,已有作品自动跟随还是固定旧版本?
- 运行加载:同步加载、异步加载、流式加载,失败和取消怎样处理?
- 发布闭包:制品是否包含运行所需的完整依赖,能否按摘要复现?
绿洲启元的资产要求
创作者直接面对 Blueprint、DataAsset、材质、贴图、动画和 .uasset,因此需要较多 UE 资产知识:
- 区分类资产和实例资产,知道 Blueprint 继承或组件结构改变会影响哪些关卡实例;
- 避免硬引用把不相关大资源拉入 Cook,理解软引用、加载时机和资源生命周期;
- 控制纹理尺寸、材质复杂度、骨骼/动画、粒子和音频预算;
- 处理重命名、资产重定向、丢失依赖和引擎版本变化;
- 理解编辑器预览可用不代表目标平台 Cook 后一定可用。
平台负责白名单、最终 Cook、审核和运行环境,创作者负责让资产图合理。ECA 作者可以不理解 C++,但只要允许自定义资产,就很难完全绕开依赖和性能问题。
Roblox 的资产要求
Roblox 资产是云端对象,通过 Asset ID/URI 被 Mesh、Texture、Audio 等 Instance 引用。创作者不处理 UE Cook,但要负责:
- 资产所有权和 Experience 使用权限;
- 内容审核状态,被拒绝或归档的资产在运行时不可用;
- 导入尺度、碰撞、网格、纹理和音频的运行预算;
- 第三方 Model/Package 中是否夹带不可信 Script;
- Package 的版本、AutoUpdate、嵌套包和本地覆盖。
Package 把 Instance 子树、脚本和资产引用一起复用,但被引用的受限资产仍需作品权限。创作者不需要构建资源包,却要理解云资产权限和包版本传播。
UGC1.0 的资产要求
普通作者主要从平台资源库选择元件、外观和受控资源。源码显示平台注册项会关联 bpPath、分包信息和资源投放条件;保存流程维护资源描述、引用来源、公共贴图与自定义资源,并分别生成地图包和资源包。
创作者主要负责:
- 在官方允许的资源类型和配额内选择或上传内容;
- 知道删除一张公共贴图可能影响 UI、公告牌、成就图标等多个业务域;
- 控制地图中元件和自定义资源数量;
- 避免引用未下载、被封禁、已下架或仅在特定分包中的内容。
平台负责把资源 ID 解析成 UE 资产、压缩/分包、建立引用清单和发布检查。代价是作者不能任意定义新的资产处理方式;一种新资源类型往往需要平台增加上传、审核、描述文件、下载、缓存和运行加载整条链路。
UGC2.0 的资产要求
UGC2.0 更希望创作者持有逻辑引用,而不是直接传播 UE 路径。当前 AssetService 源码给出了一个具体例子:特效通过精确 Asset ID 异步加载,调用必须发生在可 yield 的脚本协程;加载可以重试、轮询或在 StopPlay/运行环境切换时取消;精简运行包可先用于客户端播放,完整资源再后台升级,编辑参数仍依赖源资产。
因此作者需要处理的是异步语义:
请求 Asset ID
→ 显示占位/加载态
→ 等待资产服务
→ 成功后得到 Parent=nil 的 Instance 根
→ 设置 Parent,进入对象树和生命周期
→ 失败、取消或对象已销毁时停止后续逻辑不能在发出请求后假定下一行资产已经存在,也不能把下载请求句柄写入场景。平台负责 Asset ID 解析、原生下载、运行实例化、派生资源与 Artifact 收集;创作者负责加载时机、降级表现、对象归属和释放。
发布时,Snapshot 固定源数据,Processor 生成派生结果,Manifest 记录处理器集合与文件摘要,Artifact 再上传。UI 图集之类的 Derived 内容必须属于本次 source digest,不能沿用上次构建的陈旧结果。这是 UGC2.0 相对“把当前目录压成 zip”的实质提升。
资产例子:给 Boss 更换模型
| 系统 | 创作者的主要动作 | 最容易遗漏的问题 |
|---|---|---|
| 绿洲启元 | 替换 Blueprint/Mesh、材质、Skeleton 和动画引用,再验证 Cook | 骨骼不兼容、硬引用扩大包体、旧实例覆盖默认值 |
| Roblox | 上传 Mesh/Texture,设置 Asset ID,更新 Package | 权限/审核、Package 本地修改阻止自动更新、第三方脚本 |
| UGC1.0 | 从资源库选择可用外观或平台支持的自定义资源 | 资源下架、引用计数、分包未下载、元件不支持该资源类型 |
| UGC2.0 | 更新 Prefab 的逻辑资产引用,由 Artifact 固化依赖 | 异步加载失败、源/派生版本不一致、运行时对象在回调前销毁 |
19.3 战斗链:平台提供对象,不等于已经提供正确战斗
一个完整技能至少经过:
输入与本地预览
→ 施放请求
→ 权威校验:角色/槽位/冷却/资源/目标/频率
→ Prepare / Active / End 状态机
→ 命中或范围查询
→ 伤害、护盾、治疗、阵营与状态结算
→ Health/资源等权威状态变化
→ 动画、特效、音频、UI 反馈
→ 死亡、奖励、任务和回合系统消费结果绿洲启元下的创作者责任
如果平台只开放 Actor/Component、Blueprint/Lua 和 Gameplay Framework,战斗框架主要由高级作者搭建:输入放在 Client/Controller,施放和伤害在 DS 校验,角色状态放在可复制对象,动画/特效在客户端表现。若平台额外提供成品战斗组件,作者门槛才会下降。
高级作者需要直接处理 UE 世界查询、碰撞通道、Actor 所有权、RPC、复制状态、动画通知和对象生命周期。引擎提供物理与网络机制,但“冷却是否权威、重复 RPC 是否二次伤害、Projectile 销毁后迟到命中如何处理”仍是作品逻辑。
Roblox 下的创作者责任
Roblox 提供角色、Instance、物理、Raycast 和 Remote 等平台原语,但具体武器框架通常由作品编写。客户端可以立即播放瞄准和后坐表现,服务端必须重新校验射速、弹药、角色状态、目标和空间关系。
还要考虑 Physics Network Ownership:客户端拥有某些非锚定物理对象时能影响模拟结果,因此不能仅凭客户端 Touched 或命中报告结算关键伤害。作者不用写物理复制器,却必须知道哪些观测不可信。
UGC1.0 下的创作者责任
UGC1.0 已有枪械、近战、辅助瞄准、伤害区域、技能编辑等专用能力。普通作者主要配置武器数值、目标规则、事件和表现引用,平台元件负责输入接入、Actor/Component、权威伤害和同步。
优点是作者不必重建一套战斗状态机。限制是可表达范围受元件契约约束:如果要增加“蓄力中可移动、被特定伤害打断、释放后生成持续跟踪弹并继承施法快照”,现有属性无法表达时,就需要扩展元件/Coding API,甚至修改运行时。
UGC2.0 下的创作者责任
UGC2.0 源码已经把一些通用战斗不变量下沉到平台:
- AbilityComponent 挂在 Humanoid 下,保存设计态初始槽位和只读运行镜像。
- 每次施放有单调递增
CastId,槽位有SlotGeneration,组件状态有Revision,用于拒绝旧槽位、旧施放和乱序结果。 - 执行阶段区分 Preparing、Active、Ended;结束原因区分完成、准备期取消、激活期打断、Owner 死亡、被其他动作替换和脚本错误。
- TargetInfo 和 Properties 在施放上下文中冻结,防止执行中途配置变化改变本次技能含义。
- 拒绝码覆盖冷却、权限、执行上限、过期 Revision、过期 Execution、参数过大和状态转换非法。
- DamageService 只允许服务端结算,校验 Target 和 Source,处理阵营、伤害类型、攻击/受伤倍率、暴击、护盾、Health 和致死结果。
- 对同一目标设置结算互斥,避免伤害回调重入导致重复修改;成功后再广播结构化 DamageResult/HealResult。
平台原语解决了“状态机和结算必须一致”,创作者仍负责:
- 定义技能类的
CanActivate/OnPrepare/OnActivate/OnEnd; - 选择 TargetInfo 结构和服务端验证方式;
- 设计伤害公式所需属性、效果持续时间和叠加规则;
- 把 CastId 关联到持续特效、Projectile 和清理动作;
- 让死亡、掉落、任务、经验和回合订阅结构化结算结果,而不是各自再次扣血。
这里的边界很重要:DamageService 是唯一结算入口,不代表作者只传一个数字就完成战斗。作者仍要设计技能语义,平台负责把公共不变量做对。
战斗例子:实现一把蓄力法杖
| 阶段 | 绿洲启元 | Roblox | UGC1.0 | UGC2.0 |
|---|---|---|---|---|
| 资产 | Mesh/材质/动画/特效进入 UE 资产链 | 云 Mesh/Animation/Audio ID | 选择官方武器/技能资源与支持的自定义表现 | Prefab + Animation/Effect Asset ID 进入 Artifact |
| 输入预览 | Client Blueprint/Lua 自行实现 | LocalScript 自行实现 | 元件/技能编辑器提供固定预览 | Client Script + 指示器 Prefab |
| 施放状态机 | 作者实现或复用平台组件 | 作者 ModuleScript 实现 | 官方武器/技能元件内置 | AbilityService + 自定义 Ability 类 |
| 权威校验 | DS 校验冷却、资源、目标 | Server Script 校验 Remote | 元件内部校验;Coding 只能走允许接口 | RequestCast → Revision/CastId/Phase 校验 |
| 伤害 | DS 世界查询与伤害逻辑 | 服务端 Raycast/空间校验和 Health 修改 | 官方伤害/武器组件 | Server Ability 调 DamageService |
| 表现 | Client RPC/复制状态驱动 | 本地预测,服务端结果纠正 | 元件事件驱动动画特效 | CastId 绑定持续 FX,OnEnd 清理 |
| 扩展代价 | 需要 UE/Blueprint/Lua 能力 | 脚本自由,但安全和框架自负 | 超出属性边界需要平台扩元件 | 多数玩法扩展写脚本;底层查询/结算仍走 Service |
19.4 AI 与 NPC:行为配置和运行时状态必须分开
AI 系统至少包含 NPC 身份、出生/销毁、感知、目标选择、Blackboard、行为调度、导航、战斗、重生和多人权威。
- 绿洲启元:高级作者使用平台开放的 UE Actor、AI、导航和 Blueprint/Lua 能力。需要理解 AI 运行在 DS、导航数据属于场景构建结果、客户端只消费位置和表现;大量 AI 的 Tick、感知和路径查询必须预算化。
- Roblox:作者通常用 Model/角色对象、Pathfinding 等服务和 Luau 组织状态机。需要自己处理路径失败、动态障碍、目标失效、网络所有权与服务端判定,平台不自动提供完整 NPC 行为框架。
- UGC1.0:NPCCharacter、四足生物、路径、波次、出生点等作为领域元件存在。作者配置模板、路径和触发条件;平台维护 NPC Actor、导航接入和同步。自定义行为受已开放参数与事件限制。
- UGC2.0:NPC 是 Workspace 中带正整数 NPCId 的非玩家 Humanoid;NPCManager 可按 ID 查找,未注册时懒扫描 Workspace 并回填,重复 ID 保留首个实例。BehaviorTree 只能挂在 BehaviorComponent 下,内置树类型和名称固定,同类不能重复。
UGC2.0 的 BehaviorTree 还清楚地区分设计态与运行态:
EnableSignal/DisableSignal/bResetBehaviorOnRespawn属于可保存的作者配置;Enabled/bActive是运行态,不保存、不克隆、不复制;- Blackboard 数据按密封 Schema 校验,只允许 Server 修改;
- BlackboardChanged、Activated、Deactivated 是本地运行 Signal,不作为网络状态复制。
这意味着作者不能把 bActive 保存成“下次进游戏继续追人”,也不能在 Client 直接改 Blackboard。需要跨端显示的 Boss 目标或阶段,应由独立可复制状态表达;行为树内部工作内存只服务 DS 决策。
AI 例子:Boss 低血量进入狂暴
DamageService 完成一次权威伤害
→ Boss Health 低于阈值
→ Server Script 原子设置 Phase = Enraged
→ 更新 BehaviorTree Blackboard / 激活狂暴树
→ 可复制 Phase 驱动客户端血条、音乐和场景特效
→ 行为树内部目标、路径和临时冷却仍只留在 Server不要直接把客户端血条百分比作为 AI 切阶段条件,也不要复制整份 Blackboard。复制稳定业务事实,保留高频 AI 工作状态在服务端。
19.5 剧情 NPC:不是给 NPC 挂一段文本
战斗 NPC 的核心是感知、目标和行动;剧情 NPC 的核心是会话、条件、分支和副作用。两者可以共用角色外观、移动和动画,却不应共用一份模糊的“NPC 状态”。一个可用的剧情系统至少包含八层:
NPC 身份与交互入口
→ 会话作用域:个人 / 队伍 / 全局
→ 前置条件:任务、道具、阵营、时间、已看分支
→ 当前节点:说话人、文本、语音、立绘、动作、镜头
→ 可选分支及其条件
→ 权威副作用:接任务、扣道具、发奖励、开门、切阶段
→ 表现副作用:UI、动画、音频、镜头、角色走位
→ 检查点、跳过、重进、多人冲突和版本迁移只把台词存成字符串数组,最多能做线性聊天;一旦出现“持有钥匙才显示选项”“选择后全队开门”“中途断线后恢复”“两个人同时找同一个 NPC 对话”,就必须把剧情当作状态机。
19.5.1 剧情数据和运行会话要分开
设计态剧情图可以是只读内容:
DialogueGraph
GraphId / Version
Nodes[nodeId]
SpeakerId / TextKey / VoiceAssetId
EnterPresentation[]
Choices[choiceId]
Condition[]
NextNodeId
AuthoritativeEffects[]运行时则应创建独立会话,而不是直接修改剧情资产:
StorySession
SessionId
ScopeOwner -- Player / Party / World
GraphId + Version
CurrentNodeId
Revision
State -- Entering / WaitingChoice / Applying / Ended
ParticipantIds
AppliedEffectKeys -- 幂等副作用记录客户端提交的不是“我要获得奖励”,而是:
Choose(SessionId, NodeId, ChoiceId, Revision)服务端重新检查会话所有者、距离、当前节点、Revision、选项条件和副作用资格,再以 SessionId:NodeId:ChoiceId 一类稳定键保证奖励、扣道具和开门只执行一次。提交完成后先更新权威剧情/任务状态,再把新节点快照发给客户端。这样重复点击、迟到请求和断线重试不会重复发奖。
19.5.2 四个平台为什么会给出不同解法
| 平台 | 简单对话怎样做 | 复杂分支怎样做 | 创作者最需要负责什么 |
|---|---|---|---|
| 绿洲启元 | 在平台开放范围内组合交互对象、UI、Blueprint/Lua 与角色表现 | 用作品侧剧情数据/状态机连接 GamePart、任务、镜头和关卡对象 | 定义 DS 权威状态、剧情资产结构、跳过/重进和演出恢复;公开文档不能证明平台已内置完整剧情图编辑器 |
| Roblox | Dialog/DialogChoice 可承载简单气泡和选择,ProximityPrompt 负责通用近距离交互 | ModuleScript 保存剧情图,Server Script 管会话与副作用,LocalScript 管 GUI、相机、语音和动画 | 自建条件、分支、存档、本地化、Remote 校验和多人会话策略 |
| UGC1.0 | 剧情编辑器直接配置 NPC 交互与对话内容 | 对话组、步骤、选项、立绘与任务/场景 Signal 形成领域配置 | 主要做内容连接和条件配置;超出编辑器语法时依赖 Coding 或平台扩展 |
| UGC2.0 | 可由 NPC/Humanoid、ClickDetector、GUI、Animation/Sound 和脚本拼出 | 当前已核实对象层未见完整 DialogueGraph/StoryService,需要作品脚本自建,或平台继续产品化 | 自己维护会话、Revision、权威副作用和 UI Presenter;不要误以为有 NPCManager 就等于有剧情系统 |
Roblox 官方 Dialog 解决的是“玩家和一个世界对象进行简单选择式交流”,ProximityPrompt 解决的是“靠近并触发交互”;它们都不是任务事务或完整叙事系统。UGC2.0 当前的 ClickDetector 同样只提供交互距离与点击 Signal,不会自动生成剧情分支。
UGC1.0 源码资产能看到独立的 UGCPlotEditor、NPCInteractEditor、对话组/选项/图片属性组件,以及任务对话下拉关联。这说明平台把剧情内容结构做成了领域编辑器,而不是要求普通作者先搭一套通用脚本框架。它的代价也很直接:可用节点、条件、表现轨和副作用由平台预先定义。
UGC2.0 更合理的演进方向不是给每个 NPC 再塞几十个属性,而是把剧情拆成可复用的 DialogueGraph 资产、服务端 StoryService、客户端 DialoguePresenter 和受控 Effect Registry。这里是架构建议,不是对当前仓库完成度的陈述。
19.5.3 多人剧情真正难在作用域
| 问题 | 个人剧情 | 队伍剧情 | 世界剧情 |
|---|---|---|---|
| 当前节点归谁 | 每个 Player 一份 | PartyId 一份 | 当前 Server/世界事件一份 |
| 谁能选择 | 当前玩家 | 队长、投票或任一成员 | 需要明确抢占、投票或系统自动推进 |
| 新加入怎样恢复 | 读取个人检查点 | 获取队伍会话快照 | 获取世界阶段和当前演出降级状态 |
| 奖励怎样发 | 按玩家幂等发放 | 逐成员资格校验 | 世界状态与个人奖励分开提交 |
| NPC 是否忙碌 | 通常不必全局锁 | 可对队伍独占 | 必须定义并发、排队或多会话实例 |
镜头、字幕和语音一般是客户端局部表现;开门、任务完成、道具增减和 Boss 出生是服务端事实。不要因为剧情看起来“只是播放动画”,就把分支和奖励也交给客户端。
19.5.4 端到端例子:守卫走来、对话、接任务并开门
玩家进入交互范围
→ Server 校验守卫/NPC 身份、距离和前置任务
→ 创建个人或队伍 StorySession,返回节点快照
→ Client 打开对话 UI,播放守卫转身、招手和局部镜头
→ 玩家选择“我带来了通行证”
→ Server 用 Revision 校验选择,原子扣除通行证并推进任务
→ Server 将 DoorState 设为 Opening
→ 所有客户端根据 DoorState 播放同一段开门运动
→ 当前玩家继续下一句;其他玩家只看到世界门状态,不必进入其私人对话 UI四个平台的落点不同:
- 绿洲启元:交互、角色移动、UI、镜头和门 Actor 可用 Blueprint/Lua/GamePart 连接,DS 保存任务与门状态;Level Sequence 类演出属于客户端呈现,不能作为扣道具的权威入口。
- Roblox:用 ProximityPrompt 或 Dialog 起交互,Server Module 管 StorySession,Remote 下发节点,LocalScript 管 ScreenGui/Camera;守卫用 Humanoid/Pathfinding 移动,门用 Tween/Pivot 或约束运动。
- UGC1.0:NPC 交互编辑器、对话组/选项、任务关联和 Signal Modifier 表达主链,守卫路径和门运动使用平台元件;普通作者主要配置内容,不直接实现会话协议。
- UGC2.0:Server Script 组合 NPCManager、ClickDetector、自定义 StoryController、RemoteEvent 和可复制 DoorState;Client Script 组合 ScreenGui、Camera、AnimationTrack。守卫导航走 Humanoid/行为树,门走 Mover/Tween,存档通过服务 API。当前缺少的标准剧情层需要明确补齐,而不是让每个作品复制一套脆弱实现。
19.6 怎样让一个东西动起来:先确定是哪一种“动”
“把东西从 A 移到 B”看起来是最简单的问题,实际上至少有六种不同语义。选错方案会直接造成穿模、不同步、晚加入位置错误或动画和碰撞脱节。
| 运动类型 | 典型对象 | 合理驱动 | 权威与同步 | 不能混淆的点 |
|---|---|---|---|---|
| 瞬时位姿 | 传送、出生、编辑摆放 | 设置 Transform/CFrame 或 Pivot | 由权威端写最终状态 | 不是连续运动;需要 Sweep/落点校验时不能直接瞬移 |
| 确定性插值 | 门、升降台、UI、无物理机关 | Tween/Curve/Timeline | 复制运动状态与起始时间,客户端可重建 | 不适合让非 Anchored 刚体“假装受力” |
| 角色移动 | 玩家、NPC、Boss | CharacterMovement/Humanoid + 导航/控制输入 | Server 决策,Movement 系统负责位置演进 | MoveTo 是移动命令,不等于已经算好避障路径 |
| 物理运动 | 球、载具、可推动箱子 | 力、速度、约束和物理模拟 | 明确 Server/Client network owner,并校验结果 | 不能只在某个客户端 Tween Transform 后当作全局物理事实 |
| 骨骼/形变动画 | 人物挥手、怪物攻击、机械骨架 | Animator/AnimationTrack/AnimBP | 通常复制动作语义或轨道状态,客户端求 Pose | 骨骼在动不代表角色根节点已经位移 |
| 演出运动 | 镜头、角色站位、灯光、多对象表演 | Sequence/Timeline + 业务状态机 | 关键剧情阶段权威;镜头和多数表现本地播放 | 必须处理跳过、晚加入、打断和结束时状态恢复 |
19.6.1 选择方法
可以按下面的顺序判断:
只是瞬时换位置?
→ Transform / PivotTo
需要碰撞和外力真实参与?
→ Physics / Constraint / MovementComponent
是会避障的角色?
→ Pathfinding 产出路径 + Humanoid/CharacterMovement 执行
只是确定性机关或 UI 过渡?
→ Tween / Timeline
只改变骨骼姿态?
→ Animator / AnimationTrack
需要多个对象、镜头和音频同步演出?
→ Sequence/剧情状态机;关键结果另存权威状态动画、导航和位移经常同时发生,但职责不同。例如 NPC 走向玩家:导航决定“走哪条路”,Movement 决定“根节点怎样移动和碰撞”,动画系统根据速度播放 Walk/Run,转向系统决定朝向,网络层让其他客户端看到结果。只播放 Walk 动画,NPC 会原地踏步;只改 Position,NPC 会滑行且可能穿墙。
19.6.2 绿洲启元:直接使用 UE 生产范式
在平台允许的能力范围内,高级作者可以按 UE 语义选择方案:
- 静态 Actor/Component 的位姿由 Transform 和 Attachment 层级表达;
- 门、平台等确定性机关可用 Timeline、曲线或封装好的移动 Component;
- Character/Pawn 通过 Movement、AI Controller 和导航体系移动;
- 刚体通过 Physics、Constraint、力和速度驱动;
- Skeletal Mesh 使用 Animation Blueprint/Montage/Sequence 表现;
- 多对象演出使用 Sequence 或作品自己的演出管理器。
作者需要额外决定运动在哪一端驱动、Actor 是否复制、初始状态怎样到达晚加入客户端、移动平台上的角色怎样跟随,以及演出被跳过后最终 Transform/碰撞/输入模式怎样恢复。平台开放 UE 能力越多,这些问题越接近传统客户端/玩法开发。
19.6.3 Roblox:平台原语齐全,但方案由作品选择
- Part/Model 的整体摆放可以设置 CFrame,Model 更适合使用统一 Pivot 和
PivotTo(); - Anchored 门、UI 和装饰运动可用
TweenService:Create(); - Humanoid 移动可结合
PathfindingService:CreatePath()、ComputeAsync()、Waypoints 和 Move/MoveTo;动态障碍出现后要监听 Blocked 并重新规划; - 物理物体使用 Assembly 速度、约束或力,关键玩法还要考虑 Network Ownership 和服务端校验;
- Animator/AnimationTrack 管骨骼动画;相机和玩家专属演出由 LocalScript 播放。
Roblox 帮作者实现了跨平台运行、物理和对象复制,但不会自动决定“门的 Tween 应在 Server 播还是每个 Client 播”“NPC 路径失败后怎么办”“物理球由谁拥有”。简单演示几行 Luau 就能动,稳定多人玩法仍需要状态设计。
19.6.4 UGC1.0:把常用运动做成可配置句子
源码中的 MoeMoveStateComponent 及大量专用机关说明,UGC1.0 倾向于把运动做成元件能力。移动平台、自动门、旋转杆、锤子、摆动物、激光、浮台、转盘和木排都复用运动状态或在其上做领域封装。
创作者通常配置:
- 位移、旋转或缩放类型;
- 世界/相对坐标和目标增量;
- 持续时间、开始延迟、停留时间;
- 自动播放、往返、循环次数和停止行为;
- 由哪个 Signal 启动、暂停或重置;
- NPC 路径、速度、等待点以及动画资源。
平台元件负责 UE Component、碰撞、复制、父子跟随和运行生命周期。普通作者不写 Tick,也不需要自己复制每帧位置;但如果要做贝塞尔轨道、动态避障载具或多个运动混合,就可能超出 MoveState 契约,需要平台扩展。
19.6.5 UGC2.0:同一个对象平台上保留多条运动通道
UGC2.0 源码不是只提供一个 SetPosition:
TweenService支持 number、Vector、CFrame、Color、UDim 等属性的缓动,处理 Play/Pause/Cancel/Completed;- 对同一对象同一属性的重叠 Tween 通过 compositor 合成,Server/shadow 层与 Client-local 层有稳定顺序;
- 对空间对象按观察距离降采样,但 Play、Pause、Resume、结束和快照是强制呈现边界;
- 对未 Anchored 的 BasePart,Position/CFrame/Rotation/Size 等物理属性 Tween 会被拒绝,避免缓动与物理模拟争夺控制权;
- 可插入的
Mover_Tween/Mover 类把 Position、Rotation、Scale、Waypoint,Target/Speed、World/Local、缓动、延迟、往返、重复和 Start/Pause Event 做成配置; - 模型/父子层级运动应走 Pivot/层级语义,源码中直接写 CFrame 曾会绕过父动子随,因此局部旋转实现优先尝试
PivotTo; - NPC 行为通过服务端
Humanoid:MoveTo和MoveToFinished执行,PatrolBehaviorTree 保存有序 Waypoints/WaitTime,并可在运行时查询当前路点; - 动画由 Animator 加载 Animation Asset,返回 AnimationTrack;轨道支持 Priority、Weight、Speed、TimePosition、Override/Additive、Layer、BoneMask 和 Marker;Marker 是否上报 Server 需要显式选择。
这组能力的关键不是 API 数量,而是作者要选对通道:
| 需求 | UGC2.0 应优先使用 | 原因 |
|---|---|---|
| 门或升降台 | Mover/Tween + 权威开关状态 | 轨迹确定、可恢复,不需要物理求解 |
| 巡逻 NPC | PatrolBehaviorTree/Humanoid MoveTo | 有到达、取消、路点等待和 Server 决策语义 |
| 可推动箱子 | 物理对象与服务端规则 | Tween 会和碰撞/所有权冲突 |
| NPC 挥手 | AnimationTrack | 只改变 Pose,不应改世界位置 |
| 玩家专属镜头 | Client Camera/本地 Timeline | 不应复制给其他玩家,也不应阻塞权威逻辑 |
| 多段路径机关 | Mover Waypoint 或平台化轨迹类 | 需要段级缓动、往返、暂停和晚加入恢复 |
Animation Marker 适合触发脚步声、镜头震动等表现,也可以上报供服务端观察;但关键伤害不应只靠客户端 Marker 发生。战斗的有效帧仍应由 Ability/Cast 状态和服务端查询决定,动画只是与它对齐的表现轨。
19.6.6 不要每帧复制 Transform,复制“运动事实”
对于确定性门和平台,更稳定的网络模型是:
MotionState {
MotionId,
Revision,
Phase, -- Closed / Opening / Open / Closing
StartServerTime,
FromTransform,
ToTransform,
Duration,
CurveId
}客户端根据 ServerTime 计算当前 alpha:
alpha = clamp((ServerTimeNow - StartServerTime) / Duration, 0, 1)
transform = Evaluate(CurveId, From, To, alpha)晚加入只需拿当前 MotionState,就能直接落到正确进度;丢失一个中间样本也不会永久错误。Server 保留碰撞、完成条件和 Revision 的权威,客户端负责平滑呈现。对于不可确定的刚体运动,才需要物理状态快照、速度和纠偏,而不是套用这套 Tween 公式。
19.6.7 运动例子:一扇会打断、可晚加入的自动门
| 平台 | 作者怎样实现 | 平台承担什么 | 关键验收点 |
|---|---|---|---|
| 绿洲启元 | 门 Actor/Blueprint 保存 DoorState,DS 改状态,Timeline/Component 按起始时间求位置 | UE Transform、碰撞、复制与客户端插值能力 | 中途反向、玩家被门夹住、晚加入、Sequence 跳过后的最终碰撞 |
| Roblox | Server 保存 Attribute/Value 状态;客户端或 Server 根据统一时间 Tween/Pivot 模型 | Instance 复制、Tween/Pivot、物理和 Remote | Model Pivot 正确、Tween 被替换时取消、Network Ownership 不参与 Anchored 门权威 |
| UGC1.0 | 配置自动门/MoveState 的距离、时长、往返和触发 Signal | 专用元件兑现运动、碰撞、同步和保存 | 配置边界、触发去重、元件版本兼容 |
| UGC2.0 | 可复制 DoorState/Revision 驱动 Mover/Tween;客户端表现层续播,Server 判定完成 | Tween compositor、Mover 配置、Instance 属性与复制底座 | 重叠 Tween、暂停/恢复、Anchored 检查、父子 Pivot、StopPlay 清理 |
19.6.8 运动例子:剧情 NPC 走到玩家面前再开口
正确顺序不是“同时播放走路动画和对话计时器”,而是:
- Server 创建 StorySession,并暂时锁定该会话的 NPC 行为槽。
- 计算或选择接近点,调用导航/MoveTo,而不是直接把 NPC Tween 到玩家坐标。
- Movement 产生速度,动画状态机或 AnimationTrack 播放 Walk;转向独立处理。
- 收到到达成功后再切 Idle/Turn,并让会话进入 WaitingChoice。
- 移动失败、目标离开、NPC 死亡或超时都必须走明确降级分支,而不是永远卡住剧情。
- Client 才在节点进入后打开 UI、切镜头和播放台词。
- 对话结束后释放行为锁,恢复 Patrol/Follow;若玩家跳过,直接应用最终站位与权威副作用。
UGC1.0 可以把这条链封装成“NPC 路径 + 对话触发 + Signal”;Roblox/UGC2.0 的高级作者更可能写状态机;绿洲作者可以用 AI/Movement 和演出工具编排。差异在工具形状,运行时必须处理的成功、失败、取消和恢复语义并不会消失。
19.7 UI、任务、商店和成就:界面只是系统投影
UI 最常见的架构错误,是把按钮和面板状态当成业务真相。正确链路应是:
UI 发出意图
→ 权威系统校验和修改状态
→ 状态/结果通知 UI
→ UI 渲染、动画和本地交互四种方案的要求
- 绿洲启元:UI 主要在 Client。作者需要把公共状态放在 GameState/PlayerState/Actor,而不是 Widget;Widget 销毁重建后应能从当前状态恢复。
- Roblox:Gui Instance 与 LocalScript 在客户端。商店购买、任务完成和奖励发放必须走 Server Script;ScreenGui 只显示服务器认可的库存、货币和进度。
- UGC1.0:UI 编辑器、任务、商店、成就、对话、音乐和摄像机均有独立模型与 Signal Modifier。作者通过官方控件和配置关联领域对象,平台负责保存和运行。复杂度集中在跨域引用,例如任务奖励引用商店道具、成就图标引用公共贴图、对话选项触发场景元件。
- UGC2.0:ScreenGui、Frame、Button、Text、ItemSlot 等都是 Instance,Client Script 订阅 Service/复制状态。UI 资产可在 Artifact 构建中生成图集等派生数据。作者可以自由组织 UI,但必须自己做 ViewModel/Presenter 边界,避免每个按钮直接操作 DamageService、DataStore 或场景对象。
系统例子:任务“击败 Boss 并领取奖励”
- DamageService 产生致死结果,Boss 系统确认目标身份与当前回合。
- Server 任务系统把任务从
InProgress改为Completed,使用幂等任务实例 ID 防止重复完成。 - 奖励服务在服务端提交货币/道具;失败时不能先把 UI 显示成已领取。
- 玩家存档记录任务与奖励事务版本。
- 客户端 UI 观察任务快照,显示完成与可领取状态。
- 点击领取只是请求;Server 再校验任务、背包容量和是否已领取。
UGC1.0 中这条链更多由任务、成就、商店等成品模块连接;Roblox/UGC2.0 中高级作者需要建立服务模块和事务边界;绿洲中可用 GameMode/State 与平台后端 API 编排。UI 技术不同,但权威数据流相同。
19.8 系统服务与持久化:先分清四种生命周期
| 数据生命周期 | 例子 | 合理位置 |
|---|---|---|
| 单帧/短时表现 | 命中特效、悬停状态、相机震动 | Client 本地内存 |
| 单局服务端状态 | Boss 血量、波次、临时分数、掉落实体 | Server/DS 内存 + 必要复制 |
| 玩家跨局状态 | 等级、背包、任务、货币 | 服务端持久化服务 |
| 作品发布内容 | 场景、脚本、Prefab、资产依赖 | 源工程 + 版本化制品 |
四类数据不能混存。SceneSerializer 不是玩家数据库,DataStore 不是实时战斗状态,Artifact 也不应包含某一局 Boss 当前血量。
Roblox
DataStore 是跨会话、跨 Place/Server 的持久化键值服务,只允许服务端脚本访问;高频临时数据应放内存或适合的临时存储。创作者要设计 key、Schema version、重试、幂等更新、并发冲突和玩家离场保存,不能每次金币变化都立刻发一次持久化请求。
UGC1.0
大多数作者使用平台已经定义的局内系统和存档接口,少接触底层数据库。作者仍要区分“地图发布配置”“玩家长期进度”和“本局状态”,并按元件能力配置。新增长期系统通常需要平台提供服务端协议、数据结构、限额、迁移与审核能力。
UGC2.0
当前源码中的 DataStoreService 明确标为 1.0 使用体验的临时兼容层,不能把它描述成已经成熟的长期 DataStore 终态。它仍展示了持久化服务应该承担的工程问题:
- 仅 Server 可读写,当前兼容接口限制 key/value 为 string;
- 写入先更新 DS 本地缓存,同玩家同 key 多次修改只保留最新值;
- 常规周期合并批量写,玩家离开时补最终写;
- 请求失败保留 dirty 数据,后续重试;
- 使用 revision 防止较晚返回的读请求覆盖更新的本地写;
- 对 key 长度、value 大小、dirty key 数和缓存总量设硬上限;
- 当前身份透传仍有测试期安全限制,不能当成正式可信账号链路。
创作者应该调用服务 API 并处理异步成功/失败,不应该依赖“调用 setValue 就已经永久落盘”。平台则必须补齐正式身份、条件更新、冲突策略、配额监控、数据迁移和线上恢复。
19.9 性能与诊断:开放越多,越要把成本展示给创作者
| 成本 | 创作者能做什么 | 平台应该提供什么 |
|---|---|---|
| 资产 | 控制贴图、网格、动画、音频和依赖规模 | 导入检查、派生处理、引用分析、包体报告 |
| 场景 | 减少无意义对象、Tick、碰撞和全局查询 | 实例/Actor 统计、热点分类、Streaming 诊断 |
| 战斗 | 限制空间查询、Projectile、Buff 和结算频率 | 服务耗时、拒绝码、命中链和伤害审计 |
| AI | 降低高频感知和寻路,按距离/阶段激活 | AI 预算、导航失败原因、行为树/Blackboard 调试 |
| UI | 避免高频全树更新和重复资源 | 重绘、布局、图集和内存报告 |
| 网络 | 区分状态、可靠事件和非可靠样本 | 每类/每实例带宽、队列、晚加入和丢弃统计 |
| 脚本 | 避免死循环、无界任务和泄漏连接 | VM CPU/内存、调用栈、任务 owner、配额与强制终止 |
| 持久化 | 合并写、幂等、处理失败 | 请求预算、重试、dirty 队列、最终保存和告警 |
UGC1.0 可以把限制做成“最多放多少个元件”的产品规则;UGC2.0/Roblox 这类开放平台则需要更细的 Cost Explorer,因为相同 Instance 数可能对应完全不同的脚本、物化和网络成本。只给一个总对象上限,会让创作者不知道该优化哪里。
UGC2.0 粒子源码已经体现这种治理思路:属性本身带取值校验,Beam 有最大绘制数、最大光束数和插值点上限;这比文档里写一句“不要放太多特效”更可执行。类似约束还应覆盖脚本时间、Remote 频率、动态对象创建和持久化请求。
19.10 端到端例子:制作一场多人 Boss 战
| 工作流 | 绿洲启元 | Roblox | UGC1.0 | UGC2.0 |
|---|---|---|---|---|
| Boss 资产 | UE Mesh/Skeleton/Animation/Material,Blueprint 聚合 | 云 Asset ID,Model/Package 聚合 | 选择 NPC/四足元件与官方资源 | Humanoid/SpatialObject Prefab + 逻辑资产引用 |
| 出生与身份 | DS Spawn Actor,保留权威引用 | Server 克隆 Model 到 Workspace,设 Attribute/Tag | 波次、出生点、NPC 元件 | Server 创建 Prefab;NPCId/UniqueId 建立身份 |
| AI | AIController/导航/Blueprint/Lua | 服务端 Luau + Pathfinding/状态机 | 行为和路径元件配置 | BehaviorComponent + BehaviorTree + Server Blackboard |
| 技能 | 作者或平台战斗组件实现 | 作者 Ability 模块与 Remote | 技能编辑器/武器元件 | AbilityComponent/Service + 自定义 Ability Class |
| 伤害 | DS 查询并修改权威属性 | Server 校验命中并修改状态 | 官方伤害与武器元件 | DamageService 统一处理来源、阵营、护盾、暴击和 Health |
| 阶段切换 | GameMode 驱动,GameState 复制 Phase | Server Module 驱动,复制 Attribute/Value | 计数/血量/事件元件和 Signal | Server Script 写可复制 Phase,并切 BehaviorTree/Ability |
| UI | Client Widget 观察复制状态 | LocalScript/Gui 观察服务端状态 | UI 编辑器绑定平台信号 | ScreenGui/Client Script 观察 Round/Boss 快照 |
| 奖励任务 | DS + 平台后端接口 | Server DataStore/服务模块 | 任务、成就、商店等成品系统 | 任务/奖励 Service + DataStore API |
| 特效音频 | UE 资产和客户端表现 | 云资产 + Client 表现 | 元件预设和资源库 | AssetService 异步加载 Effect,CastId/Phase 管清理 |
| 发布 | UE 依赖/Cook/白名单检查 | Place/Asset/Package 云发布 | 地图/资源/业务配置分域保存和打包 | Snapshot → Processor → Manifest → Artifact → 分片发布 |
四种创作者画像
- 绿洲作者更像“受约束的 UE 游戏开发者”:资产、Actor、框架和网络知识要求高,但能直接利用 UE 生产能力。
- Roblox 作者更像“平台全栈玩法开发者”:不用懂引擎内部,却要自己组织 Luau 服务、战斗、安全、云存储和 Package。
- UGC1.0 作者更像“游戏规则搭建者”:使用平台已经产品化的 NPC、技能、任务和 UI 句子,技术门槛低,但超出句法后依赖平台扩展。
- UGC2.0 作者更像“对象平台上的游戏开发者”:用 Class/Service/Prefab 和三 VM 写系统,平台提供统一对象、战斗原语、资产与制品底座,但作者仍要完成领域架构。
19.11 最终判断:自由度上升时,创作者负担转移到哪里
| 方案 | 平台替作者承担最多的部分 | 作者最主要的技术负担 |
|---|---|---|
| 绿洲启元 | 平台运行环境、开放范围、最终 Cook 与审核 | UE 资产、对象生命周期、玩法框架和网络配置 |
| Roblox | 引擎实现、跨端对象复制、云资产和服务基础设施 | Luau 系统架构、安全、战斗、持久化和性能 |
| UGC1.0 | 元件的资产、运行、同步、保存和领域系统 | 正确组合元件、理解契约、控制复杂连接 |
| UGC2.0 | Instance 内核、Binding、构造/属性复制、Artifact 与核心 Service | Class/VM 建模、领域脚本、权威、安全、异步资源和系统边界 |
所以 UGC2.0 的目标不应是“让创作者承担和 UE 程序员一样多的事情”。更合理的目标是:隐藏 UE 与传输细节,保留游戏设计不可避免的技术概念;再通过官方 Service、样例工程、模板 Prefab、静态检查、性能面板和错误诊断降低使用成本。
二十、三个常见误区
20.1 误区一:用了 UEEditor,就一定比 Roblox 更底层、更开放
不成立。绿洲启元更靠近 UE 的资产与 Actor/Component 语义,但开放范围仍由白名单和平台 API 决定。Roblox 不暴露底层引擎,却提供了更统一、更完整的平台对象与脚本组合能力。两者是在不同轴上开放。
20.2 误区二:UGC1.0 和 UGC2.0 都基于 UEClient,所以只是 UI 和脚本语言升级
不成立。UEClient 只说明宿主。UGC1.0 的核心抽象是官方元件;UGC2.0 的核心抽象是自研 Instance、Class、Service 与多 VM。前者扩展一种能力经常要新增一种元件,后者试图让创作者通过稳定原语自行组合新能力。
20.3 误区三:UGC2.0 就是“Roblox on UE”
可以把 Roblox 当作目标参照,但不能等同。二者都强调对象树、脚本和服务,但 UGC2.0 还要处理既有 UE 客户端、跨语言绑定、移动端资源限制、历史内容格式和业务发布链。架构形态相似,不代表对象语义、网络协议或工具成熟度已经相同。
二十一、面试回答模板
21.1 30 秒版本
四者最大的区别不是用了什么脚本语言,而是创作者面对什么对象。绿洲启元把一部分 UE 资产、Actor 和 Component 能力开放出来;Roblox 让创作者操作统一的 DataModel/Instance;UGC1.0 主要让创作者组合平台封装好的元件;UGC2.0 则在 UE 上自建 Instance、Class、Service 和 Edit/Client/Server 三类脚本运行域。对象封装层不同,脚本的身份、生命周期、网络权威和交付方式自然都会不同。
21.2 90 秒版本
我会沿一条端到端链比较。绿洲的源数据是 UE 资产,运行时是 Actor/Component,Lua 与 ECA 接入 UE 生命周期,网络和发布继续依赖 DS、复制与 Cook。Roblox 用 DataModel 贯穿 Studio、Server、Client,Script 也是 Instance,状态由平台复制,行为通过 Remote 跨界,Package 管理对象子树版本。UGC1.0 的能力单位是官方元件,一个元件要纵穿类型注册、属性组件、信号、Actor 工厂、保存和网络,所以创作者简单,但平台扩展成本高。UGC2.0 则把共性收进 Instance/Class/Service 内核,用三类 VM、构造复制、属性复制、SceneSerializer 和 Artifact 支撑上层脚本组合。它不是换 UI 或加 Luau,而是把能力交付单位从“专用元件”改成“通用对象原语”。
21.3 三分钟源码版
我不按开放程度简单排序,而看能力交付单位和端到端闭环。绿洲交付 UE 资产类型,创作者组合 Blueprint、Actor 和 Component,Lua/ECA 进入 Actor 生命周期,DS 和 Client 分工沿用 UE;优势是资产表达强,代价是 Cook、类型白名单和引擎版本治理重。Roblox 交付 Instance 与 Service,DataModel 同时是编辑、运行和复制模型,脚本、场景、UI、Package 都在同一对象语义里;服务端状态自动复制,跨边界意图通过 Remote,安全责任落在服务端校验。
UGC1.0 交付的是业务元件。源码里,一个元件从 typeId、bpPath、属性组件一直贯穿到 Signal Modifier、Actor 实例、保存和版本规则;场景、Coding、资源又是分域保存,所以每加一种能力都要做纵向全链路接入。Coding 虽然提高了表达力,但又增加了一套 RPC 序列化、批处理和压缩链,没有让旧元件体系自动消失。
UGC2.0 把这些共性集中到对象内核。Instance 统一处理 Parent、Signal 和生命周期,Class 属性描述符同时服务类型检查、编辑反射与复制;ObjectManager 管理 Lua 和原生对象;网络上把对象构造/拓扑、属性增量和 RemoteEvent 分开,晚加入先恢复对象图再接增量;保存侧由 SceneSerializer 处理 Class、Parent、引用和 VM 过滤,发布侧用 Snapshot、摘要和校验构建可追溯 Artifact。所以 2.0 的收益是上层玩法边际成本下降,风险是底座协议一旦错误会影响所有类。这是我认为四种架构最核心的差异。
21.4 追问:哪一种最好
没有脱离目标的最好。要快速交付、强治理和低门槛,UGC1.0 的厚封装有优势;要复用 UE 资产生产能力,绿洲启元的路线更直接;要形成长期开放生态,统一对象模型和安全脚本运行时更关键,Roblox 是成熟参照,UGC2.0 是向这个方向演进。但开放层级越低或组合能力越强,平台越要投入兼容、安全、调试、网络和发布基础设施。
21.5 追问:UGC2.0 为什么不等于 Roblox on UE
相似点只到“统一对象树、Service 和脚本运行时”这一层。Roblox 的 DataModel、复制、Package、沙箱和云服务是长期一体化平台;UGC2.0 还要桥接现有 UE World、原生 Actor/Component、移动端资源、历史业务系统和既有发布链。尤其是 Instance 是否物化为 UE 对象、三 VM 怎样切换、构造复制怎样和 UE 网络共存、Artifact 怎样固定原生依赖,都是额外问题。所以 Roblox 是产品和对象语义参照,不是可以直接照搬的实现。
21.6 追问:为什么不立刻淘汰 UGC1.0
1.0 的元件契约已经沉淀了大量内容、审核规则、移动端性能经验和线上兼容数据。2.0 的自由度更高,但对象内核、脚本安全、网络复制、调试和发布都要重新达到线上强度。合理演进不是把旧系统一次性推倒,而是识别哪些稳定能力继续作为平台原语或兼容适配,哪些高频专用元件迁成 Prefab/脚本库,再用数据迁移和双轨验证逐步收敛。
21.7 追问:为什么不能所有同步都用 RemoteEvent
因为 RemoteEvent 表达的是一次行为消息,不保证新玩家能恢复过去。血量、门状态、回合阶段这类持续事实必须有权威状态和快照;技能释放请求适合可靠消息;瞄准点这类高频样本适合可丢弃通道;特效可以本地预测。把四类数据都塞进可靠 Remote,会同时带来晚加入缺状态、重放不幂等和高频消息阻塞。
21.8 追问:为什么 Instance 不能全部映射成 Actor
Instance 是平台语义,Actor 是 UE World 中的实现载体。Folder、Script、Service、UI 和引用对象都不需要 Actor;只有需要 Transform、碰撞、渲染、物理或 World 查询的空间对象才应物化。全部 Actor 化会放大 Tick、GC、网络和 World 管理成本,全部 Lua 化又无法承接原生能力,所以需要按 Class 定义物化策略和生命周期。
21.9 追问:你怎样设计晚加入
我会把对象构造、属性基线和实时增量分层。服务端先确定基线序号,发送当前可见对象的 Class、身份、Parent 和初值;期间的新变化暂存。客户端完成对象图和引用恢复后确认,再按序应用增量并切实时流。协议还要处理重复、迟到、创建后立即删除、悬空引用和旧 session 数据。只补发几个当前属性不叫完整晚加入。
21.10 追问:四种方案下创作者都要懂同步吗
要区分同步机制和同步语义。UGC1.0 的普通作者只连接官方元件,复制和权威由元件实现,基本不用懂机制;绿洲的高级作者直接使用 UE 复制、RPC 和 Gameplay Framework,要求最高;Roblox 与 UGC2.0 隐藏了底层传输,但脚本作者仍要决定 Server/Client、状态还是事件、Remote 校验和晚加入。平台能隐藏“怎么发包”,不能替创作者决定“什么才是真实状态”。
21.11 追问:既然要降低门槛,为什么不把同步全部隐藏
可以隐藏传输,不能隐藏业务权威。平台能自动复制门的
IsOpen,却不知道谁有钥匙、客户端请求是否作弊、断线后是否保留占用、重连应恢复哪一轮状态。全部隐藏只能靠固定元件覆盖固定语义,也就是回到 UGC1.0。UGC2.0 要获得可编程性,就必须把少量稳定的分布式概念交给高级作者,同时通过 Service、模板、静态检查和调试工具降低出错成本。
21.12 追问:四种方案下做一把武器,创作者分别负责什么
绿洲高级作者要处理 UE 资产、Actor/Component、输入、DS 校验、复制和表现;Roblox 作者不用懂底层引擎,但要自己搭 Luau 武器框架、Remote 安全和服务端命中;UGC1.0 作者主要配置枪械/近战元件、数值和事件,平台兑现伤害和同步;UGC2.0 作者用 Ability、DamageService、Prefab 和三 VM 编排技能,平台负责状态机、结算入口、对象复制和资产制品。差异不是都能不能做,而是战斗不变量由平台还是作品负责。
21.13 追问:UGC2.0 已有 DamageService,为什么战斗仍然需要作者设计
DamageService 只能保证服务端入口、目标/来源校验、阵营、护盾、暴击和 Health 结算等公共不变量。技能的目标规则、前摇、打断、资源、Projectile、Buff 叠加、Boss 阶段、表现与奖励仍是领域逻辑。平台应统一“怎样安全结算伤害”,不能替每个作品决定“什么情况下应该造成什么伤害”。
21.14 追问:四种方案的资产体系差别是什么
绿洲最接近 UE 资产图,作者直接面对 uasset、Blueprint、依赖和 Cook;Roblox 使用云 Asset ID,并用 Package 管 Instance 子树、权限和版本;UGC1.0 主要把官方资源与受控自定义资源登记进资源描述、引用表和地图/资源包;UGC2.0 希望以逻辑 Asset ID、Prefab 和 Artifact 隔离 UE 路径,运行时异步解析,发布时用 source digest、处理器和 Manifest 固化依赖。对应地,作者从“懂引擎资产”逐步转向“懂平台资产身份、异步和版本”。
21.15 追问:四种平台怎样实现剧情 NPC
剧情 NPC 不是一组台词,而是交互入口、剧情图、运行会话、权威副作用和客户端演出的组合。绿洲作者会用开放的 UE/GamePart/Blueprint/Lua 能力自行编排;Roblox 简单场景可用 Dialog 或 ProximityPrompt,复杂场景通常自建 Server Story Module 和客户端 UI;UGC1.0 已把对话组、步骤、选项、立绘、任务关联和 Signal 做成领域编辑器;UGC2.0 当前能用 NPC、ClickDetector、GUI、Remote、动画和脚本组合,但我没有在已核实源码中看到成熟的 StoryService,所以应继续平台化 DialogueGraph、会话 Revision 和受控副作用,而不能把 NPCManager 当成剧情框架。
21.16 追问:怎样让一个东西动起来
我不会先回答“改 Position”。先判断它是瞬移、确定性机关、角色导航、物理刚体、骨骼动画还是多对象演出。门和升降台适合状态 + Tween/Timeline;NPC 应由寻路给路径、Movement 执行根节点位移、Animator 根据速度出 Pose;箱子和载具要走物理与所有权;镜头只在 Client。多人下尽量复制 MotionId、Phase、StartServerTime、起终点和曲线,让客户端重建进度,而不是每帧可靠复制 Transform。晚加入、打断、反向、碰撞和失败恢复必须在方案里一起回答。
21.17 追问:NPC 走到玩家面前再触发对话,怎么设计
Server 先创建带 SessionId/Revision 的剧情会话并锁定 NPC 行为槽,然后计算接近点并下发 MoveTo;到达成功后切 Idle/Turn,再把会话推进到 WaitingChoice,客户端此时才打开 UI、镜头和台词。移动失败、玩家离开、NPC 死亡或超时都要有降级分支。选择提交只带 SessionId、NodeId、ChoiceId 和 Revision,Server 重验条件并幂等执行任务、道具或开门副作用。结束或跳过后应用最终权威状态,再释放锁并恢复 Patrol。运动、动画、剧情和任务是四个协作系统,不能靠一个动画完成。
二十二、源码证据索引
以下索引用于复核,不需要在面试中逐个背文件名。
| 结论 | 主要源码入口 | 能证明什么 |
|---|---|---|
| UGC1.0 元件是注册契约 | PlaceableConfigModel/PlaceableConfigModel.lua | typeId、类路径、属性映射、缩放/显隐/绑定、版本、白名单和投放规则共同定义元件 |
| UGC1.0 Signal 是跨领域寻址 | Signal/UGCSignalItem.lua、UGCSignalData.lua | Signal 保存模块、scene、组件、索引、属性、发送/接收方向,并按业务域组织 |
| UGC1.0 依赖领域适配 | SignalModify/SignalModifier/* | PlaceableActor、UI、对话、任务、商店、音乐、摄像机以及计数/定时/加分各有 Modifier |
| UGC1.0 保存是跨域编排 | SceneFlow/UGCEditorSaveFlowManager.lua、UGCEditorLoadFlowMananger.lua | 场景分层、玩法配置、Coding、UI 和资源分别导出、校验、打包与分阶段恢复 |
| UGC1.0 Coding 有独立网络链 | UGCPFramework/Replication/UGCPSerializerRouter.lua、UGCPFuncRepMgr.lua | 调用要序列化、批处理、压缩、限制包大小,并区分上行、单播和广播 |
| UGC1.0 剧情是领域编辑器 | Blueprints/UI/UGCPlotEditor/*、BPAC_Dialogue*Attr、任务对话关联控件 | 对话组、步骤、选项、图片、NPC 交互和任务关联不是一段自由文本,而是结构化创作模型 |
| UGC1.0 常用运动由元件兑现 | MoeMoveStateComponent 的调用方、移动平台/自动门/旋转机关等 Actor | 位移/旋转/缩放、世界/相对坐标、持续时间、往返、循环和触发被复用为领域运动能力 |
| UGC2.0 Parent 是受约束拓扑 | Objects/Instance.lua | SetParent、Destroy、Child/Descendant/Ancestry Signal 与合法性检查属于对象内核 |
| UGC2.0 属性是统一协议 | Core/Class.lua | 类型、只读、服务端写、复制、getter/setter、Signal 和脏标记由描述符统一承载 |
| UGC2.0 管理 Lua/原生双对象 | Manager/ObjectManager.lua | 类注册、ObjectId 注册、原生创建、网络绑定、Parent 与 OnInit 存在明确次序 |
| UGC2.0 保存对象图而非只存 JSON | Serialization/SceneSerializer.lua、SceneSerializerFB.lua | Class、Parent、引用、目标 VM、Prefab 来源、pending 引用、JSON/FlatBuffer 与结构回滚 |
| UGC2.0 区分构造与属性复制 | Networking/InstanceConstruction*.lua、Replication.lua | 对象存在/拓扑走构造协议,属性变化走脏标记增量,包含流、编解码和传输边界 |
| UGC2.0 区分可靠与非可靠消息 | Objects/Networking/RemoteEvent.lua、UnreliableRemoteEvent.lua | 相同创作 API 背后使用不同传输语义,并校验角色和可序列化参数 |
| UGC2.0 战斗有统一状态与结算入口 | AbilityComponent.lua、AbilitySystem/.../AbilityService.lua、DamageService.lua | CastId/Revision/Phase、拒绝码、来源/阵营/护盾/暴击/Health 与结果事件形成战斗协议 |
| UGC2.0 AI 区分设计态和运行态 | BehaviorTree.lua、NPCManager.lua | 行为树父子约束、Server Blackboard、非序列化运行态、NPCId 注册与 Workspace 懒扫描 |
| UGC2.0 角色移动走行为与 Humanoid | BehaviorMovementAdapter.lua、PatrolExecutor.lua、PatrolBehaviorTree.lua | Server MoveTo、到达/取消、路点等待、运行查询和失败处理属于行为运行链,而非直接改 Transform |
| UGC2.0 机关运动有独立缓动层 | TweenService.lua、Tween.lua、ClassDefinition/Mover_Tween/* | 属性合成、物理属性限制、距离预算、Target/Speed、路点、事件、暂停与完成形成可配置运动协议 |
| UGC2.0 骨骼动画不等于世界移动 | Animation.lua、AnimationController.lua、AnimationTrack.lua | Asset ID 经 Animator 变成轨道,轨道管理优先级、混合、层、Mask、Marker 和 C++ Pose 求值 |
| UGC2.0 当前剧情层仍需建设 | ClickDetector.lua、GUI/Remote/Value/Camera/Animation 等对象;当前已核实 Objects 未见 StoryService | 已有能力能拼装剧情,但交互 Signal 不等于剧情图、会话、幂等副作用和多人作用域 |
| UGC2.0 资产加载是异步运行协议 | AssetService.lua、Manager/EffectAssetRuntime.lua | Asset ID 规范化、yield、重试/轮询、取消、实例化和精简/完整资源边界 |
| UGC2.0 存档服务有缓存与失败语义 | DataStoreService.lua、DataStore/DataStorePrototype.lua | Server-only、批量合并、revision、dirty 重试、离场最终写和容量限制 |
| UGC2.0 有三种 VM 角色 | QZUGCVMRole.h、QZUGCModule.h/.cpp | Edit/Server/Client 的创建、销毁、World 约束和带代际的 VM Handle 在原生层管理 |
| UGC2.0 发布冻结可验证制品 | Pipeline/ArtifactV2/Plugin_CaptureSnapshot.lua、Plugin_BuildArtifact.lua、Plugin_Publish.lua | Snapshot、构建摘要、上传恢复和发布是独立阶段 |
| 构造复制有专项验证 | Test/InstanceConstruction*Test.lua、Test/Integration/*/PairedStateReplication.lua | 编解码、流、运行态、复制与双端状态有独立测试入口 |
这张表也给出了事实强度:看到类名,只能证明“存在该抽象”;看到调用路径,才能证明“抽象被接入主链”;看到双端与异常测试,才能进一步证明“关键时序被验证”。不能因为仓库里存在 DamageService 或 Prefab,就直接推导所有玩法、安全和升级能力已经产品化完毕。
二十三、事实边界与资料
本文刻意区分三种证据:
- 公开事实:绿洲启元与 Roblox 的编辑对象、脚本方式、Client/Server 模型,来自官方文档。
- 内部事实:UGC1.0/2.0 的对象模型、三 VM、PixUI、制品和跨语言边界,来自项目事实底座与源码复盘。
- 架构推断:例如“哪些 Instance 需要物化为 Actor”的原则可以从分层得出,但具体类映射、复制策略和生命周期仍需用对应源码验证。
绿洲启元官方资料:
Roblox 官方资料:
- Data model
- Client-server runtime
- Packages
- Assets
- Script locations
- Remote events and callbacks
- Instance streaming
- Client-server boundary
- Network ownership and movement validation
- Dialog
- Proximity prompts
- Pathfinding
- TweenService
- Pivot tools and PivotTo
- Animation
- Data stores
- Data store best practices
内部材料:UGC 客户端架构白板稿、UGC2.0 三 VM、PixUI 与跨语言通信、UGC1.0 事实底座、UGC2.0 事实底座、UGC 客户端架构深度复盘。