// =============================================================================== // AutoSaveTool - Persistent & Robust Auto-Saving for Unity Editor // // Creator: Scove // Last Updated: 2024-05-08 // Version: 2.0 // // Purpose: // This tool provides a persistent, background auto-saving mechanism for the Unity Editor. // It runs automatically when Unity starts and saves all open scenes and modified assets // at a user-defined interval, even when the settings window is closed. // // Key Features: // 1. Runs persistently in the background via [InitializeOnLoad]. // 2. Saves configuration (interval, status) using EditorPrefs, persisting across Unity sessions. // 3. Uses System.DateTime for accurate time tracking, unaffected by Play Mode reloads. // 4. Pauses counting down during Play Mode or compilation to prevent accidental saving. // // How to Use: // 1. Place this script in an 'Editor' folder in your project. // 2. Open the settings window via: Menu -> Tools -> Auto Save Settings. // 3. Enable "Bật Auto Save" (Enable Auto Save). // 4. Set the desired "Thời gian (phút)" (Interval in minutes). // 5. The tool will now save automatically in the background according to the schedule. // =============================================================================== using System; using UnityEditor; using UnityEditor.SceneManagement; using UnityEngine; namespace Editor { [InitializeOnLoad] // Critical: Ensures the script runs in the background upon Unity startup public class AutoSaveTool : EditorWindow { // Static Configuration Variables (Persisted via EditorPrefs) private static bool isAutoSaveEnabled; private static float saveIntervalMinutes; private static bool showDebugLog; // Time Tracking Variable private static DateTime nextSaveTime; // Static Constructor: Runs when Unity starts or recompiles static AutoSaveTool() { // 1. Load settings from EditorPrefs (persistent storage) isAutoSaveEnabled = EditorPrefs.GetBool("AutoSave_Enabled", false); saveIntervalMinutes = EditorPrefs.GetFloat("AutoSave_Interval", 5f); showDebugLog = EditorPrefs.GetBool("AutoSave_Log", true); // 2. Initialize the timer ResetTimer(); // 3. Register the background update loop EditorApplication.update += OnEditorUpdate; } [MenuItem("Tools/Auto Save Settings")] public static void ShowWindow() { GetWindow("Auto Save"); } private void OnGUI() { GUILayout.Label("Auto Save Configuration (Runs in Background)", EditorStyles.boldLabel); EditorGUILayout.Space(); // Begin tracking changes on the GUI EditorGUI.BeginChangeCheck(); isAutoSaveEnabled = EditorGUILayout.Toggle("Enable Auto Save", isAutoSaveEnabled); saveIntervalMinutes = EditorGUILayout.FloatField("Interval (Minutes)", saveIntervalMinutes); showDebugLog = EditorGUILayout.Toggle("Show Debug Log", showDebugLog); // If any setting changed, save it immediately to EditorPrefs if (EditorGUI.EndChangeCheck()) { if (saveIntervalMinutes < 0.5f) saveIntervalMinutes = 0.5f; // Minimum 30 seconds EditorPrefs.SetBool("AutoSave_Enabled", isAutoSaveEnabled); EditorPrefs.SetFloat("AutoSave_Interval", saveIntervalMinutes); EditorPrefs.SetBool("AutoSave_Log", showDebugLog); ResetTimer(); // Reset timer based on new settings } EditorGUILayout.Space(); if (isAutoSaveEnabled) { TimeSpan timeRemaining = nextSaveTime - DateTime.Now; if (timeRemaining.TotalSeconds < 0) timeRemaining = TimeSpan.Zero; // Display warning if Play Mode or Compiling if (EditorApplication.isPlaying || EditorApplication.isCompiling) { EditorGUILayout.HelpBox("Currently in Play Mode or Compiling. Saving is temporarily paused to prevent errors.", MessageType.Warning); } else { string timeStr = string.Format("{0:00}:{1:00}", timeRemaining.Minutes, timeRemaining.Seconds); EditorGUILayout.HelpBox($"System is running in background.\nAuto-saving in: {timeStr}", MessageType.Info); } if (GUILayout.Button("Save Now", GUILayout.Height(30))) { SaveNow(); } } else { EditorGUILayout.HelpBox("Auto Save System is currently DISABLED.", MessageType.Error); } } // This function runs continuously in the background private static void OnEditorUpdate() { if (!isAutoSaveEnabled) return; // If playing or compiling -> PAUSE the countdown (do not reset) if (EditorApplication.isPlaying || EditorApplication.isCompiling) { // Add delta time to nextSaveTime to compensate for time elapsed while paused nextSaveTime = nextSaveTime.AddSeconds(Time.unscaledDeltaTime); return; } // Check if it's time to save if (DateTime.Now >= nextSaveTime) { SaveNow(); } // Repaint the GUI if the window is open to keep the countdown smooth if (HasOpenInstances()) { GetWindow().Repaint(); } } private static void ResetTimer() { nextSaveTime = DateTime.Now.AddMinutes(saveIntervalMinutes); } private static void SaveNow() { var currentScene = EditorSceneManager.GetActiveScene(); bool isSaved = false; // 1. Save Current Scene if it is dirty AND has a path (not Untitled) if (currentScene.isDirty && !string.IsNullOrEmpty(currentScene.path)) { EditorSceneManager.SaveOpenScenes(); isSaved = true; } // 2. Always save Assets (Prefabs, ScriptableObjects, etc.) AssetDatabase.SaveAssets(); isSaved = true; if (isSaved && showDebugLog) { Debug.Log($"[AutoSave] Project saved automatically at {DateTime.Now.ToString("HH:mm:ss")}"); } ResetTimer(); } } }