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