《UIS》 第 3 篇 物品系統核心
本篇文章、圖片出處來自 Ultimate Inventory System
軟體版本與本翻譯文件可能會有落差,本翻譯文件僅供參考。
本譯文為本站譯者原創翻譯內容,文字著作權歸本站所有。
未經授權,請勿任意轉載、改作或商業使用。
物品系統核心
庫存(Inventory)
Inventory 是多個 Item Collection 物件的集合。以一個實際例子來說明:假設角色可以將物品放在背包、口袋,或手持裝備中,這樣就需要三個 Item Collections:Bag、Pocket 和 Equipped。建立 Inventory 時,可以新增任意數量的 Item Collections,並透過命名和旗標(Flag)來指定它們的用途。Item Collection 名稱在同一個 Inventory 中必須唯一,且 Inventory 至少需要一個 Item Collection。
Inventory 是一個 MonoBehaviour,因此可以掛載在任意 GameObject 上,適用於角色庫存、敵人庫存、商店庫存、寶箱庫存、倉庫、撿取物等各種使用情境。
Inspector 搜尋 Token
在 Inventory Inspector 的物品選擇清單中,支援以下特殊搜尋語法:
| Token | 說明 | 範例 |
|---|---|---|
| a:<屬性名稱> | 篩選擁有指定屬性的物品 | a:Attack |
| c:<類別名稱> | 篩選繼承指定類別的物品 | c:Weapon |
| 組合查詢 | 可以混合字串、c:、a: 三種條件 | str c:Weapon a:Attack |
Inventory API
取得所有物品
// 取得 Inventory 中所有物品(不含 Hide 和 Loadout 的 Collection)
var allItems = inventory.AllItemInfos;
以名稱新增 / 移除
// 以名稱新增物品
m_Inventory.AddItem("potion", 5);
// 以名稱移除物品
m_Inventory.RemoveItem("potion", 3);
建立物品並以 ItemInfo 操作
// 取得 Item Definition 並建立物品
var myItemDefinition = InventorySystemManager.GetItemDefinition("MyItemDefinition");
var myItem = InventorySystemManager.CreateItem(myItemDefinition);
// 建立 ItemInfo(注意雙括號,(1,myItem) 是 ItemAmount)
var myItemInfo = new ItemInfo( (1, myItem) );
inventory.AddItem(myItemInfo);
// 或直接用強制轉型(兩者都是 struct)
// 回傳值記錄了實際加入的數量與加入哪個 Stack
var itemInfoActuallyAdded = inventory.AddItem((ItemInfo)(1, myItem));
取得 Item Collection
// 以名稱取得 Item Collection
var itemCollection = m_Inventory.GetItemCollection("Item Collection Name");
// 若有特定類型,直接轉型
var itemSlotCollection = m_Inventory.GetItemCollection("Equipment") as ItemSlotCollection;
檢查物品是否可被加入
var itemInfoToAdd = (ItemInfo)(5, myItem);
var targetCollection = inventory.GetItemCollection(ItemCollectionPurpose.Equipped);
// CanAddItem 回傳 Nullable ItemInfo(? 代表可為 null)
var canAddResult = targetCollection.CanAddItem(itemInfoToAdd);
if (canAddResult.HasValue) {
if (canAddResult.Value.Amount == itemInfoToAdd.Amount) {
// 可以完整加入
} else {
// 只能部分加入
}
} else {
// 完全無法加入
}
取得與移除物品
// 取得 Inventory 中第一個符合的物品 Stack
var retrievedItemInfo = inventory.GetItemInfo(myItem);
if (retrievedItemInfo.HasValue == false) {
// 庫存中找不到該物品
}
// 移除物品,回傳值記錄實際被移除的 ItemInfo
var itemInfoRemoved = inventory.RemoveItem((ItemInfo)(1, myItem));
// 指定 ItemStack 移除(會優先從該 Stack 移除)
inventory.RemoveItem(retrievedItemInfo.Value);
// 指定不同的數量移除
inventory.RemoveItem( (1, retrievedItemInfo.Value) );
以篩選器取得物品清單
// 建議使用 Pool 陣列避免重複建立(注意用完後務必歸還)
var pooledArray = GenericObjectPool.Get<ItemInfo[]>();
var filterParameter = InventorySystemManager.GetItemCategory("MyCategory");
// 取得所有繼承指定 Category 的物品
var itemInfoListSlice = inventory.GetItemInfos(ref pooledArray, filterParameter,
(candidateItemInfo, category) => { return category.InherentlyContains(candidateItemInfo.Item); }
);
// 重要:用完後一定要歸還
GenericObjectPool.Return(pooledArray);
物品集合(Item Collections)
Item Collection 是物品的集合容器,物品以 Item Stack(物品堆疊)的形式儲存在清單中。Item Stack 是一個類別(不是 struct),有物品和數量,並與一個 Item Collection 連結。
Item Collection 的用途(Purpose)
| 用途 | 說明 |
|---|---|
| None | 無指定用途(亦即 Other)。 |
| Main | 庫存的預設主要 Collection。 |
| Secondary | 自訂用途的 Collection。 |
| Equipped | 已裝備的物品。 |
| Loadout | 配裝用,物品不會顯示在 UI 中。 |
| Hide | 隱藏用,物品不會顯示在 UI 中。 |
| Drop | 用於丟棄物品。 |
基礎 Item Collection 的特性
- 物品數量只能為正數,不允許負值。
- Unique 物品預設數量只能為 1;若相同 Unique 物品被加入兩次,會建立一個擁有新 ID 的複製品。
- 預設每個物品只有一個 Item Stack(若需多個 Stack,請使用 Multi Stack Item Collection)。
- 預設沒有物品數量上限(使用 Item Restrictions 可以添加限制)。
Overflow(溢出)選項
當 Item Restrictions 導致物品無法加入時,Item Collection 的 Overflow 機制會被觸發。預設行為是退回至物品的來源,也可以設定 Item Overflow Action 來客製化處理邏輯(例如設定「Drop Item Action」讓物品自動掉落)。
建立 Item Overflow Action:
// 在專案視窗右鍵:
Create → Ultimate Inventory System → Item Overflow Actions
也可以監聽溢出事件:
// 監聽溢出事件
"c_Inventory_OnAddItemOverflow_ItemInfoToAdd_ItemInfoAdded_ItemInfoRejected"
Item Collection API
新增 / 移除物品
// 新增物品
var myItemInfo = new ItemInfo( (1, myItem) );
itemCollection.AddItem(myItemInfo);
// 移除物品
var itemInfoRemoved = itemCollection.RemoveItem((ItemInfo)(1, myItem));
查詢物品數量
// 取得物品在 Collection 中的總數量(包含多個 Stack)
var amount = itemCollection.GetItemAmount(myItem);
// 以 Item Definition 查詢(布林參數決定是否包含繼承定義)
var amountDefinition = itemCollection.GetItemAmount(myItemDefinition, true);
遍歷所有物品
// 用 Item Stack 遍歷
var allItemStacks = itemCollection.GetAllItemStacks();
for (int i = 0; i < allItemStacks.Count; i++) {
var item = allItemStacks[i].Item;
if (item == null) { continue; }
// 對物品進行操作
}
// 用 Item Info 遍歷(需要 Pool 陣列)
var pooledArray = GenericObjectPool.Get<ItemInfo[]>();
var allItemInfos = itemCollection.GetAllItemInfos(ref pooledArray);
for (int i = 0; i < allItemInfos.Count; i++) {
var itemInfo = allItemInfos[i];
// 對 ItemInfo 進行操作
}
// 重要:用完歸還
GenericObjectPool.Return(pooledArray);
使用篩選與排序取得物品
// 取得所有 Mutable 物品,並依 Item Definition 名稱排序
var filteredAndSorted = itemCollection.GetItemInfos(ref itemInfos,
x => x.Item.IsMutable,
Comparer<ItemStack>.Create((i1, i2) =>
i1.Item.ItemDefinition.Name.CompareTo(i2.Item.ItemDefinition.Name))
);
Item Slot Collection
Item Slot Collection 是一種特殊的 Item Collection,只允許將物品加入由 Item Slot Set(ScriptableObject)定義的特定槽位中。這種 Collection 類型常用於儲存裝備中的物品。
建立 Item Slot Set:
// 在專案視窗右鍵:
Create → Ultimate Inventory System → Inventory → Item Slot Set
Item Slot Collection API
// 取得 Item Slot Collection 並轉型
var equipmentCollection = m_Inventory.GetItemCollection(m_EquipmentItemCollectionID)
as ItemSlotCollection;
// 以槽位索引新增 / 取得 / 移除
var itemAdded = equipmentCollection.AddItem(itemInfo, slotIndex);
var itemInSlot = equipmentCollection.GetItemInfoAtSlot(slotIndex);
var itemRemoved = equipmentCollection.RemoveItem(slotIndex, amountToRemove);
// 也可以用槽位名稱操作
itemAdded = equipmentCollection.AddItem(itemInfo, slotName);
itemInSlot = equipmentCollection.GetItemInfoAtSlot(slotName);
// 取得物品實際所在槽位的索引
var realSlotIndex = equipmentCollection.GetItemSlotIndex(myItem);
// 取得物品若加入時可能放入哪個槽位
var potentialSlotIndex = equipmentCollection.GetTargetSlotIndex(myItem);
Item Transaction Collection
Item Transaction Collection 是一種特殊的 Item Collection,一次只持有一個物品。它通常作為主要 Item Collection(Main),負責將新加入的物品自動轉移至正確的 Item Collection。當你的 Inventory 有多個子 Collection 且需要依規則分配物品時,這個元件非常實用。
可設定屬性
| 屬性 | 說明 |
|---|---|
| Item Collection Names | 指定要依序嘗試加入的 Item Collection 名稱清單。 |
| Overflow Back To Origin | 物品被拒絕時,是否退回至來源位置(需要 ItemInfo 中含有來源資訊)。 |
| Rejected Item Actions | 物品被拒絕時,對該物品執行的 Item Actions。 |
| Return Real Added Item Amount | 是否只回傳實際加入的數量,或也包含被拒絕的數量。 |
運作邏輯:依序檢查所有連結的 Item Collections,使用 Item Restrictions 判斷物品是否符合,符合則加入;若全部都不符合,則觸發拒絕事件。
// 監聽物品被拒絕的事件
EventHandler.RegisterEvent<ItemInfo>(m_Inventory,
EventNames.c_Inventory_OnRejected_ItemInfo, HandleItemInfoRejected);
Multi Stack Item Collection
Multi Stack Item Collection 允許同一個物品擁有多個 Item Stack。每個 Stack 有一個上限(由預設值或物品屬性指定),超過上限後會溢出到新的 Stack。
| 屬性 | 說明 |
|---|---|
| Default Stack Size Limit | Common 物品每個 Stack 的預設上限。 |
| Stack Size Limit Attribute Name | 指向整數屬性的屬性名稱,用來針對特定 Common 物品覆寫 Stack 上限。 |
物品限制(Item Restrictions)
大多數遊戲的庫存都有限制(如空間、堆疊上限等)。UIS 預設沒有限制,你可以依需求自行設定。設置限制的三種主要方式:
- 使用 Item Restriction Set Object(ScriptableObject)
- 建立繼承
IItemRestriction介面的自訂元件 - 建立自訂 Item Collections
Item Restriction Set Object
建立路徑:
Create → Ultimate Inventory System → Inventory → Item Restriction Set
最常用的兩種內建限制類型:
自訂 Item Restriction
實作 IItemRestriction 介面即可建立自訂限制,並掛在 Inventory 旁邊,或加入 Item Restriction Set 中。
/// <summary>物品集合限制介面。</summary>
public interface IItemRestriction
{
/// <summary>初始化。</summary>
void Initialize(IInventory inventory, bool force);
/// <summary>判斷物品是否可被加入。回傳 null 表示不允許。</summary>
ItemInfo? AddCondition(ItemInfo itemInfo, ItemCollection receivingCollection);
/// <summary>判斷物品是否可被移除。回傳 null 表示不允許。</summary>
ItemInfo? RemoveCondition(ItemInfo itemInfo);
}
監聽物品拒絕事件
private void Start() {
EventHandler.RegisterEvent<ItemInfo, ItemInfo, ItemInfo>(m_Inventory,
EventNames.c_Inventory_OnAddItemRejected_ItemInfoToAdd_ItemInfoAdded_ItemInfoRejected,
HandleItemRejection);
}
private void HandleItemRejection(ItemInfo itemInfoToAdd, ItemInfo itemInfoAdded, ItemInfo itemInfoRejected) {
// ItemInfoToAdd:原始嘗試加入的 ItemInfo
// ItemInfoAdded:成功加入的部分(部分加入時)
// ItemInfoRejected:被拒絕的數量
}
動態庫存大小(Dynamic Inventory Size)
Dynamic Inventory Size 元件用來動態調整 Inventory 可以攜帶的物品數量上限。它會自動尋找「Bag」類型的物品(可以增減庫存空間上限的物品),並依此調整庫存大小。
- 可以設定多個 Dynamic Inventory Size 元件,分別限制不同的 Item Collection 子集(例如武器欄最多 X 個,資源欄最多 Y 個)。
- 可綁定至 Inventory Grid,透過 Dynamic Inventory Size Inventory Grid Binding 元件在 UI 中視覺化顯示庫存限制。
Display Amount Format中使用{0}顯示目前物品數量,{1}顯示最大空間。
物品(Item)
Item 類別是庫存系統的核心。Items 是簡單的資料物件(非 MonoBehaviour 或 Unity Object),可以是唯一的(Unique)或共用的(Common)。詳細差異請參考第 1 篇的術語說明。
Item API
建立物品
// 以名稱建立
var potion = InventorySystemManager.CreateItem("Potion");
// 指定預設 ID 建立(適合 Unique/Mutable 物品的存檔或網路同步)
var armor = InventorySystemManager.CreateItem("Armor", 777);
var armorIsUnique = armor.IsUnique;
var armorIsMutable = armor.IsMutable;
// 以 Item Definition 建立
var swordDef = InventorySystemManager.GetItemDefinition("Sword");
var sword = InventorySystemManager.CreateItem(swordDef);
// 複製物品(連同執行期間修改過的屬性值一起複製)
var swordCopy = InventorySystemManager.CreateItem(sword);
取得與設定屬性
// 取得屬性並讀取值
var attackAttr = sword.GetAttribute<Attribute<int>>("Attack");
var attack = attackAttr.GetValue();
// 設定 Override 值(僅 Mutable 物品允許)
attackAttr.SetOverrideValue(attack + 5);
檢查類別 / 定義歸屬
var weapon = InventorySystemManager.GetItemCategory("Weapon");
// 檢查物品是否屬於指定 Category
var swordIsWeapon = weapon.InherentlyContains(sword);
// 檢查物品是否屬於指定 Definition(及其子 Definition)
var swordIsDef = swordDef.InherentlyContains(sword);
// 取得系統中所有已載入物件
var allCategories = InventorySystemManager.ItemCategoryRegister.GetAll();
var allDefinitions = InventorySystemManager.ItemDefinitionRegister.GetAll();
var allItems = InventorySystemManager.ItemRegister.GetAll();
物品資訊(Item Info)
ItemInfo 是一個包含豐富資訊的 struct,理解它需要先區分以下幾個容易混淆的概念:
| 類型 | 說明 | 特性 |
|---|---|---|
| Item | 執行期間從 Item Definition 建立的物件 | Unique / Immutable 物品可能共用同一個參照 |
| Item Amount | Item + 數量的簡單結構(struct) | 不含 Stack 或 Collection 資訊;常用於 Inspector 序列化 |
| Item Stack | Item + 數量(class),連結至 Item Collection | 不應在 Collection 外使用 |
| Item Info | Item Amount + Item Stack + Item Collection 的完整資訊(struct) | 可明確指定來源和目標 Stack / Collection |
假設庫存中有兩個 Stack:
Stack 1: 5 顆蘋果Stack 2: 10 顆蘋果若要從 Stack 2 移除 3 顆蘋果,可以使用 Item Info 明確指定來源 Stack。
操作後的回傳值:
Item Amount: 3(實際移除數量)Item Stack: Stack 2 剩餘 7 顆(原 Stack 參照)Item Collection: 該 Stack 所在的 Collection
建立 Item Info 的多種方式
new ItemInfo(itemAmount, itemCollection, itemStack);
new ItemInfo("MyItem", 1);
new ItemInfo(item, amount);
new ItemInfo(itemAmount);
new ItemInfo(itemStack);
new ItemInfo(itemAmount, otherItemInfo);
new ItemInfo(amount, otherItemInfo);
new ItemInfo(itemDefinition, amount);
new ItemInfo(otherItemInfo, amount);
new ItemInfo(amount, item);
物品升級(Item Upgrades)
UIS 提供多種實作物品升級系統的方式,沒有絕對最佳解,選擇取決於你遊戲的設計需求。以下介紹四種主要方案:
為每個升級等級建立一個獨立的 Item Definition,以父子關係連結(例如 Sword → Sword+1 → Sword+2)。
透過 Upgrade 屬性(型別為 Item Definition)記錄下一個升級目標。
簡單直觀 線性升級在 Item Definition 上設定陣列屬性(如 UpgradeName[]、UpgradeAttack[]),在 Item 上設定當前等級和攻擊力,透過腳本計算升級結果。
物品數量少時適用可升級物品擁有 Slots 屬性(ItemAmounts 類型),可插入「升級物品」。升級物品可套用至任何同類別的可升級物品。
高度靈活 需要自訂腳本以合成配方定義升級邏輯(如 1 武器 + 5 材料),客製化 Crafting Processor 計算升級結果,同一配方可用於所有同類別武器。
複雜系統適用 中階程式能力需求物品技能(Item Skills)
由於屬性系統的高度彈性,UIS 非常適合將物品實作為「技能」——即在同一類別下,每個物品都有獨特的功能邏輯。這與 UI 中使用 Category Item Action Set 讓同類別物品執行相同動作的方式不同。以下是三種主要實作選項:
選項 1:使用 ScriptableObject 作為屬性
在 Item 上設定一個 ScriptableObject 型別的屬性(例如 Item Action Set),透過程式碼取得該屬性並呼叫其中的函式,以 Item 和相關資訊作為參數。
/// <summary>嘗試使用物品技能。</summary>
protected bool TryUseItem(ItemInfo itemInfo, ItemUser itemUser) {
if (itemInfo.Item == null) { return false; }
// 取得 ScriptableObject 屬性
var attribute = itemInfo.Item.GetAttribute<Attribute<ItemActionSet>>(m_AttributeName);
if (attribute == null) { return false; }
var actionSet = attribute.GetValue();
if (actionSet == null) { return false; }
// 執行指定索引的 Item Action
if (m_UseOne) { itemInfo = (1, itemInfo); }
actionSet.ItemActionCollection[m_ActionIndex].InvokeAction(itemInfo, itemUser);
if (m_RemoveOnUse) { itemInfo.ItemCollection?.RemoveItem(itemInfo); }
return true;
}
Use Item Action Set Attribute 這個內建 Item Action,可以在 UI 中無需撰寫任何程式碼,直接將物品作為技能使用。透過 itemUser.gameObject.GetCachedComponent<T>() 可以高效取得角色身上的元件。
選項 2:在 Item Object 旁邊加入元件
只適用於物品作為 Item Object 出現在場景中的情況。透過 Item Object Behaviours Handler,根據綁定的 Item 動態切換不同的 Item Object Behaviour,改變 Item Object 在場景中的行為邏輯。
選項 3:結合選項 1 和選項 2
最靈活的方案。用 ScriptableObject 屬性定義技能邏輯,在需要場景互動時(如角色位置、物理偵測)透過 Item User 取得場景中的元件。適合複雜的技能系統設計。
物品數值(Item Stats)
物品透過屬性系統,可以非常靈活地影響角色或武器的各種數值。以下是幾種常見的實作模式:
方式 1:統計 Collection 中屬性總和
// 直接用 Item Collection 取得所有物品指定屬性的 Float 總和
var statSum = itemCollection.GetFloatSum("MyStat");
// 或使用 AttributeUtility 對任意 Item Stack 清單求和
var statSum = AttributeUtility.GetFloatSum("MyStat", itemCollection.GetAllItemStacks());
方式 2:角色數值(搭配 Equipper)
最常見的模式是裝備物品時更新角色數值。監聽 Equipper 的 c_Equipper_OnChange 事件,在裝備變更時重新計算。
public class ExampleCharacterStats
{
protected IEquipper m_Equipper;
protected int m_BaseMaxHp, m_BaseAttack, m_BaseDefense;
protected int m_MaxHp, m_Attack, m_Defense;
public ExampleCharacterStats(int maxHp, int attack, int defense, IEquipper equipper)
{
m_BaseMaxHp = maxHp; m_BaseAttack = attack; m_BaseDefense = defense;
m_Equipper = equipper;
if (m_Equipper != null) {
// 監聽裝備變更事件
EventHandler.RegisterEvent(m_Equipper, EventNames.c_Equipper_OnChange, UpdateStats);
}
UpdateStats();
}
public void UpdateStats()
{
// 每次裝備變更都重新計算(避免浮點數累積誤差)
m_MaxHp = m_BaseMaxHp + m_Equipper.GetEquipmentStatInt("MaxHp");
m_Attack = m_BaseAttack + m_Equipper.GetEquipmentStatInt("Attack");
m_Defense = m_BaseDefense + m_Equipper.GetEquipmentStatInt("Defense");
}
}
物品動作(Item Actions)
Item Actions 用於從庫存中對物品執行操作,例如「使用」、「丟棄」、「裝備」等。不應與 Item Object Behaviours 混淆——後者是在遊戲世界中使用物品(如揮劍、射擊),需要 GameObject 的參照。
Item Actions 有兩個核心方法:
CanInvoke(ItemInfo, ItemUser):判斷動作是否可以執行。Invoke(ItemInfo, ItemUser):執行動作邏輯。
Item User 允許傳入玩家角色的參照,方便取得角色身上的元件。
Item Action Set 與 Category Item Action Set
建立路徑:
Create → Ultimate Inventory System → Item Actions → Item Action Set
Item Action Set 可以設定一組與特定 Item Category 相關的 Item Actions(例如「Pickupable」類別對應「Drop」動作)。也可以設定「排除類別(Exclude Categories)」,由 Category Item Action Set 使用。
Category Item Action Set 是多個 Item Action Sets 的集合,由 Item View Slots Container 上的 Binding 元件使用,可根據選取的物品自動匹配可用的動作。
自訂 Item Action
[System.Serializable]
public class MyItemAction : ItemAction
{
/// <summary>判斷動作是否可執行。</summary>
protected override bool CanInvokeInternal(ItemInfo itemInfo, ItemUser itemUser)
{
return true; // 自訂判斷邏輯
}
/// <summary>執行動作。</summary>
protected override void InvokeActionInternal(ItemInfo itemInfo, ItemUser itemUser)
{
// 自訂動作邏輯
}
}
透過程式碼直接呼叫 Item Action
public void ManuallyCallDropItemAction()
{
// 1. 取得物品的 ItemInfo
var result = m_Inventory.GetItemInfo(InventorySystemManager.GetItemDefinition("Apple"));
if (!result.HasValue) { return; }
var appleItemInfo = result.Value;
appleItemInfo = new ItemInfo(Mathf.Max(2, appleItemInfo.Amount), appleItemInfo);
// 2. 取得 Item User
var itemUser = m_Inventory.GetComponent<ItemUser>();
// 3. 建立並初始化 Item Action(可快取重複使用)
var dropItemAction = new DropItemAction();
dropItemAction.Initialize(false);
// 執行動作
dropItemAction.InvokeAction(appleItemInfo, itemUser);
// 部分動作提供額外資訊(例如 Drop Action 記錄最後掉落的物件)
var droppedPickup = dropItemAction.PickUpGameObject;
}
IActionWithPanel,需要 ItemViewSlotContainer 或面板的參照,無法直接在程式碼中呼叫,必須透過 UI 觸發或手動在程式碼中設定這些參照。
內建物品動作
簡單動作(立即執行)
| 動作名稱 | 說明 |
|---|---|
| Debug Item Action | 在 Console 印出物品所有屬性值,適合除錯。 |
| Debug Item Object Action | 同上,同時也印出 Item Object 的資訊。 |
| Drop Item Action | 將物品從庫存丟出,需設定含有 Item Pickup 元件的 Prefab 參照。物品會在 Item User 附近生成。 |
| Duplicate Item Action | 複製物品並加入至同一 Inventory。 |
| Move To Collection Item Action | 在同一 Inventory 的不同 Item Collection 之間移動物品,可用來實作裝備/取下。 |
| Remove Item Action | 從庫存刪除物品(不掉落,直接移除)。 |
| Use Item Action Set Attribute | 呼叫物品上 Item Action Set 屬性中的 Item Action,並支援自動設定 Cooldown。 |
| Multi Item Action | 將多個簡單 Item Actions 合併成一個,不需撰寫程式碼即可組合複雜動作。 |
帶面板的動作(需玩家互動)
| 動作名稱 | 說明 |
|---|---|
| Quantity Drop Item Action | 顯示數量選擇面板讓玩家選擇丟棄數量。 |
| Assign Hotbar Item Action | 讓玩家將物品指派至快捷列某個槽位。需要 Item Hotbar Owner 介面。 |
| Item Action With Confirmation Pop Up | 在執行巢狀 Item Action 前,先顯示確認視窗讓玩家確認。 |
| Move Item Action | 使用 Item View Slot Move Cursor 在 Item View Slots 之間移動物品。 |
| Open Other Item View Slot Container | 開啟另一個面板,監聽物品選取事件(例如搜尋並選擇物品)。 |
物品物件(Item Objects)
Item Object 元件將特定物品(Item)與 GameObject 連結起來。任何需要在遊戲世界中顯示的物品(如撿取物、裝備)都應加上 Item Object 元件。Item Object 可以靈活地綁定/解綁物品,因此非常適合物件池(Object Pool)重複利用,只需更換綁定的物品即可。
當物品被附加或分離時,Item Object 會觸發事件,其他元件可監聽此事件更新遊戲世界中的外觀。
Item Object API
// 監聽物品變更事件
EventHandler.RegisterEvent(m_ItemObject,
EventNames.c_ItemObject_OnItemChanged, HandleItemChanged);
private void HandleItemChanged() {
var itemInfo = m_ItemObject.ItemInfo;
}
// 設定物品
var newItem = InventorySystemManager.CreateItem("MyItemName");
m_ItemObject.SetItem(newItem);
m_ItemObject.SetAmount(5);
m_ItemObject.SetItem((ItemInfo)(5, newItem));
// 從 Unique 物品取得其 Item Object
var itemObject = newItem.GetLastItemObject();
// 若物品非 Unique,可能綁定多個 Item Objects
var count = newItem.GetItemObjectCount();
for (int i = 0; i < count; i++) {
var obj = newItem.GetItemObjectAt(i);
}
Item Object Behaviour Handler
Item Object Behaviour Handler 坐在 Item Object 旁邊,由 Equipper 使用來觸發已裝備物品的使用行為。一個 Handler 可以連結多個 Item Object Behaviours(例如「槍」可以有開火、換彈、瞄準等動作)。
自訂 Item Object Behaviour 繼承基礎類別即可:
public abstract class ItemObjectBehaviour : MonoBehaviour
{
protected float m_NextUseTime;
/// <summary>是否可以使用(預設:冷卻時間已過)。</summary>
public virtual bool CanUse => Time.time >= m_NextUseTime;
/// <summary>使用物品物件。</summary>
public abstract void Use(ItemObject itemObject, ItemUser itemUser);
}
裝備系統(Equipping Items)
裝備系統由三個核心元件組成:Item Slot Collection、Item Slot Set、以及 Equipper。
Item Slot Set(槽位定義)
Item Slot Set 是一個 ScriptableObject,定義了一組裝備槽位。每個槽位由名稱、Item Category 限制和數量上限組成。例如「Right Hand」槽位可以設定只允許 Weapon 類別的物品,且上限為 1 個。
Item Slot Collection(裝備 Collection)
物品加入 Item Slot Collection 時,系統會依序:
- 確認物品符合某個槽位的限制。
- 若槽位為空則直接加入;若槽位已有物品,則替換之,被替換的物品會移至 Main Item Collection。
Equipper(裝備器)
Equipper 負責生成並視覺化裝備 Item Slot Collection 中的物品。針對每個槽位,可設定是否為 Skinned Mesh,以及生成位置。
Equipper 支援兩種 Prefab 屬性:
這種分離設計讓多個物品可共用同一套使用邏輯 Prefab,只需替換外觀模型。搭配 Item Binding 可動態切換「攻擊力」、「子彈數量」等屬性。
Skinned Mesh 裝備
Skinned Mesh 裝備 Prefab 的結構建議為:頂層 Transform → 兩個子物件(Skinned Mesh 和完整角色骨架)。
屬性綁定(Item Binding)
Item Binding 元件可以將物品的屬性值自動同步至遊戲物件上任何元件的同類型 Property。這讓同步物品資料變得極為簡單,完全可以在 Inspector 中設定,無需撰寫程式碼。
設定步驟
- 選取要加入 Binding 的 GameObject(通常是武器的視覺呈現物件)。
- 在 GameObject 上加入 Item Object(或 Usable Item Object)元件。
- 加入 Item Binding 元件。
- 選取資料庫和要綁定的屬性所屬 Item Category。
- 指定包含目標 Property 的元件。
- 選擇要綁定的 Property。
[SerializeField] protected int m_BulletsPerFire; // 這是 Field,無法綁定public int BulletsPerFire { // 這是 Property,可以綁定 get => m_BulletsPerFire; set => m_BulletsPerFire = value;}
物品撿取(Item Pickups)
Item Pickup 系統使用 Interactable(可互動物件)和 Interactor(互動者)機制。主要的撿取類型有四種:
| 類型 | 說明 |
|---|---|
| Item Pickup | 以 Item Object 元件決定撿取哪個物品。支援 Item Object Visualizer 動態更換外觀。 |
| Inventory Pickup | 讓互動者撿取整個 Inventory 中所有物品的複製品。 |
| Currency Pickup | 使用 Currency Collection 設定金幣數量,撿取後給予互動者。 |
| Random Inventory Pickup | 以 Inventory 中的物品數量作為機率表,隨機抽取部分物品(適合戰利品箱)。 |
撿取物件的必要元件
角色 GameObject 需要:
- Rigidbody / Rigidbody2D + Collider / Collider2D
- Inventory 元件
- 繼承「Interactor With Inventory」介面的元件(負責讓撿取物知道角色有 Inventory)
撿取物 GameObject 需要:
- Item Pickup 元件(或其他撿取類型)
- Interactable 元件 + Rigidbody / Rigidbody2D + Collider / Collider2D
- Collider 必須設為 Trigger
- 在 Interactable 元件上選擇 2D 或 3D 觸發事件,並設定角色所在的 Layer
丟棄物品的多種方式
- Item Action:Drop Item Action / Quantity Drop Item Action
- Item View Drop Action(拖放動作):Spawn Item Object(需場景中有 Item Object Spawner)
- Item Object Spawner 元件(通常在 Game 物件上)
- 自訂腳本(生成 Pickup Prefab 後設定 Item Object 元件上的物品)
物品掉落(Droppers)
Dropper 元件是最方便設計師使用的掉落解決方案,適用於敵人死亡、解謎完成、砍樹等各種觸發情境。共有四種 Dropper:
| 元件 | 說明 |
|---|---|
| Item Dropper | 掉落特定物品,支援 Item 和 Inventory Pickups。 |
| Currency Dropper | 掉落特定貨幣。 |
| Random Item Dropper | 以 Inventory 中的物品數量作為機率表,隨機掉落。適合敵人死亡時的戰利品生成。 |
| Random Currency Dropper | 以正態分佈在最小和最大範圍內隨機掉落貨幣(以 Currency Owner 的基礎貨幣金額加上百分比偏移為範圍)。 |
Item Object Spawner API
// 取得 Item Object Spawner(建議放在 InventorySystemManager 附近)
var itemObjectSpawner = InventorySystemManager.GetGlobal<ItemObjectSpawner>(spawnerID);
// 生成物品
itemObjectSpawner.Spawn(itemInfo, spawnPosition);
itemObjectSpawner.SpawnWithDelay(itemInfo, spawnPosition, delay);
itemObjectSpawner.SpawnWithAutoDestroy(itemInfo, spawnPosition, autoDestroyTimer);
itemObjectSpawner.Spawn(itemInfo, spawnPosition, delay, autoDestroyTimer);
呼叫 Dropper
// 取得 Dropper 元件並呼叫 Drop()
dropper.Drop();
// 指定掉落特定物品
dropper.DropItemPickup(item);
dropper.DropInventoryPickup(itemList);
自訂 Random Item Dropper(隨機屬性範例)
繼承 RandomItemDropper 並覆寫 Drop() 方法,可以在掉落前修改物品的隨機屬性(如稀有度、攻擊力)。以下是完整實作範例:
[Serializable]
public enum Rarity { Common, Rare, Legendary }
[Serializable]
public struct RarityAmount {
public Rarity Rarity;
public int Amount;
}
[Serializable]
public struct RarityDistribution {
public Rarity Rarity;
public AnimationCurve Distribution; // X 軸需在 0~1 之間
}
public class CustomRandomRarityStatDropper : RandomItemDropper
{
[SerializeField] protected RarityAmount[] m_RarityProbability;
[SerializeField] protected RarityDistribution[] m_AttackProbability;
public override void Drop()
{
var itemsToDrop = GetItemsToDrop();
AssignRandomStats(itemsToDrop); // 在掉落前設定隨機屬性
DropItemsInternal(itemsToDrop);
}
protected virtual void AssignRandomStats(ListSlice<ItemInfo> itemAmounts)
{
for (int i = 0; i < itemAmounts.Count; i++) {
var item = itemAmounts[i].Item;
if (item == null || !item.HasAttribute("Rarity")) { continue; }
// 設定隨機稀有度
var itemRarity = GetRandomRarity();
item.GetAttribute<Attribute<Rarity>>("Rarity").SetOverrideValue(itemRarity);
// 依稀有度設定隨機攻擊力
var newAttack = GetAttackForRarity(itemRarity);
item.GetAttribute<Attribute<float>>("Attack").SetOverrideValue(newAttack);
}
}
public float GetAttackForRarity(Rarity rarity) {
foreach (var dist in m_AttackProbability) {
if (dist.Rarity == rarity)
return dist.Distribution.Evaluate(Random.value);
}
return 0;
}
}