拓冰建站拓冰建站
首页 / 资讯中心 / 正文

Unity Netcode for Entities 实战:在 HelloNetcode 示例中实现世界空间血条(World-Space Health Bars)

Unity Netcode for Entities 实战在 HelloNetcode 示例中实现世界空间血条World-Space Health Bars【免费下载链接】EntityComponentSystemSamples项目地址: https://gitcode.com/GitHub_Trending/en/EntityComponentSystemSamples本文基于 Unity EntitiesDOTS仓库中 HelloNetcode 系列高级示例01_HealthBars完整讲解如何在多人游戏中为每个玩家生成并维护一个跟随角色、始终朝向摄像机的世界空间血条。读完后你将掌握如何把托管侧 GameObject 预制体引入 ECS 世界并在客户端动态实例化、如何通过IDisposable/ICloneable托管组件解决 Ghost 实体的 UI 生命周期问题以及如何正确区分本地玩家与远端对手的血条放置和死亡服务器权威状态处理。示例定位与前置要求01_HealthBars属于 HelloNetcode 示例体系 的3_Advanced高级层级它构建在两个中级示例之上HitScanWeapon命中扫描武器负责射击 → 命中 → 扣血源码见 ShootingSystem.cs 与 HitScanWeapon 示例文档Respawning重生负责为角色添加Health组件、死亡判定与重生逻辑见 Respawning 示例文档。HealthBars.md 中列出的前置示例链为GoInGameSpawnPlayerPhysicsCharacterControllerHitScanWeaponRespawning运行方式进入 Play Mode 时在 Multiplayer Play Mode 工具窗口中至少启用一个Thin Client即可看到所有玩家头顶出现血条黑色背景 绿色血条。射击命中后血条递减耗尽即触发 Respawning 示例描述的重生流程角色在新位置以满血状态重生。核心数据Health 组件从哪来血条显示的数值并非本示例自定义而是复用 Respawning 示例中的Health组件HealthAuthoring.cspublic struct Health : IComponentData { [GhostField(Smoothing SmoothingAction.Clamp)] public short MaximumHitPoints; [GhostField(Smoothing SmoothingAction.Clamp)] public short CurrentHitPoints; }几个值得注意的设计点使用short而非浮点数官方注释HealthAuthoring.cs解释避免浮点精度问题且允许取负值以兼容治疗等加血操作两个字段都是 Ghost 字段并指定Smoothing SmoothingAction.Clamp即插值平滑时不允许数值在插值过程中越过实际端点保证血条数值变化平滑但不超前Authoring 默认MaximumHitPoints 100Baker 同时用它初始化CurrentHitPoints。扣血逻辑在 DamageSystem.cs每次命中使受害者的CurrentHitPoints - 20因此满血 100 点、5 次命中致死与示例文档Five successful hits will be enough to knock them down完全对应。第一步Authoring 烘焙生成器配置HealthBarSpawnerAuthoring.cs 定义了一个 ECS 组件与对应的 MonoBehaviour 作者化组件public class HealthBarSpawner : IComponentData { public GameObject HealthBarPrefab; // 血条 UI 预制体引用 public float OpponentHeightOffset; // 对手头顶偏移向上 public float PlayerTowardCameraOffset; // 本地玩家向摄像机方向的推拉距离 public float PlayerHeightOffset; // 本地玩家高度偏移 } public class HealthBarSpawnerAuthoring : MonoBehaviour { public GameObject HealthBarPrefab; public float OpponentHeightOffset 0.5f; public float PlayerTowardCameraOffset 1.8f; public float PlayerHeightOffset -1.5f; class Baker : BakerHealthBarSpawnerAuthoring { public override void Bake(HealthBarSpawnerAuthoring authoring) { var entity GetEntity(TransformUsageFlags.Dynamic); AddComponentObject(entity, new HealthBarSpawner { HealthBarPrefab authoring.HealthBarPrefab, OpponentHeightOffset authoring.OpponentHeightOffset, PlayerTowardCameraOffset authoring.PlayerTowardCameraOffset, PlayerHeightOffset authoring.PlayerHeightOffset, }); } } }要点解析三个偏移参数是血条放置策略的全部配置语义上区分了两类实体对手Opponent血条悬于头顶0.5单位与本地玩家第一/三人称下头顶血条会被模型或视角挡住故用PlayerHeightOffset -1.5与PlayerTowardCameraOffset 1.8把血条压到角色下方并沿血条→摄像机方向拉近/推远保证可见且不被遮挡之所以用AddComponentObject而非AddComponent烘焙是因为HealthBarSpawner是一个类class组件其内部持有对GameObject预制体的引用。这正是 HealthBars.md Note 一节强调的设计说明HealthBarSpawnerAuthoring刻意把 IComponent 实现为 class 而非 struct就是为了保持对血条预制体 GameObject 的引用。整个组件及两套#if !UNITY_DISABLE_MANAGED_COMPONENTS条件编译意味着该功能仅在允许托管组件的构建下可用该组件所在的实体在场景中是HealthBar实体场景内的一个 Authoring 对象最终由 SpawnHealthBarSystem 以GetSingleton方式读取。第二步SpawnHealthBarSystem —— 客户端实例化 UI 并挂托管组件SpawnHealthBarSystem.cs 是整个示例最关键的文件它同时定义了 UI 承载组件HealthUI与生成系统。HealthUI可销毁、可克隆的托管组件public class HealthUI : IComponentData, IDisposable, ICloneable { public Transform HealthBar; public Image HealthSlider; public float OpponentHeightOffset; public float PlayerHeightOffset; public float PlayerTowardCameraOffset; public void Dispose() { // 由于实现了 IDisposable可以在 Ghost 实体被销毁时 // 触发对 HealthBar GameObject 的销毁 if (HealthBar ! null) Object.Destroy(HealthBar.gameObject); } public object Clone() { if (HealthBar null || HealthBar.gameObject null) return new HealthUI(); var newHealthBar Object.Instantiate(HealthBar.gameObject); var images HealthBar.gameObject.GetComponentsInChildrenImage(); return new HealthUI { HealthBar newHealthBar.GetComponentTransform(), HealthSlider images[1] }; } }这套设计解决了托管 UI 与 DOTS 实体生命周期错位的经典问题IDisposable当 Netcode 在服务器权威下销毁 Ghost 实体例如玩家断线、重生重建实体时ECS 会自动调用Dispose()从而Object.Destroy掉对应的血条 GameObject避免场景里残留幽灵 UIICloneableGhost 实体在客户端本地预测重建如本地玩家重新生成时会克隆组件Clone()通过Object.Instantiate为新实体创建一份全新的血条 GameObject 并返回新组件保证每个实体各有一份独立 UI。生成系统的查询与实例化系统声明为SpawnHealthBarSystem.cs[WorldSystemFilter(WorldSystemFilterFlags.ClientSimulation)] [UpdateInGroup(typeof(PresentationSystemGroup))] public partial struct SpawnHealthBarSystem : ISystemOnUpdate的核心逻辑SpawnHealthBarSystem.csvar ecb new EntityCommandBuffer(Allocator.Temp); var query state.EntityManager.CreateEntityQuery(ComponentType.ReadOnlyHealthBarSpawner()); var spawner query.GetSingletonHealthBarSpawner(); foreach (var (_, entity) in SystemAPI.QueryRefROHealth() .WithEntityAccess().WithNoneHealthUI()) { var go Object.Instantiate(spawner.HealthBarPrefab); var image go.GetComponentsInChildrenImage(); ecb.AddComponent(entity, new HealthUI { HealthBar go.transform, HealthSlider image[1], OpponentHeightOffset spawner.OpponentHeightOffset, PlayerTowardCameraOffset spawner.PlayerTowardCameraOffset, PlayerHeightOffset spawner.PlayerHeightOffset, }); } ecb.Playback(state.EntityManager);实现细节与文档呼应之处只用常规Object.Instantiate实例化——这是 HealthBars.md Note 一节明确指出的做法血条 UI 不是 DOTS 实体不走烘焙/子场景流程而是在客户端运行时直接实例化托管侧 UI 预制体HealthBar.prefab主查询QueryRefROHealth().WithNoneHealthUI()是幂等设计只处理拥有Health但尚未挂HealthUI的实体因此无论是新连接的远端玩家、还是重生后重建的本地玩家都恰好会被实例化一次血条OnCreate中通过RequireForUpdateHealthBarSpawner()与RequireForUpdateHealth()做惰性激活——场景里没有血条生成器实体或没有玩家时系统自动停用零开销组件添加通过EntityCommandBuffer缓冲后统一Playback符合 ECS 中批量变更的标准写法。HealthBarPrefab内部的层级约定是GetComponentsInChildrenImage()取第[0]个为黑色背景底板、第[1]个为作为fillAmount滑动条的血条填充层白色像素贴图着色。配套的 CharacterWithHealthbar.prefab 展示了带血条的角色装配形态纹理资源为同目录下的 32x32WhitePixel.jpg。第三步UpdateHealthBarSystem —— 跟随、朝向与死亡状态UpdateHealthBarSystem.cs 负责每帧把血条钉在角色上并刷新血量同样运行于ClientSimulation的PresentationSystemGroup并标注[RequireMatchingQueriesForUpdate]查询无匹配实体时不执行。防御性启用检查if (Camera.main null) { state.Enabled false; return; }场景中不存在 Main 摄像机例如纯服务器/无 UI 世界时直接停用系统避免空引用。本地玩家与对手的差异化放置主查询UpdateHealthBarSystem.cs同时取HealthUI、RefROHealth、RefROAutoCommandTarget、RefROGhostOwner、RefROLocalToWorld五类数据并按实体区分两类放置策略if (state.EntityManager.IsComponentEnabledGhostOwnerIsLocal(entity)) { // 本地玩家抬高后沿血条→摄像机方向推拉并整体 LookRotation 朝向摄像机 var targetHealthBarPos ltw.ValueRO.Position; targetHealthBarPos.y ui.PlayerHeightOffset; var n mainCamera.transform.position - ui.HealthBar.position; targetHealthBarPos (float3)(n.normalized * ui.PlayerTowardCameraOffset); ui.HealthBar.SetPositionAndRotation(targetHealthBarPos, Quaternion.LookRotation(n)); } else { // 远端对手直接置于头顶 OpponentHeightOffset 处并朝向摄像机 var targetHealthBarPos ltw.ValueRO.Position; targetHealthBarPos.y ui.OpponentHeightOffset; var n mainCamera.transform.position - ui.HealthBar.position; ui.HealthBar.SetPositionAndRotation(targetHealthBarPos, Quaternion.LookRotation(n)); }通过GhostOwnerIsLocal组件判断该 Ghost 是否属于本地玩家从而套用两套 Authoring 中配置好的偏移参数两者都用Quaternion.LookRotation让血条平面始终正对主摄像机实现永远可读的世界空间 UI位置来源是实体的LocalToWorld.Position因此血条天然跟随角色的插值/预测位置无需额外同步。血量渲染与等待服务器确认的死亡处理var hpNormalized math.saturate((float)health.ValueRO.CurrentHitPoints / health.ValueRO.MaximumHitPoints); var playerColor NetworkIdDebugColorUtility.GetColor(owner.ValueRO.NetworkId); // Killed by server: if (act.ValueRO.Enabled) { // 存活血条填充为玩家调试色 ui.HealthSlider.color playerColor; } else { // 无视预测直接置 0并把背景调成半透明alpha0.3表示权威死亡 hpNormalized 0; playerColor.a 0.3f; ui.HealthSlider.transform.parent.GetComponentImage().color playerColor; } ui.HealthSlider.fillAmount hpNormalized;这里的AutoCommandTarget是 Netcode 的权威状态信号act.ValueRO.Enabled true表示服务器已确认该实体存活血条按CurrentHitPoints / MaximumHitPoints归一化后驱动Image.fillAmount填充颜色用NetworkIdDebugColorUtility.GetColor(owner.NetworkId)按玩家 NetworkId 着色便于在多人场景中区分个体act.ValueRO.Enabled false表示服务器已判定死亡。由于本地预测可能尚未追上客户端可能仍认为目标存活此处选择无论预测如何一律置 0并将底板透明度降到 0.3 呈现灰色权威死亡外观。源码注释解释了只在此条件成立时处理的意图Were waiting for server confirmation——即死亡表现以服务器权威为准不跟随预测回滚死亡后由 RespawnSystem 重建玩家实体旧实体销毁触发HealthUI.Dispose()销毁旧血条新实体因再次满足WithNoneHealthUI()查询而获得新血条血量随之满血恢复——形成完整闭环。关键设计小结设计点做法依据UI 预制体引用用 class 型IComponentDataHealthBarSpawner承载GameObject引用AddComponentObject烘焙HealthBars.md Note 一节、HealthBarSpawnerAuthoring.csUI 实例化客户端系统内常规Object.Instantiate非 DOTS 实体SpawnHealthBarSystem.cs生成幂等性QueryRefROHealth().WithNoneHealthUI()只处理未挂 UI 的实体SpawnHealthBarSystem.csUI 生命周期HealthUI实现IDisposable随实体销毁与ICloneable预测重建时克隆SpawnHealthBarSystem.cs数值权威死亡表现等待AutoCommandTarget服务器确认期间血条强制置 0UpdateHealthBarSystem.cs运行前提托管组件可用!UNITY_DISABLE_MANAGED_COMPONENTS至少一个 Thin Client 进 Play Mode 可观察HealthBars.md、三处源码文件顶部的条件编译如何运行与验证打开仓库中的 NetcodeSamples 工程确认当前场景包含3_Advanced/01_HealthBars的 HealthBar 场景/实体场景依赖链确认GoInGame、SpawnPlayer、Physics、CharacterController、HitScanWeapon、Respawning 各示例的 Authoring 组件均在场景装配对应文档 Requirements 一节进入 Play Mode 并在 Multiplayer Play Mode 工具中启用至少一个 Thin Client观察点所有玩家头顶出现黑底绿条血条 → 射击命中后血条递减每次 -20→ 血量归零时服务器确认后血条灰化 → 角色随机位置重生且血条满血恢复。如需继续深入可参考同一体系中的 HitScanWeapon 文档命中判定与Hit组件流转和 Respawning 文档实体销毁重建时CommandTargetComponent、LinkedEntityGroup的重新装配要求三者共同构成了本血条示例的完整数据链路。【免费下载链接】EntityComponentSystemSamples项目地址: https://gitcode.com/GitHub_Trending/en/EntityComponentSystemSamples创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门