initial commit
This commit is contained in:
@@ -0,0 +1,631 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using UnityEditor;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
[Serializable]
|
||||
public class Blackboard : Dictionary<string, object>, ISerializationCallbackReceiver
|
||||
{
|
||||
[Serializable]
|
||||
//struct represents the string key, and the serialized value from the dictionary.
|
||||
private struct SaveItem
|
||||
{
|
||||
public string key;
|
||||
public string value;
|
||||
public int index;
|
||||
|
||||
public SaveItem(string key, string val, int index)
|
||||
{
|
||||
this.key = key;
|
||||
this.value = val;
|
||||
this.index = index;
|
||||
}
|
||||
}
|
||||
|
||||
//All serialized items except for objects in a scene, which have to be handled separately.
|
||||
[SerializeField, HideInInspector]
|
||||
private List<SaveItem> saveItems;
|
||||
|
||||
//We need a different struct and list for Object references in scene :(
|
||||
[Serializable]
|
||||
private struct NonAssetSaveItem
|
||||
{
|
||||
public string key;
|
||||
public UnityEngine.Object obj;
|
||||
public int index;
|
||||
|
||||
public NonAssetSaveItem(string key, UnityEngine.Object obj, int index)
|
||||
{
|
||||
this.key = key;
|
||||
this.obj = obj;
|
||||
this.index = index;
|
||||
}
|
||||
}
|
||||
[SerializeField, HideInInspector]
|
||||
private List<NonAssetSaveItem> sceneObjectSaveItems;
|
||||
|
||||
/// <summary>
|
||||
/// Takes all of the keyvalue pairs from the Dictionary and stores them as Serializable lists.
|
||||
/// </summary>
|
||||
public void OnBeforeSerialize()
|
||||
{
|
||||
sceneObjectSaveItems = new List<NonAssetSaveItem>();
|
||||
saveItems = new List<SaveItem>();
|
||||
List<string> keys = this.Keys.ToList();
|
||||
List<object> values = this.Values.ToList();
|
||||
|
||||
for (int i = 0; i < Count; i++)
|
||||
{
|
||||
object value = values[i];
|
||||
string encode = "";
|
||||
|
||||
//Unhandled Enum Types
|
||||
if (value is Enum)
|
||||
{
|
||||
Enum enumValue = (Enum)value;
|
||||
Console.WriteLine("Enum Value: " + enumValue.ToString());
|
||||
encode = $"({value.GetType().AssemblyQualifiedName}){enumValue}";
|
||||
saveItems.Add(new SaveItem(keys[i], encode, i));
|
||||
continue;
|
||||
}
|
||||
|
||||
switch (value)
|
||||
{
|
||||
case null: encode = "(null)"; break;
|
||||
case int: encode = "(int)" + ((int)value).ToString("F9"); break;
|
||||
case float: encode = "(float)" + ((float)value).ToString("F9"); break;
|
||||
case double: encode = "(double)" + ((double)value).ToString("F9"); break;
|
||||
case long: encode = "(long)" + ((long)value).ToString(); break;
|
||||
case string: encode = "(string)" + (string)value; break;
|
||||
case bool: encode = "(bool)" + (((bool)value) == true ? "true" : "false"); break;
|
||||
case Vector2Int: encode = "(Vector2Int)" + ((Vector2Int)value).ToString(); break;
|
||||
case Vector3Int: encode = "(Vector3Int)" + ((Vector3Int)value).ToString(); break;
|
||||
case Vector2: encode = "(Vector2)" + ((Vector2)value).ToString(); break;
|
||||
case Vector3: encode = "(Vector3)" + ((Vector3)value).ToString(); break;
|
||||
case Vector4: encode = "(Vector4)" + ((Vector4)value).ToString(); break;
|
||||
case Bounds: encode = "(Bounds)" + ((Bounds)value).ToString(); break;
|
||||
case Rect: encode = "(Rect)" + ((Rect)value).ToString("F9"); break;
|
||||
case Color: encode = "(Color)" + JsonUtility.ToJson((Color)value); break;
|
||||
case AnimationCurve: encode = "(AnimationCurve)" + Serializer.SerializeAnimationCurve((AnimationCurve)value); break;
|
||||
case Gradient: encode = "(Gradient)" + Serializer.SerializeGradient((Gradient)value); break;
|
||||
case UnityEngine.Object obj:
|
||||
string assetPath = Application.isEditor ? AssetDatabase.GetAssetPath(obj) : null;
|
||||
if (!string.IsNullOrEmpty(assetPath))
|
||||
{
|
||||
encode = "(UnityEngine.Object)" + assetPath;
|
||||
}
|
||||
else
|
||||
{
|
||||
sceneObjectSaveItems.Add(new NonAssetSaveItem(keys[i], obj, i));
|
||||
}
|
||||
break;
|
||||
//Try to serialize to JSON. May be empty if type is not supported
|
||||
default: encode = $"({value.GetType().AssemblyQualifiedName}){JsonUtility.ToJson(value)}"; break;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(encode))
|
||||
{
|
||||
saveItems.Add(new SaveItem(keys[i], encode, i));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Loads the two lists back into the Dictionary, using the Merge Linked Lists method.
|
||||
/// </summary>
|
||||
public void OnAfterDeserialize()
|
||||
{
|
||||
this.Clear();
|
||||
int i = 0;
|
||||
int j = 0;
|
||||
|
||||
//Ensure that the lists are not null to ensure no errors when accessing list.Count
|
||||
saveItems = saveItems == null ? new List<SaveItem>() : saveItems;
|
||||
sceneObjectSaveItems = sceneObjectSaveItems == null ? new List<NonAssetSaveItem>() : sceneObjectSaveItems;
|
||||
|
||||
while (i < saveItems.Count && j < sceneObjectSaveItems.Count)
|
||||
{
|
||||
if (saveItems[i].index < sceneObjectSaveItems[j].index)
|
||||
{
|
||||
string key = saveItems[i].key;
|
||||
int openIndex = saveItems[i].value.IndexOf('(');
|
||||
int closeIndex = saveItems[i].value.IndexOf(')');
|
||||
string contentType = saveItems[i].value.Substring(openIndex + 1, closeIndex - openIndex - 1);
|
||||
string encode = saveItems[i].value.Substring(closeIndex + 1);
|
||||
DeserializeItem(contentType, key, encode);
|
||||
i++;
|
||||
}
|
||||
else
|
||||
{
|
||||
Add(sceneObjectSaveItems[j].key, sceneObjectSaveItems[j].obj);
|
||||
j++;
|
||||
}
|
||||
}
|
||||
|
||||
for(; i < saveItems.Count;i++)
|
||||
{
|
||||
string key = saveItems[i].key;
|
||||
int openIndex = saveItems[i].value.IndexOf('(');
|
||||
int closeIndex = saveItems[i].value.IndexOf(')');
|
||||
string contentType = saveItems[i].value.Substring(openIndex + 1, closeIndex - openIndex - 1);
|
||||
string encode = saveItems[i].value.Substring(closeIndex + 1);
|
||||
DeserializeItem(contentType, key, encode);
|
||||
}
|
||||
|
||||
for (; j < sceneObjectSaveItems.Count; j++)
|
||||
{
|
||||
Add(sceneObjectSaveItems[j].key, sceneObjectSaveItems[j].obj);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Takes the key and encoded string from a serialized item and adds it back into the dictionary.
|
||||
/// </summary>
|
||||
private void DeserializeItem(string contentType, string key, string encodedValue)
|
||||
{
|
||||
switch (contentType)
|
||||
{
|
||||
case "null": Add(key, null); return;
|
||||
case "int": Add(key, (int)int.Parse(encodedValue)); return;
|
||||
case "float": Add(key, (float)float.Parse(encodedValue)); return;
|
||||
case "double": Add(key, (double)double.Parse(encodedValue)); return;
|
||||
case "long": Add(key, (long)long.Parse(encodedValue)); return;
|
||||
case "string": Add(key, (string)encodedValue); return;
|
||||
case "bool": Add(key, (bool)(encodedValue == "true" ? true : false)); return;
|
||||
case "Vector2": Add(key, Serializer.ParseVector2(encodedValue)); return;
|
||||
case "Vector3": Add(key, Serializer.ParseVector3(encodedValue)); return;
|
||||
case "Vector2Int": Add(key, Serializer.ParseVector2Int(encodedValue)); return;
|
||||
case "Vector3Int": Add(key, Serializer.ParseVector3Int(encodedValue)); return;
|
||||
case "Vector4": Add(key, Serializer.ParseVector4(encodedValue)); return;
|
||||
case "Bounds": Add(key, Serializer.ParseBounds(encodedValue)); return;
|
||||
case "Rect": Add(key, Serializer.ParseRect(encodedValue)); return;
|
||||
case "Color": Add(key, JsonUtility.FromJson<Color>(encodedValue)); return;
|
||||
case "AnimationCurve": Add(key, Serializer.DeserializeAnimationCurve(encodedValue)); return;
|
||||
case "Gradient": Add(key, Serializer.DeserializeGradient(encodedValue)); return;
|
||||
case "UnityEngine.Object":
|
||||
if(Application.isEditor)
|
||||
{
|
||||
EditorApplication.delayCall += () => Add(key, AssetDatabase.LoadAssetAtPath<UnityEngine.Object>(encodedValue));
|
||||
}
|
||||
else
|
||||
{
|
||||
Add(key, Resources.Load(encodedValue));
|
||||
}
|
||||
return;
|
||||
default: break;
|
||||
}
|
||||
|
||||
//Different process for enums (of any type)
|
||||
if(Serializer.EnumDeserialize(contentType, encodedValue, out object enumValue))
|
||||
{
|
||||
Add(key, enumValue);
|
||||
}
|
||||
|
||||
//Tries to de-serialize a struct or class using JsonUtility.FromJson
|
||||
if(Serializer.TryDeserializeJSON(contentType, encodedValue, out object result))
|
||||
{
|
||||
Add(key, result);
|
||||
}
|
||||
}
|
||||
|
||||
public void SetOrAdd(string key, object ob)
|
||||
{
|
||||
if (this.ContainsKey(key))
|
||||
{
|
||||
this[key] = ob;
|
||||
}
|
||||
else
|
||||
{
|
||||
this.Add(key, ob);
|
||||
}
|
||||
}
|
||||
|
||||
[System.Serializable]
|
||||
private static class Serializer
|
||||
{
|
||||
#region GradientSerialization
|
||||
[System.Serializable]
|
||||
private class SerializableGradient
|
||||
{
|
||||
public SerializableColorKey[] colorKeys;
|
||||
public SerializableAlphaKey[] alphaKeys;
|
||||
public GradientMode mode;
|
||||
|
||||
public SerializableGradient(Gradient gradient)
|
||||
{
|
||||
colorKeys = new SerializableColorKey[gradient.colorKeys.Length];
|
||||
for (int i = 0; i < gradient.colorKeys.Length; i++)
|
||||
{
|
||||
colorKeys[i] = new SerializableColorKey(gradient.colorKeys[i]);
|
||||
}
|
||||
|
||||
alphaKeys = new SerializableAlphaKey[gradient.alphaKeys.Length];
|
||||
for (int i = 0; i < gradient.alphaKeys.Length; i++)
|
||||
{
|
||||
alphaKeys[i] = new SerializableAlphaKey(gradient.alphaKeys[i]);
|
||||
}
|
||||
|
||||
mode = gradient.mode;
|
||||
}
|
||||
|
||||
public Gradient ToGradient()
|
||||
{
|
||||
Gradient gradient = new Gradient();
|
||||
GradientColorKey[] gradientColorKeys = new GradientColorKey[colorKeys.Length];
|
||||
for (int i = 0; i < colorKeys.Length; i++)
|
||||
{
|
||||
gradientColorKeys[i] = colorKeys[i].ToGradientColorKey();
|
||||
}
|
||||
|
||||
GradientAlphaKey[] gradientAlphaKeys = new GradientAlphaKey[alphaKeys.Length];
|
||||
for (int i = 0; i < alphaKeys.Length; i++)
|
||||
{
|
||||
gradientAlphaKeys[i] = alphaKeys[i].ToGradientAlphaKey();
|
||||
}
|
||||
|
||||
gradient.SetKeys(gradientColorKeys, gradientAlphaKeys);
|
||||
gradient.mode = mode;
|
||||
|
||||
return gradient;
|
||||
}
|
||||
}
|
||||
|
||||
[System.Serializable]
|
||||
private class SerializableColorKey
|
||||
{
|
||||
public Color color;
|
||||
public float time;
|
||||
|
||||
public SerializableColorKey(GradientColorKey colorKey)
|
||||
{
|
||||
color = colorKey.color;
|
||||
time = colorKey.time;
|
||||
}
|
||||
|
||||
public GradientColorKey ToGradientColorKey()
|
||||
{
|
||||
return new GradientColorKey(color, time);
|
||||
}
|
||||
}
|
||||
|
||||
[System.Serializable]
|
||||
private class SerializableAlphaKey
|
||||
{
|
||||
public float alpha;
|
||||
public float time;
|
||||
|
||||
public SerializableAlphaKey(GradientAlphaKey alphaKey)
|
||||
{
|
||||
alpha = alphaKey.alpha;
|
||||
time = alphaKey.time;
|
||||
}
|
||||
|
||||
public GradientAlphaKey ToGradientAlphaKey()
|
||||
{
|
||||
return new GradientAlphaKey(alpha, time);
|
||||
}
|
||||
}
|
||||
|
||||
public static string SerializeGradient(Gradient gradient)
|
||||
{
|
||||
SerializableGradient serializableGradient = new SerializableGradient(gradient);
|
||||
return JsonUtility.ToJson(serializableGradient);
|
||||
}
|
||||
|
||||
public static Gradient DeserializeGradient(string json)
|
||||
{
|
||||
SerializableGradient serializableGradient = JsonUtility.FromJson<SerializableGradient>(json);
|
||||
|
||||
if (serializableGradient != null)
|
||||
{
|
||||
return serializableGradient.ToGradient();
|
||||
}
|
||||
|
||||
Debug.LogError("Failed to deserialize Gradient from JSON: " + json);
|
||||
return new Gradient(); // Return a default Gradient or handle the error as needed
|
||||
}
|
||||
#endregion
|
||||
#region AnimationCurveSerialization
|
||||
[System.Serializable]
|
||||
private struct SerializableKeyframe
|
||||
{
|
||||
public float time;
|
||||
public float value;
|
||||
public float inTangent;
|
||||
public float outTangent;
|
||||
|
||||
public SerializableKeyframe(Keyframe keyframe)
|
||||
{
|
||||
time = keyframe.time;
|
||||
value = keyframe.value;
|
||||
inTangent = keyframe.inTangent;
|
||||
outTangent = keyframe.outTangent;
|
||||
}
|
||||
}
|
||||
|
||||
[System.Serializable]
|
||||
private struct SerializableAnimationCurve
|
||||
{
|
||||
public WrapMode preWrapMode;
|
||||
public WrapMode postWrapMode;
|
||||
public SerializableKeyframe[] keys;
|
||||
|
||||
public SerializableAnimationCurve(AnimationCurve curve)
|
||||
{
|
||||
preWrapMode = curve.preWrapMode;
|
||||
postWrapMode = curve.postWrapMode;
|
||||
keys = new SerializableKeyframe[curve.length];
|
||||
for (int i = 0; i < curve.length; i++)
|
||||
{
|
||||
keys[i] = new SerializableKeyframe(curve[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Serializes an AnimationCurve to a JSON string.
|
||||
/// </summary>
|
||||
public static string SerializeAnimationCurve(AnimationCurve curve)
|
||||
{
|
||||
SerializableAnimationCurve serializableCurve = new SerializableAnimationCurve(curve);
|
||||
string json = JsonUtility.ToJson(serializableCurve);
|
||||
return json;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Produces an AnimationCurve from a json string.
|
||||
/// </summary>
|
||||
public static AnimationCurve DeserializeAnimationCurve(string json)
|
||||
{
|
||||
SerializableAnimationCurve serializableCurve = JsonUtility.FromJson<SerializableAnimationCurve>(json);
|
||||
|
||||
Keyframe[] keyframes = new Keyframe[serializableCurve.keys.Length];
|
||||
for (int i = 0; i < keyframes.Length; i++)
|
||||
{
|
||||
keyframes[i] = new Keyframe(
|
||||
serializableCurve.keys[i].time,
|
||||
serializableCurve.keys[i].value,
|
||||
serializableCurve.keys[i].inTangent,
|
||||
serializableCurve.keys[i].outTangent
|
||||
);
|
||||
}
|
||||
|
||||
AnimationCurve curve = new AnimationCurve(keyframes);
|
||||
curve.postWrapMode = serializableCurve.postWrapMode;
|
||||
curve.preWrapMode = serializableCurve.postWrapMode;
|
||||
return curve;
|
||||
}
|
||||
#endregion
|
||||
#region VectorSerialization
|
||||
public static Vector2 ParseVector2(string vectorString)
|
||||
{
|
||||
vectorString = vectorString.Replace("(", "").Replace(")", "");
|
||||
string[] components = vectorString.Split(',');
|
||||
|
||||
if (components.Length == 2 &&
|
||||
float.TryParse(components[0], out float x) &&
|
||||
float.TryParse(components[1], out float y))
|
||||
{
|
||||
return new Vector2(x, y);
|
||||
}
|
||||
|
||||
Debug.LogError("Failed to parse Vector2 from string: " + vectorString);
|
||||
return Vector2.zero;
|
||||
}
|
||||
|
||||
public static Vector2Int ParseVector2Int(string vectorString)
|
||||
{
|
||||
vectorString = vectorString.Replace("(", "").Replace(")", "");
|
||||
string[] components = vectorString.Split(',');
|
||||
|
||||
if (components.Length == 2 &&
|
||||
int.TryParse(components[0], out int x) &&
|
||||
int.TryParse(components[1], out int y))
|
||||
{
|
||||
return new Vector2Int(x, y);
|
||||
}
|
||||
|
||||
Debug.LogError("Failed to parse Vector2 from string: " + vectorString);
|
||||
return Vector2Int.zero;
|
||||
}
|
||||
|
||||
public static Vector3 ParseVector3(string vectorString)
|
||||
{
|
||||
vectorString = vectorString.Replace("(", "").Replace(")", "");
|
||||
string[] components = vectorString.Split(',');
|
||||
|
||||
if (components.Length == 3 &&
|
||||
float.TryParse(components[0], out float x) &&
|
||||
float.TryParse(components[1], out float y) &&
|
||||
float.TryParse(components[2], out float z))
|
||||
{
|
||||
return new Vector3(x, y, z);
|
||||
}
|
||||
|
||||
Debug.LogError("Failed to parse Vector3 from string: " + vectorString);
|
||||
return Vector3.zero;
|
||||
}
|
||||
|
||||
public static Vector3Int ParseVector3Int(string vectorString)
|
||||
{
|
||||
vectorString = vectorString.Replace("(", "").Replace(")", "");
|
||||
string[] components = vectorString.Split(',');
|
||||
|
||||
if (components.Length == 3 &&
|
||||
int.TryParse(components[0], out int x) &&
|
||||
int.TryParse(components[1], out int y) &&
|
||||
int.TryParse(components[2], out int z))
|
||||
{
|
||||
return new Vector3Int(x, y, z);
|
||||
}
|
||||
|
||||
Debug.LogError("Failed to parse Vector3Int from string: " + vectorString);
|
||||
return Vector3Int.zero;
|
||||
}
|
||||
|
||||
public static Vector4 ParseVector4(string vectorString)
|
||||
{
|
||||
vectorString = vectorString.Replace("(", "").Replace(")", "");
|
||||
string[] components = vectorString.Split(',');
|
||||
|
||||
if (components.Length == 4 &&
|
||||
float.TryParse(components[0], out float x) &&
|
||||
float.TryParse(components[1], out float y) &&
|
||||
float.TryParse(components[2], out float z) &&
|
||||
float.TryParse(components[3], out float w))
|
||||
{
|
||||
return new Vector4(x, y, z, w);
|
||||
}
|
||||
|
||||
Debug.LogError("Failed to parse Vector4 from string: " + vectorString);
|
||||
return Vector4.zero;
|
||||
}
|
||||
#endregion
|
||||
#region BoundsSerialization
|
||||
/// <summary>
|
||||
/// Produces a Bounds object from the result of Bounds.ToString(). Returns a Bounds with all zero values if unable to parse.
|
||||
/// </summary>
|
||||
public static Bounds ParseBounds(string boundsString)
|
||||
{
|
||||
// Remove parentheses and labels from the string
|
||||
boundsString = Regex.Replace(boundsString, @"[^\d\.\-,]", "");
|
||||
|
||||
string[] components = boundsString.Split(',');
|
||||
|
||||
if (components.Length == 6 &&
|
||||
float.TryParse(components[0], out float center_x) &&
|
||||
float.TryParse(components[1], out float center_y) &&
|
||||
float.TryParse(components[2], out float center_z) &&
|
||||
float.TryParse(components[3], out float extent_x) &&
|
||||
float.TryParse(components[4], out float extent_y) &&
|
||||
float.TryParse(components[5], out float extent_z))
|
||||
{
|
||||
Vector3 center = new Vector3(center_x, center_y, center_z);
|
||||
Vector3 size = new Vector3(extent_x, extent_y, extent_z) * 2f;
|
||||
return new Bounds(center, size);
|
||||
}
|
||||
|
||||
Debug.LogWarning("Failed to parse Bounds from string: " + boundsString);
|
||||
return new Bounds(Vector3.zero, Vector3.zero);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Produces a BoundsInt object from the result of BoundsInt.ToString(). Returns a Bounds with all zero values if unable to parse.
|
||||
/// </summary>
|
||||
public static BoundsInt ParseBoundsInt(string boundsString)
|
||||
{
|
||||
// Remove parentheses and labels and any unwanted decimals from the string
|
||||
boundsString = Regex.Replace(boundsString, @"[^\d\-,]", "");
|
||||
|
||||
string[] components = boundsString.Split(',');
|
||||
|
||||
if (components.Length == 6 &&
|
||||
int.TryParse(components[0], out int center_x) &&
|
||||
int.TryParse(components[1], out int center_y) &&
|
||||
int.TryParse(components[2], out int center_z) &&
|
||||
int.TryParse(components[3], out int extent_x) &&
|
||||
int.TryParse(components[4], out int extent_y) &&
|
||||
int.TryParse(components[5], out int extent_z))
|
||||
{
|
||||
Vector3Int center = new Vector3Int(center_x, center_y, center_z);
|
||||
Vector3Int size = new Vector3Int(extent_x, extent_y, extent_z) * 2;
|
||||
return new BoundsInt(center, size);
|
||||
}
|
||||
|
||||
Debug.LogWarning("Failed to parse BoundsInt from string: " + boundsString);
|
||||
return new BoundsInt(Vector3Int.zero, Vector3Int.zero);
|
||||
}
|
||||
#endregion
|
||||
#region RectSerialization
|
||||
|
||||
/// <summary>
|
||||
/// Takes the string result of Rect.ToString() and produces the original Rect. Returns a zero-rect if unable to parse.
|
||||
/// </summary>
|
||||
public static Rect ParseRect(string rectString)
|
||||
{
|
||||
// Remove parentheses and labels from the string
|
||||
rectString = Regex.Replace(rectString, @"[^\d\.\-,]", "");
|
||||
|
||||
string[] components = rectString.Split(',');
|
||||
|
||||
if (components.Length == 4 &&
|
||||
float.TryParse(components[0], out float x) &&
|
||||
float.TryParse(components[1], out float y) &&
|
||||
float.TryParse(components[2], out float width) &&
|
||||
float.TryParse(components[3], out float height))
|
||||
{
|
||||
Rect rect = new Rect(x, y, width, height);
|
||||
return rect;
|
||||
}
|
||||
|
||||
Debug.LogWarning("Failed to parse Rect from string: " + rectString);
|
||||
return new Rect(0, 0, 0, 0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Takes the string result of RectInt.ToString() and produces the original RectInt. Returns a zero-rect if unable to parse.
|
||||
/// </summary>
|
||||
public static RectInt ParseRectInt(string rectString)
|
||||
{
|
||||
// Remove parentheses and labels from the string
|
||||
rectString = Regex.Replace(rectString, @"[^\d\-,]", "");
|
||||
|
||||
string[] components = rectString.Split(',');
|
||||
|
||||
if (components.Length == 4 &&
|
||||
int.TryParse(components[0], out int x) &&
|
||||
int.TryParse(components[1], out int y) &&
|
||||
int.TryParse(components[2], out int width) &&
|
||||
int.TryParse(components[3], out int height))
|
||||
{
|
||||
RectInt rect = new RectInt(x, y, width, height);
|
||||
return rect;
|
||||
}
|
||||
|
||||
Debug.LogWarning("Failed to parse RectInt from string: " + rectString);
|
||||
return new RectInt(0, 0, 0, 0);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Takes the type, encoded as string, and the enum value and produces an Enum of the proper type.
|
||||
/// </summary>
|
||||
public static bool EnumDeserialize(string contentType, string encodedValue, out object enumValue)
|
||||
{
|
||||
Type type = Type.GetType(contentType);
|
||||
if (type != null && type.IsEnum)
|
||||
{
|
||||
if (Enum.TryParse(type, encodedValue, out object enumIntermediateValue))
|
||||
{
|
||||
enumValue = Convert.ChangeType(enumIntermediateValue, type);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
enumValue = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Takes as input a string to be converted to a type, which is used to produce a Deserialized object.
|
||||
/// </summary>
|
||||
public static bool TryDeserializeJSON(string contentType, string json, out object result)
|
||||
{
|
||||
Type type = Type.GetType(contentType);
|
||||
if (type != null)
|
||||
{
|
||||
result = JsonUtility.FromJson(json, type);
|
||||
if (result != null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
result = null;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5514fe3cae16a7e4bb756eb9393ae1fd
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e2431d2b86f83dd47938b9d9cdc4d41e
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3f1c7553ca7b3bb4399eab9e93b9a6ff
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,19 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
[System.Serializable]
|
||||
public class GameData
|
||||
{
|
||||
public long lastUpdated;
|
||||
|
||||
public PlayerData player = new PlayerData();
|
||||
|
||||
public List<ShelfData> shelves = new List<ShelfData>();
|
||||
|
||||
// the values defined in this constructor will be the default values
|
||||
// the game starts with when there's no data to load
|
||||
public GameData()
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 89cbdf7a2dcd8ab40a929072c7493273
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,11 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
[System.Serializable]
|
||||
public class InventoryData
|
||||
{
|
||||
public int capacity = 0;
|
||||
|
||||
public SerializableDictionary<string, int> items = new SerializableDictionary<string, int>();
|
||||
}
|
||||
@@ -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,156 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using System.Linq;
|
||||
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,16 @@
|
||||
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,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9dc07c3462db23d45a4aa37b7d15244c
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,4 @@
|
||||
internal interface IInventoryHolder
|
||||
{
|
||||
public Inventory Inventory { get; }
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 33df6761ae7d94945a747839ae11b55f
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 77e8db4934918fe4eaa57a707ac4d043
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,61 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEditor.Tilemaps;
|
||||
|
||||
[Serializable]
|
||||
public class Inventory : IDataPersistence<InventoryData>
|
||||
{
|
||||
private int capacity = 0;
|
||||
private Dictionary<string, int> items = new Dictionary<string, int>();
|
||||
|
||||
public Inventory(int capacity)
|
||||
{
|
||||
this.capacity = capacity;
|
||||
}
|
||||
|
||||
public IReadOnlyDictionary<string, int> Items => items;
|
||||
|
||||
public int Capacity => capacity;
|
||||
|
||||
public int Load => items.Sum(kvp => kvp.Value);
|
||||
|
||||
public int Free => Capacity < 0 ? int.MaxValue : Capacity - Load;
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
string text = string.Join(Environment.NewLine, items);
|
||||
text += Environment.NewLine + Environment.NewLine + $"Занято {Load}";
|
||||
if (Capacity >= 0)
|
||||
text += $", свободно {Capacity - Load}";
|
||||
|
||||
return text;
|
||||
}
|
||||
|
||||
public int Add(string key, int value, bool allowPartialAdd = false)
|
||||
{
|
||||
if (!allowPartialAdd && (Capacity >= 0) && (Capacity < Load + value))
|
||||
return 0;
|
||||
|
||||
value = Math.Min(value, Free);
|
||||
|
||||
if (items.ContainsKey(key))
|
||||
items[key] += value;
|
||||
else
|
||||
items[key] = value;
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
public void LoadData(InventoryData data)
|
||||
{
|
||||
items = data.items;
|
||||
capacity = data.capacity;
|
||||
}
|
||||
|
||||
public void SaveData(InventoryData data)
|
||||
{
|
||||
data.items = new SerializableDictionary<string, int>(items);
|
||||
data.capacity = capacity;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ce5eba69c9106f64baa1f264b567c861
|
||||
@@ -0,0 +1,51 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.UIElements;
|
||||
|
||||
public class UIToolkitInfoPanel : MonoBehaviour
|
||||
{
|
||||
public Transform targetObject;
|
||||
public Vector3 worldOffset = new Vector3(0, 1f, 0);
|
||||
|
||||
private UIDocument uiDocument;
|
||||
private VisualElement infoPanel;
|
||||
private Label infoLabel;
|
||||
private Camera mainCamera;
|
||||
|
||||
void Start()
|
||||
{
|
||||
uiDocument = GetComponent<UIDocument>();
|
||||
mainCamera = Camera.main;
|
||||
|
||||
if (uiDocument != null)
|
||||
{
|
||||
infoPanel = uiDocument.rootVisualElement.Q<VisualElement>("InfoPanel");
|
||||
infoLabel = uiDocument.rootVisualElement.Q<Label>("InfoLabel");
|
||||
}
|
||||
}
|
||||
|
||||
void Update()
|
||||
{
|
||||
if (targetObject != null && infoPanel != null)
|
||||
{
|
||||
// Convert world position to screen space
|
||||
Vector3 screenPos = mainCamera.WorldToScreenPoint(
|
||||
targetObject.position + worldOffset
|
||||
);
|
||||
|
||||
// Update panel position (UI Toolkit uses top-left origin)
|
||||
infoPanel.style.left = screenPos.x;
|
||||
infoPanel.style.top = Screen.height - screenPos.y;
|
||||
|
||||
// Update content
|
||||
UpdateInfoContent();
|
||||
}
|
||||
}
|
||||
|
||||
void UpdateInfoContent()
|
||||
{
|
||||
if (infoLabel != null)
|
||||
{
|
||||
infoLabel.text = $"{targetObject.name}\nPosition: {targetObject.position}";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: bf6bf11968b160c47b04fbb2d137051e
|
||||
@@ -0,0 +1,36 @@
|
||||
using UnityEngine;
|
||||
|
||||
public class Player : MonoBehaviour, IInventoryHolder, IDataPersistence<GameData>
|
||||
{
|
||||
private Inventory inventory = new Inventory(50);
|
||||
|
||||
public Inventory Inventory => inventory;
|
||||
|
||||
void Awake()
|
||||
{
|
||||
// Add("Лимонад", 5);
|
||||
// Add("Тортик", 22);
|
||||
// Add("Спичка", 100);
|
||||
}
|
||||
|
||||
// private void Add(string key, int value)
|
||||
// {
|
||||
// int added = inventory.inventory.Add(key, value, true);
|
||||
// Debug.Log($"{key}: добавлено {added} из {value}");
|
||||
// if (added < value) {
|
||||
// int dropped = DropToInventory.inventory.Add(key, value - added);
|
||||
// Debug.Log($"{key}: брошено {dropped} из {value - added}");
|
||||
// }
|
||||
// }
|
||||
|
||||
|
||||
public void LoadData(GameData data)
|
||||
{
|
||||
inventory.LoadData(data.player.inventory);
|
||||
}
|
||||
|
||||
public void SaveData(GameData data)
|
||||
{
|
||||
inventory.SaveData(data.player.inventory);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: cffc1654223acc54f9aa045c5a7730a3
|
||||
@@ -0,0 +1,44 @@
|
||||
using UnityEngine;
|
||||
|
||||
public class Shelf : MonoBehaviour, IInventoryHolder, IDataPersistence<ShelfData>
|
||||
{
|
||||
private Inventory inventory = new Inventory(50);
|
||||
|
||||
public Inventory Inventory => inventory;
|
||||
|
||||
public string type = "shelf";
|
||||
|
||||
void Awake()
|
||||
{
|
||||
// Add("Лимонад", 5);
|
||||
// Add("Тортик", 22);
|
||||
// Add("Спичка", 100);
|
||||
}
|
||||
|
||||
// private void Add(string key, int value)
|
||||
// {
|
||||
// int added = inventory.inventory.Add(key, value, true);
|
||||
// Debug.Log($"{key}: добавлено {added} из {value}");
|
||||
// if (added < value) {
|
||||
// int dropped = DropToInventory.inventory.Add(key, value - added);
|
||||
// Debug.Log($"{key}: брошено {dropped} из {value - added}");
|
||||
// }
|
||||
// }
|
||||
|
||||
|
||||
public void LoadData(ShelfData data)
|
||||
{
|
||||
type = data.type;
|
||||
transform.position = new Vector3(data.location.x, data.location.y, 0);
|
||||
transform.rotation = Quaternion.Euler(0, 0, data.angle);
|
||||
inventory.LoadData(data.inventory);
|
||||
}
|
||||
|
||||
public void SaveData(ShelfData data)
|
||||
{
|
||||
data.type = type;
|
||||
data.location = new Vector2(transform.position.x, transform.position.y);
|
||||
data.angle = transform.rotation.z;
|
||||
inventory.SaveData(data.inventory);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 03d5f191219b3d84e99c5f72e0017dc7
|
||||
@@ -0,0 +1,74 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
public class Shelves : MonoBehaviour, IDataPersistence<GameData>
|
||||
{
|
||||
public GameObject ShelfPrefab;
|
||||
|
||||
private List<Shelf> items = new List<Shelf>();
|
||||
|
||||
void Awake()
|
||||
{
|
||||
Clear();
|
||||
}
|
||||
|
||||
public void LoadData(GameData data)
|
||||
{
|
||||
Clear();
|
||||
foreach (ShelfData shelfData in data.shelves)
|
||||
{
|
||||
Add(shelfData);
|
||||
}
|
||||
}
|
||||
|
||||
public void SaveData(GameData data)
|
||||
{
|
||||
data.shelves.Clear();
|
||||
|
||||
foreach (Shelf shelf in GetComponentsInChildren<Shelf>())
|
||||
{
|
||||
ShelfData shelfData = new ShelfData();
|
||||
shelf.SaveData(shelfData);
|
||||
|
||||
data.shelves.Add(shelfData);
|
||||
}
|
||||
}
|
||||
|
||||
public void Add(ShelfData data)
|
||||
{
|
||||
GameObject instance = Instantiate(ShelfPrefab, new Vector3(data.location.x, data.location.y, 0), Quaternion.Euler(0, 0, data.angle), transform);
|
||||
Shelf shelf = instance.GetComponent<Shelf>();
|
||||
shelf.LoadData(data);
|
||||
|
||||
items.Add(shelf);
|
||||
}
|
||||
|
||||
private void Clear()
|
||||
{
|
||||
items.Clear();
|
||||
|
||||
while (transform.childCount > 0)
|
||||
DestroyImmediate(transform.GetChild(0).gameObject);
|
||||
}
|
||||
|
||||
[ContextMenu("Test Initialize")]
|
||||
void TestInitialize()
|
||||
{
|
||||
Clear();
|
||||
|
||||
for (int i = 0; i < UnityEngine.Random.Range(3, 5); i++)
|
||||
{
|
||||
ShelfData shelf = new ShelfData()
|
||||
{
|
||||
type = "random shelf",
|
||||
location = new Vector2(UnityEngine.Random.value * 20 - 10, UnityEngine.Random.value * 10 - 5),
|
||||
angle = 0,
|
||||
};
|
||||
shelf.inventory.capacity = UnityEngine.Random.Range(10, 40);
|
||||
shelf.inventory.items.Add("123", UnityEngine.Random.Range(5, 10));
|
||||
Add(shelf);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9b472ba6d08a54940a9db7abce45bb03
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 74497ae23c27c4d4c89e9c2cee58a0d8
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,62 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.EventSystems;
|
||||
using UnityEngine.UIElements;
|
||||
|
||||
public class InfoBox : MonoBehaviour, IPointerEnterHandler, IPointerExitHandler
|
||||
{
|
||||
// public Vector3 worldOffset = new Vector3(0, 1f, 0);
|
||||
public Vector3 worldOffset = new Vector3(0, 0, 0);
|
||||
|
||||
public UIDocument uiDocument;
|
||||
public bool AlwaysVisible;
|
||||
|
||||
private Camera mainCamera;
|
||||
private IInventoryHolder inventoryHolder;
|
||||
private Label infoBox;
|
||||
|
||||
void Start()
|
||||
{
|
||||
mainCamera = Camera.main;
|
||||
|
||||
if (uiDocument != null)
|
||||
{
|
||||
infoBox = uiDocument.rootVisualElement.Q<Label>("InfoBox");
|
||||
inventoryHolder = GetComponent<IInventoryHolder>();
|
||||
}
|
||||
}
|
||||
|
||||
void Update()
|
||||
{
|
||||
// if (infoBox != null)
|
||||
{
|
||||
// Convert world position to screen space
|
||||
Vector3 screenPos = mainCamera.WorldToScreenPoint(transform.position + worldOffset);
|
||||
|
||||
// Update panel position (UI Toolkit uses top-left origin)
|
||||
infoBox.style.left = screenPos.x;
|
||||
infoBox.style.top = Screen.height - screenPos.y;
|
||||
infoBox.style.visibility = AlwaysVisible ? Visibility.Visible : Visibility.Hidden;
|
||||
|
||||
UpdateText();
|
||||
}
|
||||
}
|
||||
|
||||
void UpdateText()
|
||||
{
|
||||
// if (infoBox != null)
|
||||
{
|
||||
infoBox.text = inventoryHolder.Inventory.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
public void OnPointerEnter(PointerEventData eventData)
|
||||
{
|
||||
infoBox.style.visibility = Visibility.Visible;
|
||||
}
|
||||
|
||||
public void OnPointerExit(PointerEventData eventData)
|
||||
{
|
||||
if (!AlwaysVisible)
|
||||
infoBox.style.visibility = Visibility.Hidden;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 687937cfe3daa04489c5323f36aef2e7
|
||||
Reference in New Issue
Block a user