Save System Implementation
Learn how to implement and customize the save/load system for persistent game data.
Save System Overview
The save system preserves all important game data across play sessions:
📊 Player Data
- Character stats and level
- Current health and mana
- Experience points
- Player position and scene
🎒 Inventory Data
- All items and quantities
- Equipped gear
- Gold amount
- Item durability
🎯 Quest Progress
- Active quests
- Completed quests
- Quest objectives progress
- Quest rewards claimed
🌍 World State
- Game flags and variables
- NPC dialogue states
- Discovered locations
- Game settings
Save Data Structure
Core Save Data Class
The main container for all persistent data:
1[System.Serializable]
2"keyword">public "keyword">class GameSaveData
3{
4 [Header("Save Info")]
5 "keyword">public "keyword">string saveName = "New Save";
6 "keyword">public "keyword">string saveDateTime;
7 "keyword">public "keyword">float playTime;
8 "keyword">public "keyword">string gameVersion;
9
10 [Header("Player Data")]
11 "keyword">public PlayerSaveData playerData;
12
13 [Header("Inventory Data")]
14 "keyword">public InventorySaveData inventoryData;
15
16 [Header("Quest Data")]
17 "keyword">public QuestSaveData questData;
18
19 [Header("World Data")]
20 "keyword">public WorldSaveData worldData;
21
22 [Header("Settings")]
23 "keyword">public GameSettings settings;
24
25 "keyword">public GameSaveData()
26 {
27 saveDateTime = System.DateTime.Now.ToString();
28 gameVersion = Application.version;
29 playerData = "keyword">new PlayerSaveData();
30 inventoryData = "keyword">new InventorySaveData();
31 questData = "keyword">new QuestSaveData();
32 worldData = "keyword">new WorldSaveData();
33 settings = "keyword">new GameSettings();
34 }
35}
Player Save Data
Character-specific information:
1[System.Serializable]
2"keyword">public "keyword">class PlayerSaveData
3{
4 [Header("Basic Info")]
5 "keyword">public "keyword">string playerName = "Hero";
6 "keyword">public "keyword">int level = 1;
7 "keyword">public "keyword">int experience = 0;
8
9 [Header("Stats")]
10 "keyword">public "keyword">int currentHealth = 100;
11 "keyword">public "keyword">int maxHealth = 100;
12 "keyword">public "keyword">int currentMana = 50;
13 "keyword">public "keyword">int maxMana = 50;
14
15 [Header("Attributes")]
16 "keyword">public "keyword">int strength = 10;
17 "keyword">public "keyword">int defense = 5;
18 "keyword">public "keyword">int agility = 8;
19 "keyword">public "keyword">int intelligence = 7;
20
21 [Header("Position")]
22 "keyword">public "keyword">string currentScene = "MainScene";
23 "keyword">public "keyword">float positionX = 0f;
24 "keyword">public "keyword">float positionY = 0f;
25 "keyword">public "keyword">float positionZ = 0f;
26
27 [Header("Gameplay")]
28 "keyword">public "keyword">float totalPlayTime = 0f;
29 "keyword">public "keyword">int deathCount = 0;
30 "keyword">public "keyword">int enemiesKilled = 0;
31
32 "keyword">public "keyword">void UpdateFromPlayer(PlayerStats player)
33 {
34 playerName = player.playerName;
35 level = player.level;
36 experience = player.experience;
37 currentHealth = player.health.Value;
38 maxHealth = player.maxHealth.Value;
39 currentMana = player.mana.Value;
40 maxMana = player.maxMana.Value;
41
42 "comment">// Update position
43 var transform = player.transform;
44 positionX = transform.position.x;
45 positionY = transform.position.y;
46 positionZ = transform.position.z;
47 currentScene = UnityEngine.SceneManagement.SceneManager.GetActiveScene().name;
48 }
49
50 "keyword">public "keyword">void ApplyToPlayer(PlayerStats player)
51 {
52 player.playerName = playerName;
53 player.SetLevel(level);
54 player.SetExperience(experience);
55 player.health.SetBaseValue(maxHealth);
56 player.health.SetCurrentValue(currentHealth);
57 player.mana.SetBaseValue(maxMana);
58 player.mana.SetCurrentValue(currentMana);
59
60 "comment">// Apply position (usually done by SaveManager)
61 "keyword">if (currentScene == UnityEngine.SceneManagement.SceneManager.GetActiveScene().name)
62 {
63 player.transform.position = "keyword">new Vector3(positionX, positionY, positionZ);
64 }
65 }
66}
Inventory Save Data
Items and equipment persistence:
1[System.Serializable]
2"keyword">public "keyword">class InventorySaveData
3{
4 [Header("Currency")]
5 "keyword">public "keyword">int goldAmount = 0;
6
7 [Header("Items")]
8 "keyword">public List<ItemSaveData> items = "keyword">new List<ItemSaveData>();
9
10 [Header("Equipment")]
11 "keyword">public EquipmentSaveData equippedItems = "keyword">new EquipmentSaveData();
12
13 "keyword">public "keyword">void UpdateFromInventory(PlayerInventory inventory)
14 {
15 goldAmount = inventory.GetGoldAmount();
16 items.Clear();
17
18 "comment">// Save all inventory items
19 "keyword">for ("keyword">int i = 0; i < inventory.inventorySize; i++)
20 {
21 var slot = inventory.GetSlot(i);
22 "keyword">if (slot != null && !slot.IsEmpty)
23 {
24 items.Add("keyword">new ItemSaveData
25 {
26 itemId = slot.item.name, "comment">// ScriptableObject name
27 quantity = slot.quantity,
28 slotIndex = i,
29 durability = slot.durability
30 });
31 }
32 }
33
34 "comment">// Save equipped items
35 equippedItems.UpdateFromEquipment(inventory.GetEquippedItems());
36 }
37}
38
39[System.Serializable]
40"keyword">public "keyword">class ItemSaveData
41{
42 "keyword">public "keyword">string itemId;
43 "keyword">public "keyword">int quantity;
44 "keyword">public "keyword">int slotIndex;
45 "keyword">public "keyword">float durability = 1f;
46 "keyword">public Dictionary<"keyword">string, object> customData = "keyword">new Dictionary<"keyword">string, object>();
47}
48
49[System.Serializable]
50"keyword">public "keyword">class EquipmentSaveData
51{
52 "keyword">public "keyword">string weaponId = "";
53 "keyword">public "keyword">string shieldId = "";
54 "keyword">public "keyword">string helmetId = "";
55 "keyword">public "keyword">string armorId = "";
56 "keyword">public "keyword">string bootsId = "";
57 "keyword">public "keyword">string ringId = "";
58
59 "keyword">public "keyword">void UpdateFromEquipment(Dictionary<EquipmentSlot, Equipment> equipped)
60 {
61 weaponId = GetEquipmentId(equipped, EquipmentSlot.Weapon);
62 shieldId = GetEquipmentId(equipped, EquipmentSlot.Shield);
63 helmetId = GetEquipmentId(equipped, EquipmentSlot.Helmet);
64 armorId = GetEquipmentId(equipped, EquipmentSlot.Armor);
65 bootsId = GetEquipmentId(equipped, EquipmentSlot.Boots);
66 ringId = GetEquipmentId(equipped, EquipmentSlot.Ring);
67 }
68
69 "keyword">private "keyword">string GetEquipmentId(Dictionary<EquipmentSlot, Equipment> equipped, EquipmentSlot slot)
70 {
71 "keyword">return equipped.ContainsKey(slot) && equipped[slot] != null ? equipped[slot].name : "";
72 }
73}
Save Manager Implementation
Core Save Manager
The central system for handling saves:
1"keyword">public "keyword">class SaveManager : MonoBehaviour
2{
3 "keyword">public "keyword">static SaveManager Instance;
4
5 [Header("Save Settings")]
6 "keyword">public "keyword">string saveFileName = "gamesave";
7 "keyword">public "keyword">string saveFileExtension = ".sav";
8 "keyword">public "keyword">bool encryptSaves = true;
9 "keyword">public "keyword">int maxSaveSlots = 3;
10
11 [Header("Auto Save")]
12 "keyword">public "keyword">bool enableAutoSave = true;
13 "keyword">public "keyword">float autoSaveInterval = 300f; "comment">// 5 minutes
14
15 "keyword">private "keyword">string SaveDirectory => Path.Combine(Application.persistentDataPath, "Saves");
16 "keyword">private "keyword">float lastAutoSaveTime;
17
18 "keyword">void Awake()
19 {
20 "keyword">if (Instance == null)
21 {
22 Instance = "keyword">this;
23 DontDestroyOnLoad(gameObject);
24
25 "comment">// Ensure save directory exists
26 "keyword">if (!Directory.Exists(SaveDirectory))
27 {
28 Directory.CreateDirectory(SaveDirectory);
29 }
30 }
31 "keyword">else
32 {
33 Destroy(gameObject);
34 }
35 }
36
37 "keyword">void Update()
38 {
39 "comment">// Handle auto-save
40 "keyword">if (enableAutoSave && Time.time - lastAutoSaveTime > autoSaveInterval)
41 {
42 "keyword">if (GameManager.Instance.currentState == GameState.Playing)
43 {
44 AutoSave();
45 lastAutoSaveTime = Time.time;
46 }
47 }
48 }
49
50 "keyword">public "keyword">void SaveGame("keyword">int slotIndex = 0, "keyword">string customName = "")
51 {
52 try
53 {
54 "comment">// Create save data
55 GameSaveData saveData = CreateSaveData();
56
57 "keyword">if (!"keyword">string.IsNullOrEmpty(customName))
58 {
59 saveData.saveName = customName;
60 }
61
62 "comment">// Serialize to JSON
63 "keyword">string jsonData = JsonUtility.ToJson(saveData, true);
64
65 "comment">// Encrypt "keyword">if enabled
66 "keyword">if (encryptSaves)
67 {
68 jsonData = EncryptString(jsonData);
69 }
70
71 "comment">// Write to file
72 "keyword">string filePath = GetSaveFilePath(slotIndex);
73 File.WriteAllText(filePath, jsonData);
74
75 Debug.Log($"Game saved to slot {slotIndex}: {filePath}");
76
77 "comment">// Show save confirmation
78 UIManager.Instance?.ShowNotification($"Game saved to slot {slotIndex + 1}");
79
80 "comment">// Trigger save event
81 OnGameSaved?.Invoke(slotIndex, saveData);
82 }
83 catch (System.Exception e)
84 {
85 Debug.LogError($"Failed to save game: {e.Message}");
86 UIManager.Instance?.ShowNotification("Save failed!");
87 }
88 }
89
90 "keyword">public "keyword">bool LoadGame("keyword">int slotIndex = 0)
91 {
92 "keyword">string filePath = GetSaveFilePath(slotIndex);
93
94 "keyword">if (!File.Exists(filePath))
95 {
96 Debug.LogWarning($"Save file not found: {filePath}");
97 "keyword">return false;
98 }
99
100 "comment">// Read file
101 "keyword">string jsonData = File.ReadAllText(filePath);
102
103 "comment">// Decrypt "keyword">if needed
104 "keyword">if (encryptSaves)
105 {
106 jsonData = DecryptString(jsonData);
107 }
108
109 "comment">// Deserialize
110 GameSaveData saveData = JsonUtility.FromJson<GameSaveData>(jsonData);
111
112 "keyword">if (saveData == null)
113 {
114 Debug.LogError("Failed to deserialize save data");
115 "keyword">return false;
116 }
117
118 "comment">// Apply save data
119 ApplySaveData(saveData);
120
121 Debug.Log($"Game loaded from slot {slotIndex}");
122
123 "comment">// Show load confirmation
124 UIManager.Instance?.ShowNotification($"Game loaded from slot {slotIndex + 1}");
125
126 "comment">// Trigger load event
127 OnGameLoaded?.Invoke(slotIndex, saveData);
128
129 "keyword">return true;
130 }
131 catch (System.Exception e)
132 {
133 Debug.LogError($"Failed to load game: {e.Message}");
134 UIManager.Instance?.ShowNotification("Load failed!");
135 "keyword">return false;
136 }
137 }
138
139 "keyword">private GameSaveData CreateSaveData()
140 {
141 GameSaveData saveData = "keyword">new GameSaveData();
142
143 "comment">// Player data
144 var playerStats = GameManager.Instance.playerStats;
145 "keyword">if (playerStats != null)
146 {
147 saveData.playerData.UpdateFromPlayer(playerStats);
148 }
149
150 "comment">// Inventory data
151 var playerInventory = FindObjectOfType<PlayerInventory>();
152 "keyword">if (playerInventory != null)
153 {
154 saveData.inventoryData.UpdateFromInventory(playerInventory);
155 }
156
157 "comment">// Quest data
158 "keyword">if (QuestManager.Instance != null)
159 {
160 saveData.questData.UpdateFromQuestManager(QuestManager.Instance);
161 }
162
163 "comment">// World data
164 saveData.worldData.UpdateFromGameManager(GameManager.Instance);
165
166 "comment">// Update play time
167 saveData.playTime += Time.realtimeSinceStartup;
168
169 "keyword">return saveData;
170 }
171
172 "keyword">private "keyword">void ApplySaveData(GameSaveData saveData)
173 {
174 "comment">// Apply player data
175 var playerStats = GameManager.Instance.playerStats;
176 "keyword">if (playerStats != null)
177 {
178 saveData.playerData.ApplyToPlayer(playerStats);
179 }
180
181 "comment">// Apply inventory data
182 var playerInventory = FindObjectOfType<PlayerInventory>();
183 "keyword">if (playerInventory != null)
184 {
185 saveData.inventoryData.ApplyToInventory(playerInventory);
186 }
187
188 "comment">// Apply quest data
189 "keyword">if (QuestManager.Instance != null)
190 {
191 saveData.questData.ApplyToQuestManager(QuestManager.Instance);
192 }
193
194 "comment">// Apply world data
195 saveData.worldData.ApplyToGameManager(GameManager.Instance);
196
197 "comment">// Load correct scene "keyword">if different
198 "keyword">if (saveData.playerData.currentScene != UnityEngine.SceneManagement.SceneManager.GetActiveScene().name)
199 {
200 GameManager.Instance.LoadScene(saveData.playerData.currentScene);
201 }
202 }
203
204 "comment">// Events
205 "keyword">public "keyword">static event System.Action<"keyword">int, GameSaveData> OnGameSaved;
206 "keyword">public "keyword">static event System.Action<"keyword">int, GameSaveData> OnGameLoaded;
207
208 "comment">// Utility methods
209 "keyword">private "keyword">string GetSaveFilePath("keyword">int slotIndex)
210 {
211 "keyword">return Path.Combine(SaveDirectory, $"{saveFileName}_{slotIndex}{saveFileExtension}");
212 }
213
214 "keyword">public "keyword">bool SaveExists("keyword">int slotIndex)
215 {
216 "keyword">return File.Exists(GetSaveFilePath(slotIndex));
217 }
218
219 "keyword">public GameSaveData GetSaveInfo("keyword">int slotIndex)
220 {
221 "keyword">if (!SaveExists(slotIndex)) "keyword">return null;
222
223 try
224 {
225 "keyword">string filePath = GetSaveFilePath(slotIndex);
226 "keyword">string jsonData = File.ReadAllText(filePath);
227
228 "keyword">if (encryptSaves)
229 {
230 jsonData = DecryptString(jsonData);
231 }
232
233 "keyword">return JsonUtility.FromJson<GameSaveData>(jsonData);
234 }
235 catch
236 {
237 "keyword">return null;
238 }
239 }
240
241 "keyword">public "keyword">void DeleteSave("keyword">int slotIndex)
242 {
243 "keyword">string filePath = GetSaveFilePath(slotIndex);
244 "keyword">if (File.Exists(filePath))
245 {
246 File.Delete(filePath);
247 Debug.Log($"Save slot {slotIndex} deleted");
248 }
249 }
250
251 "keyword">public "keyword">void AutoSave()
252 {
253 SaveGame(0, "Auto Save");
254 Debug.Log("Auto save completed");
255 }
256}
Save/Load UI Implementation
Create user interface for save management:
1"keyword">public "keyword">class SaveLoadUI : MonoBehaviour
2{
3 [Header("UI References")]
4 "keyword">public GameObject saveLoadPanel;
5 "keyword">public Transform saveSlotParent;
6 "keyword">public GameObject saveSlotPrefab;
7 "keyword">public Button saveButton;
8 "keyword">public Button loadButton;
9 "keyword">public Button deleteButton;
10 "keyword">public InputField saveNameInput;
11
12 [Header("Save Slot Display")]
13 "keyword">public Text selectedSlotInfo;
14
15 "keyword">private List<SaveSlotUI> saveSlots = "keyword">new List<SaveSlotUI>();
16 "keyword">private "keyword">int selectedSlotIndex = -1;
17
18 "keyword">void Start()
19 {
20 CreateSaveSlots();
21 UpdateUI();
22
23 "comment">// Setup button listeners
24 saveButton.onClick.AddListener(SaveToSelectedSlot);
25 loadButton.onClick.AddListener(LoadFromSelectedSlot);
26 deleteButton.onClick.AddListener(DeleteSelectedSlot);
27 }
28
29 "keyword">void CreateSaveSlots()
30 {
31 "comment">// Create UI slots "keyword">for each save slot
32 "keyword">for ("keyword">int i = 0; i < SaveManager.Instance.maxSaveSlots; i++)
33 {
34 GameObject slotObj = Instantiate(saveSlotPrefab, saveSlotParent);
35 SaveSlotUI slotUI = slotObj.GetComponent<SaveSlotUI>();
36
37 slotUI.Initialize(i, "keyword">this);
38 saveSlots.Add(slotUI);
39 }
40 }
41
42 "keyword">public "keyword">void SelectSlot("keyword">int slotIndex)
43 {
44 selectedSlotIndex = slotIndex;
45
46 "comment">// Update slot visuals
47 "keyword">foreach (var slot in saveSlots)
48 {
49 slot.SetSelected(slot.SlotIndex == slotIndex);
50 }
51
52 UpdateUI();
53 }
54
55 "keyword">void UpdateUI()
56 {
57 "keyword">bool hasSelection = selectedSlotIndex >= 0;
58 "keyword">bool saveExists = hasSelection && SaveManager.Instance.SaveExists(selectedSlotIndex);
59
60 "comment">// Update buttons
61 saveButton.interactable = hasSelection;
62 loadButton.interactable = saveExists;
63 deleteButton.interactable = saveExists;
64
65 "comment">// Update slot info
66 "keyword">if (hasSelection)
67 {
68 "keyword">if (saveExists)
69 {
70 var saveData = SaveManager.Instance.GetSaveInfo(selectedSlotIndex);
71 "keyword">if (saveData != null)
72 {
73 selectedSlotInfo.text = $"Save: {saveData.saveName}\n" +
74 $"Level: {saveData.playerData.level}\n" +
75 $"Play Time: {FormatTime(saveData.playTime)}\n" +
76 $"Location: {saveData.playerData.currentScene}\n" +
77 $"Saved: {saveData.saveDateTime}";
78 }
79 }
80 "keyword">else
81 {
82 selectedSlotInfo.text = $"Slot {selectedSlotIndex + 1}\nEmpty";
83 }
84 }
85 "keyword">else
86 {
87 selectedSlotInfo.text = "No slot selected";
88 }
89
90 "comment">// Update save slots
91 "keyword">foreach (var slot in saveSlots)
92 {
93 slot.UpdateDisplay();
94 }
95 }
96
97 "keyword">void SaveToSelectedSlot()
98 {
99 "keyword">if (selectedSlotIndex < 0) "keyword">return;
100
101 "keyword">string saveName = "keyword">string.IsNullOrEmpty(saveNameInput.text) ?
102 $"Save {selectedSlotIndex + 1}" : saveNameInput.text;
103
104 SaveManager.Instance.SaveGame(selectedSlotIndex, saveName);
105 UpdateUI();
106 }
107
108 "keyword">void LoadFromSelectedSlot()
109 {
110 "keyword">if (selectedSlotIndex < 0 || !SaveManager.Instance.SaveExists(selectedSlotIndex))
111 "keyword">return;
112
113 "keyword">if (SaveManager.Instance.LoadGame(selectedSlotIndex))
114 {
115 "comment">// Close save/load UI
116 saveLoadPanel.SetActive(false);
117
118 "comment">// Resume game
119 GameManager.Instance.ChangeGameState(GameState.Playing);
120 }
121 }
122
123 "keyword">void DeleteSelectedSlot()
124 {
125 "keyword">if (selectedSlotIndex < 0) "keyword">return;
126
127 "comment">// Show confirmation dialog
128 ConfirmationDialog.Instance.ShowDialog(
129 "Delete Save",
130 $"Are you sure you want to delete ">this save?",
131 () => {
132 SaveManager.Instance.DeleteSave(selectedSlotIndex);
133 UpdateUI();
134 }
135 );
136 }
137
138 "keyword">private "keyword">string FormatTime("keyword">float totalSeconds)
139 {
140 "keyword">int hours = Mathf.FloorToInt(totalSeconds / 3600f);
141 "keyword">int minutes = Mathf.FloorToInt((totalSeconds % 3600f) / 60f);
142 "keyword">return $"{hours:00}:{minutes:00}";
143 }
144}
145
146"comment">// Individual save slot UI component
147"keyword">public "keyword">class SaveSlotUI : MonoBehaviour
148{
149 [Header("UI References")]
150 "keyword">public Text slotNumber;
151 "keyword">public Text saveName;
152 "keyword">public Text saveInfo;
153 "keyword">public Text saveDate;
154 "keyword">public Image thumbnail;
155 "keyword">public Button slotButton;
156 "keyword">public GameObject selectedIndicator;
157
158 "keyword">public "keyword">int SlotIndex { get; "keyword">private set; }
159 "keyword">private SaveLoadUI parentUI;
160
161 "keyword">public "keyword">void Initialize("keyword">int index, SaveLoadUI parent)
162 {
163 SlotIndex = index;
164 parentUI = parent;
165
166 slotButton.onClick.AddListener(() => parentUI.SelectSlot(SlotIndex));
167 slotNumber.text = $"Slot {index + 1}";
168
169 UpdateDisplay();
170 }
171
172 "keyword">public "keyword">void UpdateDisplay()
173 {
174 "keyword">bool saveExists = SaveManager.Instance.SaveExists(SlotIndex);
175
176 "keyword">if (saveExists)
177 {
178 var saveData = SaveManager.Instance.GetSaveInfo(SlotIndex);
179 "keyword">if (saveData != null)
180 {
181 saveName.text = saveData.saveName;
182 saveInfo.text = $"Level {saveData.playerData.level} • {FormatTime(saveData.playTime)}";
183 saveDate.text = saveData.saveDateTime;
184
185 "comment">// You could add screenshot thumbnails here
186 thumbnail.color = Color.white;
187 }
188 }
189 "keyword">else
190 {
191 saveName.text = "Empty Slot";
192 saveInfo.text = "";
193 saveDate.text = "";
194 thumbnail.color = Color.gray;
195 }
196 }
197
198 "keyword">public "keyword">void SetSelected("keyword">bool selected)
199 {
200 selectedIndicator.SetActive(selected);
201 }
202
203 "keyword">private "keyword">string FormatTime("keyword">float totalSeconds)
204 {
205 "keyword">int hours = Mathf.FloorToInt(totalSeconds / 3600f);
206 "keyword">int minutes = Mathf.FloorToInt((totalSeconds % 3600f) / 60f);
207 "keyword">return $"{hours:00}:{minutes:00}";
208 }
209}
Custom Save Data
Adding Custom Systems
Extend the save system for your custom components:
1"comment">// Interface "keyword">for saveable components
2"keyword">public "keyword">interface ISaveable
3{
4 "keyword">string GetSaveId();
5 object GetSaveData();
6 "keyword">void LoadSaveData(object data);
7}
8
9"comment">// Example: Custom building system
10"keyword">public "keyword">class BuildingSystem : MonoBehaviour, ISaveable
11{
12 [System.Serializable]
13 "keyword">public "keyword">class BuildingSaveData
14 {
15 "keyword">public List<BuildingData> buildings = "keyword">new List<BuildingData>();
16 "keyword">public "keyword">int totalBuildingsBuilt = 0;
17 "keyword">public "keyword">float totalResourcesSpent = 0f;
18 }
19
20 [System.Serializable]
21 "keyword">public "keyword">class BuildingData
22 {
23 "keyword">public "keyword">string buildingType;
24 "keyword">public "keyword">float posX, posY, posZ;
25 "keyword">public "keyword">int level;
26 "keyword">public "keyword">bool isCompleted;
27 }
28
29 "keyword">private List<Building> buildings = "keyword">new List<Building>();
30
31 "keyword">public "keyword">string GetSaveId()
32 {
33 "keyword">return "BuildingSystem";
34 }
35
36 "keyword">public object GetSaveData()
37 {
38 BuildingSaveData saveData = "keyword">new BuildingSaveData();
39
40 "keyword">foreach (var building in buildings)
41 {
42 saveData.buildings.Add("keyword">new BuildingData
43 {
44 buildingType = building.buildingType,
45 posX = building.transform.position.x,
46 posY = building.transform.position.y,
47 posZ = building.transform.position.z,
48 level = building.level,
49 isCompleted = building.isCompleted
50 });
51 }
52
53 saveData.totalBuildingsBuilt = buildings.Count;
54 "keyword">return saveData;
55 }
56
57 "keyword">public "keyword">void LoadSaveData(object data)
58 {
59 "keyword">if (data is BuildingSaveData saveData)
60 {
61 "comment">// Clear existing buildings
62 "keyword">foreach (var building in buildings)
63 {
64 "keyword">if (building != null) DestroyImmediate(building.gameObject);
65 }
66 buildings.Clear();
67
68 "comment">// Recreate buildings from save data
69 "keyword">foreach (var buildingData in saveData.buildings)
70 {
71 Vector3 position = "keyword">new Vector3(buildingData.posX, buildingData.posY, buildingData.posZ);
72 Building building = CreateBuilding(buildingData.buildingType, position);
73 building.level = buildingData.level;
74 building.isCompleted = buildingData.isCompleted;
75 buildings.Add(building);
76 }
77 }
78 }
79}
80
81"comment">// Enhanced Save Manager with custom saveables
82"keyword">public partial "keyword">class SaveManager
83{
84 "keyword">private List<ISaveable> saveableComponents = "keyword">new List<ISaveable>();
85
86 "keyword">void Start()
87 {
88 "comment">// Find all saveable components
89 RegisterSaveables();
90 }
91
92 "keyword">void RegisterSaveables()
93 {
94 saveableComponents.Clear();
95
96 "comment">// Find all ISaveable components in the scene
97 ISaveable[] saveables = FindObjectsOfType<MonoBehaviour>().OfType<ISaveable>().ToArray();
98 saveableComponents.AddRange(saveables);
99
100 Debug.Log($"Registered {saveableComponents.Count} saveable components");
101 }
102
103 "keyword">private GameSaveData CreateSaveData()
104 {
105 GameSaveData saveData = "keyword">new GameSaveData();
106
107 "comment">// ... existing save data creation ...
108
109 "comment">// Add custom component data
110 saveData.customData = "keyword">new Dictionary<"keyword">string, object>();
111
112 "keyword">foreach (var saveable in saveableComponents)
113 {
114 "keyword">if (saveable != null)
115 {
116 try
117 {
118 "keyword">string saveId = saveable.GetSaveId();
119 object data = saveable.GetSaveData();
120 saveData.customData[saveId] = data;
121 }
122 catch (System.Exception e)
123 {
124 Debug.LogError($"Failed to get save data from {saveable.GetSaveId()}: {e.Message}");
125 }
126 }
127 }
128
129 "keyword">return saveData;
130 }
131
132 "keyword">private "keyword">void ApplySaveData(GameSaveData saveData)
133 {
134 "comment">// ... existing save data application ...
135
136 "comment">// Load custom component data
137 "keyword">if (saveData.customData != null)
138 {
139 "keyword">foreach (var saveable in saveableComponents)
140 {
141 "keyword">if (saveable != null)
142 {
143 try
144 {
145 "keyword">string saveId = saveable.GetSaveId();
146 "keyword">if (saveData.customData.ContainsKey(saveId))
147 {
148 saveable.LoadSaveData(saveData.customData[saveId]);
149 }
150 }
151 catch (System.Exception e)
152 {
153 Debug.LogError($"Failed to load save data ">for {saveable.GetSaveId()}: {e.Message}");
154 }
155 }
156 }
157 }
158 }
159}
Save Security & Encryption
Basic Encryption
Protect saves from casual modification:
1"keyword">using System.Security.Cryptography;
2"keyword">using System.Text;
3
4"keyword">public partial "keyword">class SaveManager
5{
6 "keyword">private readonly "keyword">string encryptionKey = "YourGameSpecificKey2024"; "comment">// Change "keyword">this!
7
8 "keyword">private "keyword">string EncryptString("keyword">string plainText)
9 {
10 byte[] iv = "keyword">new byte[16];
11 byte[] array;
12
13 "keyword">using (Aes aes = Aes.Create())
14 {
15 aes.Key = Encoding.UTF8.GetBytes(encryptionKey);
16 aes.IV = iv;
17
18 ICryptoTransform encryptor = aes.CreateEncryptor(aes.Key, aes.IV);
19
20 "keyword">using (MemoryStream memoryStream = "keyword">new MemoryStream())
21 {
22 "keyword">using (CryptoStream cryptoStream = "keyword">new CryptoStream(memoryStream, encryptor, CryptoStreamMode.Write))
23 {
24 "keyword">using (StreamWriter streamWriter = "keyword">new StreamWriter(cryptoStream))
25 {
26 streamWriter.Write(plainText);
27 }
28 array = memoryStream.ToArray();
29 }
30 }
31 }
32
33 "keyword">return System.Convert.ToBase64String(array);
34 }
35
36 "keyword">private "keyword">string DecryptString("keyword">string cipherText)
37 {
38 byte[] iv = "keyword">new byte[16];
39 byte[] buffer = System.Convert.FromBase64String(cipherText);
40
41 "keyword">using (Aes aes = Aes.Create())
42 {
43 aes.Key = Encoding.UTF8.GetBytes(encryptionKey);
44 aes.IV = iv;
45
46 ICryptoTransform decryptor = aes.CreateDecryptor(aes.Key, aes.IV);
47
48 "keyword">using (MemoryStream memoryStream = "keyword">new MemoryStream(buffer))
49 {
50 "keyword">using (CryptoStream cryptoStream = "keyword">new CryptoStream(memoryStream, decryptor, CryptoStreamMode.Read))
51 {
52 "keyword">using (StreamReader streamReader = "keyword">new StreamReader(cryptoStream))
53 {
54 "keyword">return streamReader.ReadToEnd();
55 }
56 }
57 }
58 }
59 }
60
61 "comment">// Checksum validation
62 "keyword">private "keyword">string CalculateChecksum("keyword">string data)
63 {
64 "keyword">using (SHA256 sha256Hash = SHA256.Create())
65 {
66 byte[] bytes = sha256Hash.ComputeHash(Encoding.UTF8.GetBytes(data + encryptionKey));
67 "keyword">return System.Convert.ToBase64String(bytes);
68 }
69 }
70
71 "keyword">private "keyword">bool ValidateChecksum("keyword">string data, "keyword">string checksum)
72 {
73 "keyword">string calculatedChecksum = CalculateChecksum(data);
74 "keyword">return calculatedChecksum == checksum;
75 }
76}
Save Validation
Detect corrupted or tampered saves:
1"comment">// Enhanced save data with validation
2[System.Serializable]
3"keyword">public "keyword">class ValidatedSaveData
4{
5 "keyword">public GameSaveData gameData;
6 "keyword">public "keyword">string checksum;
7 "keyword">public "keyword">string version;
8 "keyword">public long timestamp;
9
10 "keyword">public ValidatedSaveData(GameSaveData data)
11 {
12 gameData = data;
13 version = Application.version;
14 timestamp = System.DateTimeOffset.UtcNow.ToUnixTimeSeconds();
15
16 "comment">// Calculate checksum of the game data
17 "keyword">string jsonData = JsonUtility.ToJson(gameData);
18 checksum = SaveManager.Instance.CalculateChecksum(jsonData);
19 }
20
21 "keyword">public "keyword">bool IsValid()
22 {
23 "keyword">if (gameData == null) "keyword">return false;
24
25 "keyword">string jsonData = JsonUtility.ToJson(gameData);
26 "keyword">return SaveManager.Instance.ValidateChecksum(jsonData, checksum);
27 }
28
29 "keyword">public "keyword">bool IsCompatibleVersion()
30 {
31 "comment">// Add version compatibility logic here
32 "keyword">return version == Application.version;
33 }
34}
35
36"comment">// Save with validation
37"keyword">public "keyword">void SaveGameSecure("keyword">int slotIndex = 0, "keyword">string customName = "")
38{
39 try
40 {
41 GameSaveData gameData = CreateSaveData();
42 ValidatedSaveData saveData = "keyword">new ValidatedSaveData(gameData);
43
44 "keyword">if (!"keyword">string.IsNullOrEmpty(customName))
45 {
46 saveData.gameData.saveName = customName;
47 }
48
49 "keyword">string jsonData = JsonUtility.ToJson(saveData, true);
50
51 "keyword">if (encryptSaves)
52 {
53 jsonData = EncryptString(jsonData);
54 }
55
56 "keyword">string filePath = GetSaveFilePath(slotIndex);
57 File.WriteAllText(filePath, jsonData);
58
59 Debug.Log($"Secure save completed: {filePath}");
60 }
61 catch (System.Exception e)
62 {
63 Debug.LogError($"Secure save failed: {e.Message}");
64 }
65}
66
67"keyword">public "keyword">bool LoadGameSecure("keyword">int slotIndex = 0)
68{
69 try
70 {
71 "keyword">string filePath = GetSaveFilePath(slotIndex);
72
73 "keyword">if (!File.Exists(filePath))
74 {
75 Debug.LogWarning("Save file not found");
76 "keyword">return false;
77 }
78
79 "keyword">string jsonData = File.ReadAllText(filePath);
80
81 "keyword">if (encryptSaves)
82 {
83 jsonData = DecryptString(jsonData);
84 }
85
86 ValidatedSaveData saveData = JsonUtility.FromJson<ValidatedSaveData>(jsonData);
87
88 "keyword">if (saveData == null)
89 {
90 Debug.LogError("Failed to parse save data");
91 "keyword">return false;
92 }
93
94 "keyword">if (!saveData.IsValid())
95 {
96 Debug.LogError("Save data validation failed - file may be corrupted");
97 UIManager.Instance?.ShowNotification("Save file corrupted!");
98 "keyword">return false;
99 }
100
101 "keyword">if (!saveData.IsCompatibleVersion())
102 {
103 Debug.LogWarning($"Save version mismatch: {saveData.version} vs {Application.version}");
104 "comment">// You could show a dialog asking "keyword">if user wants to try loading anyway
105 }
106
107 ApplySaveData(saveData.gameData);
108 "keyword">return true;
109 }
110 catch (System.Exception e)
111 {
112 Debug.LogError($"Secure load failed: {e.Message}");
113 "keyword">return false;
114 }
115}
Save System Best Practices
🔄 Regular Auto-Save
Implement auto-save at key moments like level completion, major progress, or time intervals
🚨 Error Handling
Always handle save/load failures gracefully and provide user feedback
📱 Platform Considerations
Test saves on all target platforms - mobile may have different restrictions
🔒 Security Balance
Use encryption for competitive games, but remember it's not foolproof
📊 Version Management
Plan for save compatibility when updating your game
🎮 User Experience
Provide clear feedback about save status and multiple save slots