Skip to content

Widget、血条与飘字开发指南 ​

当任务涉及单位头顶血条、飘字、轻量场景标记、屏幕坐标提示、Widget 数据表或运行时修改 Widget 文本/图片/进度条时,先读:

  • docs/systems/WidgetSystem.md
  • 涉及 editor/data 批量修改时再读 docs/ai/skills/data-editor/SKILL.md
  • 需要普通控件布局、按钮、输入、滚动列表时改读 docs/ai/skills/ui-layout-api/SKILL.md
  • 需要逐帧自由绘制、棋盘、小地图、弹幕或复杂 2D 游戏循环时改读 docs/ai/skills/canvas-2d-game/SKILL.md

先判断是不是 Widget ​

Widget 是血条和飘字使用的轻量绘制模板,不是 GameUI 控件,也不是 Canvas 即时绘制。

优先选择:

  • StatusBar:单位当前绑定的血条,通过 unit.StatusBar 访问。
  • FloatingText:一次性战斗反馈,例如伤害、治疗、经验、金币提示。
  • StatusBarCustom:代码主动创建和销毁的无主血条,适合轻量世界标记、屏幕标记、临时进度条。

不要用 Widget 做复杂交互界面。需要点击、焦点、输入框、滚动、复杂布局或控件树管理时使用 GameUI。

数据配置规则 ​

Widget 数据是独立 GameData,运行时代码应通过 IGameLink<GameDataWidget...> 查找,不要按名字或 index 绑定业务逻辑。

常见类型:

  • GameDataWidgetPanel:容器。
  • GameDataWidgetText:文字或图片字。
  • GameDataWidgetImage:图片。
  • GameDataWidgetBar:条形进度。
  • GameDataWidgetRuler:刻度。

文本字段要区分:

  • Font:普通文字字体文件路径,例如 UI/font/Regular/RegularBold.otf。
  • ImageFontPrefix:图片字/数字图集前缀,例如 回血、物理、法爆。
  • TextPrefix / TextSuffix:显示前后缀,只影响显示文本。
  • DisplayValueAsInteger:显示为整数。
  • ReadValueAsInteger:按整数载荷读取属性值。等级等整数语义字段优先用这个。

图片字段:

  • Texture 通常使用项目 ui/ 下的相对资源路径,保持和现有 Widget 数据一致。
  • 运行时 WidgetImage.SetImage(...) 也传 Widget canvas 使用的图片路径或图集 key,不是 GameUI Image 控件对象。

属性绑定 WidgetPropertyBinding 只填一个来源:

  • Property:GameData 定义的单位属性。
  • Vital:内置生命、最大生命等 Vital。
  • UnitField:PropertyUnit 单位字段,包含框架定义字段和项目扩展字段,例如 Level、StatusBarDisplayName、StatusBarDisplayImage。

如果历史数据里多个字段同时存在,序列化按 Property、Vital、UnitField 取第一个非空值。新配置不要依赖这个兜底。

血条长期显示名字或图片时,优先在服务端写 Unit 的便利属性:

csharp
#if SERVER
unit.StatusBarDisplayName = "Boss";
unit.StatusBarDisplayImage = "game_hud/icon/boss_title.png";
#endif

如果自己选择 UnitField 绑定项目扩展的 PropertyUnit,且该字段承载文字或图片路径,写入属性时必须使用 UTF8String,不要用 string:

csharp
#if SERVER
unit.SetProperty(CustomPropertyUnit.StatusTitle, new UTF8String("蓄力中"));
unit.SetProperty(CustomPropertyUnit.StatusIcon, new UTF8String("game_hud/icon/casting.png"));
#endif

运行时 API 习惯 ​

单位血条:

csharp
#if CLIENT
var statusBar = unit.StatusBar;

statusBar.OnInitialized += () =>
{
    statusBar.GetWidgetText(GameEntry.ScopeData.GameDataWidget.BossNameText)?.SetText("Boss");
    statusBar.GetWidgetBar(GameEntry.ScopeData.GameDataWidget.BossHpBar)?.SetPercentage(0.5f);
};
#endif

飘字:

csharp
#if CLIENT
var floating = GameEntry.ScopeData.GameDataFloatingText.Heal.Data
    .CreateFloatingText(unit, "+120");

floating?.GetWidgetText(GameEntry.ScopeData.GameDataWidget.FloatingTextContent)
    ?.SetColor(Color.Green);
#endif

无主血条:

csharp
#if CLIENT
var marker = GameEntry.ScopeData.GameDataStatusBar.MainBaseHealthBar.Data
    .CreateStatusBarCustom(new Vector3(10, 0, 20));

marker?.GetWidgetText(GameEntry.ScopeData.GameDataWidget.MarkerTitle)?.SetText("采集中");
marker?.GetWidgetBar(GameEntry.ScopeData.GameDataWidget.MarkerProgress)?.SetPercentage(0.3f);
marker?.Destroy();
#endif

示例里的 GameEntry.ScopeData.GameDataWidget.* 名字必须替换成当前项目实际生成的链接名。

生命周期与缓存 ​

  • Widget handle 指向当前 native 实例,宿主重建后可能失效。
  • 长期逻辑缓存 Widget 数据链接,不缓存 Widget handle;使用前重新 GetWidgetText / GetWidgetImage / GetWidgetBar 并检查 IsValid。
  • 单位血条有 OnInitialized / OnDestroyed。订阅 OnInitialized 时如果血条已经存在,会立即调用一次;在回调里重新获取 Widget handle。
  • 事件没有订阅时 native 不会转发生命周期事件。不要为了轮询血条生命周期去挂每帧逻辑。
  • StatusBarCustom 由创建者负责 Destroy()。
  • FloatingText 通常让模板动画自然结束;提前结束时调用 Destroy()。
  • 单位血条由单位和可见性系统管理,不要手动销毁。
  • GetWidgetByIndex 只适合调试和通用遍历,业务逻辑优先用 link 查找。

排查路线 ​

拿不到 Widget 时:

  1. 确认传入的是 Widget 数据链接,不是 GameDataStatusBar 或 GameDataFloatingText 链接。
  2. 用 HasWidget(link) 确认当前实例包含该 Widget。
  3. 确认当前单位实际使用的血条模板,unit.StatusBar.Link 可用于查看当前模板链接。
  4. 模板或可见性变化后重新获取 handle。

set 后又变回去时:

  • 检查该 Widget 是否绑定了 Value,绑定刷新可能覆盖临时 set。
  • 需要长期生效的名字或图片,优先写入 unit.StatusBarDisplayName / unit.StatusBarDisplayImage;自定义 UnitField 文本或图片属性要写 UTF8String。
  • 需要临时表现再使用 handle set。

渲染不符合预期时:

  • 字体文件文字看 Font,图片字看 ImageFontPrefix。
  • 资源路径先对照现有 Widget JSON 和 docs/guides/ResourcePathGuide.md。
  • 如果目标是复杂界面,不要继续堆 Widget,改用 GameUI 或 Canvas。