A comprehensive, modular save/load system for Unity games with support for multiple save slots, automatic screenshots, and flexible data management - similar to Minecraft's world system.
- Multiple Save Slots: Create, load, delete, and duplicate game saves
- Automatic Screenshots: Capture and display save thumbnails
- Flexible Data Structure: Modular data packs for different game systems
- Async Operations: Non-blocking save/load operations
- Metadata Management: Rich save information with play time, timestamps, and custom data
- Error Handling: Robust error handling with fallback to defaults
- Priority-Based Loading: Load data packs in specific order
- Selective Saving: Save individual data packs or all at once
- Data Validation: Version compatibility checking
- Auto-Save: Automatic saving on application pause/focus loss
- Memory Management: Efficient resource cleanup
Copy all the provided scripts into your Unity project:
MultiSaveManager.cs
SaveLoadUI.cs
EnhancedSaveManager.cs
- Core interfaces and classes
// Create your data structures
[Serializable]
public class PlayerData
{
public int Level = 1;
public int Experience = 0;
public int Gold = 100;
public Vector3 Position = Vector3.zero;
}
// Create data packs
public class PlayerDataPack : DataPack<PlayerData>
{
public override string FileName => "player.json";
public override int Priority => 1; // Load first
}
- Create an empty GameObject and attach
MultiSaveManager
script - Set up your UI with the provided
SaveLoadUI
script - Configure screenshot settings if using
EnhancedSaveManager
public class GameManager : MonoBehaviour
{
private MultiSaveManager saveManager;
void Start()
{
saveManager = FindObjectOfType<MultiSaveManager>();
// Create a new save
await saveManager.CreateNewSave("My Adventure", "Starting my journey!");
// Load an existing save
await saveManager.LoadGameById("20240712_143022_1234");
// Access data
var playerData = saveManager.PlayerData;
playerData.Level = 5;
// Save changes
await saveManager.SaveCurrentGame();
}
}
MultiSaveManager
├── IDataOperationService (DataOperationService)
├── IDataService (FileDataService)
├── IDataPack (PlayerDataPack, WorldDataPack, etc.)
└── GameSaveInfo (Metadata management)
- Save Creation: Generate unique ID → Create directory → Initialize data packs
- Data Loading: Load metadata → Initialize data service → Load data packs by priority
- Data Saving: Update metadata → Save data packs → Capture screenshot
- Save Management: List saves → Handle duplicates → Delete saves
MyGameSaves/
├── 20240712_143022_1234/
│ ├── metadata.json
│ ├── screenshot.jpg
│ ├── player.json
│ ├── world.json
│ └── settings.json
├── 20240712_150045_5678/
│ ├── metadata.json
│ ├── screenshot.jpg
│ └── ...
{
"SaveId": "20240712_143022_1234",
"SaveName": "My Adventure",
"Description": "Starting my journey!",
"CreatedDate": "2024-07-12T14:30:22.123Z",
"LastPlayedDate": "2024-07-12T15:45:30.456Z",
"PlayTime": "01:15:08",
"Version": "1.0.0",
"CustomMetadata": {
"PlayerLevel": 5,
"QuestsCompleted": 3,
"CurrentScene": "Forest_Level_1"
}
}
[Serializable]
public class InventoryData
{
public List<ItemData> Items = new List<ItemData>();
public int MaxSlots = 20;
public Dictionary<string, int> ItemCounts = new Dictionary<string, int>();
}
public class InventoryDataPack : DataPack<InventoryData>
{
public override string FileName => "inventory.json";
public override int Priority => 2; // Load after player data
protected override void OnDataLoaded()
{
// Custom logic after loading
Debug.Log($"Loaded {Data.Items.Count} items");
}
protected override void OnDataReset()
{
// Custom logic when resetting to defaults
Data.Items.Clear();
Data.ItemCounts.Clear();
}
}
public class DataOperationService : IDataOperationService
{
public DataOperationService(IDataService dataService)
{
// ... existing code ...
// Register your new data pack
RegisterDataPack(new InventoryDataPack());
}
}
- Create a Canvas with the following structure:
Canvas
├── SaveLoadPanel
│ ├── SaveSlotContainer (Vertical Layout Group)
│ ├── CreateNewSaveButton
│ └── ClosePanelButton
├── NewSaveDialog
│ ├── SaveNameInput
│ ├── SaveDescriptionInput
│ ├── ConfirmCreateButton
│ └── CancelCreateButton
└── DeleteConfirmDialog
├── ConfirmDeleteButton
└── CancelDeleteButton
- Create a SaveSlot prefab with:
SaveSlotPrefab
├── SaveNameText
├── SaveDescriptionText
├── LastPlayedText
├── PlayTimeText
├── ScreenshotImage
├── LoadButton
├── DeleteButton
├── DuplicateButton
└── CurrentSaveIndicator
public class PlayerUI : MonoBehaviour
{
private MultiSaveManager saveManager;
void Start()
{
saveManager = FindObjectOfType<MultiSaveManager>();
// Subscribe to save events
saveManager.OnSaveLoaded += OnSaveLoaded;
UpdatePlayerInfo();
}
private void OnSaveLoaded(GameSaveInfo saveInfo)
{
UpdatePlayerInfo();
}
private void UpdatePlayerInfo()
{
var playerData = saveManager.PlayerData;
if (playerData != null)
{
// Update UI elements
levelText.text = $"Level: {playerData.Level}";
goldText.text = $"Gold: {playerData.Gold}";
}
}
}
public class ScreenshotManager : MonoBehaviour
{
[SerializeField] private Camera screenshotCamera;
[SerializeField] private Image saveSlotImage;
public void DisplaySaveScreenshot(string saveId)
{
var saveManager = FindObjectOfType<EnhancedSaveManager>();
Texture2D screenshot = saveManager.GetSaveScreenshot(saveId);
if (screenshot != null)
{
Sprite sprite = Sprite.Create(screenshot,
new Rect(0, 0, screenshot.width, screenshot.height),
Vector2.one * 0.5f);
saveSlotImage.sprite = sprite;
}
}
}
public class GameProgressTracker : MonoBehaviour
{
private MultiSaveManager saveManager;
void Start()
{
saveManager = FindObjectOfType<MultiSaveManager>();
}
public void TrackBossDefeated(string bossName)
{
// Add custom metadata
saveManager.AddCustomMetadata($"Boss_{bossName}_Defeated", true);
saveManager.AddCustomMetadata($"Boss_{bossName}_DefeatTime", DateTime.Now);
// Save immediately
saveManager.SaveCurrentGame();
}
public bool IsBossDefeated(string bossName)
{
return saveManager.GetCustomMetadata<bool>($"Boss_{bossName}_Defeated");
}
}
public class SaveValidator : MonoBehaviour
{
public bool ValidateSave(GameSaveInfo saveInfo)
{
// Check version compatibility
if (saveInfo.CustomMetadata.ContainsKey("GameVersion"))
{
string saveVersion = saveInfo.CustomMetadata["GameVersion"].ToString();
if (!IsVersionCompatible(saveVersion))
{
Debug.LogWarning($"Save version {saveVersion} may not be compatible");
return false;
}
}
// Check required data
if (!saveInfo.CustomMetadata.ContainsKey("PlayerLevel"))
{
Debug.LogError("Save missing required player data");
return false;
}
return true;
}
private bool IsVersionCompatible(string saveVersion)
{
// Implement your version compatibility logic
Version save = new Version(saveVersion);
Version current = new Version(Application.version);
return save.Major == current.Major;
}
}
- Use Async Operations: Always prefer async save/load operations
- Batch Operations: Group multiple save operations together
- Lazy Loading: Only load data when needed
- Memory Management: Properly dispose of resources
// Good - Async operation
await saveManager.SaveGameAsync();
// Good - Batch save specific packs
saveManager.Save<PlayerDataPack>();
saveManager.Save<WorldDataPack>();
// Avoid - Synchronous operations in main thread
saveManager.SaveCurrentGameSync(); // Only use in OnApplicationPause
public async Task<bool> SafeLoadSave(string saveId)
{
try
{
return await saveManager.LoadGameById(saveId);
}
catch (Exception ex)
{
Debug.LogError($"Failed to load save {saveId}: {ex.Message}");
// Fallback to default data
saveManager.Reset<PlayerDataPack>();
return false;
}
}
public class SaveMigrator : MonoBehaviour
{
public void MigrateSave(GameSaveInfo saveInfo)
{
if (saveInfo.CustomMetadata.ContainsKey("DataVersion"))
{
int version = (int)saveInfo.CustomMetadata["DataVersion"];
if (version < 2)
{
// Migrate from version 1 to 2
MigrateFromV1ToV2(saveInfo);
}
}
}
private void MigrateFromV1ToV2(GameSaveInfo saveInfo)
{
// Implement migration logic
saveInfo.CustomMetadata["DataVersion"] = 2;
}
}
- Unity 2020.3 or later
- Newtonsoft.Json (com.unity.nuget.newtonsoft-json)
- TextMeshPro (com.unity.textmeshpro)
{
"dependencies": {
"com.unity.nuget.newtonsoft-json": "3.0.2",
"com.unity.textmeshpro": "3.0.6"
}
}
This project is licensed under the MIT License - see the LICENSE file for details.
- Initial release
- Multi-save system with UI
- Screenshot support
- Metadata management
- Async operations
- Error handling and validation
Made with ❤️ for Unity developers