Inventory reimplementation (from MonsterShop). Top panel is temporarily broken

This commit is contained in:
Aggtaa
2026-01-20 22:47:58 +03:00
parent a9fb1864a5
commit 29899842e4
33 changed files with 913 additions and 129 deletions
+8
View File
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 3f1c7553ca7b3bb4399eab9e93b9a6ff
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,15 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
[System.Serializable]
public class GameData
{
public long lastUpdated;
public PlayerData player = new PlayerData();
public StorageData storage = new StorageData();
public List<ShelfData> shelves = new List<ShelfData>();
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 89cbdf7a2dcd8ab40a929072c7493273
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,12 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
[System.Serializable]
public class InventoryData
{
public float capacity = 0;
public SerializableDictionary<string, float> items =
new SerializableDictionary<string, float>();
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 3bcd40c816f3fb24a8aa90a6438d53a0
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,7 @@
using System;
[System.Serializable]
public class PlayerData
{
public InventoryData inventory = new InventoryData();
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: ff587676af26ad344af1761388bb9016
@@ -0,0 +1,11 @@
using UnityEngine;
[System.Serializable]
public class ShelfData
{
public string type = "shelf";
public Vector2 location = Vector2.zero;
public float angle = 0;
public InventoryData inventory = new InventoryData();
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: e220bc2634b66924c99448f4bd3a824c
@@ -0,0 +1,7 @@
using System;
[System.Serializable]
public class StorageData
{
public InventoryData inventory = new InventoryData();
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 9691713479005104ea634e90c3338df3
@@ -0,0 +1,169 @@
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using UnityEngine.SceneManagement;
public class DataPersistenceManager : MonoBehaviour
{
// [Header("Debugging")]
// [SerializeField] private bool initializeDataIfNull = false;
[Header("File Storage Config")]
[SerializeField]
private string fileName;
[SerializeField]
private bool useEncryption;
[Header("Auto Saving Configuration")]
[SerializeField]
private float autoSaveTimeSeconds = 60f;
private GameData gameData = new GameData();
private List<IDataPersistence<GameData>> dataPersistenceObjects;
private FileDataHandler dataHandler;
private Coroutine autoSaveCoroutine;
public static DataPersistenceManager instance { get; private set; }
private void Awake()
{
if (instance != null)
{
Debug.Log(
"Found more than one Data Persistence Manager in the scene. Destroying the newest one."
);
Destroy(this.gameObject);
return;
}
instance = this;
DontDestroyOnLoad(this.gameObject);
this.dataHandler = new FileDataHandler(
Application.persistentDataPath,
fileName,
useEncryption
);
}
private void OnEnable()
{
SceneManager.sceneLoaded += OnSceneLoaded;
Initialize();
}
private void OnDisable()
{
SceneManager.sceneLoaded -= OnSceneLoaded;
}
public void OnSceneLoaded(Scene scene, LoadSceneMode mode)
{
// Start();
}
private void Initialize()
{
LoadGame();
// start up the auto saving coroutine
if (autoSaveCoroutine != null)
{
StopCoroutine(autoSaveCoroutine);
}
autoSaveCoroutine = StartCoroutine(AutoSave());
}
public void NewGame()
{
Debug.Log("new game");
this.gameData = new GameData();
}
public void LoadGame()
{
Debug.Log("Loading game");
this.dataPersistenceObjects = FindAllDataPersistenceObjects();
// load any saved data from a file using the data handler
this.gameData = dataHandler.Load();
// start a new game if the data is null and we're configured to initialize data for debugging purposes
if (this.gameData == null)
{
NewGame();
return;
}
// if no data can be loaded, don't continue
// if (this.gameData == null)
// {
// Debug.Log("No data was found. A New Game needs to be started before data can be loaded.");
// return;
// }
// push the loaded data to all other scripts that need it
foreach (IDataPersistence<GameData> dataPersistenceObj in dataPersistenceObjects)
{
dataPersistenceObj.LoadData(gameData);
}
}
public void SaveGame()
{
Debug.Log("Saving game");
this.dataPersistenceObjects = FindAllDataPersistenceObjects();
// if we don't have any data to save, log a warning here
if (this.gameData == null)
{
Debug.LogWarning(
"No data was found. A New Game needs to be started before data can be saved."
);
return;
}
// pass the data to other scripts so they can update it
foreach (IDataPersistence<GameData> dataPersistenceObj in dataPersistenceObjects)
{
dataPersistenceObj.SaveData(gameData);
}
// timestamp the data so we know when it was last saved
gameData.lastUpdated = System.DateTime.Now.ToBinary();
// save that data to a file using the data handler
dataHandler.Save(gameData);
}
private void OnApplicationQuit()
{
SaveGame();
}
private List<IDataPersistence<GameData>> FindAllDataPersistenceObjects()
{
// FindObjectsofType takes in an optional boolean to include inactive gameobjects
IEnumerable<IDataPersistence<GameData>> dataPersistenceObjects =
FindObjectsByType<MonoBehaviour>(FindObjectsSortMode.None)
.OfType<IDataPersistence<GameData>>();
var list = new List<IDataPersistence<GameData>>(dataPersistenceObjects);
return list;
}
private IEnumerator AutoSave()
{
while (true)
{
yield return new WaitForSeconds(autoSaveTimeSeconds);
SaveGame();
Debug.Log("Auto Saved Game");
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: a43c513eda07bf94db46b159ebbc8b51
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,185 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System;
using System.IO;
public class FileDataHandler
{
private string dataDirPath = "";
private string dataFileName = "";
private bool useEncryption = false;
private readonly string encryptionCodeWord = "word";
private readonly string backupExtension = ".bak";
public FileDataHandler(string dataDirPath, string dataFileName, bool useEncryption)
{
this.dataDirPath = dataDirPath;
this.dataFileName = dataFileName;
this.useEncryption = useEncryption;
}
public GameData Load(bool allowRestoreFromBackup = true)
{
// use Path.Combine to account for different OS's having different path separators
string fullPath = Path.Combine(dataDirPath, dataFileName);
GameData loadedData = null;
if (File.Exists(fullPath))
{
try
{
// load the serialized data from the file
string dataToLoad = "";
using (FileStream stream = new FileStream(fullPath, FileMode.Open))
{
using (StreamReader reader = new StreamReader(stream))
{
dataToLoad = reader.ReadToEnd();
}
}
// optionally decrypt the data
if (useEncryption)
{
dataToLoad = EncryptDecrypt(dataToLoad);
}
// deserialize the data from Json back into the C# object
loadedData = JsonUtility.FromJson<GameData>(dataToLoad);
}
catch (Exception e)
{
// since we're calling Load(..) recursively, we need to account for the case where
// the rollback succeeds, but data is still failing to load for some other reason,
// which without this check may cause an infinite recursion loop.
if (allowRestoreFromBackup)
{
Debug.LogWarning("Failed to load data file. Attempting to roll back.\n" + e);
bool rollbackSuccess = AttemptRollback(fullPath);
if (rollbackSuccess)
{
// try to load again recursively
loadedData = Load(false);
}
}
// if we hit this else block, one possibility is that the backup file is also corrupt
else
{
Debug.LogError("Error occured when trying to load file at path: "
+ fullPath + " and backup did not work.\n" + e);
}
}
}
return loadedData;
}
public void Save(GameData data)
{
// use Path.Combine to account for different OS's having different path separators
string fullPath = Path.Combine(dataDirPath, dataFileName);
Debug.Log("Saving to " + fullPath);
string backupFilePath = fullPath + backupExtension;
try
{
// create the directory the file will be written to if it doesn't already exist
Directory.CreateDirectory(Path.GetDirectoryName(fullPath));
// serialize the C# game data object into Json
string dataToStore = JsonUtility.ToJson(data, true);
// optionally encrypt the data
if (useEncryption)
{
dataToStore = EncryptDecrypt(dataToStore);
}
// write the serialized data to the file
using (FileStream stream = new FileStream(fullPath, FileMode.Create))
{
using (StreamWriter writer = new StreamWriter(stream))
{
writer.Write(dataToStore);
}
}
// verify the newly saved file can be loaded successfully
GameData verifiedGameData = Load();
// if the data can be verified, back it up
if (verifiedGameData != null)
{
File.Copy(fullPath, backupFilePath, true);
}
// otherwise, something went wrong and we should throw an exception
else
{
throw new Exception("Save file could not be verified and backup could not be created.");
}
}
catch (Exception e)
{
Debug.LogError("Error occured when trying to save data to file: " + fullPath + "\n" + e);
}
}
public void Delete()
{
string fullPath = Path.Combine(dataDirPath, dataFileName);
try
{
// ensure the data file exists at this path before deleting the directory
if (File.Exists(fullPath))
{
// delete the profile folder and everything within it
Directory.Delete(Path.GetDirectoryName(fullPath), true);
}
else
{
Debug.LogWarning("Tried to delete profile data, but data was not found at path: " + fullPath);
}
}
catch (Exception e)
{
Debug.LogError("Failed to delete data at path: " + fullPath + "\n" + e);
}
}
// the below is a simple implementation of XOR encryption
private string EncryptDecrypt(string data)
{
string modifiedData = "";
for (int i = 0; i < data.Length; i++)
{
modifiedData += (char) (data[i] ^ encryptionCodeWord[i % encryptionCodeWord.Length]);
}
return modifiedData;
}
private bool AttemptRollback(string fullPath)
{
bool success = false;
string backupFilePath = fullPath + backupExtension;
try
{
// if the file exists, attempt to roll back to it by overwriting the original file
if (File.Exists(backupFilePath))
{
File.Copy(backupFilePath, fullPath, true);
success = true;
Debug.LogWarning("Had to roll back to backup file at: " + backupFilePath);
}
// otherwise, we don't yet have a backup file - so there's nothing to roll back to
else
{
throw new Exception("Tried to roll back, but no backup file exists to roll back to.");
}
}
catch (Exception e)
{
Debug.LogError("Error occured when trying to roll back to backup file at: "
+ backupFilePath + "\n" + e);
}
return success;
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: c4d960248b76a174e8de8eb7d18afdb5
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,14 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public interface IDataPersistenceBase { }
public interface IDataPersistence<TData> : IDataPersistenceBase
{
void LoadData(TData data);
// The 'ref' keyword was removed from here as it is not needed.
// In C#, non-primitive types are automatically passed by reference.
void SaveData(TData data);
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: dadb1cc8e7af5394ca73356b1c0f1289
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: e335e31db871f2246b93b4540f64cb92
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,57 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using Unity.Collections;
using UnityEngine;
[System.Serializable]
public class SerializableDictionaryEntry<TKey, TValue>
{
public TKey key;
public TValue value;
}
[System.Serializable]
public class SerializableDictionary<TKey, TValue>
: Dictionary<TKey, TValue>,
ISerializationCallbackReceiver
{
[SerializeField]
private List<SerializableDictionaryEntry<TKey, TValue>> entries =
new List<SerializableDictionaryEntry<TKey, TValue>>();
public SerializableDictionary() { }
public SerializableDictionary(Dictionary<TKey, TValue> source)
{
foreach (var item in source)
Add(item.Key, item.Value);
}
public void OnBeforeSerialize()
{
entries.Clear();
foreach (KeyValuePair<TKey, TValue> pair in this)
{
entries.Add(
new SerializableDictionaryEntry<TKey, TValue>()
{
key = pair.Key,
value = pair.Value,
}
);
}
}
// load the dictionary from lists
public void OnAfterDeserialize()
{
this.Clear();
foreach (SerializableDictionaryEntry<TKey, TValue> entry in entries)
{
this.Add(entry.key, entry.value);
}
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 9dc07c3462db23d45a4aa37b7d15244c