《UIS》第 5 篇 輸入處理、存檔、互動與 UI 框架

本篇文章、圖片出處來自 Ultimate Inventory System
軟體版本與本翻譯文件可能會有落差,本翻譯文件僅供參考。
本譯文為本站譯者原創翻譯內容,文字著作權歸本站所有。
未經授權,請勿任意轉載、改作或商業使用。

輸入處理、存檔、互動與 UI 框架

輸入處理器(Handlers)

Handler 元件使用 PlayerInput 元件透過輸入觸發 UIS 的 API,並以 PlayerInput 作為抽象層,讓你可以隨時更換底層輸入系統而不需要修改任何遊戲邏輯。

Handler 元件用途
Display Panel Manager Handler透過輸入開啟與關閉 Display Panels。
Usable Equipped Item Handler設定輸入以使用預設裝備系統中已裝備的 Usable Item Object。
Inventory Interactor可設定透過輸入觸發互動。
Hotbar Handler提供輸入以使用快捷列特定槽位的物品。
Item View Slot Container Item Action Handler將輸入映射至 Item Action,並對目前選取的 Item View Slot 觸發。
Grid BaseItem Info Grid 和其他格子元件的基礎類別,處理切換至下一個或前一個 Tab 的輸入。

分割畫面多人(Split Screen Co-op)

UIS 支援分割畫面的多人合作 UI。兩名玩家各自擁有獨立的 Canvas 和 Inventory。若兩個 Display Panel Manager 都將「開啟面板時暫停時間縮放為 0」設為關閉,就可以讓一個玩家在村莊購物時,另一個玩家同時在場景中自由移動。商店、合成等選單也可以同時各自開啟使用。

分割畫面的限制條件

  • 兩個角色必須各自擁有獨立的 UI Canvas。
  • 必須使用 Unity 新版 Input SystemRewired
  • 使用 Unity 新版 Input System 時,只有遊戲手把(Gamepad)有效,鍵盤和滑鼠在 UI 中無法使用(這是 Unity Input System 目前的已知限制)。

設定步驟(以 Demo 場景為例)

步驟一:匯入輸入系統整合套件

Unity 新版 Input System:

  1. 從 Package Manager 匯入 Input System 套件,並匯入對應的 UIS 整合套件。
  2. 在 Player Character GameObject 上,將「Unity Input」元件替換為整合套件中的「Unity Input System」元件。
  3. 確認 Player Character 上有 Input System 的 Player Input 元件,並參照 Input Action Asset(建議使用整合套件提供的 Character Input)。
  4. 測試 Demo 確認功能正常。

Rewired:

  1. 從 Asset Store 或 Package Manager 匯入 Rewired,並匯入 UIS 整合套件。
  2. 在 Player Character 上,將「Unity Input」元件替換為「Rewired Input」元件。
  3. 將整合套件中的「Rewired Input Manager Co-op」Prefab 加入場景(Player1 ID = 0,Player2 ID = 1)。
  4. 測試 Demo 確認功能正常。

步驟二:複製玩家與 UI

  1. 複製玩家 GameObject 和 Inventory Canvas。
  2. 將頂層 Panel 縮放至半個畫面大小。
  3. 為兩個玩家設定不同的 Inventory Identifier ID(建議設為 1 和 2)。若使用 Dynamic Panel Owner,Panel Manager ID 需與 Inventory Identifier ID 相同。
  4. 移除原有的 Event System GameObject,替換為兩個分別的 Event System 1 和 Event System 2。

步驟三:設定 Event System(Unity Input System)

  1. 在兩個 Event System GameObjects 上各加入 Multiplayer Event SystemInput System UI Input Module 元件(均來自 Unity Input System 套件)。
  2. 在 Multiplayer Event System 的「Player Root」欄位指派對應的 Canvas。
  3. 移除 Input System UI Input Module 預設的 UI Action Asset,改為參照角色 Player Input 上的 Input System UI Input Module,且每個玩家需指向不同的 Event System GameObject。
  4. 在 Player Input 上將 Default Scheme 從 <Any> 改為 Gamepad(鍵盤輸入不支援多人 Event System)。

步驟四:設定 Canvas

  1. 為每個 Inventory Canvas 設定不同的 ID,應與對應玩家的 Inventory Identifier ID 相同。
  2. 在 Canvas 上加入 Event System Identifier 元件,並參照正確的 Event System。
  3. 在 Game GameObject 上加入 Event System Manager(與其他 Managers 一起)。

音效(Audio)

UIS 的音效系統與 Ultimate Character Controller 共用同一套 Audio System。詳細的音效設定說明請參閱官方 Audio 文件。

在版本 1.2 的更新中,Audio Manager 已全面重寫為共用元件,支援 Audio Config ScriptableObject 定義音效清單、Audio Mixer、音量、音調(固定值或隨機值),也可以繼承 Audio Manager Module 接入自訂或第三方音效系統(如 FMOD、Wwise)。

存檔系統(Save System)

存檔系統可以將庫存的當前狀態(以及其他元件的狀態)儲存至磁碟,並在之後載入還原。UIS 的存檔系統設計上是用來擴展或嵌入現有存檔系統中使用的。

Save System Manager

Save System Manager 負責將資料序列化並寫入磁碟。序列化資料透過 SaverBase 元件向 Manager 註冊,存檔時 Manager 會遍歷所有 SaverBase 元件取得資料再序列化存入。

預設存檔路徑使用 Unity 的 Application.persistentDataPath。在 Save System Manager 上按右鍵可以在 Console 顯示你電腦的存檔資料夾路徑。

Load On Start 若 Save System Manager 在 Saver 元件完成註冊之前就嘗試載入資料,Saver 元件需將「Load On Start」欄位設為 ON,讓它在 Start 函式中主動取得已載入的存檔資料。

內建 Saver 元件

InventorySystemManagerItemSaver

主要 Saver,儲存所有 Item 的狀態(屬性值、名稱等)。其他 Saver 只儲存 Item ID,必須有這個 Saver 才能正確還原(Immutable & Common 物品除外)。

InventorySaver

儲存 Inventory 元件的內容(各 Item Collection 中的 Item ID 和數量)。

CurrencyOwnerSaver

儲存 Currency Owner 的貨幣內容。

InventoryGridSaver

儲存物品在 Inventory Grid 中的位置。

GameObjectSaver

通用 Saver,可儲存 GameObject 的位置、旋轉、縮放和啟用/停用狀態。

ItemShapeGridDataSaver

儲存 Item Shape Grid Data,記錄物品在形狀格子中的位置。

InventoryBridgeSaver

整合用(UCC):儲存綁定至 Ultimate Character Controller 角色的 Inventory 內容。

重要順序 InventorySystemManagerItemSaver 必須存在,其他 Saver 才能正確儲存和載入物品。若只有 InventorySaver 而沒有這個 Saver,Unique/Mutable 物品的屬性值將無法被正確還原。

Save Data 與 Metadata

存檔資料分為兩個檔案:Save Data(完整序列化狀態)和 Save Meta Data(簡短的存檔摘要)。這樣可以避免一次載入所有存檔到記憶體,只需要在 Save Menu 中顯示摘要時才讀取。

Save Meta Data 和 Save Meta Data Creator 可以繼承擴展,加入自訂的遊戲時間、玩家進度等資訊,並在 Save Menu UI 的 Save Views 中顯示。

自訂 Save Meta Data 範例

// 自訂 Save Meta Data Creator(ScriptableObject)
[CreateAssetMenu(fileName = "BasicSaveMetaDataCreator",
    menuName = "Opsive/Save System/Save Meta Data Creator.")]
public class BasicSaveMetaDataCreator : SaveMetaDataCreator
{
    public override SaveMetaData CreateMetaData(
        SaveSystemManager saveSystemManager, SaveDataInfo saveDataInfo)
    {
        return new BasicSaveMetaData(saveSystemManager, saveDataInfo);
    }

    public override SaveMetaData CreateEmpty() => new BasicSaveMetaData();
}

// 自訂 Save Meta Data(記錄存檔時間)
[Serializable]
public class BasicSaveMetaData : SaveMetaData
{
    [SerializeField] protected long m_DateTimeTicks;
    public long DateTimeTicks => m_DateTimeTicks;

    public BasicSaveMetaData() : base()
    { m_DateTimeTicks = new DateTime().Ticks; }

    public BasicSaveMetaData(SaveSystemManager mgr, SaveDataInfo info) : base(mgr, info)
    { m_DateTimeTicks = DateTime.Now.Ticks; }

    public void SetDateTime(DateTime newDateTime)
    { m_DateTimeTicks = newDateTime.Ticks; }
}

Save System Manager API

// 存入第 0 號存檔槽
SaveSystemManager.Save(0);
// 從第 0 號槽載入
SaveSystemManager.Load(0);
// 刪除第 0 號存檔
SaveSystemManager.DeleteSave(0);

// 取得目前的存檔資訊
var saveData = SaveSystemManager.GetCurrentSaveDataInfo();
// 取得特定 Saver 的序列化資料
SaveSystemManager.TryGetSaveData("The Saver Key", out var serializedData);

嵌入第三方存檔系統

若你的遊戲已有自訂的存檔系統,仍可以嵌套使用 UIS 的存檔系統,只需在第三方系統的 Save/Load 介面中呼叫 UIS 的 Save/Load 即可:

public class NestedInventorySaver : ISave
{
    public int saveSlot = 0;

    public object RecordData()
    {
        // false 表示不寫入磁碟,只在記憶體中序列化
        SaveSystemManager.Save(saveSlot, false);
        var saveDataInfo = SaveSystemManager.GetCurrentSaveDataInfo();
        return saveDataInfo.Data; // 可再用自訂序列化器處理
    }

    public void ApplyData(object data)
    {
        if (data == null) { return; }
        var saveData = data as SaveData;
        // 直接傳入 SaveData 跳過磁碟讀取
        SaveSystemManager.Load(saveSlot, saveData);
    }
}

自訂 Saver 元件

public class ExampleSaver : SaverBase
{
    [System.Serializable]
    public struct ExampleSaveData
    {
        // 在此加入可序列化的欄位
        // 若要儲存物品或貨幣,使用 IDAmountSaveData
    }

    /// <summary>序列化存檔資料。</summary>
    public override Serialization SerializeSaveData()
    {
        var saveData = new ExampleSaveData() {
            // 設定要儲存的資料
        };
        return Serialization.Serialize(saveData);
    }

    /// <summary>反序列化並載入存檔資料。</summary>
    public override void DeserializeAndLoadSaveData(Serialization serializedSaveData)
    {
        var savedData = serializedSaveData
            .DeserializeFields(MemberVisibility.All) as ExampleSaveData?;
        if (!savedData.HasValue) { return; }
        // 將載入的資料套用至目標元件
    }
}

互動系統(Interaction System)

UIS 的互動系統使用 IInteractor(互動者)和 IInteractable(可互動物件)兩個介面。可互動物件可以被「選取」、「取消選取」和「互動」;互動者可以新增和移除可互動物件的參照。

Inventory Interactor
實作 IInteractor 的便利元件,應放置在 Inventory 旁邊。若「Auto Interact」設為 false,需要輸入才能觸發互動;使用 Inventory Standard Input 元件設定對應輸入。
注意:Interactor 需要一個 Collider 才能偵測到 Interactable。
Interactable
加在任何需要被互動的 GameObject 上。需確保 Interactor 的 Layer Mask 包含此 GameObject 的 Layer。設定「Auto Interact」為 true 可在 Trigger 觸發時自動互動,而無需等待 Interactor 主動互動。
注意:Interactable 需要一個 Trigger Collider 才能偵測到 Interactor。
Interactable Behavior
放置在 Interactable 旁邊,監聽 Select、Deselect 和 Interact 事件。內建範例包括 PickupBase、MenuInteractableBehavior 等,可以繼承自訂。

自訂 Interactable Behavior

public class ExampleInteractableBehavior : InteractableBehavior
{
    public override bool CanInteract(IInteractor interactor)
        => base.CanInteract(interactor);

    public override void OnSelect(IInteractor interactor)
    {
        base.OnSelect(interactor);
        // 角色靠近時的行為(例如顯示提示 UI)
    }

    public override void OnDeselect(IInteractor interactor)
    {
        base.OnDeselect(interactor);
        // 角色離開時的行為
    }

    protected override void OnInteractInternal(IInteractor interactor)
    {
        // 若需要確認 Interactor 具有 Inventory
        if (!(interactor is IInteractorWithInventory withInventory)) { return; }
        var inventory = withInventory.Inventory;
        // 執行互動邏輯
    }
}

UI 系統總覽

UIS 的 UI 系統採用高度模組化的設計,大量依賴元件、Prefab 和 ScriptableObject,讓你可以靈活地客製化每一個 UI 細節。四個最核心的類別是:

  • Item View:顯示物品的視覺元件。
  • Item View Slot:偵測點擊、選取、拖放的互動容器。
  • Item View Slots Container:管理多個 Item View Slots 的容器(Inventory Grid、Item Hotbar、Equipment Panel 等)。
  • Display Panel:控制 UI 面板的開啟與關閉。
UI 系統完全可選 庫存核心系統(Inventory、Item Collections 等)與 UI 系統是完全解耦的。你可以選擇不使用任何內建 UI 元件,改用自己的或第三方 UI 解決方案。

Display Panel & Manager

Display Panel 和 Manager 系統讓你可以完整控制面板的開啟/關閉順序與回滾邏輯。每次開啟面板時,可以傳入前一個面板的參照,關閉時會自動回到前一個面板。

Display Panel Manager

Display Panel Manager 管理其子物件中的所有 Display Panels,確保同一時間只有一個 Menu Panel 開啟。

Panel Owner 很重要 強烈建議(部分情況下甚至必須)將角色 GameObject(擁有 Inventory、Item User、Inventory Input 等元件的物件)設為 Panel Owner。Panel Owner 會接收關於面板開啟/關閉的各種事件。

Display Panel Manager API

// 以 ID 從 ISM 取得 Display Panel Manager
var displayPanelManager = InventorySystemManager.GetDisplayPanelManager(ID);

// 取得 Panel Owner(通常是角色 GameObject)
var panelOwner = displayPanelManager.PanelOwner;

// 以名稱取得面板
var panel = displayPanelManager.GetPanel(panelName);

// 開啟面板(使用目前選取的面板作為前一個面板)
displayPanelManager.OpenPanel(panel);

// 取得目前選取的面板和選單
var selectedPanel = displayPanelManager.SelectedDisplayPanel;
var selectedMenu  = displayPanelManager.SelectedDisplayMenu;

// 關閉目前選取的面板
displayPanelManager.CloseSelectedPanel();

// 監聽面板開啟/關閉事件
EventHandler.RegisterEvent<OpenClosePanelInfo>(m_PanelOwner,
    EventNames.c_GameObject_OnPanelOpenClose_OpenClosePanelInfo,
    HandlePanelOpenedOrClosed);

Display Panel 屬性

屬性說明
Unique Name讓 Display Panel Manager 建立名稱到面板的字典,方便從程式碼任意位置存取。
Is Menu Panel設為 true 時作為 Menu,同一時間只能有一個 Menu 開啟。
Is Non Selectable開啟/關閉時不更改 Panel 鏈(適合 Tooltip 類面板)。
Start Enabled預設所有面板以停用狀態開始,勾選此項可使面板從啟用狀態開始。
Open on Start類似 Start Enabled,但會在啟動時觸發 OnOpen 事件。
Set Active On Open開啟時設定 GameObject 為 active。
Set Disable On Close關閉時設定 GameObject 為 inactive。
Selectable On Open面板開啟後自動選取的 UI 元素。
Main ContentUI Designer 用來知道 Prefab 應生成在哪個 Transform 下。
BindingsPanel Bindings 會在同一 GameObject 上自動找到。面板的 Open/Close 等事件都由 Bindings 處理。

Display Panel API

// 透過 Panel Manager 的上下文開啟/關閉(推薦)
panel.SmartOpen();
panel.SmartClose();

// 取得面板狀態
DisplayPanel previousPanel    = panel.PreviousePanel;
Selectable   prevSelectable   = panel.PreviousSelectable;
bool         isOpen           = panel.IsOpen;
var          panelManager     = panel.Manager;
string       panelName        = panel.UniqueName;

// 指定前一個面板和 Selectable 開啟
panel.Open(previousPanel, previousSelectable);

// 關閉(true = 關閉後選取前一個面板)
panel.Close(true);

Item View Slots Container

Item View Slots Container 是 Inventory Grid、Item Hotbar、Item Slot Collection View 等元件共用的基礎類別,提供統一的與 Item View Slots 互動方式:選取、點擊、移動、新增、移除和交換。

核心屬性

屬性說明
Container Name用來區分不同的 Container,在 Drop Action 條件中特別有用。
Slot Cursor參照 Item View Slot Cursor Manager(選用)。
Item View Drawer依物品類別選擇對應的 Item View Prefab 來顯示。Container 的 Content Transform 和 Drawer 的 Content Transform 必須指向同一個物件。
Content包含所有 Item View Slots 的父 Transform。

Item View Slots Container API

// 監聽 Item View Slot 事件
m_ItemViewSlotContainer.OnItemViewSlotSelected += HandleSelected;
private void HandleSelected(ItemViewSlotEventData slotEventData) {
    var itemInfo = slotEventData.ItemViewSlot.ItemInfo;
}
m_ItemViewSlotContainer.OnItemViewSlotClicked   += HandleClicked;
m_ItemViewSlotContainer.OnItemViewSlotEndDragE  += HandleEndDrag;

// 取得所有 Item View Slots
var slots = m_ItemViewSlotContainer.ItemViewSlots;

// 新增、移除、移動物品
m_ItemViewSlotContainer.AddItem(itemInfo, slotIndex);
m_ItemViewSlotContainer.RemoveItem(itemInfo, slotIndex);
m_ItemViewSlotContainer.MoveItem(sourceIndex, destinationIndex);

// 取得特定槽位的物品
var slot     = m_ItemViewSlotContainer.GetItemViewSlot(slotIndex);
var itemView = m_ItemViewSlotContainer.GetItemView(slotIndex);
var itemInfo = m_ItemViewSlotContainer.GetItemAt(slotIndex);

// 選取槽位
m_ItemViewSlotContainer.SelectSlot(slotIndex);
var selected = m_ItemViewSlotContainer.GetSelectedSlot();

// 手動重繪 Container
m_ItemViewSlotContainer.Draw();

Item View Slots Container Bindings(附加元件)

Item View Slot Move Cursor
讓玩家在不使用滑鼠拖放的情況下,透過 Unity UI Event System 移動物品(適合鍵盤/手把操作)。
Item View Slots Container Item Action Binding
綁定 Item Actions,在點擊或透過腳本對選取的物品觸發動作。
Item View Slots Container Category Item Action Set Binding
同上,但使用 Category Item Action Set,可依物品類別自動匹配動作。
Item View Slots Container Description Binding
選取物品時,在 Item Description 元件中顯示該物品的說明。
Item View Slot Panel To Tooltip
將任意 Rect Transform 轉換為 Tooltip,在選取或點擊 Item View Slot 時顯示。

Inventory Grid

Inventory Grid 是 Item View Slots Container 的子類別,整合了格子系統的 Tab 切換、導航和高效捲動機制。格子使用固定數量的按鈕,透過映射物品索引到格子視圖索引來實現高效捲動,不需要為每個物品都建立 UI 元素。

Inventory Grid 可以顯示玩家庫存、商店庫存、倉庫等各種 Inventory 的內容。使用 UI Designer 的「Inventory Grid」Tab 可以快速建立和設定。

必要元件

  • Item Info Grid:包含所有格子資訊,定義格子何時以及如何刷新。
  • Grid Event System:偵測按鈕的所有事件(選取、點擊等),並在 Unity Event System 嘗試超出格子邊界時觸發事件,讓格子可以捲動或切換 Tab。
  • Item View Drawer:依物品類別選擇正確的 Item View Prefab。

Item Info Grid 重要欄位

欄位說明
Grid Size格子大小,初始化後執行期間不會改變。
Set Real Element Count As Maxtrue:格子元素數量隨物品增減動態調整;false:使用 Max Element Count 的固定值。
Max Element Count格子可顯示的最大元素數量。
Disable Element Option超出上限的 Item Slots 的隱藏方式選項。
Grid Navigator格子導航器,支援捲動、分頁、用 Scroll View 導航等。

Inventory Grid API

// 取得格子 ID
var gridID = m_InventoryGrid.GridID;

// 設定 Inventory
m_InventoryGrid.SetInventory(inventory);

// 對格子中的物品排序
m_InventoryGrid.SortItemIndexes(itemInfoComparer);

// 綁定篩選器/排序器
m_InventoryGrid.BindGridFilterSorter(itemInfoSorterFilter);

Item Shape Grid

Item Shape Grid 允許物品在有限的格子中佔用多個(任意形狀的)格位,類似《暗黑破壞神》風格的背包系統。它與一般 Inventory Grid 在架構上完全不同,需要 Inventory 本身持有格子資料。

雙層結構

  • Background Layer:顯示每個格子的選取或拖放預覽(透過顏色濾鏡)。點擊事件在此層處理。
  • Foreground Layer:顯示依物品形狀縮放的物品圖示,此層不可點擊(點擊穿透到 Background Layer)。

Item Shape 屬性

物品的形狀由 Shape 屬性(類型為 ItemShape)定義,使用布林值格子加上一個錨點(錨點必須在設為 true 的格子上)。可設定在 Item Category 或 Item Definition 屬性上。

核心元件

Item Shape Inventory Grid Controller
管理多個 Grid Data,判斷物品能否加入格子,以及應加入哪個 Grid Data。必須放置在 Inventory 旁邊。
Item Shape Grid Data
追蹤物品在格子中的位置。Item Collection ID 設為 None 時監控所有 Item Collections;否則只監控指定的 Item Collection。Grid Size 必須與 UI 中的格子大小一致。
Item Shape Inventory Grid Binding
放置在 Inventory Grid 旁邊,將 Item Shape Grid Data 綁定至 Inventory Grid 以顯示物品。

Item Shape Grid 專用的 Item View Modules

Module說明
Item Shape Item View依物品形狀正確縮放並顯示圖示;可依層(Background/Foreground)啟用/停用不同 GameObject。
Item Shape Drop Preview Item View拖放時預覽物品能否放置(改變 Background Layer 格子的顏色濾鏡)。
Item Shape Selected Item View選取時在 Background Layer 對應格子加上顏色標示。
Item Shape Rect Place Item View在不規則形狀的物品中,精確定位數量顯示文字的位置(例如放在右下角)。
Canvas Group控制圖示的顯示/隱藏和互動狀態(Foreground Layer 不可互動時用)。
建議 盡量使用 UI Designer 的「Item Shape Grid」Tab 來修改 Grid Size 和 Cell Size,確保所有相關元件都正確設定一致。直接修改可能導致 Grid Data 和 UI 大小不符。

Item Hotbar

Item Hotbar 使用 Item View Slots Container 顯示物品,並允許透過 Item Action 或拖放來指派物品至槽位。Item Hotbar 透過事件從 Inventory Input 接收輸入,可以在每次綁定的 Inventory 更新時刷新,保持物品數量和狀態的最新顯示。

Hotbar 的設計考量 Hotbar 是高度遊戲專屬的元件。物品被「指派」到 Hotbar 槽位,但在 Inventory 中的位置不會改變。若需要物品存在於獨立 Item Collection 中的 Hotbar,可以使用 Item Slot Collection View(Equipment UI)來替代。

三種內建 Hotbar 類型

Item Hotbar
透過 Item Action 或拖放,從 Inventory 將物品指派至快捷列槽位。
Inventory Mirror Hotbar
鏡像顯示 Inventory Grid 的前幾個槽位,不需要單獨指派。
Item Slot Collection View(裝備欄作為 Hotbar)
若需要 Hotbar 物品位於獨立的 Item Collection 中,可將 Equipment UI 作為 Hotbar 使用。

Item Hotbar API

// 指派物品至槽位
hotbar.AssignItemToSlot(itemInfo, itemSlotIndex);

// 使用槽位中的物品
hotbar.UseItem(itemSlotIndex);

Item Slot Collection View(Equipment Panel)API

// 以槽位名稱取得 Item View Slot
var headSlot = m_ItemSlotCollectionView.GetItemViewSlot("Head");

// 取得 Item View Slot 對應的 Item Slot
var headItemSlot = m_ItemSlotCollectionView.GetItemSlot(headSlot);

// 綁定 Inventory 至 Item Slot Collection View
m_ItemSlotCollectionView.SetInventory(m_Inventory);

拖放系統(Move Items / Drag & Drop)

物品可以透過滑鼠拖放或 Item View Slot 的 Move Cursor(鍵盤/手把)在 Item View Slots Containers 之間移動。系統由以下核心元件組成:

Item View Slots Cursor Manager
放置在 Canvas 上,負責追蹤移動中的物品來源,並在螢幕上生成跟隨滑鼠的 Item View。所有拖放/移動元件都需要此元件才能運作。
Item View Drop Handler
拖放的核心邏輯。接收來源和目標 Item View Slot 的資訊,透過 Item View Slot Drop Action Set 決定執行哪個動作(交換、給予、新增、移除等)。
Item View Slot Drag Handler
放在 Drop Handler 旁邊,監聽 BeginDrag、Drag、EndDrag 事件,讓 Cursor Manager 追蹤拖曳狀態和滑鼠位置。
Item View Slot Move Cursor
讓玩家不需要滑鼠拖放即可移動物品(鍵盤/手把適用)。透過 StartMove 函式觸發,通常由 Move Item Action 呼叫。可選配 Move Display Panel 讓玩家按 Escape 取消移動。

Item View Slot Drop Action Set

建立路徑:

Create → Ultimate Inventory System → UI → Item View Slot Drop Action Set

內建放置條件(Drop Conditions)

條件(Conditions)
條件說明
Drop Container Can Add檢查來源/目標 Container 是否可以接受交換的物品。
Drop Container Can Give檢查 Container 是否可以將物品給予另一個 Container。
Drop Container Can Move檢查物品是否可以在同一 Container 內移動索引。
Drop Container Has Name檢查來源/目標 Container 的名稱是否符合指定名稱。
Drop From Item Collection檢查來源 Item Collection 是否符合指定的 Item Collection。
Drop Item Amount Condition比較來源物品數量是否符合最小/最大值。
Drop Null Item檢查來源/目標物品是否為 null。
Drop Same Container檢查兩個 Item View Slots 是否來自同一個 Container。
Drop Container Can Smart Exchange智慧交換條件,考慮多種情境。
Item View Shape Drop使用 Item Shape Grid 時,檢查物品形狀是否可以放入目標位置。
動作(Actions)
動作說明
Drop Action To Item從 Drop Action 觸發一個 Item Action。
Drop Container Exchange在兩個 Container 之間交換物品(移除再新增)。
Drop Container Give將物品從來源給予目標,可選擇是否移除/新增。
Drop Container Smart Exchange智慧交換,考慮多種情境。
Drop Inventory Exchange同 Container Exchange,但直接操作 Inventory(繞過 Item View Slots Container)。
Drop Inventory Give同 Container Give,但直接操作 Inventory。
Drop Move Index在同一 Container 內移動物品的 Stack 索引。
Drop Spawn Item Object以放下的物品生成一個 Item Object。
Item View Shape Drop使用 Item Shape Grid 的函式放置物品,考慮物品形狀。

自訂 Drop 條件與動作

// 自訂條件:檢查來源和目標是否在同一 Container
[Serializable]
public class ItemViewDropSameContainerCondition : ItemViewDropCondition
{
    public override bool CanDrop(ItemViewDropHandler handler)
        => handler.SourceContainer == handler.DestinationContainer;
}

// 自訂動作:移動物品在 Container 中的索引
[Serializable]
public class ItemViewDropMoveIndexAction : ItemViewDropAction
{
    public override void Drop(ItemViewDropHandler handler)
    {
        handler.SourceContainer.MoveItem(
            handler.StreamData.SourceIndex,
            handler.StreamData.DestinationIndex);
    }
}

Filter & Sorters

Item Info Filter 和 Sorter 用於 Item Info Grid、Inventory Grid 等元件,對物品清單進行篩選和排序。可在 UI Designer 中以下拉選單輕鬆新增,自訂的 Filter/Sorter 也會自動出現在清單中。

內建 Filter & Sorter

名稱說明
Inventory Search Filter透過輸入欄位搜尋物品名稱,同時套用指定的排序器。
Item Info Category Filter依 Item Category 篩選。
Item Info Item Collection Filter依所在 Item Collection 篩選。
Item Info Amount Sorter依數量排序。
Item Info Attribute Value Sorter依屬性值排序(屬性值需實作 IComparable)。
Item Info Category Name Sorter依 Item Category 名稱排序。
Item Info Name Sorter依物品名稱排序。
Item Info Multi Filter Sorter組合多個 Filter/Sorter,依序套用(順序重要)。

自訂 Filter / Sorter 範例

// 多重篩選器(組合多個 Filter Sorter)
public class ItemInfoMultiFilterSorter : ItemInfoFilterSorterBase
{
    [SerializeField] internal List<ItemInfoFilterSorterBase> m_GridFilters;

    public override ListSlice<ItemInfo> Filter(ListSlice<ItemInfo> input, ref ItemInfo[] outputPooledArray)
    {
        var list = input;
        foreach (var filter in m_GridFilters)
            list = filter.Filter(list, ref outputPooledArray);
        return list;
    }

    public override bool CanContain(ItemInfo input)
    {
        foreach (var filter in m_GridFilters)
            if (!filter.CanContain(input)) { return false; }
        return true;
    }
}

// 依名稱排序器
public class ItemInfoNameSorter : ItemInfoSorterBase
{
    [SerializeField] protected bool m_Ascending = false;
    protected Comparer<ItemInfo> m_ItemNameComparer;
    public override Comparer<ItemInfo> Comparer => m_ItemNameComparer;

    protected override void Awake()
    {
        base.Awake();
        m_ItemNameComparer = Comparer<ItemInfo>.Create((i1, i2) => {
            if (i1.Item == null && i2.Item == null) { return 0; }
            if (i1.Item == null) { return 1; }
            if (i2.Item == null) { return -1; }
            return m_Ascending
                ? i2.Item.name.CompareTo(i1.Item.name)
                : i1.Item.name.CompareTo(i2.Item.name);
        });
    }
}

// 依 Item Collection 篩選器
public class ItemInfoItemCollectionFilter : ItemInfoFilterBase
{
    [SerializeField] protected ItemCollectionID[] m_ShowItemCollections;
    [SerializeField] protected ItemCollectionID[] m_HideItemCollections =
        { ItemCollectionPurpose.Loadout, ItemCollectionPurpose.Hide };

    public override bool Filter(ItemInfo itemInfo)
    {
        var show = m_ShowItemCollections.Length == 0;
        foreach (var id in m_ShowItemCollections)
            if (id.Compare(itemInfo.ItemCollection)) { show = true; break; }
        if (!show) { return false; }
        foreach (var id in m_HideItemCollections)
            if (id.Compare(itemInfo.ItemCollection)) { return false; }
        return true;
    }
}

Views 系統

View 和 View Module 類別提供了高度可擴展的 UI 顯示架構。View 是一個泛型基礎類別,Item View 繼承自 View,負責將操作(Clear、Select、Click、Hide、SetValue、Refresh 等)傳遞給其所有的 View Module 子元件。

View Module 元件每個只負責一件事,多個 Module 可以在同一個 Prefab 上組合使用。這種設計讓 View Module 極易撰寫且高度可重用。

Item View Drawer & Category Item View Set

Item View Drawer 配合 Category Item View Set(ScriptableObject)運作,將 Item Category 映射至對應的 Item View Prefab。這讓 View Drawer 知道該使用哪個 Prefab 來顯示特定物品。Drawer 也提供了「繪製前」和「繪製後」的事件,例如商店選單可以監聽「繪製後」事件,顯示依商店或角色計算的特殊價格。

內建 Item View Modules

Module說明
Name Item View顯示物品名稱。
Icon Item View顯示物品的 Icon 屬性圖示。
Amount Item View顯示庫存中的物品數量。
Select Image View依選取狀態改變 Image 的 Sprite。
Equipped Select Item View依物品所在的 Item Collection(是否已裝備)改變圖示。
Int Attribute Item View顯示整數屬性的值。
Item Slots Item View依物品所在 Item Collection 啟用/停用 GameObjects。
Item Shape Item ViewItem Shape Grid 專用,依形狀顯示物品圖示。
Cooldown Item View顯示 Use Item Action Set Attribute 物品動作的冷卻時間。

自訂 Item View Module 範例

/// <summary>顯示物品數量的 Item View Module。</summary>
public class AmountItemView : ItemViewModule
{
    [SerializeField] protected Text m_AmountText;
    [SerializeField] protected bool m_HideAmountIfSingle;

    public override void SetValue(ItemInfo info)
    {
        m_AmountText.text = (m_HideAmountIfSingle && info.Amount <= 1)
            ? ""
            : $"x {info.Amount}";
    }

    public override void Clear() { m_AmountText.text = ""; }
}

特殊 Item View Module 介面

介面 / 抽象類別說明
IInventoryDependent讓 Module 可以參照外部 Inventory(例如數量比較用)。
IViewModuleSelectable接收 Item View 的 Select 事件。
IItemViewSlotDropHoverSelectable接收 Drop Handler 的 Hover 事件,可顯示放置預覽。

Item Description & Attribute View

Item Description

Item Description 是一種特殊的 Item View,專門顯示物品的詳細說明。可以搭配任何 Item View Module,最常見的搭配是 Category Attribute View Set Item View module——依據被描述的物品動態生成對應的 Attribute Views。

Item Description 有三種綁定方式:

  • Container 綁定:以 Item View Slots Container Description Binding 元件綁定至任何 Item View Slots Container,顯示目前選取槽位的物品說明。
  • Panel 綁定:以 Item Description Panel Binding 元件綁定至 Display Panel,確保說明面板在使用前完成初始化。
  • Tooltip:使用 Item View Slot Panel To Tooltip 將 Item Description 作為 Tooltip 使用。

Attribute View

Attribute View 的設計與 Item View 相同,但用來顯示屬性值。通常在 Item Description 中使用。內建的 Attribute View Modules:

Module說明
Float Value Attribute Box顯示 float 屬性,可自訂小數位數(Format String)。
Int Value Attribute Box顯示整數屬性。
String Attribute Box顯示字串屬性。
Name Value Attribute Box顯示屬性名稱和值(支援任意物件類型)。

Recipe View

Recipe View 的設計與 Item View 相同,但用來顯示 Crafting Recipe。內建的 Recipe View Module:

  • First Output Recipe View:參照 Item Box 物件,顯示配方的第一個輸出結果。

Currency View & Monitor

Multi Currency View

Multi Currency View 將多種 Currency 對應至各自的 Currency View 元件。設定 Currency Amount 或 Currency Collection 後,會自動顯示每種貨幣的數量。

// 依 Currency Collection 繪製貨幣
m_MultiCurrencyView.DrawCurrency(currencyCollection);
// 或依 Currency Amount 陣列繪製
m_MultiCurrencyView.DrawCurrency(currencyAmounts);
// 設定文字顏色(例如:購物時資金不足顯示紅色)
m_MultiCurrencyView.SetTextColor(color);

Currency View

// 設定要顯示的貨幣數量
m_CurrencyView.SetValue(currencyAmount);

Currency Owner Monitor

Currency Owner Monitor 使用 Multi Currency View 顯示一個 Currency Owner 的貨幣數量,可透過 Inventory Identifier ID 自動找到 Currency Owner。

// 設定要監視的 Currency Owner
m_CurrencyOwnerMonitor.SetCurrencyOwner(currencyOwner);

Save View

Save View 的設計與 Item View 相同,由 Save Menu 用來顯示 Save Data Info。若使用自訂 Save Meta Data,可以建立自訂 Save View Modules 來客製化 Save Menu 中顯示的資訊。


此網誌的熱門文章

哥利亞遙控炸彈 (Leichter Ladungsträger Goliath)

O-I(試製120t超重戰車「オイ」)