保存和加载库存

Save and load inventory

我已经创建了清单并且一切正常。现在我应该实现库存(存档)的保存和加载。但是,我发现自己陷入了如何进行的困境。我正在考虑创建一个库存数据脚本来获取可序列化的数据,然后保存它。我没有使用脚本对象。你对我有什么建议吗?下面是库存代码。

 public class Inventory2 : MonoBehaviour
 {
     public bool inventoryEnabled;
     public GameObject inventory, slotHolder;
     private Transform[] slot;
     public int level;

     void Start()
     {
         level = SceneManager.GetActiveScene().buildIndex;
         GetAllSlots();        
     }   

     void Update()
     {
         if (Input.GetAxis("Inventory") != 0)
         {
             inventoryEnabled = !inventoryEnabled;
         }

         if(inventoryEnabled)
         {
             inventory.SetActive(true);
         }
         else
         {
             inventory.SetActive(false);
         }
     }

     public void OnTriggerEnter(Collider other)
     {
         if (other.tag == "Clues")
         {            
             AddClue(other.GetComponent<Clue2>());            
         }
     }
     public void AddClue(Clue2 clue)
     {
         Text description = clue.GetComponent<Text>();

         for (int i = 0; i < 2; i++)
         {
             if(slot[i].GetComponent<Slot2>().empty == true && clue.pickedUp == false)
             {
                 slot[i].GetComponent<Slot2>().clue = clue;
                 slot[i].GetComponent<Slot2>().descriptionFinal = description;
                 slot[i].GetComponent<Slot2>().empty = false;
                 clue.GetComponent<Clue2>().pickedUp = true;
             }
         }
     }

     public void GetAllSlots()
     {
         slot = new Transform[Clue2.objects];

         for(int i = 0; i < Clue2.objects; i++)
         {
             slot[i] = slotHolder.transform.GetChild(i);
             slot[i].GetComponent<Slot2>().empty = true;
         }
     }
 }
 public class Slot2 : MonoBehaviour
 {
     public Clue2 clue;
     public bool empty;
     public Text descriptionFirst, descriptionFinal;

     void Awake()
     {

     }

     void Update()
     {
         if (clue)
         {
             this.GetComponentInChildren<Text>().text = descriptionFinal.text;
         }
         else
         {
             this.GetComponentInChildren<Text>().text = descriptionFirst.text;
             empty = true;
         }
     }
 }
 public class Clue2 : MonoBehaviour
 {
     public static int objects = 0;
     public static int objectsCollected = 0;
     public Text description;
     public bool pickedUp;

     public GameObject cluePopUpPanel, canvasCluesPanel;
     public Canvas canvasHUD;
     public static bool activeClue = false;

     void Awake()
     {
         objects++;
     }

     void Update()
     {
         if (canvasCluesPanel.gameObject.activeSelf == true && Input.GetAxis("PickUp") != 0)
         {
             activeClue = true;
             cluePopUpPanel.gameObject.GetComponent<UnityEngine.UI.Text>().text = description.text;
         }
     }

     private void OnTriggerEnter(Collider other)
     {
         if (other.tag == "Player")
         {
             if (canvasCluesPanel.gameObject.activeSelf == false)
             {
                 canvasCluesPanel.gameObject.SetActive(true);                
             }
         }
     }

     private void OnTriggerStay(Collider other)
     {
         if (other.tag == "Player" && activeClue == true)
         {
             cluePopUpPanel.gameObject.SetActive(true);
             cluePopUpPanel.GetComponentInChildren<Text>().text = this.GetComponent<Text>().text;
             canvasCluesPanel.gameObject.SetActive(false);
         }

         if (other.tag == "Player" && activeClue == false)
         {
             cluePopUpPanel.gameObject.SetActive(false);
             canvasCluesPanel.gameObject.SetActive(true);
         }        
     }

     private void OnTriggerExit(Collider other)
     {
         if (other.tag == "Player" && activeClue == true)
         {
             cluePopUpPanel.gameObject.SetActive(false);
             canvasCluesPanel.gameObject.SetActive(false);
             activeClue = false;
             if(objectsCollected < objects)
             {
                 objectsCollected++;
             }
         }
         else
         {
             cluePopUpPanel.gameObject.SetActive(false);
             canvasCluesPanel.gameObject.SetActive(false);
             activeClue = false;
         }

         canvasHUD.gameObject.SetActive(true);
     }
 }

save/load您的数据有多种方法。 通常它涉及将您的数据序列化到某个地方的某些存储(内存、硬盘驱动器、网络服务器等),然后读取它。

我发现最简单的解决方案是使用 JSON 序列化(JSON.NET for Unity 是一个很好的方法)。

假设我的库存商店为 List<Item> Inventory

您使用 JsonObjectAttribute(MemberSerialization.OptIn) 标记您的 Item class(以避免序列化从 MonoBehaviour 继承的内容),以及您希望存储的每个 属性用你装饰 JsonPropertyAttribute.

然后您可以将数据序列化为 PlayerPrefs(它基本上是在本地设备上存储数据的抽象)并从那里加载回来。

伪代码:

private const string PlayerStatePlayerPrefKey = "PlayerSaveData";

void SavePlayerState(PlayerState state)
{
    var serializedState = JsonConvert.Serialize(state);
    PlayerPrefs.SetString(PlayerStatePlayerPrefKey, serializedState);
    PlayerPrefs.Save();
}

PlayerState LoadPlayerState()
{
    var serializedState = PlayerPrefs.GetString(PlayerStatePlayerPrefKey, null);
    if (serializedState == null)
        return new PlayerState();
    return JsonConvert.DeserializeObject<PlayerState>(serializedState);
}

您应该确保将整个过程包装在 Try... Catch 中并处理可能发生的序列化问题 - 最常见的问题是更改保存数据的数据结构会阻止读取旧保存。

我还建议将库存数据存储在 POCO(普通旧 C# 对象)中,并将数据和逻辑与游戏的视觉效果分开。


顺便说一句,我假设您对编程还比较陌生,我建议您继续阅读 SOLID principle, and read the great book Clean Code

我发现了一些编码标准来清理我的代码:

而不是 if (boolean == true) 只需使用 if(boolean)

逆合并return ifs去除嵌套:

private void OnTriggerEnter(Collider other)
{
    if (other.tag == "Player")
    {
        if (canvasCluesPanel.gameObject.activeSelf == false)
        {
            canvasCluesPanel.gameObject.SetActive(true);                
        }
    }
}

转为

private void OnTriggerEnter(Collider other)
{
    if (!other.tag == "Player" || !canvasCluesPanel.gameObject.activeSelf)
        return;
    canvasCluesPanel.gameObject.SetActive(true);    
}

恕我直言,制作游戏非常复杂,拥有更简洁、更易于维护的代码将极大地帮助您的开发。

祝你好运

可以找到很好的教程here

1 - 创建数据类型

作为一种好的做法,将要保存的数据存储在 class 中。像这样创建 SlotData。所以尝试像这样转换它们。此 class 必须具有属性 System.Serializable.

一个例子就是这个

[System.Serializable]
public class SlotData
{
    public bool containsItem = false;
    public string Description;
    //other possible elements
    public int amount; 
}

InventoryData 将只是一个 SlotData 数组。

[System.Serializable]
public class InventoryData
{
    public SlotData[] inventorySlots;
}

2 - 保存前设置数据

您在代码中的某处更新了清单。您必须设置此数据,可能来自单一行为。基本上你在 class.

中添加你想要的所有信息
public void PrepareToSave()
{
    //setup the inventory
    var yourInventory = new InventoryData();
    //fill all the slots, you must also calculate their amount
    yourInventory.inventorySlots = new SlotData[CalculateYourAmount];
    //fill all the slots here
    for (int   i= 0; i  < CalculateYourAmount;  i++)
    {   
        //fill all the slots
        yourInventory.inventorySlots[i] = CreateTheSlot();
    }

    //this go to the next step
    SaveYourInventory(yourInventory);
}

3 - 保存库存

如您所问,您可以使用 BinaryFormatter and FileStream

private void SaveYourInventory(InventoryData yourInventory)
{
    var             savePath = Application.persistentDataPath + "/inventory.dat";
    BinaryFormatter bf       = new BinaryFormatter();
    FileStream      file     = File.Create(savePath);
    bf.Serialize(file, yourInventory);
    file.Close();
}

4 - 加载库存

然后你可以像这样加载库存。

public void LoadYourInventory()
{
    var savePath = Application.persistentDataPath + "/inventory.dat";
    if (File.Exists(savePath))
    {
        BinaryFormatter bf   = new BinaryFormatter();
        FileStream      file = File.Open(savePath, FileMode.Open);
        InventoryData yourInventory = (InventoryData) bf.Deserialize(file);
        file.Close();

        //do what you want with the inventory
        ...
    }
}

您还可以找到一个保存管理器系统here

如果您想更高级,可以使用此 optimized formatter(如果您回到 save/load 业务,则将其保存以备将来使用)。