A game about forced loneliness, made by TACStudios
1using System; 2using System.Linq; 3using UnityEngine; 4using UnityObject = UnityEngine.Object; 5 6namespace Unity.VisualScripting 7{ 8 public static class SavedVariables 9 { 10 #region Storage 11 12 public const string assetPath = "SavedVariables"; 13 14 public const string playerPrefsKey = "LudiqSavedVariables"; 15 16 private static VariablesAsset _asset; 17 18 public static VariablesAsset asset 19 { 20 get 21 { 22 if (_asset == null) 23 { 24 Load(); 25 } 26 27 return _asset; 28 } 29 } 30 31 public static void Load() 32 { 33 _asset = Resources.Load<VariablesAsset>(assetPath) ?? ScriptableObject.CreateInstance<VariablesAsset>(); 34 } 35 36 #endregion 37 38 #region Lifecycle 39 40 public static void OnEnterEditMode() 41 { 42 FetchSavedDeclarations(); 43 DestroyMergedDeclarations(); // Required because assemblies don't reload on play mode exit 44 } 45 46 public static void OnExitEditMode() 47 { 48 SaveDeclarations(saved); 49 } 50 51 internal static void OnEnterPlayMode() 52 { 53 FetchSavedDeclarations(); 54 MergeInitialAndSavedDeclarations(); 55 56 // The variables saver gameobject is only instantiated if its needed 57 // It's only needed if a variable in our merged collection changes, requiring re-serialization as 58 // the runtime ends 59 merged.OnVariableChanged += () => 60 { 61 if (VariablesSaver.instance == null) 62 VariablesSaver.Instantiate(); 63 }; 64 } 65 66 internal static void OnExitPlayMode() 67 { 68 SaveDeclarations(merged); 69 } 70 71 #endregion 72 73 #region Declarations 74 75 public static VariableDeclarations initial => asset.declarations; 76 77 public static VariableDeclarations saved { get; private set; } 78 79 public static VariableDeclarations merged { get; private set; } 80 81 public static VariableDeclarations current => Application.isPlaying ? merged : initial; 82 83 public static void SaveDeclarations(VariableDeclarations declarations) 84 { 85 WarnAndNullifyUnityObjectReferences(declarations); 86 87 try 88 { 89 var data = declarations.Serialize(); 90 91 if (data.objectReferences.Length != 0) 92 { 93 // Hopefully, WarnAndNullify will have prevented this exception, 94 // but in case an object reference was nested as a member of the 95 // serialized objects, it wouldn't have caught it, and thus we need 96 // to abort the save process and inform the user. 97 throw new InvalidOperationException("Cannot use Unity object variable references in saved variables."); 98 } 99 100 PlayerPrefs.SetString(playerPrefsKey, data.json); 101 PlayerPrefs.Save(); 102 } 103 catch (Exception ex) 104 { 105 Debug.LogWarning($"Failed to save variables to player prefs: \n{ex}"); 106 } 107 } 108 109 public static void FetchSavedDeclarations() 110 { 111 if (PlayerPrefs.HasKey(playerPrefsKey)) 112 { 113 try 114 { 115 saved = (VariableDeclarations)new SerializationData(PlayerPrefs.GetString(playerPrefsKey)).Deserialize(); 116 } 117 catch (Exception ex) 118 { 119 Debug.LogWarning($"Failed to fetch saved variables from player prefs: \n{ex}"); 120 saved = new VariableDeclarations(); 121 } 122 } 123 else 124 { 125 saved = new VariableDeclarations(); 126 } 127 } 128 129 private static void MergeInitialAndSavedDeclarations() 130 { 131 merged = initial.CloneViaFakeSerialization(); 132 133 WarnAndNullifyUnityObjectReferences(merged); 134 135 foreach (var name in saved.Select(vd => vd.name)) 136 { 137 if (!merged.IsDefined(name)) 138 { 139 merged[name] = saved[name]; 140 } 141 else if (merged[name] == null) 142 { 143 if (saved[name] == null || saved[name].GetType().IsNullable()) 144 { 145 merged[name] = saved[name]; 146 } 147 else 148 { 149 Debug.LogWarning($"Cannot convert saved player pref '{name}' to null.\n"); 150 } 151 } 152 else 153 { 154 if (saved[name].IsConvertibleTo(merged[name].GetType(), true)) 155 { 156 merged[name] = saved[name]; 157 } 158 else 159 { 160 Debug.LogWarning($"Cannot convert saved player pref '{name}' to expected type ({merged[name].GetType()}).\nReverting to initial value."); 161 } 162 } 163 } 164 } 165 166 private static void DestroyMergedDeclarations() 167 { 168 merged = null; 169 } 170 171 private static void WarnAndNullifyUnityObjectReferences(VariableDeclarations declarations) 172 { 173 Ensure.That(nameof(declarations)).IsNotNull(declarations); 174 175 foreach (var declaration in declarations) 176 { 177 if (declaration.value is UnityObject) 178 { 179 Debug.LogWarning($"Saved variable '{declaration.name}' refers to a Unity object. This is not supported. Its value will be null."); 180 declarations[declaration.name] = null; 181 } 182 } 183 } 184 185 #endregion 186 } 187}