主题
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,不是 GameUIImage控件对象。
属性绑定 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 时:
- 确认传入的是 Widget 数据链接,不是
GameDataStatusBar或GameDataFloatingText链接。 - 用
HasWidget(link)确认当前实例包含该 Widget。 - 确认当前单位实际使用的血条模板,
unit.StatusBar.Link可用于查看当前模板链接。 - 模板或可见性变化后重新获取 handle。
set 后又变回去时:
- 检查该 Widget 是否绑定了
Value,绑定刷新可能覆盖临时 set。 - 需要长期生效的名字或图片,优先写入
unit.StatusBarDisplayName/unit.StatusBarDisplayImage;自定义UnitField文本或图片属性要写UTF8String。 - 需要临时表现再使用 handle set。
渲染不符合预期时:
- 字体文件文字看
Font,图片字看ImageFontPrefix。 - 资源路径先对照现有 Widget JSON 和
docs/guides/ResourcePathGuide.md。 - 如果目标是复杂界面,不要继续堆 Widget,改用 GameUI 或 Canvas。