Files
BABA_YAGA/Assets/Editors/AutoSaveTool.cs
2026-07-04 18:01:40 +07:00

283 lines
11 KiB
C#

// ===============================================================================
// 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.
// ===============================================================================
#if UNITY_EDITOR
using System;
using UnityEditor;
using UnityEditor.SceneManagement;
using UnityEngine;
using UnityEngine.UIElements;
using UnityEditor.UIElements;
namespace Editor
{
[InitializeOnLoad]
public class AutoSaveTool : EditorWindow
{
private static bool isAutoSaveEnabled;
private static float saveIntervalMinutes;
private static bool showDebugLog;
private static DateTime nextSaveTime;
private Label statusLabel;
private VisualElement progressBar;
[InitializeOnLoadMethod]
static void Init()
{
isAutoSaveEnabled = EditorPrefs.GetBool("AutoSave_Enabled", false);
saveIntervalMinutes = EditorPrefs.GetFloat("AutoSave_Interval", 5f);
showDebugLog = EditorPrefs.GetBool("AutoSave_Log", true);
ResetTimer();
EditorApplication.update += OnEditorUpdate;
}
[MenuItem("Tools/Auto Save Settings")]
public static void ShowWindow()
{
GetWindow<AutoSaveTool>("Auto Save");
}
public void CreateGUI()
{
VisualElement root = rootVisualElement;
root.style.paddingTop = 15;
root.style.paddingBottom = 15;
root.style.paddingLeft = 15;
root.style.paddingRight = 15;
Label title = new Label("Auto Save Settings");
title.style.fontSize = 22;
title.style.unityFontStyleAndWeight = FontStyle.Bold;
title.style.color = new StyleColor(new Color(0.3f, 0.7f, 1f));
title.style.marginBottom = 15;
root.Add(title);
// Settings Card
VisualElement settingsCard = CreateCard();
Toggle enableToggle = new Toggle("Enable Auto Save");
enableToggle.value = isAutoSaveEnabled;
enableToggle.RegisterValueChangedCallback(evt => {
isAutoSaveEnabled = evt.newValue;
EditorPrefs.SetBool("AutoSave_Enabled", isAutoSaveEnabled);
ResetTimer();
UpdateUIStatus();
});
enableToggle.style.unityFontStyleAndWeight = FontStyle.Bold;
settingsCard.Add(enableToggle);
Slider intervalSlider = new Slider("Interval (Minutes)", 0.5f, 60f);
intervalSlider.value = saveIntervalMinutes;
intervalSlider.style.marginTop = 15;
intervalSlider.RegisterValueChangedCallback(evt => {
saveIntervalMinutes = evt.newValue;
EditorPrefs.SetFloat("AutoSave_Interval", saveIntervalMinutes);
ResetTimer();
});
// Add a label to show exact slider value
Label intervalLabel = new Label($"Current: {saveIntervalMinutes:F1} min");
intervalLabel.style.alignSelf = Align.FlexEnd;
intervalLabel.style.color = new StyleColor(new Color(0.7f, 0.7f, 0.7f));
intervalSlider.RegisterValueChangedCallback(evt => {
intervalLabel.text = $"Current: {evt.newValue:F1} min";
});
settingsCard.Add(intervalSlider);
settingsCard.Add(intervalLabel);
Toggle logToggle = new Toggle("Show Debug Log");
logToggle.value = showDebugLog;
logToggle.style.marginTop = 10;
logToggle.RegisterValueChangedCallback(evt => {
showDebugLog = evt.newValue;
EditorPrefs.SetBool("AutoSave_Log", showDebugLog);
});
settingsCard.Add(logToggle);
root.Add(settingsCard);
// Status Card
VisualElement statusCard = CreateCard();
statusCard.style.marginTop = 15;
Label statusTitle = new Label("System Status");
statusTitle.style.fontSize = 16;
statusTitle.style.unityFontStyleAndWeight = FontStyle.Bold;
statusTitle.style.marginBottom = 15;
statusCard.Add(statusTitle);
statusLabel = new Label();
statusLabel.style.fontSize = 15;
statusLabel.style.marginBottom = 10;
statusCard.Add(statusLabel);
// Progress bar
VisualElement progressBg = new VisualElement();
progressBg.style.height = 12;
progressBg.style.backgroundColor = new StyleColor(new Color(0.1f, 0.1f, 0.1f));
progressBg.style.borderTopLeftRadius = 6;
progressBg.style.borderTopRightRadius = 6;
progressBg.style.borderBottomLeftRadius = 6;
progressBg.style.borderBottomRightRadius = 6;
progressBg.style.marginBottom = 20;
progressBg.style.overflow = Overflow.Hidden;
progressBar = new VisualElement();
progressBar.style.height = 12;
progressBar.style.backgroundColor = new StyleColor(new Color(0.3f, 0.7f, 1f));
progressBar.style.width = Length.Percent(0);
progressBg.Add(progressBar);
statusCard.Add(progressBg);
Button saveNowBtn = new Button(() => SaveNow());
saveNowBtn.text = "Force Save Now";
saveNowBtn.style.height = 40;
saveNowBtn.style.backgroundColor = new StyleColor(new Color(0.2f, 0.6f, 0.3f));
saveNowBtn.style.color = new StyleColor(Color.white);
saveNowBtn.style.fontSize = 14;
saveNowBtn.style.unityFontStyleAndWeight = FontStyle.Bold;
saveNowBtn.style.borderTopLeftRadius = 6;
saveNowBtn.style.borderTopRightRadius = 6;
saveNowBtn.style.borderBottomLeftRadius = 6;
saveNowBtn.style.borderBottomRightRadius = 6;
statusCard.Add(saveNowBtn);
root.Add(statusCard);
// Periodically update the countdown text and progress bar
root.schedule.Execute(UpdateUIStatus).Every(100);
UpdateUIStatus();
root.Add(ScovySignature.CreateSignatureBox());
}
private VisualElement CreateCard()
{
VisualElement card = new VisualElement();
card.style.backgroundColor = new StyleColor(new Color(0.18f, 0.18f, 0.18f, 0.9f));
card.style.borderTopLeftRadius = 10;
card.style.borderTopRightRadius = 10;
card.style.borderBottomLeftRadius = 10;
card.style.borderBottomRightRadius = 10;
card.style.paddingTop = 15;
card.style.paddingBottom = 15;
card.style.paddingLeft = 15;
card.style.paddingRight = 15;
card.style.borderTopWidth = 1;
card.style.borderBottomWidth = 1;
card.style.borderLeftWidth = 1;
card.style.borderRightWidth = 1;
card.style.borderTopColor = new StyleColor(new Color(0.12f, 0.12f, 0.12f));
card.style.borderBottomColor = new StyleColor(new Color(0.12f, 0.12f, 0.12f));
card.style.borderLeftColor = new StyleColor(new Color(0.12f, 0.12f, 0.12f));
card.style.borderRightColor = new StyleColor(new Color(0.12f, 0.12f, 0.12f));
return card;
}
private void UpdateUIStatus()
{
if (statusLabel == null || progressBar == null) return;
if (!isAutoSaveEnabled)
{
statusLabel.text = "Auto Save is DISABLED.";
statusLabel.style.color = new StyleColor(new Color(0.85f, 0.35f, 0.35f));
progressBar.style.width = Length.Percent(0);
return;
}
if (EditorApplication.isPlaying || EditorApplication.isCompiling)
{
statusLabel.text = "PAUSED (Play Mode / Compiling)";
statusLabel.style.color = new StyleColor(new Color(0.9f, 0.75f, 0.2f));
progressBar.style.backgroundColor = new StyleColor(new Color(0.9f, 0.75f, 0.2f));
return;
}
TimeSpan timeRemaining = nextSaveTime - DateTime.Now;
if (timeRemaining.TotalSeconds < 0) timeRemaining = TimeSpan.Zero;
string timeStr = string.Format("{0:00}:{1:00}", timeRemaining.Minutes, timeRemaining.Seconds);
statusLabel.text = $"Auto-saving in: {timeStr}";
statusLabel.style.color = new StyleColor(new Color(0.6f, 0.8f, 1f));
progressBar.style.backgroundColor = new StyleColor(new Color(0.3f, 0.7f, 1f));
float totalSeconds = saveIntervalMinutes * 60f;
float elapsed = totalSeconds - (float)timeRemaining.TotalSeconds;
float percent = Mathf.Clamp01(elapsed / totalSeconds) * 100f;
progressBar.style.width = Length.Percent(percent);
}
private static void OnEditorUpdate()
{
if (!isAutoSaveEnabled) return;
if (EditorApplication.isPlaying || EditorApplication.isCompiling)
{
nextSaveTime = nextSaveTime.AddSeconds(Time.unscaledDeltaTime);
return;
}
if (DateTime.Now >= nextSaveTime)
{
SaveNow();
}
}
private static void ResetTimer()
{
nextSaveTime = DateTime.Now.AddMinutes(saveIntervalMinutes);
}
private static void SaveNow()
{
var currentScene = EditorSceneManager.GetActiveScene();
bool isSaved = false;
if (currentScene.isDirty && !string.IsNullOrEmpty(currentScene.path))
{
EditorSceneManager.SaveOpenScenes();
isSaved = true;
}
AssetDatabase.SaveAssets();
isSaved = true;
if (isSaved && showDebugLog)
{
Debug.Log($"<color=#00FF00><b>[AutoSave]</b></color> Project saved automatically at {DateTime.Now.ToString("HH:mm:ss")}");
}
ResetTimer();
}
}
}
#endif