揹包面板、箱子面板、鍛造合成面板、裝備佩戴面板、商店面板等html
面板的顯示和隱藏、保存和加載、拾起物品、物品移動、物品出售和購買等算法
導入素材UI.unitypackagejson
UML圖設計:canvas
物品Item分爲幾類:消耗品Consumable、裝備Equipment、武器Weapon、材料Material
消耗品影響HP/MP
裝備影響strength/ intelligence/ agility/ stamina等
裝備類型有:head/ neck/ chest/ ring/ leg/ bracer/ boots/ shoulder/ belt/ offHand
武器影響damage
武器類型有:offHand/ mainHand
材料用於合成裝備和武器c#
物品共有變量:
id/ name/ type/ quality/ description/ capacity/ buyprice/ sellprice
消耗品變量:
hp/ mp
裝備變量:
strength/ intelligence/ agility/ stamina等/ 還有equipmentType
武器變量:
damage/ 還有weaponType
材料變量:無數組
使用get;set;的方式,能夠很靈活地控制變量的訪問權限安全
public class Item { public int ID { get; set; } public string Name { get; set; } public ItemType Type { get; set; } public ItemQuality Quality { get; set; } public string Description { get; set; } public int Capacity { get; set; } public int buyprice { get; set; } public int sellprice { get; set; } public Item(int id, string name, ItemType type, ItemQuality quality, string desc, int capacity, int buyprice, int sellprice){ this.ID = id; this.Name = name; ... this.buyprice = buyprice; this.sellprice = sellprice; } public enum ItemType { Consumable, Equipment, Weapon, Material } public enum ItemQuality { Common, Uncommon, Rare, Epic, Legendary, Artifact }}
-- 注意:兩個枚舉類型ItemType和ItemQuality是在類內部聲明的,在外部使用時須要經過類名,好比Item.ItemType來使用
並且聲明的時候須要爲public的
-- 改進:每一個Item都有本身的UI圖標
public string SpritePath { get; set; }
並在Project中建立Resources文件夾,將全部Item圖標的Sprite移入該文件夾
其餘類的構造函數裏也得加上spritePathapp
public class Consumable : Item { public int HP { get; set; } public int MP { get; set; } public Consumable(int id, string name, ItemType type, ItemQuality quality, string desc, int capacity, int buyprice, int sellprice, int hp, int mp) : base(id, name, type, quality, desc, capacity, buyprice, sellprice) { this.HP = hp; this.MP = mp; }}
public class Equipment : Item { public int Strength { get; set; } public int Intelligence { get; set; } public int Agility { get; set; } public int Stamina { get; set; } public EquipmentType EquipType { get; set; } public Equipment(int id, string name, ItemType type, ItemQuality quality, string desc, int capacity, int buyprice, int sellprice, int strength, int intelligence, int agility, int stamina, EquipmentType equipType) : base(id, name, type, quality, desc, capacity, buyprice, sellprice) { this.Strength = strength; this.Intelligence = intelligence; this.Agility = agility; this.Stamina = stamina; this.EquipType = equipType; } public enum EquipmentType { Head, Neck, Chest, Ring, Leg, Bracer, Boots, Shoulder, Belt, OffHand }}
public class Weapon : Item { public int Damage { get; set; } public WeaponType WeapType { get; set; } public Weapon(int id, string name, ItemType type, ItemQuality quality, string desc, int capacity, int buyprice, int sellprice, int damage, WeaponType weapType) : base(id, name, type, quality, desc, capacity, buyprice, sellprice) { this.Damage = damage; this.WeapType = weapType; } public enum WeaponType { OffHand, MainHand }}
-- 注意,這裏由於Weapon不是繼承與Equipment,所以這裏使用的EquipmentType須要寫成Equipment.EquipmentTypedom
public class Material : Item { public Material(int id, string name, ItemType type, ItemQuality quality, string desc, int capacity, int buyprice, int sellprice) : base(id, name, type, quality, desc, capacity, buyprice, sellprice) { }}
-- 由於子類必須提供一個構造方法去構造父類,而父類沒有空的構造方法,因此Material必須寫對應的構造方法去構造父類
不然須要在Item中寫一個空的構造方法異步
https://www.bejson.com/jsoneditoronline -- 在線Json編輯器
有不少種物品,在Json文件中保存成一個數組
屬性根據類中成員變量來肯定
[ { "id": 1, "name": "血瓶", "type": "Consumable", "quality": "Common", "description": "這個是用來加血的", "capacity": 10, "buyprice": 10, "sellprice": 5, "hp": 10, "mp": 0, "spritePath": "Sprites/Items/hp" } ]
暫時先寫一個物品,用於測試
在Project->Items下保存一個記事本Items.Json文件,編碼格式改成UTF-8
建立空物體InventoryManager,添加腳本InventoryManager.cs -- 用於管理全部物品
以後還有兩個分管理器:揹包Knapsack,箱子Chest
Knapsack和Chest不是繼承於InventoryManager的,只是功能結構關係而已
揹包和箱子之間有一些交互,好比移動物品等,這些交互方法就在InventoryManager中實現
注意:InventoryManager和這些通常都爲單例模式
InventoryManager.cs中
單例模式的實現
1. _instance爲private,由於不能在外界訪問
2. Instance爲public,做爲在外界訪問的接口
3. 構造函數爲private,不能在外界直接調用,而必須經過Instance進行調用
private static InventoryManager _instance;
public static InventoryManager Instance {
get {
if(_instance == null) {
// 第一次想要獲得的時候,未賦值,給它賦值
_instance = GameObject.Find("InventoryManager").GetComponent<InventoryManager>();
}
return _instance;
}}
任務14:改進Knapsack和Chest的設計
由於Knapsack和Chest是有共有功能的,所以能夠建立一個類Inventory做爲他倆的父類
InventoryManager須要進行Items.Json數據的解析
在Json官網 www.json.org中找到c#的 LitJSON
或前往 https://litjson.net/
額。。。下載失敗,我直接在csdn下載了
https://download.csdn.net/download/blackbord/10016032
下載dll文件,導入unity中就可使用dll中的相關類了
在Project文件夾下建立Plugins文件夾,這個文件夾下的文件會被預編譯,通常用於放置插件
在InventoryManager中建立解析Json文件的方法:
ParseItemJson()
解析出來的結果爲不少Item,新建一個List列表來存儲
private List<Item> itemList;
itemList = new List<Item>();
取得Json文件的內容
TextAsset jsonTextAsset = Resources.Load<TextAsset>("Items");
string jsonString = jsonTextAsset.text; // 獲得了文本文件中的字符串
解析
using LitJson;
LitJson的教程 -- https://www.cnblogs.com/Firepad-magic/p/5532650.html
// Siki老師下載失敗後,從AssetStore上import了JsonObject
-- 會和LitJson有所區別
思路:
1. 經過API獲得存儲數據的對象(該對象爲一個集合)
2. 經過遍歷該對象,獲得每個數據對象
3. 經過"type"字段的值,判斷Item的類型
4. 聲明對應類型的對象,並經過構造函數新建對象
5. 將新建的對象添加到list中
LitJson版本:
// 獲得的jsonData爲一個集合,每個元素也是JsonData類型 JsonData jsonData = JsonMapper.ToObject(jsonString); foreach (JsonData data in jsonData) { // 將JsonData對象中存儲的值,經過Item或子類的構造函數,新建一個對應的Item對象 // 先獲得共有的屬性 int id = int.Parse(data["id"].ToString()); string name = data["name"].ToString(); string type = data["type"].ToString(); Item.ItemType itemType = (Item.ItemType)System.Enum.Parse(typeof(Item.ItemType), type); Item.ItemQuality itemQuality = (Item.ItemQuality)System.Enum.Parse(typeof(Item.ItemQuality), data["quality"].ToString()); string description = data["description"].ToString(); int capacity = int.Parse(data["capacity"].ToString()); int buyprice = int.Parse(data["buyprice"].ToString()); int sellprice = int.Parse(data["sellprice"].ToString()); string spritePath = data["spritePath"].ToString(); Item item = null; // 首先須要經過"type"的值,確認該Item是什麼類型的 switch (itemType) { case Item.ItemType.Consumable: int hp = int.Parse(data["hp"].ToString()); int mp = int.Parse(data["mp"].ToString()); // 經過JsonData的數據,新建一個Consumable對象 item = new Consumable(id, name, itemType, itemQuality, description, capacity, buyprice, sellprice, spritePath, hp, mp); break; case Item.ItemType.Equipment: break; case Item.ItemType.Weapon: break; case Item.ItemType.Material: break; default: break; } // 將新建的Item對象添加到list中 itemList.Add(item); }
JsonObject版本:
JsonObject講解:readme.txt
直接經過JSONObject的構造函數進行Json數據的解析
獲得的多個JsonObject對象會存儲在list中
事實上Json數據中的任何一個總體都是一個JsonObject類的對象
好比一個鍵值對,或一個對象,或一個數組
對於每一個對象,經過jsonObject["key"]訪問對應的value,根據value類型
經過.n表示float,.b表示bool,.str表示string等等,還有Object、數組等類型
// 獲得的jsonObject爲一個list集合,每個元素也是JsonObject類型 JSONObject jsonObject = new JSONObject(jsonString); // 遍歷JSONObject.list,獲得每個對象 foreach(JSONObject elem in jsonObject.list) { // 將對象轉換爲Item類 // 經過索引器獲得的爲JsonObject類型 // ToString()後發現,數據帶有引號"" // 不能使用 elementObject["name"].ToString()); int id = (int)elem["id"].n; string name = elem["name"].str; Item.ItemType type=(Item.ItemType)System.Enum.Parse(typeof(Item.ItemType),elem["type"].str); ... Item item = null; switch (type) { case Item.ItemType.Consumable: int hp = (int)elem["hp"].n; int mp = (int)elem["mp"].n; item = new Consumable(id, name, type, quality, description, capacity, buyprice, sellprice, spritePath, hp, mp); break; ... default: break; } itemList.Add(item); }
全部物品的信息都保存在了InventoryManager.itemList中,
如今開發數據和UI之間的連通,將item顯示在UI上
開發揹包的UI
新建UI->Panel,命名KnapsackPanel,SourceImage: panel,調節顏色
屏幕自適應
Canvas--CanvasScaler--UI Scale Mode = Scale With Screen Size
表示按控件佔屏幕的比例來顯示,而不是按像素來顯示
Match = Width,表示按寬度的比例來,而高度的肯定按照控件的寬高比而定
顯示效果很差 -- 去掉天空盒子,Window->Lighting->Skybox選擇None
新建子物體UI->Panel,命名ItemsContainer,做爲全部物品的容器
調整大小
由於不須要顯示,因此alpha=0;
新建子物體UI->Image,命名Slot,做爲一個物品的容器
SourceImage: button_square
由於須要不少個Slot,所以在ItemsContainer中添加組件Grid Layout Group,用於排序
調整Cell大小,調整Spacing
新建Knapsack的子物體,UI->Image,命名TitleBg,SourceImage: button_long
新建子物體, UI->Text,揹包,字體等微調
由於不須要交互,取消勾選Knapsack、TitleBg、Text、ItemsContainer的Raycast Target
只有Slot須要交互
Slot的完善:
實現鼠標移入的顏色變化效果
在Slot上添加組件Button
製做成prefab
在Slot下建立子物體UI->Image,命名Item,做爲Slot中存儲的物品
調整大小,SourceImage: 先隨便選一個,由於最後是動態賦值的
在Item下建立子物體UI->Text,命名Amount,用於顯示物品數量,微調顏色等
由於在Slot中作了交互,因此Item和Amount中的Raycast Target取消勾選
將Item製做成prefab
給Slot添加腳本Slot.cs,用於管理自身
給Item添加腳本ItemUI.cs,用於管理Item自身的顯示等功能
由於ItemUI表示的爲存在該Slot中的物體,所以須要保存該Item和數量
public Item Item {get; set; }
public int Amount {get; set; }
Inventory.cs腳本 -- 管理自身中全部的Slot
在Knapsack中添加腳本Knapsack 繼承自 Inventory
// 存儲全部的Slot
private Slot[] slotList;
// 在Start()中獲取全部的Slot
public virtual void Start() {
slotList = GetComponentsInChildren<Slot>();
}
-- 由於在Knapsack等子類中也須要用到這個Start(),所以設置爲virtural,方便子類訪問
拾起物品並存儲進揹包的功能:
public bool StoreItem(int id)
public bool StoreItem(Item itemToStore)
// 返回bool表示是否存儲成功,由於一些緣由好比揹包滿了
-- InventoryManager 中根據item id返回Item對象的方法
public Item GetItemById(int id) {
foreach(Item item in itemList) {
if(item.ID == id) {
return item;
}}
return null;
}
public bool StoreItem(int id) {
// 先進行轉換
Item item = InventoryManager.Instance.GetItemById(id);
return StoreItem(item);
}
public bool StoreItem(Item item) {
// 安全判斷
if(item == null) { Debug.LogWarning("要存儲的物品id不存在"); }
// 存儲
// 兩種狀況
// 1. 以前揹包沒有該類物品
實例化一個該類物體,將其放入一個Slot
2. 揹包已有該類物品
找到該Slot
若物品個數小於Capacity,Amount+1 (裝備等capacity爲1)
若放滿了,則實例化另外一個Item,並放入另外一個Slot
if(item.capacity == 1) {
Slot slotToStore = FindEmptySlot();
if(slotToStore == null) { Debug.LogWarning("沒有空位"); return false; }
else { // 將物品放入該slot
slotToStore.StoreItem(item);
}} else {
// 判斷當前是否已經存在該類物體
Slot slotToStore = FindSlotWithSameItemType(item);
if(slotToStore != null) { // 找到已存在同類未滿Slot
slotToStore.StoreItem(item);
} else { // 未找到
// 新建一個slot存儲
slotToStore = FindEmptySlot();
if(slotToStore == null) { ... 警告已滿; return false; }
else {
slotToStore.StoreItem(item);
}}}
如何找到空格子呢?
private Slot FindEmptySlot()
foreach(Slot slot in slotList) {
if slot.transform.childCount == 0) {
return Slot;
}}
return null;
}
如何找到類型相同的物品槽呢?
private Slot FindSlotWithSameItemType(Item item) {
foreach(Slot slot in slotList) {
if(slot.transform.childCount == 1) { // 有一個子物體
if(slot.GetItemId() == item.ID && !slot.IsSlotFilled()) { // 符合類型且數量未滿
// ------- 在Slot中實現GetItemType()方法
// public Item.ItemType GetItemType() {
// return transfrom.GetChild(0).GetComponent<ItemUI>().Item.Type;
// }
// ------- 任務25中發現:不該該判斷GetItemType()
// 這樣若是血瓶和藍瓶都是Consumable的Type,就會互相疊加了
// public int GetItemId() {
// return transfrom.GetChild(0).GetComponent<ItemUI>().Item.ID;
// }
// ------- 在Slot中實現IsSlotFilled()方法
// public bool IsSlotFilled() {
// ItemUI itemUI = transform.GetChild(0).GetComponent<ItemUI>();
// return itemUI.Amount >= itemUI.Item.Capacity;
// }
return slot;
}}}
return null;
}
如何將物品存入Slot呢?
在Slot.cs中
public void StoreItem(Item item) {
if(transform.ChildCount == 0) { // 空slot
// 實例化Item,並存入Slot
-- public GameObject itemPrefab;
GameObject itemObject = Instantiate(itemPrefab);
itemObject.transform.SetParent(transform);
itemObject.transform.localPosition = Vector3.zero;
// 這裏的Scale顯示會出現Bug,在任務39中(即本節最後)會詳細說明
// 給實例化出來的空Item進行賦值
// ---------在ItemUI中實現Item的賦值 public void SetItem(Item item, int amount = 1) {
// this.Item = item;
// this.Amout = amount;
// // 更新UI
// }
itemObject.GetComponent<ItemUI>().SetItem(item);
} else { // 自己已經存儲了物體
Item itemInSlot = transform.GetChild(0);
ItemUI itemUI = itemInSlot.GetComponent<ItemUI>();
// 這裏沒必要判斷Slot滿的狀況,由於在外界判斷完了
// -------- 在ItemUI中實現數量+1的方法 public void AddAmount(int num = 1) {
// this.Amount += num;
// // 更新UI
// }
itemUI.AddAmount();
}
如何更新UI呢?
// 顯示有兩個部分,一個部分是Sprite,一個部分是Amount.text
private Image itemImage;
private Text amountText;
// 若是將初始化寫在Start中,會報空指針,由於在一開始的時候就執行了賦值初始化
// 因此寫成get的形式
public Image ItemImage {
get{
if(itemImage == null) {
itemImage = GetComponent<Image>();
}
return itemImage;
}
public Text AmountText {
// 類似
amountText = GetComponentInChildren<Text>();
}
public void UpdateUISprite() {
ItemImage.sprite = Resources.Load<Sprite>(Item.SpritePath);
public void UpdateUIText() {
AmountText.text = Amount.ToString();
測試:
新建腳本Player.cs
-- 由於操做物品的來源通常爲Player(隨意啦,一個解釋而已)
-- 經過鍵盤按鍵G,隨機獲得一個物品放到揹包中
在 Update()中
if(Input.GetKeyDown(KeyCode.G) {
// 隨機生成一個id
int id = Random.Range(1, 2);
// 調用Knapsack (即Inventory中)的StoreItem(id)進行存儲
// ---------- 將Inventory作成單例模式
// 可是不能在Inventory中實現,應該在Knapsack和Chest中實現
// 由於若是在Inventory中實現,那麼Knapsack和Chest就會共用了
// 將Knapsack作成單例模式
// private static Knapsack _instance;
// public static Knapsack Instance {
// get{
// if(_instance == null) {
// _instance = GameObject.Find("KnapsackPanel").GetComponent<Knapsack>();
// }
// return _instance;
// }}
Knapsack.Instance.StoreItem(id);
代碼:
Player.cs
public class Player : MonoBehaviour { void Update () { if(Input.GetKeyDown(KeyCode.G)) { // 隨機生成一個id int id = Random.Range(1, 2); Knapsack.Instance.StoreItem(id); }}}
Knapsack.cs中只有單例模式的實現代碼
Inventory.cs
public class Inventory : MonoBehaviour { private Slot[] slotList; public virtual void Start () { slotList = GetComponentsInChildren<Slot>(); } public bool StoreItem(Item itemToStore) { // 存儲一個Item,有兩種狀況 // 1. 在Inventory中沒有此類Item,則尋找空Slot存儲 // 2. 在Inventory中已有此類Item // 若數量已滿,則尋找空Slot存儲;若數量未滿,則增長數量便可 // 另外一種判斷: // 1. 若Item.Capacity爲1,則須要尋找空Slot存儲 // 2. 若不爲1,尋找是否已經存在該類物品 // 已存在,則數量增長;沒有存在,尋找空Slot Slot slotToStore = FindSlotWithSameItemType(itemToStore); if(slotToStore == null) { // 沒有找到相同類型且未滿的Slot -- 故尋找空slot存儲 slotToStore = FindEmptySlot(); if(slotToStore == null) { Debug.LogWarning("空間已滿,不可進行存儲"); return false; } else { //找到空slot,進行存儲 slotToStore.StoreItem(itemToStore); } } else { // 找到相同類型且未滿的Slot,存儲 slotToStore.StoreItem(itemToStore); } return true; } public bool StoreItem(int itemId) { Item itemToStore = InventoryManager.Instance.GetItemById(itemId); if(itemToStore == null) { // 未找到該Item return false; } return StoreItem(itemToStore); } private Slot FindSlotWithSameItemType(Item item) { foreach(Slot slot in slotList) { if(slot.transform.childCount == 1) { // 不是空slot if(slot.GetItemType() == item.Type && !slot.IsSlotFilled()) { // 相同類型的slot,且未滿 return slot; }}} return null; } private Slot FindEmptySlot() { foreach(Slot slot in slotList) { if(slot.transform.childCount == 0) { // 找到空slot return slot; }} return null; }}
Slot.cs
public class Slot : MonoBehaviour { public GameObject itemPrefab; public Item.ItemType GetItemType() { return transform.GetChild(0).GetComponent<ItemUI>().Item.Type; } public bool IsSlotFilled() { ItemUI itemUI = transform.GetChild(0).GetComponent<ItemUI>(); return itemUI.Amount >= itemUI.Item.Capacity; } public void StoreItem(Item itemToStore) { // 兩種狀況下調用該方法: // 1. 本Slot爲空,須要實例化Item進行存儲 // 2. 本Slot不爲空,只須要增長數量便可 if(transform.childCount == 0) { // 實例化Item GameObject itemObject = GameObject.Instantiate(itemPrefab) as GameObject; itemObject.transform.SetParent(transform); itemObject.transform.localPosition = Vector3.zero; // 給該Item賦值 itemObject.GetComponent<ItemUI>().SetItem(itemToStore); } else { // 數量增長 transform.GetChild(0).GetComponent<ItemUI>().AddAmount(); }}}
ItemUI.cs
public class ItemUI : MonoBehaviour { public Item Item { get; set; } public int Amount { get; set; } private Text amountText; public Text AmountText { get { ... } } private Image itemImage; public Image ItemImage { get { ... } } public void SetItem(Item item, int amount = 1) { // amount默認爲1,由於該方法意爲被空Slot存儲item時調用 this.Item = item; this.Amount = amount; // UI更新 UpdateUISprite(); UpdateUIText(); } public void AddAmount(int num = 1) { // 默認+1,由於該方法意爲存儲item時調用,一般存儲爲1個 this.Amount += num; // UI更新 UpdateUIText(); } private void UpdateUIText() { AmountText.text = Amount.ToString(); } private void UpdateUISprite() { ItemImage.sprite = Resources.Load<Sprite>(Item.SpritePath); }}
任務39:添加物品時的動畫顯示
-- 物品添加到Slot中時,會先放大一下物品表示強調,再縮小到應有大小
在ItemUI中控制動畫的播放
流程解釋:Player.Update() -> Knapsack.StoreItem(id/item) -> Slot.StoreItem(Item) -> ItemUI.SetItem(item)
ItemUI.SetItem(item)中,傳遞設置了item和amount,並更新了sprite和text的UI顯示
所以在ItemUI.UpdateUISprite()中
添加
直接在UpdateUISprite()中完成動畫效果嗎?
不行,須要在Update()中不斷調用Lerp來實現
定義屬性
private float targetScale = 1;
Update() {
if(Mathf.Abs(transform.localScale.x - targetScale) > 0.05f) {
// 進行動畫播放
transform.localScale = Vector3.one * Mathf.Lerp(transform.localScale.x, targetScale, Time.deltaTime*smooth);
} else {
transform.localScale = Vector3.one * targetScale; // 節約性能
if(targetScale != 1) {
// 每當添加物品時,會將targetScale設大,播放動畫
// 結束動畫後localScale=targetScale>1,此時自動將targetScale設爲1,開始變小動畫
targetScale = 1;
}}}
Bug修復:在Slot.cs的StoreItem()裏有一個scale自動變化的問題
public void StoreItem(Item itemToStore) { if(transform.childCount == 0) { // 實例化Item GameObject itemObject = GameObject.Instantiate(itemPrefab) as GameObject; // 大小顯示一直有問題,在這裏手動設置 // 爲何呢,由於實例化的時候是在slot裏面實例化的 // 實例化出來的時候,首先會放在場景的根目錄下 // 而後設置位置的時候,好比設置Parent的時候纔會移動到Parent下面 // 由於Canvas自身是有scale的大小設置的,所以會影響到實例化物體的scale變化 itemObject.transform.SetParent(transform); itemObject.transform.localPosition = Vector3.zero; itemObject.transform.localScale = Vector3.one; // 給該Item賦值 itemObject.GetComponent<ItemUI>().SetItem(itemToStore); } else { // 數量增長 transform.GetChild(0).GetComponent<ItemUI>().AddAmount(); } }
什麼是ToolTip?
當光標懸浮在某個控件上時,會有一個彈窗顯示對控件的解釋說明
實現物品Item的ToolTip,顯示對應的description
新建UI->Image,命名ItemDescToolTipPanel,SourceImage: panel
新建子物體UI->Text,命名ItemDescText,微調顏色大小等
這個時候,ToolTip的大小是固定的,不會隨着Text而改變
子類會隨着父類的變化而變化,當父類的大小不會受子類的大小影響
可是由於每一個Item的desc長度不一樣,須要的Panel長度也不一樣
解決方法:取巧
將Image設置爲Text的子物體
如今只須要實現Text框大小隨着文字數量而改變便可
在Text添加組件Content Size Fitter
Horizontal/ Vertical Fit:
Preferred Size -- 讓組件隨着內容的變化而變化
Min Size
Unconstrained
如今,實現了大小的自適應變化
可是,Text的內容由於Image的覆蓋而看不見了
解決方法:取巧
複製一份Text,命名Content,做爲Image子物體,實現顯示的功能
注意,要將Image的pivot設置爲四周拉伸,纔會隨着Text而改變大小
由於鼠標懸浮時,ToolTip須要顯示在鼠標的右下方而不是以鼠標爲中心
-- 設置ToolTip的中心點爲左上角
可是縮放的時候會發現,會隨着縮放而跑偏了
緣由:pivot會根據縮放的比例而定
解決方法:pivot設置在Text的左上角,而不是背景框的左上角
ToolTip不須要進行交互,所以將全部的Raycast Target取消勾選
代碼實現:
1. ToolTip框的顯示與隱藏
2. Desc文字的變化
ToolTip.cs 信息提示類
在InventoryManager.cs中進行調用
給ToolTip物品添加ToolTip.cs腳本
// 由於須要實現Desc內容的顯示和隱藏,所以須要獲得
private Text descSizeController = GetComponent<Text>();
private Text contentText = transform.GetChild(0).Find("ContentText").GetComponent<Text>();
// contentText爲ToolTip.transform的子物體的子物體,所以須要先獲得子物體,在使用transform.Find()
// 經過Canvas Group.Alpha組件控制顯示和隱藏
-- 給ToolTip物體添加CanvasGroup組件,並取消Interactable和Blocks Raycasts的勾選
private CanvasGroup canvasGroup = GetComponent<CanvasGroup>();
// 顯示和隱藏功能
private float targetAlpha = 0; // 默認不顯示
public void DisplayToolTip(String content) {
// 改變框大小和顯示內容
descSizeController.text = content;
ContentText.text = content;
targetAlpha = 1; // 顯示
}
public void HideToolTip() {
targetAlpha = 0; // 不顯示
}
Update() {
// 控制將Alpha變化成targetAlpha值
if(canvasGroup.alpha != targetAlpha) {
canvasGroup.alpha = Mathf.Lerp(canvasGroup.alpha, targetAlpha, Time.deltaTime * smooth);
if(Mathf.Abs(canvasGroup.alpha - targetAlpha) > 0.05f) {
// 由於Lerp是逐漸趨近而不會到達
canvasGroup.alpha = targetAlpha;
}}
public class ToolTip : MonoBehaviour { private Text itemDescSizeController; private Text contentText;private CanvasGroup canvasGroup; private float targetAlpha = 1; private float smoothing = 4; void Start () { itemDescSizeController = GetComponent<Text>(); contentText = transform.GetChild(0).Find("ContentText").GetComponent<Text>(); canvasGroup = GetComponent<CanvasGroup>(); } void Update () { if(canvasGroup.alpha != targetAlpha) { // 改變透明度 canvasGroup.alpha = Mathf.Lerp( canvasGroup.alpha, targetAlpha, Time.deltaTime * smoothing); if(Mathf.Abs(targetAlpha - canvasGroup.alpha) < 0.05f) { canvasGroup.alpha = targetAlpha; }}} public void DisplayToolTip(string content) { itemDescSizeController.text = content; contentText.text = content; targetAlpha = 1; } public void HideToolTip() { targetAlpha = 0; }}
爲何不將ToolTip寫成單例模式呢?
private static ToolTip _instance;
public static ToolTip Instance {
get{
if(_instance == null) {
_instance = GameObject.Find("...").GetComponent<ToolTip>();
}
return _instance;
}}
Siki老師經過private ToolTip toolTip = GameObject.FindObjectOfType<ToolTip>(); 進行訪問
建立兩個方法進行Display和Hide
public void DisplayToolTip(string description) {
ToolTip(.Instance).DisplayToolTip();
}
public void HideToolTip() {
ToolTip(.Instance).HideToolTip();
}
檢測鼠標的進入和移出:
UnityEngine.EventSystems -- Interfaces --
IPointerEnterHandler和IPointerExitHandler分別對應鼠標的進入和移出
在Slot.cs中監聽這兩個事件
由於Slot爲button,且Item的Raycast Target取消勾選了
using UnityEngine.EventSystems;
public class Slot : MonoBehaviour, IPointerEnterHandler, IPointerExitHandler
實現這兩個接口的OnPointerEnter() 和OnPointerExit()方法
public void OnPointerEnter(PointerEventData eventData) {
// 若是Slot爲空,就不進行任何操做
if(transform.childCount != 0) {
InventoryManager.Instance.DisplayToolTip(GetComponentInChildren<ItemUI>().Item.Description);
// ---------- 傳遞的參數能夠爲Item中的用來獲得Item須要被顯示的Content的方法
// public virtual string GetToolTipContent() {
// return Name + ": " + Description;
// }
}}
public void OnPointerExit(PointerEventData eventData) {
// 類似,先判斷是否爲空slot,若不是,則執行InventoryManager中的HideToolTip()
// 若是不判斷也是能夠的,由於若是Slot爲空
// 那麼InventoryManager.HideToolTip() -> ToopTip.Instance.HideToolTip() -> targetAlpha=0;
// 沒有什麼實質影響
}
控制提示ToolTip面板的跟隨:
修改ToolTip的位置
經過RectTransformUtility.ScreenPointToLocalPointInRectangle(RectTransform rect, Vector2 screenPoint, Camera cam, out Vector2 localPos)
在InventoryManager中實現ToolTip位置的控制
上述方法參數爲:
1. rect: The RectTransform to find a point inside
2. For a RectTransform in a Canvas set to ScreenSpace-Overlay mode, it should be null.
3. localPos: Point in local space of the rect
在UpdateToolTipPosition()中控制ToolTip的位置
private void UpdateToolTipPosition() { Vector2 position; RectTransformUtility.ScreenPointToLocalPointInRectangle( canvas.transform as RectTransform, Input.mousePosition, null, out position); ToolTip.Instance.transform.localPosition = position; }
在Update()中判斷是否須要調用UpdateToolTipPosition()
// 當須要顯示ToolTip時,調用
// ---- private bool isToolTipDisplayed = false;
// ---- 在DisplayToolTip()中改變值isToolTipDisplayed = true;
// ---- 在HideToolTip()中改變值isToolTipDisplayed = false;
if(isToolTipDisplayed) {
UpdateToolTipPosition();
}
運行,發現如今實現了ToolTip的跟隨鼠標顯示效果
可是,鼠標會在ToolTip的右上角 (Text的右上角)顯示,而不是在邊框外顯示
-- 添加一個偏移 private Vector2 toolTipPosOffset = new Vector2(18, -28);
{ "id": 2, "name": "藍瓶", "type": "Consumable", "quality": "Common", "description": "這個是用來加藍的", "capacity": 10, "buyprice": 10, "sellprice": 5, "hp": 0, "mp": 10, "spritePath": "Sprites/Items/mp" }
{ "id": 3, "name": "胸甲", "type": "Equipment", "quality": "Uncommon", "description": "這個胸甲很牛逼", "capacity": 1, "buyprice": 500, "sellprice": 250, "strength": 10, "intelligence": 2, "agility": 0, "stamina": 10, "equipType": "Chest", "spritePath": "Sprites/Items/armor" }
case Item.ItemType.Equipment: int strength = int.Parse(data["strength"].ToString()); int intelligence = int.Parse(data["intelligence"].ToString()); int agility = int.Parse(data["agility"].ToString()); int stamina = int.Parse(data["stamina"].ToString()); Equipment.EquipmentType equipType = (Equipment.EquipmentType) System.Enum.Parse(typeof(Equipment.EquipmentType), data["equipType"].ToString()); // 經過JsonData的數據,新建一個Consumable對象 item = new Equipment(id, name, itemType, itemQuality, description, capacity, buyprice, sellprice, spritePath, strength, intelligence, agility, stamina, equipType); break;
改進 -- 這時能夠顯示胸甲和藍瓶了
可是:胸甲的數量就不須要顯示,由於恆爲1
在ItemUI.UpdateUIText()中加入判斷Item.Capacity的狀況
private void UpdateUIText() { if (this.Item.Capacity == 1) { // 不須要顯示Amount HideAmountText(); } else { AmountText.text = Amount.ToString(); } }
不管是已存在物品的AddAmount()或是未存在物品的SetItem(),都適用了
經過Equipment.EquipType,一共12種,一種對應一個便可
完善Json數據
Equipment的Json信息完善:
{ "id": 3, "name": "胸甲", "type": "Equipment", "quality": "Uncommon", "description": "這個胸甲很牛逼", "capacity": 1, "buyprice": 500, "sellprice": 250, "strength": 10, "intelligence": 2, "agility": 0, "stamina": 10, "equipType": "Chest", "spritePath": "Sprites/Items/armor" }, { "id": 4, "name": "皮腰帶", "type": "Equipment", "quality": "Epic", "description": "這個腰帶很靈活哦", "capacity": 1, "buyprice": 200, "sellprice": 100, "strength": 0, "intelligence": 0, "agility": 10, "stamina": 5, "equipType": "Belt", "spritePath": "Sprites/Items/belts" }, { "id": 5, "name": "靴子", "type": "Equipment", "quality": "Legendary", "description": "這個靴子很快很快", "capacity": 1, "buyprice": 300, "sellprice": 150, "strength": 0, "intelligence": 0, "agility": 30, "stamina": 5, "equipType": "Boots", "spritePath": "Sprites/Items/boots" }, { "id": 6, "name": "護腕", "type": "Equipment", "quality": "Rare", "description": "這個護腕很聰明", "capacity": 1, "buyprice": 300, "sellprice": 150, "strength": 0, "intelligence": 20, "agility": 0, "stamina": 0, "equipType": "Bracer", "spritePath": "Sprites/Items/bracers" }, { "id": 7, "name": "神奇手套", "type": "Equipment", "quality": "Artifact", "description": "這個手套很神奇", "capacity": 1, "buyprice": 5000, "sellprice": 2500, "strength": 50, "intelligence": 0, "agility": 0, "stamina": 35, "equipType": "OffHand", "spritePath": "Sprites/Items/gloves" }, { "id": 8, "name": "頭盔", "type": "Equipment", "quality": "Rare", "description": "這個頭盔很重哦", "capacity": 1, "buyprice": 1000, "sellprice": 500, "strength": 10, "intelligence": 5, "agility": 0, "stamina": 25, "equipType": "Head", "spritePath": "Sprites/Items/helmets" }, { "id": 9, "name": "白銀項鍊", "type": "Equipment", "quality": "Common", "description": "這個項鍊只是鍍了一層白銀", "capacity": 1, "buyprice": 400, "sellprice": 200, "strength": 0, "intelligence": 15, "agility": 15, "stamina": 0, "equipType": "Neck", "spritePath": "Sprites/Items/necklace" }, { "id": 10, "name": "戒指", "type": "Equipment", "quality": "Rare", "description": "這個戒指颳了一下,金色沒了誒", "capacity": 1, "buyprice": 500, "sellprice": 250, "strength": 0, "intelligence": 30, "agility": 0, "stamina": 0, "equipType": "Ring", "spritePath": "Sprites/Items/rings" }, { "id": 11, "name": "皮褲", "type": "Equipment", "quality": "Common", "description": "豬皮製成的褲子,汪峯最喜歡", "capacity": 1, "buyprice": 300, "sellprice": 150,"strength": 10, "intelligence": 0, "agility": 0, "stamina": 20, "equipType": "Leg", "spritePath": "Sprites/Items/pants" }, { "id": 12, "name": "皮護肩", "type": "Equipment", "quality": "Common", "description": "豬皮製成的褲子,此次汪峯不喜歡了", "capacity": 1, "buyprice": 300, "sellprice": 150, "strength": 10, "intelligence": 0, "agility": 0, "stamina": 15, "equipType": "Shoulder", "spritePath": "Sprites/Items/shoulders" }
武器的Json信息完善:
{ "id": 13, "name": "木斧", "type": "Weapon", "quality": "Common", "description": "砍木頭用的斧子", "capacity": 1, "buyprice": 500, "sellprice": 250, "damage": 50; "weapType": "MainHand", "spritePath": "Sprites/Items/axe" }, { "id": 14, "name": "玄鐵劍", "type": "Weapon", "quality": "Artifact", "description": "上古時期,蚩尤用玄鐵鍛造的劍", "capacity": 1, "buyprice": 15000, "sellprice": 7500, "damage": 450; "weapType": "OffHand", "spritePath": "Sprites/Items/sword" }
武器的Json解析:
case Item.ItemType.Weapon: int damage = int.Parse(data["damage"].ToString()); Weapon.WeaponType weaponType = (Weapon.WeaponType)System.Enum.Parse (typeof(Weapon.WeaponType), data["weaponType"].ToString()); item = new Weapon(id, name, itemType, itemQuality, description, capacity, buyprice, sellprice, spritePath, damage, weaponType); break;
材料的Json信息完善:
{ "id": 15, "name": "鐵塊", "type": "Material", "quality": "Common", "description": "用於合成裝備和武器", "capacity": 20, "buyprice": 10, "sellprice": 5, "spritePath": "Sprites/Items/ingots" }, { "id": 16, "name": "玄鐵劍的鍛造祕籍", "type": "Material", "quality": "Artifact", "description": "用來鍛造玄鐵劍的祕籍", "capacity": 5, "buyprice": 1000, "sellprice": 500, "spritePath": "Sprites/Items/book" }, { "id": 17, "name": "頭盔的鍛造祕籍", "type": "Material", "quality": "Rare", "description": "用於合成裝備和武器", "capacity": 5, "buyprice": 100, "sellprice": 50, "spritePath": "Sprites/Items/scroll" }
材料的Json信息解析:
由於Material中只有基類Item的成員屬性,所以不須要進行Json解析
item = new Material(id, name, itemType, itemQuality, description, capacity, buyprice, sellprice, spritePath);
以前在Item.GetToolTipContent()中簡單顯示了物品的信息,如今來完善這個功能
實現效果:Item.Name + Item.Quality + Item.Desc + Item.prices
public virtual string GetToolTipContent() { return Name + '\n' + Quality + '\n' + Description + "\n購買價格: " + Buyprice + " 賣出價格: " + Sellprice; }
實現效果:對於不一樣品質的Item,顯示的顏色、大小不一樣
對於部分文字的顏色修改:經過標記<color=...>...</color>
<color=red>text</color>
<size=16>text</size>
勾選Text中的Rich Text,表示須要解析標記
顏色列表
Quality 顏色
Common white
Uncommon lime
Rare navy
Epic megenta
Legendary orange
Artifact red
public virtual string GetToolTipContent() { string color; switch (Quality) { case ItemQuality.Common: color = "white"; break; case ItemQuality.Uncommon: color = "lime"; break; case ItemQuality.Rare: color = "navy"; break; case ItemQuality.Epic: color = "megenta"; break; case ItemQuality.Legendary: color = "orange"; break; case ItemQuality.Artifact: color = "red"; break; default: color = "white"; break; } return string.Format("<color={0}><size=16>{1}</size></color>\n{2}\n 購買價格: {3} 賣出價格: {4}", color, Name, Description, Buyprice, Sellprice); }
消耗品的自有屬性顯示:
override Item中的GetToolTipContent()
public override string GetToolTipContent() { string text = base.GetToolTipContent(); return string.Format("{0}\n\n<color=red>HP + {1}</color>\n <color=blue>MP + {2}</color>", text, HP, MP); }
裝備的自有屬性顯示:
public override string GetToolTipContent() { // Equipment.EquipmentType的中文 string equipTypeText; switch (EquipType) { case EquipmentType.Head: equipTypeText = "頭部"; break; case EquipmentType.Neck: equipTypeText = "脖子"; break; // ... "胸部" "戒指" "腿部" "護腕" "鞋子" "肩部" "腰帶" case EquipmentType.OffHand: equipTypeText = "副手"; break; default: equipTypeText = ""; break; } string oldText = base.GetToolTipContent(); return string.Format("{0}\n\n<color=blue>裝備類型: {5}\n力量 + {1}\n 智力 + {2}\n敏捷 + {3}\n體力 + {4}\n</color>", oldText, Strength, Intelligence, Agility, Stamina, equipTypeText); }
武器的自有屬性顯示:
public override string GetToolTipContent() { string oldText = base.GetToolTipContent(); string weaponTypeText; switch (WeapType) { case WeaponType.OffHand: weaponTypeText = "副手"; break; case WeaponType.MainHand: weaponTypeText = "主武器"; break; default: weaponTypeText = ""; break; } return string.Format("{0}\n<color=blue>武器類型: {2}\n攻擊 + {1}</color>", oldText, weaponTypeText, Damage); }
任務33:在InventoryManager中管理PickedItem
當物品被鼠標點擊後,會被鼠標選中,隨着鼠標位置的移動而移動
由於點擊的是Slot,因此將Slot繼承自IPointerDownHandler接口,並實現OnPointerDown()
在Canvas下建立一個Item物體PickedItem
在ItemUI中實現功能
在InventoryManager中控制該物體
在InventoryManager中:
private ItemUI pickedItem;
在Start中初始化
pickedItem = GameObject.Find("PickedItem").GetComponent<ItemUI>();
pickedItem.Hide();
// -------- ItemUI的一些基本功能,如顯示本身、隱藏本身、控制自身位置等
// public void Hide/ Display() {
// gameObject.SetActive(false/ true);
// }
// -------- 設置本身的localPosition
// public void SetLocalPosition(Vector3 pos) {
// // 由於是設置在Canvas下的位置,所以爲LocalPos
// transform.localPosition = pos;
// }
}
任務34:實現Slot中的OnPointerDown() -- 按下算選中一個Slot
分析物體移動的多種狀況:
移動功能擴展:
當按住ctrl鍵,進行PickedItem選中時,會選擇一半物品
當按住ctrl鍵,進行PickedItem放置時,會放置一個物品
1. 按下的Slot爲空
1. 鼠標按下以前,已經選中了物品 -- 將PickedItem放入slot
按下Ctrl,會放置一個物品
沒有按Ctrl,會放置鼠標上的全部物品
2. 鼠標按下以前,沒有選中物品 -- 不作操做
2. 按下的Slot不爲空
1. 鼠標按下以前,已經選中了物品
若是物品Id不一樣 -- 交換物品
若是物品Id相同
可疊加
能夠徹底疊加
按下Ctrl -- 疊加一個
沒有按Ctrl -- 疊加全部
不能夠徹底疊加 -- 將當前物品數量設爲capacity,原物品爲剩下數量
不可疊加 -- 交換物品
2. 鼠標按下以前,沒有選中物品
按下Ctrl,會選擇一半物品
沒有按Ctrl,會選擇所有物品
代碼實現:
任務35&36&37:物品的選中功能:
Slot不爲空,且鼠標按下以前沒有選中物品
if(transform.ChildCount != 0) { // 當前slot不爲空
if(InventoryManager.Instance.IsPickedItemEmpty()) { // 還未選中物體
// -------- InventoryManager.IsPickedItemEmpty() {
// return pickedItem.Item == null;
// }
ItemUI currItemUI = transform.GetCompInChildren<ItemUI>()
if(Input.GetKey(KeyCode.LeftControl)) { // 注意是按住而不是按下
// 按下Ctrl,取走一半物品
// 撿起一半物品,放置在鼠標的PickedItem上
int amountPicked = (currItem.Amount+1) / 2; // 進一法,若是原個數爲1,則取走1
int amountLeft = currItem.Amount - amountPicked;
InventoryManager.Instance.SetPickedItem(currItemUI.Item, amountPicked);
// ------- InventoryManager.SetPickedItem(Item item, int amount) {
// pickedItem.SetPickedItem(item, amount);
// }
// ------- ItemUI.SetPickedItem(Item item, int amount) {
// SetItem(item, amount);
// }
// 更新Slot中剩下的物品
if(amountLeft == 0) {
Destroy(currItemUI.gameObject);
} else {
currItem.SetAmount(amountLeft);
// ------- ItemUI.SetAmount(int amount) {
// Amount = amount;
// UpdateUIText();
// }
}
} else {
// 沒有按Ctrl,取走全部物品
// 把當前Slot中的Item設置給PickedItem中的Item,還有Amount
InventoryManager.Instance.SetPickedItem(currItemUI.Item, currItemUI.Amount);
// 銷燬原來空格中的物品顯示
Destroy(transform.GetChild(0).gameObject);
}
}}
任務38:將選中的PickedItem顯示出來,並更新位置
在InventoryManager.SetPickedItem(Item item, int amount) {
以前是作了pickedItem.SetPickedItem(item, amount);
設置了相關的Item給了PickedItem
那麼如今,pickedItem中已經包含了當前選中的item,須要顯示
pickedItem.Display();
}
在InventoryManager.Update()中控制pickedItem的位置跟隨(和以前作的toolTip的跟隨同樣)
if(!IsPickedItemEmpty()) {
UpdatePickedItemPosition();
// ------- InventoryManager.UpdatePickedItemPosition() {
// Vector2 targetPos;
// RectTransformUtility.ScreenPointToLocalPointInRectangle(
// canvas.transform as RectTransform, Input.mousePosition, null, out targetPos);
// pickedItem.SetLocalPosition(targetPos);
// }
}
如今可以使pickedItem隨鼠標移動了
可是,選中物品後,ToolTip仍然顯示,須要將其自動隱藏
在InventoryManager.SetPickedItem(Item item, int amount) {
最後一句添加上
ToolTip.Instance.HideToolTip();
}
// Siki認爲當pickedItem不爲空時,即手上已經有選定物品時,移到其餘物品時的ToolTip就不應顯示
// 若是想實現的話,能夠在InventoryManager.DisplayToolTip(string desc)中判斷IsPickeItemEmpty()便可
// 但我認爲仍是須要顯示的
任務40&41&42&43&48:放置物品
以前完成的是物品的選取
if(transform.childCount != 0) {
if(InventoryManager.IsPickedItemEmpty()) {
// 取走必定數量的物品
// ...上一節實現了
} else {
// 當前slot不爲空,且手上已經有選中物品了
ItemUI pickedItemUI = InventoryManager.Instance.PickedItem;
if(currItemUI.Item.ID == pickedItemUI.Item.ID) {
// 當兩個ID相同時
if(IsSlotFilled()) {
// 當前Slot滿了,不可疊加,交換物品位置便可
// 任務48
ExchangeWithPickeItem();
// ------- Slot.ExchangeWithPickedItem() {
// ItemUI currItemUI = GetComponentInChildren<ItemUI>();
// Item tempItem = InventoryManager.Instance.PickedItem.Item;
// int tempAmount = InventoryManager.Instance.PickedItem.Amount;
// InventoryManger.Instance.SetPickedItem(currItemUI.Item, currItemUI.Amount);
// currItemUI.SetItem(tempItem, tempAmount);
// }
} else {
// 可進行疊加
int amount = currItem.Item.Amount; // 記錄當前slot中item要變成的數量
int amountToAdd; // 須要添加到currItem中的數量
int leftAmount = pickedItemUI.Amount; // 記錄pickedItem中item要變成的數量
if(Input.GetKey(KeyCode.LeftControl)) {
// 按下Ctrl,一次放一個
// if((1+currItemUI.Amount) > currItemUI.Item.Capacity) {
// 若放入,則超出數量 -- 無操做
// 這個無需判斷,由於當slot未滿,則必然slot數量+1不會超過capacity
amountToAdd = 1;
} else {
// 沒有按Ctrl,所有放入
if((amount + leftamount) > currItemUI.Item.Capacity) {
// 須要放入的數量太多,不能徹底疊加
amountToAdd = currItemUI.Item.Capacity - amount;
} else {
// 能夠徹底疊加
amountToAdd = leftAmount;
}
}
amount += amountToAdd;
leftAmount -= amountToAdd;
currItemUI.SetAmount(amount);
// 剩餘個數判斷
if(leftAmount == 0) {
// 銷燬pickedItem
InventoryManager.Instance.ResetPickedItem();
// ------- InventoryManager.ResetPickedItem() {
// pickedItem.ResetItem();
// pickedItem.Hide();
// }
// ------- ItemUI.ResetItem() {
// this.Item = null;
// this.Amount = 0;
// }
} else {
InventoryManager.Instance.SetPickedItemAmount(leftAmount);
// 不知道爲何不直接經過pickedItem.SetAmount()解決
// 多是由於pickedItem最好統一經過InventoryManager進行訪問?
// ------- InventoryManager.SetPickedItemAmount(int amount) {
// pickedItem.SetAmount(amount);
// }
}
}
} else { // 當兩個ID不一樣時 -- 交換物品
ExchangeWithPickedItem();
}
}
} else {
// 當前slot爲空
if(!InventoryManager.Instance.IsPickedItemEmpty()) {
// pickedItem不爲空,即已經選定了物品 -- 將物品放入該空slot
ItemUI pickedItemUI = InventoryManager.Instance.PickedItem;
if(Input.GetKey(KeyCode.LeftControl) {
// 按下Ctrl -- 一次放一個
// 經過StoreItem將pickedItem存入當前slot中
StoreItem(pickedItemUI.Item);
InventoryManager.Instance.SetPickedItemAmount(pickedItemUI.Amount - 1);
} else {
// 沒有按Ctrl -- 一次性全放(由於不存在溢出的狀況)
StoreItem(pickedItemUI.Item);
transform.GetComponentInChildren<ItemUI>().SetAmount(pickedItemUI.Amount);
InventoryManager.Instance.SetPickedItemAmount(0);
}}}
複製Knapsack物體,更名Chest,設置位置大小
子物體TitleBg的Text修改成箱子
container中slot數量設置爲8個
將附帶的Knapsack.cs腳本替換爲Chest.cs腳本
Chest.cs中實現單例模式
private static Chest _instance; public static Chest Instance { get { if(_instance == null) { _instance = GetComponent<Chest>(); } return _instance; }}
思路:若是pickedItem不爲空,且鼠標點擊的位置沒有UI,則進行丟棄操做
在哪裏寫代碼呢?Slot
不,這個功能並非跟Slot掛鉤的,而是跟InventoryManager很相關
InventoryManager.Update()
// 物品丟棄操做 if (!IsPickedItemEmpty()) { if (Input.GetMouseButtonDown(0) && !UnityEngine.EventSystems.EventSystem.current.IsPointerOverGameObject(-1)) { // 按下鼠標左鍵,且點擊處沒有任何物體 int amount = pickedItem.Amount; if(Input.GetKey(KeyCode.LeftControl)) { // 若是按住ctrl,一個一個扔 amount--; } else { // 沒有按住ctrl,所有扔掉 amount = 0; } SetPickedItemAmount(amount); }}
BugFix --
運行,發現若是鼠標點擊不精確,將pickedItem放置到slot之間的空位
會執行丟棄物品的操做
由於此時EventSystem沒有獲得點擊的反饋
解決方法 -- 將Knapsack等Inventory的Image.RaycastTarget勾選上便可
在父類Inventory中實現
在Knapsack和Chest物體上添加Canvas Group,經過alpla進行透明度控制
private float targetAlpha = 1;
private float smooth = 5;
在Inventory.Update中,實現顯示和隱藏
if(Mathf.Abs(canvasGroup.alpha - targetAlpha) > 0.05f) { canvasGroup.alpha = Mathf.Lerp(canvasGroup.alpha, targetAlpha, Time.deltaTime * smooth); } else { canvasGroup.alpha = targetAlpha; }
兩個方法實現顯示和隱藏
public void Hide/ Display () {
targetAlpha = 0 / 1;
}
在Player中用I控制揹包的顯示和隱藏:
if(Input.GetKeyDown(KeyCode.I) {
// 這裏要根據當前顯示狀態進行更換顯示或隱藏狀態
// 可是在這裏實現不大好
// ------- Inventory.DisplaySwitch() {
// if(targetAlpha == 0) {
// Display();
// } else {
// Hide();
// }}
相同的,能夠用C鍵控制Chest的顯示和隱藏
發現bug -- 當箱子或揹包隱藏之後,東西仍然能夠移動給它
隱藏之後,將CanvasGroup.BlocksRaycasts = false;便可
在Hide和Display()的最後,添加一句
canvasGroup.blocksRaycasts = false/ true;
角色面板會顯示當前角色佩戴的裝備和武器 -- 一共十一個部位
複製Chest
命名Character,修改Text
增長到11個slot
刪除GridLayoutGroup,刪除Container,由於slot不須要自動排列
刪除腳本Chest,添加腳本Character.cs
運行,發現Slot裏面沒有存儲限制,即其餘物品也能夠放入裝備面板
解決方法 -- 建立Slot的子類EquipmentSlot
給每一個裝備添加對應的EquipmentType和WeaponType
public Equipment.EquipmentType equipmentType;
public Weapon.WeaponType weaponType;
修改EquipmentType和WeaponType,各添加一個None的選擇,將不屬於的slot賦值爲None
注:OffHandSlot便可以放裝備也能夠放武器
角色面板的功能(策劃)
1. 在其餘地方直接右鍵,便可穿戴;在角色面板中直接右鍵,便可脫下
2. 拖拽方式
添加腳本Character.cs,繼承自Inventory
須要使用Inventory中的slotList,就不能聲明爲private,改成protected
// 或者提供一個get方法
寫成單例模式:
private static Character _instance;
public static Character Instance {
get {
if(_instance == null) {
_instance = GameObject.Find("Character").GetComponent<Character>();
}
return _instance;
}}
由於在裝備槽中的斷定方式不大同樣
須要斷定是否符合裝備類型,並且不須要判斷ctrl的狀況
並且沒有Amount的加減問題
override OnPointerDown()
分析:
1. pickedItem爲空
當前slot爲空 -- 無操做
當前slot不爲空 -- 選取裝備
2. pickedItem不爲空
當前slot爲空
判斷是否符合類型,符合就放入,不符合則無操做
當前slot不爲空
判斷是否符合類型,符合就交換,不符合則無操做
using UnityEngine.EventSystems; public class EquipmentSlot : Slot { public Equipment.EquipmentType equipmentType; public Weapon.WeaponType weaponType; // 傳入item的類型是否與當前slot的類型匹配 public bool IsItemMatchedSlotType(Item item) { return (item is Equipment && ((Equipment)item).EquipType == equipmentType || item is Weapon && ((Weapon)item).WeapType == weaponType); } public override void OnPointerDown(PointerEventData eventData) { ItemUI pickedItemUI = InventoryManager.Instance.PickedItem; ItemUI currItemUI = GetComponentInChildren<ItemUI>(); if (InventoryManager.Instance.IsPickedItemEmpty() ) { if(transform.childCount == 1) { // pickedItem爲空,且當前slot不爲空 // 選取裝備 InventoryManager.Instance.SetPickedItem(currItemUI.Item, currItemUI.Amount); Destroy(currItemUI.gameObject); }} else { // pickedItem不爲空 if(transform.childCount == 0) { // 當前slot爲空 if (IsItemMatchedSlotType(pickedItemUI.Item)) { // pickedItem知足slot的類型 // 放入slot StoreItem(pickedItemUI.Item); InventoryManager.Instance.SetPickedItemAmount(pickedItemUI.Amount - 1); }} else { // 當前slot不爲空 if (IsItemMatchedSlotType(pickedItemUI.Item)) { // 交換物品 ExchangeWithPickedItem(); }}}}}
另外一種override的思路(未驗證可信性)
EquipmentSlot: Slot
將Slot.StoreItem()聲明爲virtual的
// 在StoreItem中判斷是否符合slot的裝備類型
public override bool StoreItem(Item itemToStore) { // 判斷是否爲Equipment,不然不能存入 if(IsItemTypeEquipment()) {base.StoreItem(itemToStore); return true; } else { return false; }}
由於以前StoreItem是確定會將Item存入的,不存在不存入的狀況
所以在Slot.OnPointerDown()中會報錯。
解決方法:在Slot.StoreItem()最後返回return true;
以前對與鼠標按鍵的檢測是經過IPointerDownHandler -- OnPointerDown()
該事件當鼠標的任意按鍵按下時觸發
所以以上操做能夠發生在鼠標左鍵/右鍵/滾輪按下時
要求:左鍵控制物品的移動,右鍵控制裝備的穿戴
PointerEventData eventData.button表示當前按下的鼠標按鍵類型
if(eventData.button == PointerEventData.InputButton.Left) {
// 物品移動代碼
} else if (eventData.button == PointerEventData.InputButton.Right) {
// 物品穿戴代碼
}
在Slot中的物品穿戴代碼 -- 由於物品的穿戴是在Slot上右鍵的而不是在Character中操做的
分析:
由於不須要判斷pickedItem的狀態,若是右鍵,就進行穿戴
-- 仍是須要判斷pickedItem的狀態
當pickedItem不爲空時,且當前slot不爲空時,進行
當前slot不爲空 -- 進行穿戴
} else if (eventData.button == PointerEventData.InputButton.Right) { // 右鍵按下,進行物品的穿戴 if (transform.childCount == 1) { ItemUI currItemUI = transform.GetChild(0).GetComponent<ItemUI>(); if (currItemUI.Item is Equipment || currItemUI.Item is Weapon) { // 當前slot不爲空,且物品爲可穿戴類型的 -- 進行穿戴 Item currItem = currItemUI.Item; Debug.Log("currItem" + currItem.Name); // DestroyImmediate是當即銷燬,當即釋放資源,作這個操做的時候,會消耗不少時間的,影響主線程運行 // Destroy是異步銷燬,通常在下一幀就銷燬了,不會影響主線程的運行。 // 可是這裏不能使用Destroy,不然在存回Knapsack時取得的EmptySlot就不許確了 DestroyImmediate(currItemUI.gameObject); Character.Instance.PutOnEquipOrWeapon(currItem); }}}
穿戴的方法Character.PutOnEquipOrWeapon(Item item):
public void PutOnEquipOrWeapon(Item item ) { EquipmentSlot slot = FindSlotWithSameItemEquipOrWeaponType(item); if (slot != null) { // 若是找到匹配類型的slot if(slot.transform.childCount == 1) { // 若是slot不爲空 // 存入物品,再將將原來裝備面板中的物品放回到揹包中 Item itemToPutBack = slot.GetComponentInChildren<ItemUI>().Item; slot.GetComponentInChildren<ItemUI>().SetItem(item); Knapsack.Instance.StoreItem(itemToPutBack); } else { // 若是slot爲空 -- 直接將pickedItem放入 slot.StoreItem(item); }}} public EquipmentSlot FindSlotWithSameItemEquipOrWeaponType(Item item) { foreach(EquipmentSlot slot in slotList) { if(slot.IsItemMatchedSlotType(item)) { Debug.Log(slot.name + " Matched!"); return slot; }} return null; }
注意點:
1. 原來打算經過PickedItem來進行物品穿戴或交換,後來改成直接執行
2. 注意knapsack中currItem的銷燬和storeBack的執行順序,會致使FindEmptySlot()的結果
3. Destroy() 和 DestroyImmediate()的區別
BugFixing --
1.
發現當手上有pickedItem時,對其餘裝備進行右鍵,仍然能夠進行穿戴,並且pickedItem不變
由於右鍵的操做沒有經過pickedItem來執行
解決方法:
在Slot.OnPointerDown()中穿戴裝備處進行判斷
if(!InventoryManager.Instance.IsPickedItemEmpty() && ...)
2.
在穿戴裝備後,仍然顯示該裝備的TooTip
解決方法:
在成功穿戴裝備後,隱藏ToolTip
ToolTip.Instance.HideToolTip();
裝備欄中裝備的右鍵卸下:
由於卸下的操做只在Character面板中,所以在EquipmentSlot.cs中
} else if (eventData.button == PointerEventData.InputButton.Right) { // 按下右鍵時,進行物品的卸下 if(InventoryManager.Instance.IsPickedItemEmpty() ) { // 當手上沒有物品時,才能卸下裝備 if(transform.childCount == 1) { // 噹噹前slot不爲空時 if (Knapsack.Instance.FindEmptySlot()) { // 揹包中有空位能夠接收物品 Destroy(currItemUI.gameObject); Character.Instance.TookOffEquipOrWeapon(currItemUI.Item); ToolTip.Instance.HideToolTip(); }}}}
對應的Character中的TookOffEquipOrWeapon() -- 很簡單,只須要存入揹包便可
public void TookOffEquipOrWeapon(Item item) {
Knapsack.Instance.StoreItem(item);
}
在Player.cs中經過E鍵控制裝備面板的顯示和隱藏
if(Input.GetKeyDown(KeyCode.E)) {
Character.Instance.DisplaySwitch();
}
在Canvas中顯示全部按鍵的提示信息
在Canvas中新建Text,命名KeyTip
內容:"G 獲得物品(換行)I揹包顯示 C箱子顯示 E裝備面板顯示"
武器和裝備對角色的屬性影響,須要在角色面板上顯示出來
在CharacterPanel下新建子物體UI->Panel,命名PropertyPanel
新建子物體UI->Text
居中,留些邊距,大小顏色微調
屬性的顯示在Character.cs中進行控制
全部屬性彙總:
裝備影響:
strength力量
intelligence智力
agility敏捷度
stamina體力
武器影響:
damage攻擊力
在Player中存放基礎屬性:
private int basicStrength = 10;
public int BasicStrength {
get {
return basicStrength;
}}
在Character.cs中
private void UpdatePropertyTextUI() { int strength = 0, intelligence = 0, agility = 0, stamina = 0, damage = 0; // 取得每個裝備的屬性,並加到總屬性值中 foreach (Slot slot in slotList) { if (slot.transform.childCount == 1) { // 若是該slot中有裝備 Item currItem = slot.GetComponentInChildren<ItemUI>().Item; if (currItem is Equipment) { strength += ((Equipment)currItem).Strength; ...... } else if (currItem is Weapon) { damage += ((Weapon)currItem).Damage; }}} // 加上基礎屬性 Player player = GameObject.FindWithTag("Player").GetComponent<Player>(); strength += player.BasicStrength; ...... // 更新UI propertyText.text = string.Format("攻擊力:{0}\n力量:{1}\n智力: {2}\n敏捷:{3}\n體力:{4}\n", damage, strength, intelligence, agility, stamina); }
何時須要調用UpdatePropertyTextUI呢?
1. Start中
2. PutOn()和TookOff()中 -- 右鍵穿戴和卸下
3. EquipmentSlot中的OnPointerDown()中 -- 拖拽穿戴和卸下
在Start和PutOn()和TookOff()中,直接調用UpdatePropertyTextUI();便可
在EquipmentSlot中,在三個左鍵穿戴脫下裝備的地方
transform.parent.SentMessage("UpdatePropertyTextUI");
// EquipmentSlot的父類是Character,向其發送消息,調用UpdatePropertyTextUI()
複製ChestPanel,命名VendorPanel
Title Text內容爲小販
Slot個數改成12格
刪除Chest.cs替換爲Vendor.cs
小販面板的功能:
不須要和其餘面板進行交換,只負責買賣
小販的Slot中只須要作
1. 右擊購買的功能
2. 左鍵銷售的功能
小販面板的初始化 -- 開始時有本身售賣的物品
將12個格子改成VendorSlot,添加VendorSlot.cs,繼承自Slot.cs
在Vendor.cs中聲明數組,表示售賣的物品
public int[] itemIdArray; // 在Inspector面板中賦值
在Start()進行根據itemId進行實例化Item
base.Start();
InitVendor();
}
其中private void InitVendor() {
for(int i = 0; i < itemIdArray.Length; i++) {
StoreItem(itemIdArray[i]);
}}
運行,報錯 --
NullReferenceException: Object reference not set to an instance of an object
InventoryManager.GetItemById (Int32 id)
緣由:由於GetItemById()中的itemList是在ParseItemJson()中初始化的,
ParseItemJson()是在InventoryManager.Start()中被調用的
而InitVendor()中調用了GetItemById(),InitVendor()也是在Vendor.Start()中被調用的
-- 同時調用,所以會報空指針
解決方法:在InventoryManager中,將ParseItemJson()在Awake()中調用
在Canvas下,新建UI->Image
SourceImage: coin,顏色金色,調整大小,位於右上角,Anchor右上角
新建UI->Text,命名CoinAmount,金色
在Player中
private int coinAmount = 100;
private Text coinAmountText;
Start中coinAmountText = GameObject.Find("Coin").GetComponentInChildren<Text>();
coinAmountText.text = coinAmount.ToString();
// 金錢的加減
public bool Consume(int num) {
if(coinAmount >= num) {
coinAmount -= num;
coinAmountText.text = coinAmount.ToString();
return true;
}
return false;
}
public void EarnCoin(int num) {
coinAmount += num;
coinAmountText.text = coinAmount.ToString();
}
物品的購買:
若是直接在VendorSlot中調用Player的方法進行買賣,會比較麻煩
買賣的操做放在Vendor中,再由VendorSlot調用
Vendor.cs中實現private void Purchase(Item item) {}
在VendorSlot中,override OnPointerDown()
若是按下右鍵,且手上沒有物品,且當前slot不爲空時,便可購買物品
public override void OnPointerDown(PointerEventData eventData) { if (eventData.button == PointerEventData.InputButton.Right && InventoryManager.Instance.IsPickedItemEmpty()) { // 當右鍵,且手上沒有東西時 if (transform.childCount == 1) { // 若是slot不爲空 // 買入物品 Item currItem = transform.GetComponentInChildren<ItemUI>().Item; transform.parent.parent.SendMessage("Purchase", currItem); }}}
Vendor.Purchase(Item item):
若是Knapsack中有空位,則進行購買
若購買成功,將item存入knapsack
若購買不成功,不進行任何操做
private void Purchase(Item item) {if (Knapsack.Instance.FindEmptySlot() != null) { // 若是Knapsack中有空slot if (player.Consume(item.Buyprice)) { // 進行購買,成功購買 Knapsack.Instance.StoreItem(item); }}}
物品的售賣:
若當前pickedItem不爲空,則進行售賣
若按下ctrl,則賣一個;若沒有按ctrl,則所有賣掉
VendorSlot中
點擊左鍵時,且手上有東西時,銷售物品
} else if(eventData.button == PointerEventData.InputButton.Left && !InventoryManager.Instance.IsPickedItemEmpty()) { // 當左鍵,且手上有東西時 -- 銷售物品 transform.parent.parent.SendMessage("Sell"); }
Vendor.SellItem()
// 注意pickedItem是有數量的
// 判斷Ctrl的按下
private void Sell() { int sellAmount = 0; ItemUI itemToSellUI = InventoryManager.Instance.PickedItem; if (Input.GetKey(KeyCode.LeftControl)) { // 若Ctrl鍵按下,一次賣一個 sellAmount = 1; } else { // Ctrl沒有按下,全賣掉 sellAmount = itemToSellUI.Amount; } player.EarnCoin(itemToSellUI.Item.Sellprice * sellAmount); InventoryManager.Instance.SetPickedItemAmount(itemToSellUI.Amount - sellAmount); }
複製Chest,命名ForgePanel
修改Text內容爲"鍛造"
替換成腳本Forge: Inventory
包含兩個Slot
刪除Grid Layout Group
添加子物體UI->Button
SourceImage: button_square
Text: "合成"
祕方類 Fomula.cs
須要兩個item的id和對應的數量,並完成完整的構造函數
public int item1ID { get; private set; } public int item2ID { get; private set; } public int item1Amount { get; private set; } public int Item2Amount { get; private set; } public int ResItemID { get; private set; }
(擴展:若是須要多種物品,能夠聲明兩個數組,分別存儲材料類型和材料數量 -- 這裏不作展開)
Material類的種類一共有三種,id分別爲15鐵塊、16玄鐵劍的鍛造祕籍、17頭盔的鍛造祕籍
Formulas.Json文件中的格式,跟Fomula類保持一致
[ { "Item1ID": 15, "Item1Amount": 5, "Item2ID": 16, "Item2Amount": 1, "ResItemId": 14 }, { "Item1ID": 15, "Item1Amount": 3, "Item2ID": 17, "Item2Amount": 1, "ResItemId": 8 } ]
解析Json數據 -- 放在Forge.cs中實現,由於只有在鍛造面板纔會使用到
須要將全部獲得的Formula對象存放
private List<Formula> formulaList;
private void ParseFormulaJson() { TextAsset textAsset = Resources.Load<TextAsset>("ItemsData/Formulas"); JsonData jsonData = JsonMapper.ToObject<JsonData>(textAsset.text); foreach(JsonData data in jsonData) { int item1ID = int.Parse(data["Item1ID"].ToString()); int item1Amount = int.Parse(data["Item1Amount"].ToString()); int item2ID = int.Parse(data["Item2ID"].ToString()); int item2Amount = int.Parse(data["Item2Amount"].ToString()); int resItemID = int.Parse(data["ResItemID"].ToString()); Formula newFormula = new Formula(item1ID,item1Amount,item2ID,item2Amount,resItemID); formulaList.Add(newFormula); }}
在Forge.Start()中進行解析
public override void Start() {
base.Start();
ParseFormulaJson();
}
點擊合成按鈕時會在Forge中進行ForgeItem()處理
Siki老師的算法:
獲得當前擁有的Material及數量
一個Material存一個id,好比有五個鐵塊,就存5個15在list中
public void ForgeItemSikiVersion() { // 獲得當前已有的材料 List<int> currMaterialIdList = new List<int>(); foreach (Slot slot in slotList) { if (slot.transform.childCount == 1) { // slot不爲空 ItemUI currItemUI = slot.GetComponentInChildren<ItemUI>(); for (int i = 0; i < currItemUI.Amount; i++) { // 有多少個物品,就存入多少個id currMaterialIdList.Add(currItemUI.Item.ID); }}} foreach(Formula formula in formulaList) { if (formula.IsMaterialCompositionMatched(currMaterialIdList)) { // 獲得了合成之後的物品 }}}
在Formula的構造函數中調用GetRequiredMaterialIdList(),初始化requiredMaterialIdList
private void GetRequiredMaterialIdList() { requiredMaterialIdList = new List<int>(); for(int i = 0; i<Item1Amount; i++) { requiredMaterialIdList.Add(Item1ID); } for (int i = 0; i < Item2Amount; i++) { requiredMaterialIdList.Add(Item2ID); }}
在Formula中實現判斷已有物品與自己Formula的配料是否匹配
// SikiVersion public bool IsMaterialCompositionMatched(List<int> idList) { GetRequiredMaterialIdList(); List<int> tempIdList = new List<int>(idList); for (int i = 0; i < requiredMaterialIdList.Count; i++) { if (tempIdList.Contains(requiredMaterialIdList[i])) { tempIdList.Remove(requiredMaterialIdList[i]); } else { return false; } } return true; }
附:遍歷List並刪除指定元素的正確方式
https://blog.csdn.net/s_GQY/article/details/52273840
上述算法效率不高,特別是當一個材料需求數量很大的時候
可是優勢是靈活 -- 若是後期修改了合成的Material種類,好比四五種,這段代碼也能夠完美執行
另外一種方法(MyVersion)能夠經過Dictionary的key:value鍵值對實現
void ForgeItem() { // 獲得當前的材料 Dictionary<int, int> currMaterialDict = new Dictionary<int, int>(); foreach (Slot slot in slotList) { if (slot.transform.childCount == 1) { // slot不爲空 ItemUI currItemUI = slot.GetComponentInChildren<ItemUI>(); currMaterialDict.Add(currItemUI.Item.ID, currItemUI.Amount); } } // 判斷符合哪個祕籍的要求 foreach (Formula formula in formulaList) { if (formula.IsMaterialCompositionMatched(currMaterialDict)) { Debug.Log(formula.ResItemID); } } // 進行合成 }
private void GetRequiredMaterialDictionary() { requiredMaterialDict = new Dictionary<int, int>(); requiredMaterialDict.Add(Item1ID, Item1Amount); requiredMaterialDict.Add(Item2ID, Item2Amount); }
public bool IsMaterialCompositionMatched(Dictionary<int, int> materialDict) { foreach (var requiredMaterial in requiredMaterialDict) { if (materialDict.ContainsKey(requiredMaterial.Key)) { int amount; materialDict.TryGetValue(requiredMaterial.Key, out amount); if(amount < requiredMaterial.Value) { // 沒有足夠數量 return false; } } else { // 沒有該類型材料 return false; } } return true; }
找到了對應的Formula配方,
須要進行合成,並消耗對應的材料
在上述ForgeItem()獲得matchedFormula後
// 判斷符合哪個祕籍的要求 Formula matchedFormula = null; foreach (Formula formula in formulaList) { if (formula.IsMaterialCompositionMatched(currMaterialDict)) { matchedFormula = formula; break; } } // 進行合成 if (matchedFormula != null) { // 有對應物品生成時 if (Knapsack.Instance.FindEmptySlot()) { // 確保揹包中有空slot能夠放置物品 // 將新生成的物品存入揹包 Knapsack.Instance.StoreItem(matchedFormula.ResItemID); // 對應材料減小 ConsumeMaterials(matchedFormula); } } }
對應材料的減小:Forge.ConsumeMaterials(Formula formula)
private void ConsumeMaterials(Formula matchedFormula) { foreach (Slot slot in slotList) { if (slot.transform.childCount == 1) { // 若該slot不爲空 ItemUI currItemUI = slot.GetComponentInChildren<ItemUI>(); if (currItemUI.Item.ID == matchedFormula.Item1ID) { // 減去對應的數量 currItemUI.SetAmount(currItemUI.Amount-matchedFormula.Item1Amount); } else if (currItemUI.Item.ID == matchedFormula.Item2ID) { // 減去對應的數量 currItemUI.SetAmount(currItemUI.Amount-matchedFormula.Item2Amount); }}}}
經過T控制商店頁面
經過F控制鍛造頁面
將揹包等Inventory所存的物品,和金幣數量保存到本地文件中
在Inventory中建立兩個方法:
public void SaveInventory() -- PlayerPrefs.SetString(string name, string value);
public void LoadInventory() -- PlayerPrefs.GetString(string name);
void SaveInventory() { StringBuilder sb = new StringBuilder(); foreach(Slot slot in slotList) { if(slot.transform.childCount == 1) { // slot不爲空 ItemUI currItemUI = slot.transform.GetComponentInChildren<ItemUI>(); // 用,隔開物品的id和amount // 用 / 隔開不一樣物品 sb.AppendFormat("{0},{1}/", currItemUI.Item.ID, currItemUI.Amount); } else { // ID是從1開始的,若是爲0,表示slot爲空 sb.Append("0/"); } } // 將上述字符串保存到本地 PlayerPrefs.SetString(this.gameObject.name, sb.ToString()); } public void LoadInventory() { if(PlayerPrefs.HasKey(this.gameObject.name)) { // 若是有匹配名稱的本地文件 // 讀取本地文件到string string str = PlayerPrefs.GetString(this.gameObject.name); string[] stringArray = str.Split('/'); for(int i=0;i<stringArray.Length;i++ ) { // 每個片斷的數據對應一個slot中的存儲狀況 if(stringArray[i]!="0") { string[] tempStr = stringArray[i].Split(','); int itemID = int.Parse(tempStr[0]); int amount = int.Parse(tempStr[1]); slotList[i].StoreItem(InventoryManager.Instance.GetItemById(int.Parse(tempStr[0]))); slotList[i].GetComponentInChildren<ItemUI>().SetAmount(int.Parse(tempStr[1])); }}}
任務74:BugFix
在運行時發現,對stringArray[i] == "0"的狀況須要進行操做
若是在遊戲中按下了加載按鈕,本地文件中爲空的slot不會被加載爲空,而是不進行操做
解決方法:
} else { if (slotList[i].transform.childCount == 1) { // slot進行清空 Destroy(slotList[i].transform.GetChild(0).gameObject); } }
在Start中調用LoadInventory()
由於itemList在InventoryManager.Awake()中初始化
slotList在Inventory.Start()中初始化
所以須要將LoadInventory等到這兩個初始化以後再調用
// 或是手動進行加載,見下
在Canvas下建立保存按鈕(和加載按鈕)
在InventoryManager中提供兩個方法
public void SaveInventory() {
Knapsack.Instance.SaveInventory();
Chest.Instance.SaveInventory();
Character.Instance.SaveInventory();
// Vendor.Instance.SaveInventory(); -- 商店是不須要保存的
Forge.Instance.SaveInventory();
}
public void LoadInventory() {
Knapsack.Instance.LoadInventory();
Chest.Instance.LoadInventory();
Character.Instance.LoadInventory();
// Vendor.Instance.LoadInventory(); -- 商店是不須要加載的
Forge.Instance.LoadInventory();
}
將這兩個方法分別註冊到上述兩個按鈕的點擊事件下
運行,保存,加載 -- 報錯:FormatException: Input string was not in the correct format
通過Debug.Log查證,出錯位置爲每一個Inventory的最後stringArray
緣由:
Save的時候,執行的是sb.AppendFormat("....../", ...);
所以,在最後一個slot保存後,string也是以'/'結尾
而在Load的時候,經過'/'進行了Split操做,分割出的最後一個string爲 ""空字符
這個空字符在Load的時候就會報錯
解決方法:
在Load中遍歷str的時候忽略最後一個string便可
for(int i = 0; i < stringArray.Length - 1; i++)
金幣數量的保存和加載:
在Player中(由於coin是Player的屬性)
public int CoinAmount {
get {
return coinAmount;
}
set {
coinAmount = value;
coinAmountText.text = coinAmount.ToString();
}}
在InventoryManager中的SaveInventory()中的最後添加一句
PlayerPrefs.SetInt("CoinAmount", GameObject.FindWithTag("Player").GetComponent<Player>().CoinAmount);
在InventoryManager中的LoadInventory()中最後添加
if(PlayerPrefs.HasKey("CoinAmount")) {
GameObject.FindWithTag("Player").GetComponent<Player>().CoinAmount = PlayerPrefs.GetInt("CoinAmount");
}
遊戲發佈:
File -> BuildSettings -> 選擇PC版本 -> 添加場景 -> 選擇路徑 -> Build
發現Bug -- 可是我好像視覺上沒有發現。。。
Bug描述:
pick up item時,item會有一幀顯示在另外一個地方,而後纔跟隨鼠標
緣由:
咱們在設置pickedItem時,InventoryManager.SetPickedItem(...)中
設置了pickedItem的item和amount,並顯示出pickedItem
在Update()中進行了PickedItem的位置跟隨
所以先後相差了一幀
解決方法:
在SetPickedItem中,進行初始位置的設置
public void SetPickedItem(Item item, int amount) { pickedItem.SetPickedItem(item, amount); UpdatePickedItemPosition(); pickedItem.Display(); ToolTip.Instance.HideToolTip(); }