Skip to content

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

相关底层文档:

推荐公开入口 ​

优先使用下表中的入口控件。其它格子、按钮、列表、拖拽辅助类更像内部组成件,建议只通过模板配置间接定制。

控件作用推荐用法
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 代码。

常用模板类型:

模板控件
GameDataControlDefaultInventoryUIDefaultInventoryUI
GameDataControlInventoryUIEntranceInventoryUIEntrance
GameDataControlQuickBarUIQuickBarUI
GameDataControlInventorySlotUI普通背包格子
GameDataControlAbilitySlotUI快捷栏技能格子
GameDataControlPickButtonPickButton
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* 模板中。