Skip to content

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.

License

Notifications You must be signed in to change notification settings

pietras333/save-load-system

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

3 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Unity Multi-Save System

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.

Features

Core Functionality

  • 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

Advanced Features

  • 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

Quick Start

1. Installation

Copy all the provided scripts into your Unity project:

  • MultiSaveManager.cs
  • SaveLoadUI.cs
  • EnhancedSaveManager.cs
  • Core interfaces and classes

2. Basic Setup

// 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
}

3. Scene Setup

  1. Create an empty GameObject and attach MultiSaveManager script
  2. Set up your UI with the provided SaveLoadUI script
  3. Configure screenshot settings if using EnhancedSaveManager

4. Basic Usage

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();
    }
}

Architecture Overview

Core Components

MultiSaveManager
├── IDataOperationService (DataOperationService)
├── IDataService (FileDataService)
├── IDataPack (PlayerDataPack, WorldDataPack, etc.)
└── GameSaveInfo (Metadata management)

Data Flow

  1. Save Creation: Generate unique ID → Create directory → Initialize data packs
  2. Data Loading: Load metadata → Initialize data service → Load data packs by priority
  3. Data Saving: Update metadata → Save data packs → Capture screenshot
  4. Save Management: List saves → Handle duplicates → Delete saves

Data Structure

Save Directory Structure

MyGameSaves/
├── 20240712_143022_1234/
│   ├── metadata.json
│   ├── screenshot.jpg
│   ├── player.json
│   ├── world.json
│   └── settings.json
├── 20240712_150045_5678/
│   ├── metadata.json
│   ├── screenshot.jpg
│   └── ...

Metadata Format

{
  "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"
  }
}

Creating Custom Data Packs

1. Define Your Data Structure

[Serializable]
public class InventoryData
{
    public List<ItemData> Items = new List<ItemData>();
    public int MaxSlots = 20;
    public Dictionary<string, int> ItemCounts = new Dictionary<string, int>();
}

2. Create the Data Pack

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();
    }
}

3. Register in DataOperationService

public class DataOperationService : IDataOperationService
{
    public DataOperationService(IDataService dataService)
    {
        // ... existing code ...
        
        // Register your new data pack
        RegisterDataPack(new InventoryDataPack());
    }
}

UI Integration

Save/Load UI Setup

  1. Create a Canvas with the following structure:
Canvas
├── SaveLoadPanel
│   ├── SaveSlotContainer (Vertical Layout Group)
│   ├── CreateNewSaveButton
│   └── ClosePanelButton
├── NewSaveDialog
│   ├── SaveNameInput
│   ├── SaveDescriptionInput
│   ├── ConfirmCreateButton
│   └── CancelCreateButton
└── DeleteConfirmDialog
    ├── ConfirmDeleteButton
    └── CancelDeleteButton
  1. Create a SaveSlot prefab with:
SaveSlotPrefab
├── SaveNameText
├── SaveDescriptionText
├── LastPlayedText
├── PlayTimeText
├── ScreenshotImage
├── LoadButton
├── DeleteButton
├── DuplicateButton
└── CurrentSaveIndicator

Accessing Save Data in UI

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}";
        }
    }
}

Advanced Features

Screenshot System

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;
        }
    }
}

Custom Metadata

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");
    }
}

Save Validation

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;
    }
}

Best Practices

Performance Optimization

  1. Use Async Operations: Always prefer async save/load operations
  2. Batch Operations: Group multiple save operations together
  3. Lazy Loading: Only load data when needed
  4. 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

Error Handling

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;
    }
}

Data Migration

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;
    }
}

Requirements

  • Unity 2020.3 or later
  • Newtonsoft.Json (com.unity.nuget.newtonsoft-json)
  • TextMeshPro (com.unity.textmeshpro)

Dependencies

{
  "dependencies": {
    "com.unity.nuget.newtonsoft-json": "3.0.2",
    "com.unity.textmeshpro": "3.0.6"
  }
}

License

This project is licensed under the MIT License - see the LICENSE file for details.

Changelog

v1.0.0

  • Initial release
  • Multi-save system with UI
  • Screenshot support
  • Metadata management
  • Async operations
  • Error handling and validation

Made with ❤️ for Unity developers

About

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.

Topics

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Languages