主题
GameSystemUI 背包控件
GameSystemUI 提供了一组可直接复用的背包 UI 控件,适合 ARPG、RPG、塔防、刷宝和有掉落物拾取的 3D 单位游戏。它们不是物品系统本身,而是 ItemSystem / InventoryManager 的客户端表现层。
常用命名空间:
csharp
#if CLIENT
using GameCore.Container;
using GameCore.EntitySystem;
using GameCore.PlayerAndUsers;
using GameSystemUI.GameInventoryUI.Advanced;
using GameUI.Control.Enum;
using GameUI.Control.Struct;
#endif相关底层文档:
- 物品系统:物品、装备、品质、拾取和背包槽位。
- 单位系统:单位创建、
GameDataUnit配置和主控单位。 - GameSystemUI 移动控件:同一类 HUD 接入方式,通常和背包 UI 一起使用。
推荐公开入口
优先使用下表中的入口控件。其它格子、按钮、列表、拖拽辅助类更像内部组成件,建议只通过模板配置间接定制。
| 控件 | 作用 | 推荐用法 |
|---|---|---|
DefaultInventoryUI | 完整背包窗口,显示单位身上的 InventoryManager.Inventories | 绑定主控单位,默认隐藏,由入口按钮打开 |
InventoryUIEntrance | 背包入口按钮 | 绑定一个 InventoryUI,放在 HUD 边角 |
QuickBarUI | 快捷栏,显示可快速使用或施法的物品槽 | 绑定主控单位,放在屏幕底部 |
PickButton | 拾取按钮,扫描附近可拾取物并弹出拾取列表 | 绑定主控单位,搭配 InventoryPickUpRange |
暂不建议作为第一层公开入口直接使用的控件包括 InventorySlotUI、AbilitySlotUI、ItemUI、PickList、PickListItem、PageButtonList、SlotContainer、SlotMartixUI 等。它们可以用于做自定义背包 UI,但更容易踩到拖拽、同步和模板细节;普通项目先用上表四个入口。
数据接线
背包 UI 只显示运行时 Unit 身上的 InventoryManager。要让 UI 有内容,先在数据层给单位配置背包。
下面示例假设项目已经像 game_entry 模板一样配置了 GSC = gamesparkcore.ScopeData 全局别名;MyItemCategory 和 MyUnit 是项目自己的分类与单位链接。
csharp
using GameCore.Container;
using GameCore.Container.Data;
using GameCore.EntitySystem.Data;
using GameCore.Struct;
public static class MyInventory
{
public static readonly GameLink<GameDataInventory, GameDataInventory> MainBag = new("MainBag"u8);
public static readonly GameLink<GameDataInventory, GameDataInventory> EquipBag = new("EquipBag"u8);
}
private static void OnGameDataInitialization()
{
_ = new GameDataInventory(MyInventory.MainBag)
{
Name = "主背包",
Slots =
[
new(), new(), new(), new(),
new(), new(), new(), new(),
new(), new(), new(), new(),
],
};
_ = new GameDataInventory(MyInventory.EquipBag)
{
Name = "装备栏",
InventoryFlags = new()
{
// 装备栏不自动接收地面拾取;玩家从主背包拖到装备栏。
HandlePickUpRequest = false,
},
Slots =
[
new()
{
Type = ItemSlotType.Equip,
Required = [MyItemCategory.Weapon],
},
new()
{
Type = ItemSlotType.Equip,
Required = [MyItemCategory.Armor],
},
],
};
_ = new GameDataUnit(MyUnit.Hero)
{
Inventories = [MyInventory.MainBag, MyInventory.EquipBag],
Properties = new UnitPropertyInitialData
{
{ GSC.GameDataUnitProperty.InventoryPickUpRange, 300 },
},
};
}关键点:
GameDataUnit.Inventories决定单位创建后挂哪些背包。GameDataInventory.Slots决定格子数量和每个格子的限制。InventoryFlags.HandlePickUpRequest = false适合装备栏、仓库页签等不应自动接收地面拾取的容器。InventoryPickUpRange影响PickButton能找到的地面物品范围。- 数据定义应在双端可见。客户端 UI 需要看到背包结构和同步来的物品状态。
创建背包 HUD
在客户端等到 Player.LocalPlayer.MainUnit 和 InventoryManager 就绪后再创建 UI。ARPG 模板就是这个模式。
csharp
#if CLIENT
private static DefaultInventoryUI? inventoryUI;
private static InventoryUIEntrance? inventoryEntrance;
private static QuickBarUI? quickBarUI;
private static PickButton? pickButton;
private static async void DelayedCreateInventoryHud()
{
for (int i = 0; i < 20; i++)
{
var mainUnit = Player.LocalPlayer?.MainUnit;
var manager = mainUnit?.GetComponent<InventoryManager>();
if (mainUnit != null && manager != null && manager.Inventories.Count > 0)
{
CreateInventoryHud(mainUnit);
return;
}
await Game.Delay(TimeSpan.FromMilliseconds(500));
}
Game.Logger.LogWarning("MainUnit inventory was not ready; inventory UI was skipped.");
}
private static void CreateInventoryHud(Unit mainUnit)
{
inventoryUI = new DefaultInventoryUI
{
BindUnit = mainUnit,
Visible = false,
ZIndex = 850,
};
inventoryUI.AddToRoot();
inventoryEntrance = new InventoryUIEntrance(inventoryUI)
{
HorizontalAlignment = HorizontalAlignment.Left,
VerticalAlignment = VerticalAlignment.Top,
Margin = new Thickness(20, 20, 0, 0),
ZIndex = 849,
};
inventoryEntrance.AddToRoot();
quickBarUI = new QuickBarUI
{
BindUnit = mainUnit,
HorizontalAlignment = HorizontalAlignment.Center,
VerticalAlignment = VerticalAlignment.Bottom,
Position = new UIPosition(-45, 0),
ZIndex = 850,
};
quickBarUI.AddToRoot();
pickButton = new PickButton(mainUnit)
{
HorizontalAlignment = HorizontalAlignment.Center,
VerticalAlignment = VerticalAlignment.Center,
Position = new UIPosition(200, 0),
ZIndex = 850,
};
pickButton.AddToRoot();
}
#endif如果你的游戏没有主控单位,或者背包属于某个非主控单位,也可以手动传入目标 Unit。但背包 UI 依赖 InventoryManager,纯 GameGraph / SceneGraph 对象不能直接绑定。
控件行为
DefaultInventoryUI
DefaultInventoryUI 会读取 BindUnit.GetComponent<InventoryManager>(),并展示该单位挂载的背包。常见做法是默认隐藏:
csharp
inventoryUI = new DefaultInventoryUI
{
BindUnit = mainUnit,
Visible = false,
};
inventoryUI.AddToRoot();如果需要按物品分类过滤,可以设置 FilterCategory。分类来自 ItemCategory 扩展枚举,物品侧通过 GameDataItem.Categories 声明。
InventoryUIEntrance
InventoryUIEntrance 绑定一个背包窗口,用作打开/关闭入口:
csharp
var entrance = new InventoryUIEntrance(inventoryUI)
{
HorizontalAlignment = HorizontalAlignment.Left,
VerticalAlignment = VerticalAlignment.Top,
};
entrance.AddToRoot();它适合直接放在 HUD 上。不要为同一个背包窗口重复创建多个入口,除非你明确需要多处打开同一窗口。
QuickBarUI
QuickBarUI 是快捷栏。它同样绑定单位,并从该单位的背包系统读取槽位。格子中如果是带主动技能的物品,快捷栏可以走 AbilitySlotUI 的能力表现。
csharp
var quickBar = new QuickBarUI
{
BindUnit = mainUnit,
HorizontalAlignment = HorizontalAlignment.Center,
VerticalAlignment = VerticalAlignment.Bottom,
};
quickBar.AddToRoot();当你通过服务端逻辑改变物品、堆叠或槽位后,通常同步会驱动 UI 更新;如果你在客户端侧做了模板或显示状态调整,可以调用 quickBar.Refresh()。
PickButton
PickButton 绑定单位后,会按周期刷新附近可拾取物,并配合拾取列表完成拾取操作:
csharp
var pickButton = new PickButton(mainUnit)
{
Period = 200,
};
pickButton.AddToRoot();要让它生效,需要满足:
- 单位有
InventoryManager,也就是GameDataUnit.Inventories不为空。 - 至少一个可接收拾取请求的背包,通常主背包保持默认
HandlePickUpRequest = true。 - 单位有合理的
InventoryPickUpRange。 - 地面物品是
GameDataItemPickable/ItemPickable,并且可以被目标单位拾取。
模板定制
项目如果只需要位置和显示隐藏,直接 new 默认控件即可。需要换图、换尺寸、换格子模板时,优先用 GameDataControl* 模板,不要复制整套 UI 代码。
常用模板类型:
| 模板 | 控件 |
|---|---|
GameDataControlDefaultInventoryUI | DefaultInventoryUI |
GameDataControlInventoryUIEntrance | InventoryUIEntrance |
GameDataControlQuickBarUI | QuickBarUI |
GameDataControlInventorySlotUI | 普通背包格子 |
GameDataControlAbilitySlotUI | 快捷栏技能格子 |
GameDataControlPickButton | PickButton |
GameDataControlPickList | 拾取列表 |
GameDataControlPickListItem | 拾取列表项 |
示例:
csharp
using GameSystemUI.GameInventoryUI.Data;
using GameUI.Control.Data;
public static class MyInventoryControls
{
public static readonly GameLink<GameDataControl, GameDataControlQuickBarUI> QuickBar =
new("MyQuickBar"u8);
}
private static void OnGameDataInitialization()
{
_ = new GameDataControlQuickBarUI(MyInventoryControls.QuickBar)
{
Width = 520,
Height = 96,
BackgroundImage = "image/my_quickbar_bg.png",
};
}
#if CLIENT
var quickBar = new QuickBarUI(MyInventoryControls.QuickBar)
{
BindUnit = mainUnit,
};
quickBar.AddToRoot();
#endif图片路径遵循 GameUI 路径规则:项目文件放在 ui/image/... 下时,代码和数据里通常从 image/... 开始写。详见 项目资源路径指南。
清理
切换模式、退出关卡或重进游戏时,移除已经创建的 UI,避免重复创建和旧事件残留:
csharp
#if CLIENT
private static void CleanupInventoryHud()
{
pickButton?.RemoveFromParent();
pickButton = null;
quickBarUI?.RemoveFromParent();
quickBarUI = null;
inventoryEntrance?.RemoveFromParent();
inventoryEntrance = null;
inventoryUI?.RemoveFromParent();
inventoryUI = null;
}
#endif如果你的 UI 生命周期里显式调用 Dispose(),确保后续不要再访问同一个控件实例。
常见问题
| 现象 | 常见原因 | 处理 |
|---|---|---|
| 背包 UI 创建了但没有格子 | 绑定单位没有 InventoryManager,或 GameDataUnit.Inventories 为空 | 给单位配置 Inventories,并等主控单位同步完成后再创建 UI |
| 拾取按钮没有可拾取物 | 没有 InventoryPickUpRange,地面物品不在范围内,或主背包不处理拾取请求 | 配置拾取范围,确认地面物品是 ItemPickable,主背包保留 HandlePickUpRequest = true |
| 装备被自动拾到装备栏 | 装备栏没有关闭拾取请求 | 装备栏 InventoryFlags.HandlePickUpRequest = false |
| 快捷栏不显示主动技能 | 物品没有授予 ActiveAbility,或没有进入快捷栏读取的槽位 | 检查 GameDataItemMod 的技能配置和物品槽位 |
| 多次进入模式后 UI 重叠 | 没有防重复创建或没有清理旧 UI | 使用初始化标志,并在退出时 RemoveFromParent() |
| 客户端能看到 UI,但服务端拾取失败 | 物品拾取、装备需求或槽位限制在服务端校验失败 | 看服务端日志,检查 Required / Excluded / Requirements / CanPickUp |
示例来源
内置 code_sample 中已有多处使用这组控件:
ARPGTemplate/ARPGClient.cs:DefaultInventoryUI、InventoryUIEntrance、QuickBarUI、PickButton的组合。ARPGTemplate/ScopeData/ARPGScopeData.cs:主背包和职业装备栏配置。TowerDefenseGame/TowerDefenseClient.cs、XianJianQiXiaZhuan/XianJianClient.cs:同一组 UI 在不同玩法中的复用。
这些示例可以作为接线参考,但项目侧推荐按本文保留最小接入代码,把外观差异放到 GameDataControl* 模板中。