NetCode 官方搭建文档
NetCode 0.1.0 文档翻译
前期准备
创建一个 GameObject 并添加 ConvertToClientSeverEntity 组件,用来负责在客户端与服务端之间同步 SharedData
配置 GhostPrefab
首先,先来创建一个我们想要同步的 Prefab,本文使用子弹(bullet)来举例
给 Prefab 添加 Ghost Authoring Component 组件,并在 Name 参数栏写上对应的名字
Default Ghost Mode 选择 Owner Predicted,并添加 GhostOwnerComponent 组件
打开 PrefabAssetRegistry 场景,选择 Prefab Asset Registry Authoring 组件的 Gather Prefabs
打开 GhostCollection 场景,选择 Ghost Collection Authoring 组件的 Update ghost list ,将创建好的 Prefab 加入 Ghost 列表中
之后我们来为服务端写一个 GhostSystem
GameModeUtil.GetGhostPrefab 获取我们配置的 Ghost Prefab,参数要与 Ghost Authoring Component 的 Name 一致
[DisableAutoCreation]
public class GhostSystem : SystemBase {
public Entity bullet;
protected override void OnUpdate() {
if (bullet == Entity.Null) {
bullet = GameModeUtil.GetGhostPrefab(EntityManager, "TopDownGhostBullet");
}
}
添加到 SeverGameWorld,此时使用 EntityManager.Instantiate 方法就可以在服务端生成 Prefab ,同时多播给所有客户端了,但子弹不会凭空出现,我们需要一个判断条件来判断玩家是否为开火状态,再去生成 bullet
注意:模式为 Owner predicted 需要设置 GhostOwnerComponent 的 NetworkId
通过 Tick 获取输入信息
protected override void OnUpdate() {
var tick = World.GetExistingSystem<GhostPredictionSystemGroup>().PredictingTick;
Entities
.WithoutBurst()
.WithStructuralChanges()
.ForEach((ref DynamicBuffer<UserCommand> user, ref Player.State state, ref GhostComponent ghost) => {
user.GetDataAtTick<UserCommand>(tick, out UserCommand Input);
}).Run();
此时从 DynamicBuffer 中根据 tick 获取到了 UserCommand 的数据
便可以用来判断玩家目前是否为开火状态,再去初始化生成子弹,并对子弹的位置信息,方向等进行操作
为客户端的 Entity 添加模型渲染
虽然子弹现在已经从服务端同步生成到了客户端,但由于服务端是不需要进行模型渲染的
在客户端的 System 需要对生成的 Entity 添加模型来进行渲染
所以需要额外生成一个 GameObject 来对生成的 Entity 进行绑定和同步
用 GameObject 来生成渲染模型的原因有两个
因为 Freamwork 使用了 XRP,会导致 Entity 的 RenderBounds 组件失效
GhostPrefab 没有 render ,方便服务端分离渲染层,减小服务端渲染压力
制作客户端同步 GameObject 的 System
在做这个 System 之前,需要先设计一个 StateComponent,继承 ISystemStateComponentData,包含一个 Entity 字段
因为当服务端删除子弹时,客户端的子弹也会被删除,没有机会去删除 GameObject 的子弹
当给 Entity 添加了 StateComponent 后,Entity 会保留 StateComponent 中存储的信息,等待我们进行删除的后续操作
所以我们要在这个 State 包含的 Entity 中去添加我们的 ComponentObject 才能保证我们去找到并删除 GameObject
示例:
// Spawn Model
Entities
.WithoutBurst()
.WithAll<TopDownGhostBulletInfo>()
.WithNone<bulletStateTag>()
.WithStructuralChanges()
.ForEach((Entity entity, in Translation translation) =>{
Transform transform = Object.Instantiate(bulletModel).GetComponent<Transform>();
var tranformEntity = EntityManager.CreateEntity();
EntityManager.AddComponentObject(tranformEntity, transform);
EntityManager.AddComponentData(entity, new bulletStateTag { entity = tranformEntity });
}).Run();
// Update
Entities
.WithoutBurst()
.WithAll<TopDownGhostBulletInfo>()
.WithAll<bulletStateTag>()
.ForEach((Entity entity,ref bulletStateTag tag, in Translation translation) =>{
Transform transform = EntityManager.GetComponentObject<Transform>(tag.entity);
transform.position = translation.Value;
}).Run();
// Destroy
Entities
.WithoutBurst()
.WithNone<TopDownGhostBulletInfo>()
.WithAll<bulletStateTag>()
.WithStructuralChanges()
.ForEach((Entity entity, in bulletStateTag tag) =>{
Object.Destroy(EntityManager.GetComponentObject<Transform>(tag.entity).gameObject);
EntityManager.DestroyEntity(tag.entity);
EntityManager.RemoveComponent<bulletStateTag>(entity);
}).Run();
Ghost Authoring Component
- Importance - 重要性
Supported Ghost Modes - Ghost 支持模式
- All - 支持插值和预测
- Interpolated - 仅支持插值
- Predicted - 仅支持预测
Default Ghost Mode - Ghost 默认模式
- Interpolated - Unity 从服务器收到的所有 Ghost 都会视为已插值
- Predicted - Unity 从服务器收到的所有 Ghost 都会视为已预测
Owner predicted - Ghost 为拥有它的客户端预测,并为所有其他客户端进行插值
- 选择此属性时,必须添加 GhostOwnerComponent 组件,并在代码中设置其 NetworkId 字段
- Unity 将该字段与每个客户的网络ID进行比较,以找到正确的所有者
Optimization Mode - 优化模式
Dynamic - 动态优化
- 对 Ghost 进行优化,在更改时和不更改时都具有较小的快照大小
Static - 静态优化
- 将不会针对更改时的快照大小进行优化,但是在不更改时不会发送快照