Skip to Content
DocsTinysaveGetting Started

Getting Started

This guide walks you through creating your first save system with TinySave. You’ll learn the core concepts and build working examples.

The Three-Step Pattern

Every TinySave implementation follows this simple pattern:

  1. Mark fields with [SaveField]
  2. Add SaveID component to GameObjects
  3. Call SaveManager methods to save/load

Let’s build a complete example.

Example: Player Stats

Step 1: Mark Your Fields

using UnityEngine; using TinySave.Runtime; public class PlayerStats : MonoBehaviour { [SaveField] public int health = 100; [SaveField] public int maxHealth = 100; [SaveField] public int level = 1; [SaveField] public float experience = 0f; [SaveField] public List<string> inventory = new List<string>(); }

The [SaveField] attribute tells TinySave to save and restore these fields automatically.

Step 2: Add SaveID Component

In the Unity Inspector:

  1. Select your GameObject with the PlayerStats component
  2. Click “Add Component”
  3. Search for “SaveID”
  4. Add the SaveID component

SaveID gives your GameObject a unique identifier that persists across save/load operations.

Step 3: Save and Load

Create a simple UI to test saving and loading:

using UnityEngine; using TinySave.Runtime; using System.Threading.Tasks; public class SaveLoadUI : MonoBehaviour { public void OnSaveButton() { _ = SaveGame(); } public void OnLoadButton() { _ = LoadGame(); } private async Task SaveGame() { await SaveManager.SaveAsync("quicksave"); Debug.Log("Game saved!"); } private async Task LoadGame() { bool success = await SaveManager.LoadAsync("quicksave"); if (success) Debug.Log("Game loaded!"); else Debug.Log("No save file found."); } }

Working with Multiple Slots

TinySave supports multiple save slots:

// Save to different slots await SaveManager.SaveAsync("slot1"); await SaveManager.SaveAsync("slot2"); await SaveManager.SaveAsync("autosave"); // Load from a specific slot await SaveManager.LoadAsync("slot1"); // Check if a slot exists before loading if (SaveManager.HasSlot("slot2")) { await SaveManager.LoadAsync("slot2"); } // Delete a slot SaveManager.DeleteSlot("autosave");

Example: Saving Transform

Transform components require a bit more work since you can’t mark Transform fields directly:

using UnityEngine; using TinySave.Runtime; public class SaveTransform : MonoBehaviour, ISaveCallbacks { [SaveField] private Vector3 savedPosition; [SaveField] private Quaternion savedRotation; public void OnBeforeSave() { // Called before save - capture current transform savedPosition = transform.position; savedRotation = transform.rotation; } public void OnAfterLoad() { // Called after load - restore transform transform.position = savedPosition; transform.rotation = savedRotation; } }

The ISaveCallbacks interface gives you hooks into the save/load process.

Example: Inventory System

using UnityEngine; using TinySave.Runtime; using System.Collections.Generic; public class Inventory : MonoBehaviour { [SaveField] public List<ItemData> items = new List<ItemData>(); [SaveField] public int maxSlots = 20; [System.Serializable] public struct ItemData { public string itemId; public int quantity; public int durability; } public void AddItem(string itemId, int quantity) { // Check if item already exists ItemData existing = items.Find(i => i.itemId == itemId); if (existing.itemId != null) { existing.quantity += quantity; return; } // Add new item if (items.Count < maxSlots) { items.Add(new ItemData { itemId = itemId, quantity = quantity, durability = 100 }); } } }

Example: ScriptableObject Settings

ScriptableObjects work seamlessly with TinySave:

using UnityEngine; using TinySave.Runtime; [CreateAssetMenu(fileName = "GameSettings", menuName = "Game/Settings")] public class GameSettings : ScriptableObject { [SaveField] public float masterVolume = 1f; [SaveField] public float musicVolume = 0.8f; [SaveField] public float sfxVolume = 1f; [SaveField] public int graphicsQuality = 2; [SaveField] public bool fullscreen = true; }

TinySave will automatically save and restore ScriptableObject instances referenced in your scene.

Save Events

Use events to perform actions before saves and after loads:

using UnityEngine; using TinySave.Runtime; public class GameManager : MonoBehaviour { void OnEnable() { SaveManager.BeforeSave += OnBeforeSave; SaveManager.AfterLoad += OnAfterLoad; } void OnDisable() { SaveManager.BeforeSave -= OnBeforeSave; SaveManager.AfterLoad -= OnAfterLoad; } void OnBeforeSave() { Debug.Log("Saving game..."); // Update timestamps, validate data, etc. } void OnAfterLoad() { Debug.Log("Game loaded!"); // Refresh UI, restart systems, etc. } }

Checking Save Metadata

You can peek at save file metadata without loading the entire file:

SaveMetadata? meta = SaveManager.PeekMeta("slot1"); if (meta.HasValue) { Debug.Log($"Save version: {meta.Value.version}"); Debug.Log($"Timestamp: {meta.Value.timestamp}"); Debug.Log($"Scenes: {string.Join(", ", meta.Value.scenes)}"); }

Synchronous Saving

For special cases like OnApplicationQuit, use synchronous saving:

void OnApplicationQuit() { // Use SaveSync to avoid deadlocks during shutdown SaveManager.SaveSync("autosave"); }

Best Practices

  1. Add SaveID early - Add SaveID components during development, not at runtime
  2. Use descriptive slot names - “quicksave”, “autosave”, “checkpoint_boss1”
  3. Check HasSlot before loading - Prevents errors when no save exists
  4. Handle load failures gracefully - LoadAsync returns false if file doesn’t exist
  5. Use async/await - Prevents frame drops during save/load
  6. Subscribe to events - Use BeforeSave/AfterLoad for initialization logic
  7. Test early and often - Save/load frequently during development

Common Patterns

Continue Game Flow

public async Task ContinueGame() { if (SaveManager.HasSlot("autosave")) { bool success = await SaveManager.LoadAsync("autosave"); if (success) { SceneManager.LoadScene("GameScene"); return; } } // No save found - start new game StartNewGame(); }

Checkpoint System

public class Checkpoint : MonoBehaviour { [SerializeField] private string checkpointName; private void OnTriggerEnter(Collider other) { if (other.CompareTag("Player")) { _ = SaveCheckpoint(); } } private async Task SaveCheckpoint() { await SaveManager.SaveAsync($"checkpoint_{checkpointName}"); Debug.Log($"Checkpoint saved: {checkpointName}"); } }

Save Slot Management

public class SaveSlotManager { public SaveSlotInfo[] GetAllSlots() { string[] slotNames = { "slot1", "slot2", "slot3" }; var slots = new SaveSlotInfo[slotNames.Length]; for (int i = 0; i < slotNames.Length; i++) { string slotName = slotNames[i]; bool exists = SaveManager.HasSlot(slotName); SaveMetadata? meta = SaveManager.PeekMeta(slotName); slots[i] = new SaveSlotInfo { slotName = slotName, exists = exists, timestamp = meta?.timestamp ?? 0, playerName = meta?.build ?? "Empty" }; } return slots; } } public struct SaveSlotInfo { public string slotName; public bool exists; public long timestamp; public string playerName; }

Next Steps

Last updated on