This commit is contained in:
Scove
2026-03-26 20:27:19 +07:00
parent a94ab0e3f6
commit f42ef22a13
129 changed files with 5517 additions and 1134 deletions

View File

@@ -0,0 +1,45 @@
using UnityEditor;
using UnityEngine;
namespace Editor
{
public static class AddStickyNoteContextMenu
{
[MenuItem("GameObject/Add Sticky Note", false, 10)] // Menu item at top, with priority 10
private static void AddStickyNote(MenuCommand menuCommand)
{
// Ensure a GameObject is selected
if (Selection.activeGameObject == null)
{
Debug.LogWarning("No GameObject selected to add Sticky Note.");
return;
}
GameObject selectedGameObject = Selection.activeGameObject;
// Check if StickyNote component already exists
if (selectedGameObject.GetComponent<StickyNote>() != null)
{
Debug.LogWarning($"StickyNote component already exists on '{selectedGameObject.name}'.");
return;
}
// Add the StickyNote component
StickyNote stickyNote = selectedGameObject.AddComponent<StickyNote>();
Undo.RegisterCreatedObjectUndo(stickyNote, "Add Sticky Note");
Debug.Log($"StickyNote added to '{selectedGameObject.name}'.");
}
// Validate the menu item.
// It will only be enabled if a GameObject is selected and it doesn't already have a StickyNote component.
[MenuItem("GameObject/Add Sticky Note", true)]
private static bool ValidateAddStickyNote()
{
if (Selection.activeGameObject == null)
{
return false;
}
return Selection.activeGameObject.GetComponent<StickyNote>() == null;
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 7ffe544b9fe35744cba464108a3b4203

View File

@@ -0,0 +1,175 @@
// ===============================================================================
// 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<AutoSaveTool>("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<AutoSaveTool>())
{
GetWindow<AutoSaveTool>().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($"<color=#00FF00><b>[AutoSave]</b></color> Project saved automatically at {DateTime.Now.ToString("HH:mm:ss")}");
}
ResetTimer();
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 5a4d73f0e418e4d4f92db04c0d6acc25

View File

@@ -0,0 +1,226 @@
// ===============================================================================
// CameraBookmarksTool - Configurable Scene View Camera Save & Load
//
// Creator: Scove
// Last Updated: 2024-05-08
// Version: 3.0
//
// Purpose:
// Allows saving and loading specific Scene View camera angles and positions.
// Includes an Editor Window interface to completely customize the keyboard shortcuts.
//
// How to Use:
// 1. Place this script in an 'Editor' folder in your project.
// 2. Open Settings: Menu -> Tools -> Camera Bookmarks Settings.
// 3. Customize your Save/Load modifiers (e.g., Control, Shift, Alt).
// 4. Customize the shortcut keys for Slot 1 to 9.
// 5. Focus the Scene View and use your assigned shortcuts to Save/Load camera angles.
// ===============================================================================
using UnityEditor;
using UnityEngine;
using System.Globalization;
using System;
namespace Editor
{
[InitializeOnLoad]
public class CameraBookmarksTool : EditorWindow
{
// Customizable Shortcut Keys
private static EventModifiers saveModifier;
private static EventModifiers loadModifier;
private static KeyCode[] slotKeys = new KeyCode[9];
// Static constructor runs automatically when Unity loads or recompiles
static CameraBookmarksTool()
{
LoadSettings();
SceneView.duringSceneGui += OnSceneGUI;
}[MenuItem("Tools/Camera Bookmarks Settings")]
public static void ShowWindow()
{
GetWindow<CameraBookmarksTool>("Cam Bookmarks");
}
private static void LoadSettings()
{
// Load settings from EditorPrefs, fallback to Control/Shift if not found
saveModifier = (EventModifiers)EditorPrefs.GetInt("CamBM_SaveMod", (int)EventModifiers.Control);
loadModifier = (EventModifiers)EditorPrefs.GetInt("CamBM_LoadMod", (int)EventModifiers.Shift);
// Load key bindings for 9 slots, fallback to Alpha1-Alpha9
for (int i = 0; i < 9; i++)
{
int defaultKey = (int)(KeyCode.Alpha1 + i);
slotKeys[i] = (KeyCode)EditorPrefs.GetInt($"CamBM_Key_{i}", defaultKey);
}
}
private static void SaveSettings()
{
EditorPrefs.SetInt("CamBM_SaveMod", (int)saveModifier);
EditorPrefs.SetInt("CamBM_LoadMod", (int)loadModifier);
for (int i = 0; i < 9; i++)
{
EditorPrefs.SetInt($"CamBM_Key_{i}", (int)slotKeys[i]);
}
}
private void OnGUI()
{
GUILayout.Label("Shortcut Configuration", EditorStyles.boldLabel);
EditorGUILayout.Space();
EditorGUI.BeginChangeCheck();
// Modifier Keys Setup
saveModifier = (EventModifiers)EditorGUILayout.EnumFlagsField("Save Modifier", saveModifier);
loadModifier = (EventModifiers)EditorGUILayout.EnumFlagsField("Load Modifier", loadModifier);
if (saveModifier == loadModifier)
{
EditorGUILayout.HelpBox("Warning: Save and Load modifiers are the SAME! This will cause conflicts.", MessageType.Warning);
}
EditorGUILayout.Space();
GUILayout.Label("Slot Keys Assignment", EditorStyles.boldLabel);
// KeyCode Setup for 9 slots
for (int i = 0; i < 9; i++)
{
EditorGUILayout.BeginHorizontal();
GUILayout.Label($"Slot {i + 1}", GUILayout.Width(100));
slotKeys[i] = (KeyCode)EditorGUILayout.EnumPopup(slotKeys[i]);
EditorGUILayout.EndHorizontal();
}
if (EditorGUI.EndChangeCheck())
{
SaveSettings(); // Save immediately if anything changes
}
EditorGUILayout.Space();
EditorGUILayout.Space();
// Reset Button
if (GUILayout.Button("Reset to Default Settings", GUILayout.Height(30)))
{
saveModifier = EventModifiers.Control;
loadModifier = EventModifiers.Shift;
for (int i = 0; i < 9; i++) slotKeys[i] = KeyCode.Alpha1 + i;
SaveSettings();
GUI.FocusControl(null); // Remove focus to refresh UI correctly
}
}
static void OnSceneGUI(SceneView view)
{
Event e = Event.current;
if (e.type == EventType.KeyDown)
{
// Mask out CapsLock, NumLock, and Function keys. We only care about main modifiers.
EventModifiers currentMods = e.modifiers & (EventModifiers.Shift | EventModifiers.Control | EventModifiers.Alt | EventModifiers.Command);
// Check if the pressed key matches any of our custom assigned slot keys
int pressedSlotIndex = -1;
for (int i = 0; i < 9; i++)
{
if (e.keyCode == slotKeys[i] && e.keyCode != KeyCode.None)
{
pressedSlotIndex = i + 1;
break;
}
}
if (pressedSlotIndex != -1)
{
string prefsKey = $"CamBookmark_Slot_{pressedSlotIndex}";
// SAVE ACTION
if (currentMods == saveModifier)
{
SaveBookmark(view, prefsKey, pressedSlotIndex);
e.Use(); // Consume the event
}
// LOAD ACTION
else if (currentMods == loadModifier)
{
LoadBookmark(view, prefsKey, pressedSlotIndex);
e.Use(); // Consume the event
}
}
}
}
private static void SaveBookmark(SceneView view, string key, int slotIndex)
{
Transform camTransform = view.camera.transform;
float size = view.size;
// Use InvariantCulture to ensure dot (.) is used for decimals, preventing regional bugs
string data = string.Format(CultureInfo.InvariantCulture,
"{0}|{1}|{2}|{3}|{4}|{5}|{6}|{7}",
camTransform.position.x, camTransform.position.y, camTransform.position.z,
camTransform.rotation.x, camTransform.rotation.y, camTransform.rotation.z, camTransform.rotation.w,
size);
EditorPrefs.SetString(key, data);
// Show feedback in Scene View
string message = $"Saved Camera Bookmark to Slot {slotIndex}";
view.ShowNotification(new GUIContent(message));
Debug.Log($"<color=#00FF00><b>[CameraBookmarks]</b></color> {message}");
}
private static void LoadBookmark(SceneView view, string key, int slotIndex)
{
if (!EditorPrefs.HasKey(key))
{
string emptyMsg = $"Slot {slotIndex} is empty!";
view.ShowNotification(new GUIContent(emptyMsg));
Debug.LogWarning($"<b>[CameraBookmarks]</b> {emptyMsg}");
return;
}
string[] parts = EditorPrefs.GetString(key).Split('|');
if (parts.Length == 8)
{
try
{
// Parse data back to floats using InvariantCulture
Vector3 pos = new Vector3(
float.Parse(parts[0], CultureInfo.InvariantCulture),
float.Parse(parts[1], CultureInfo.InvariantCulture),
float.Parse(parts[2], CultureInfo.InvariantCulture)
);
Quaternion rot = new Quaternion(
float.Parse(parts[3], CultureInfo.InvariantCulture),
float.Parse(parts[4], CultureInfo.InvariantCulture),
float.Parse(parts[5], CultureInfo.InvariantCulture),
float.Parse(parts[6], CultureInfo.InvariantCulture)
);
float size = float.Parse(parts[7], CultureInfo.InvariantCulture);
// Apply the saved transform to the Scene View camera
view.LookAtDirect(pos, rot, size);
// Show feedback in Scene View
string message = $"Loaded Camera Bookmark from Slot {slotIndex}";
view.ShowNotification(new GUIContent(message));
Debug.Log($"<color=#00FFFF><b>[CameraBookmarks]</b></color> {message}");
}
catch (Exception ex)
{
Debug.LogError($"<b>[CameraBookmarks]</b> Failed to parse data for Slot {slotIndex}. Error: {ex.Message}");
}
}
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 5044058a94d2d014bb3bd8a1bd1e5707

View File

@@ -0,0 +1,131 @@
// ===============================================================================
// DistributeTool - Professional Object Alignment & Distribution
//
// Creator: Scove
// Last Updated: 2024-05-08
// Version: 2.0
//
// Purpose:
// This tool helps organize multiple objects by distributing them evenly along
// the X, Y, or Z axis. It's essential for creating fences, grids, or UI in 3D space.
//
// Key Features:
// 1. Distribute Between Bounds: Keeps the first and last object in place, fills the gap.
// 2. Fixed Spacing: Moves objects based on a specific numerical offset.
// 3. Smart Sorting: Automatically sorts objects by position before distributing.
// 4. Undo Support: Full integration with Unity's Undo system.
//
// How to Use:
// 1. Place this script in an 'Editor' folder.
// 2. Open via: Menu -> Tools -> Distribute Tool.
// 3. Select 3 or more objects in the Hierarchy/Scene.
// 4. Click the desired axis button (X, Y, or Z).
// ===============================================================================
using System.Linq;
using UnityEditor;
using UnityEngine;
namespace Editor
{
public class DistributeTool : EditorWindow
{
private float fixedSpacing = 1.0f;
private bool useFixedSpacing = false;
[MenuItem("Tools/Distribute Tool")]
public static void ShowWindow()
{
GetWindow<DistributeTool>("Distribute");
}
private void OnGUI()
{
GUILayout.Label("Distribution Settings", EditorStyles.boldLabel);
EditorGUILayout.Space();
// Mode Selection
useFixedSpacing = EditorGUILayout.Toggle("Use Fixed Spacing", useFixedSpacing);
if (useFixedSpacing)
{
fixedSpacing = EditorGUILayout.FloatField("Distance Offset", fixedSpacing);
}
else
{
EditorGUILayout.HelpBox("Linear Mode: Objects will be distributed evenly between the first and last selected items.", MessageType.Info);
}
EditorGUILayout.Space();
GUILayout.Label("Distribute Along Axis:", EditorStyles.label);
EditorGUILayout.BeginHorizontal();
if (GUILayout.Button("X Axis", GUILayout.Height(30))) Distribute(0);
if (GUILayout.Button("Y Axis", GUILayout.Height(30))) Distribute(1);
if (GUILayout.Button("Z Axis", GUILayout.Height(30))) Distribute(2);
EditorGUILayout.EndHorizontal();
EditorGUILayout.Space();
// Helpful Reminder
if (Selection.transforms.Length < 3)
{
EditorGUILayout.HelpBox("Please select at least 3 objects to distribute.", MessageType.Warning);
}
else
{
GUILayout.Label($"Objects Selected: {Selection.transforms.Length}", EditorStyles.miniLabel);
}
}
private void Distribute(int axis) // 0=x, 1=y, 2=z
{
Transform[] selection = Selection.transforms;
if (selection.Length < 3)
{
Debug.LogWarning("[DistributeTool] You need to select at least 3 objects.");
return;
}
// Register Undo for all selected objects
Undo.RecordObjects(selection, "Distribute Objects");
// Sort selection by position on the chosen axis to maintain visual order
var sorted = selection.OrderBy(t => t.position[axis]).ToList();
if (useFixedSpacing)
{
// Fixed Spacing Logic: Move each object relative to the first one
Vector3 startPos = sorted[0].position;
for (int i = 1; i < sorted.Count; i++)
{
Vector3 newPos = sorted[i].position;
newPos[axis] = startPos[axis] + (fixedSpacing * i);
sorted[i].position = newPos;
}
}
else
{
// Linear Distribution Logic: Fill the space between first and last
float start = sorted.First().position[axis];
float end = sorted.Last().position[axis];
float totalDistance = end - start;
// Avoid division by zero if objects are at the same spot
if (Mathf.Abs(totalDistance) < 0.0001f) return;
float step = totalDistance / (sorted.Count - 1);
for (int i = 0; i < sorted.Count; i++)
{
Vector3 newPos = sorted[i].position;
newPos[axis] = start + (step * i);
sorted[i].position = newPos;
}
}
Debug.Log($"<color=#FFCC00><b>[DistributeTool]</b></color> Distributed {sorted.Count} objects along the {(axis == 0 ? "X" : axis == 1 ? "Y" : "Z")} axis.");
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 18664781a6d758c42a67a8d1c895c3dd

View File

@@ -0,0 +1,81 @@
// ===============================================================================
// HierarchyEnhancer - Quick Toggle for GameObjects
//
// Creator: Scove
// Last Updated: 2024-05-08
// Version: 2.0
//
// Purpose:
// Adds a handy toggle checkbox to the right side of every item in the Hierarchy.
// This allows you to enable or disable GameObjects instantly without selecting them.
//
// Key Features:
// 1. One-click activation/deactivation directly in Hierarchy.
// 2. Full Undo/Redo support integrated with Unity's system.
// 3. Optimized UI placement to avoid overlapping with object names.
// 4. Visual clarity: Helps quickly identify inactive objects in a complex tree.
//
// How to Use:
// 1. Place this script in an 'Editor' folder.
// 2. Look at your Hierarchy window; a small checkbox will appear on the far right.
// 3. Click the checkbox to toggle the Active/Inactive state of any GameObject.
// ===============================================================================
using UnityEditor;
using UnityEngine;
namespace Editor
{
[InitializeOnLoad]
public class HierarchyEnhancer
{
// Define the width of the toggle area
private const float TOGGLE_WIDTH = 16f;
static HierarchyEnhancer()
{
// Subscribe to the hierarchy item GUI event
EditorApplication.hierarchyWindowItemOnGUI += OnHierarchyItemGUI;
}
private static void OnHierarchyItemGUI(int instanceID, Rect selectionRect)
{
// Get the GameObject associated with this instance ID
GameObject obj = EditorUtility.EntityIdToObject(instanceID) as GameObject;
if (obj == null) return;
// Calculate the position for the Toggle (Aligned to the far right)
// selectionRect.xMax gives us the right boundary of the Hierarchy row
Rect toggleRect = new Rect(selectionRect);
toggleRect.x = selectionRect.xMax - TOGGLE_WIDTH;
toggleRect.width = TOGGLE_WIDTH;
// Check current active state
bool isActive = obj.activeSelf;
// Handle UI and changes
EditorGUI.BeginChangeCheck();
// Set the color based on active state (Optional polish)
Color originalColor = GUI.color;
if (!isActive) GUI.color = new Color(1f, 1f, 1f, 0.5f); // Dim the toggle if inactive
bool newActive = EditorGUI.Toggle(toggleRect, isActive);
GUI.color = originalColor; // Restore original color for other elements
if (EditorGUI.EndChangeCheck())
{
// Record undo before applying the change
Undo.RecordObject(obj, "Toggle GameObject Active State");
obj.SetActive(newActive);
// If it's a Prefab, mark the scene as dirty to ensure it saves
if (!Application.isPlaying)
{
EditorUtility.SetDirty(obj);
}
}
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 5a86ff34ff4b799498a6e0e250dfbd52

View File

@@ -0,0 +1,84 @@
// ===============================================================================
// HierarchySeparators - Visual Organization for Unity Hierarchy
//
// Creator: Scove
// Last Updated: 2024-05-08
// Version: 2.0
//
// Purpose:
// Converts GameObjects starting with "//" into visual separators or headers.
// This helps organize large scenes by creating clear, readable sections.
//
// Key Features:
// 1. Automatic formatting: "// player" becomes a bold, centered "PLAYER" header.
// 2. Custom background: Draws a distinctive bar to separate different logic groups.
// 3. Clean UI: Strips out the "//" prefix for a professional look in the editor.
//
// How to Use:
// 1. Place this script in an 'Editor' folder.
// 2. Create an Empty GameObject in your Hierarchy.
// 3. Rename it starting with "//" (e.g., "// --- ENVIRONMENT ---").
// ===============================================================================
using UnityEditor;
using UnityEngine;
namespace Editor
{
[InitializeOnLoad]
public class HierarchySeparators
{
// Custom styling colors
private static readonly Color HeaderBackgroundColor = new Color(0.22f, 0.22f, 0.22f, 1f);
private static readonly Color TextColor = new Color(0.9f, 0.9f, 0.9f, 1f);
private static readonly Color BorderColor = new Color(0.15f, 0.15f, 0.15f, 1f);
static HierarchySeparators()
{
// Subscribe to the hierarchy item GUI event
EditorApplication.hierarchyWindowItemOnGUI += OnHierarchyItemGUI;
}
private static void OnHierarchyItemGUI(int instanceID, Rect selectionRect)
{
// Get the object from the instance ID
GameObject obj = EditorUtility.EntityIdToObject(instanceID) as GameObject;
// Trigger only if the name starts with "//"
if (obj != null && obj.name.StartsWith("//"))
{
// 1. Draw Background
EditorGUI.DrawRect(selectionRect, HeaderBackgroundColor);
// 2. Draw Subtle Bottom Border for better depth
Rect borderRect = new Rect(selectionRect.x, selectionRect.yMax - 1f, selectionRect.width, 1f);
EditorGUI.DrawRect(borderRect, BorderColor);
// 3. Configure Text Style
GUIStyle headerStyle = new GUIStyle(EditorStyles.boldLabel)
{
alignment = TextAnchor.MiddleCenter,
normal = { textColor = TextColor },
fontSize = 11,
fontStyle = FontStyle.Bold
};
// 4. Clean and Format the string
// Removes "//", trims spaces, and converts to Uppercase
string headerName = obj.name.Replace("//", "").Trim().ToUpper();
// 5. Draw the Header Label
EditorGUI.LabelField(selectionRect, headerName, headerStyle);
// Optional: To prevent selecting the separator as a normal object
// (keeps focus on actual game objects), uncomment the lines below:
if (Event.current.type == EventType.MouseDown && selectionRect.Contains(Event.current.mousePosition))
{
Selection.activeGameObject = null;
}
}
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 840e668e5bda80441802a7b8ffef62f9

View File

@@ -0,0 +1,189 @@
// ===============================================================================
// LevelDecorator - Professional Environment Randomizer (Chaos Maker)
//
// Creator: Scove
// Last Updated: 2024-05-08
// Version: 2.0
//
// Purpose:
// Quickly adds natural variety to your levels by randomizing the rotation
// and scale of selected objects. Perfect for placing foliage, rocks, or debris.
//
// Key Features:
// 1. Persistent Settings: Remembers your min/max values even after closing Unity.
// 2. Uniform Scale: Toggle between independent axes or proportional scaling.
// 3. Smart Rotation: Control specific Y-axis variance and subtle "tilt" separately.
// 4. Undo Integrated: One click to randomize, one click to undo (Ctrl+Z).
//
// How to Use:
// 1. Place this script in an 'Editor' folder.
// 2. Open via: Menu -> Tools -> Level Decorator (Chaos Maker).
// 3. Select the objects you want to randomize in the Scene.
// 4. Adjust the sliders and click "Apply" or "Randomize Everything".
// ===============================================================================
using UnityEditor;
using UnityEngine;
namespace Editor
{
public class LevelDecorator : EditorWindow
{
// Settings Variables
private Vector3 minScale = new Vector3(0.9f, 0.9f, 0.9f);
private Vector3 maxScale = new Vector3(1.1f, 1.1f, 1.1f);
private float maxRotationY = 180f;
private float maxTilt = 5f;
private bool uniformScale = true;
[MenuItem("Tools/Level Decorator (Chaos Maker)")]
public static void ShowWindow()
{
GetWindow<LevelDecorator>("Chaos Maker");
}
private void OnEnable()
{
LoadSettings();
}
private void LoadSettings()
{
maxRotationY = EditorPrefs.GetFloat("LD_MaxRotY", 180f);
maxTilt = EditorPrefs.GetFloat("LD_MaxTilt", 5f);
uniformScale = EditorPrefs.GetBool("LD_Uniform", true);
minScale.x = EditorPrefs.GetFloat("LD_MinScaleX", 0.9f);
minScale.y = EditorPrefs.GetFloat("LD_MinScaleY", 0.9f);
minScale.z = EditorPrefs.GetFloat("LD_MinScaleZ", 0.9f);
maxScale.x = EditorPrefs.GetFloat("LD_MaxScaleX", 1.1f);
maxScale.y = EditorPrefs.GetFloat("LD_MaxScaleY", 1.1f);
maxScale.z = EditorPrefs.GetFloat("LD_MaxScaleZ", 1.1f);
}
private void SaveSettings()
{
EditorPrefs.SetFloat("LD_MaxRotY", maxRotationY);
EditorPrefs.SetFloat("LD_MaxTilt", maxTilt);
EditorPrefs.SetBool("LD_Uniform", uniformScale);
EditorPrefs.SetFloat("LD_MinScaleX", minScale.x);
EditorPrefs.SetFloat("LD_MinScaleY", minScale.y);
EditorPrefs.SetFloat("LD_MinScaleZ", minScale.z);
EditorPrefs.SetFloat("LD_MaxScaleX", maxScale.x);
EditorPrefs.SetFloat("LD_MaxScaleY", maxScale.y);
EditorPrefs.SetFloat("LD_MaxScaleZ", maxScale.z);
}
private void OnGUI()
{
GUILayout.Label("CHAOS MAKER - RANDOMIZER", EditorStyles.boldLabel);
EditorGUILayout.Space();
EditorGUI.BeginChangeCheck();
// --- ROTATION SECTION ---
EditorGUILayout.BeginVertical("box");
GUILayout.Label("1. Rotation", EditorStyles.boldLabel);
maxRotationY = EditorGUILayout.Slider("Random Y Axis (0-360)", maxRotationY, 0, 360);
maxTilt = EditorGUILayout.Slider("Random Tilt (X & Z)", maxTilt, 0, 45);
if (GUILayout.Button("Randomize Rotation"))
{
ApplyRotation();
}
EditorGUILayout.EndVertical();
EditorGUILayout.Space();
// --- SCALE SECTION ---
EditorGUILayout.BeginVertical("box");
GUILayout.Label("2. Scale", EditorStyles.boldLabel);
uniformScale = EditorGUILayout.Toggle("Uniform Scale", uniformScale);
if (uniformScale)
{
float minU = minScale.x;
float maxU = maxScale.x;
minU = EditorGUILayout.FloatField("Min Scale", minU);
maxU = EditorGUILayout.FloatField("Max Scale", maxU);
minScale = new Vector3(minU, minU, minU);
maxScale = new Vector3(maxU, maxU, maxU);
}
else
{
minScale = EditorGUILayout.Vector3Field("Min Scale", minScale);
maxScale = EditorGUILayout.Vector3Field("Max Scale", maxScale);
}
if (GUILayout.Button("Randomize Scale"))
{
ApplyScale();
}
EditorGUILayout.EndVertical();
EditorGUILayout.Space(10);
// --- MASTER ACTION ---
GUI.backgroundColor = new Color(0.7f, 1f, 0.7f); // Light green button
if (GUILayout.Button("RANDOMIZE EVERYTHING", GUILayout.Height(40)))
{
ApplyRotation();
ApplyScale();
}
GUI.backgroundColor = Color.white;
if (EditorGUI.EndChangeCheck())
{
SaveSettings();
}
EditorGUILayout.Space();
int selectCount = Selection.transforms.Length;
EditorGUILayout.HelpBox($"Objects Selected: {selectCount}\nSettings are automatically saved.", MessageType.None);
}
private void ApplyRotation()
{
if (Selection.transforms.Length == 0) return;
Undo.RecordObjects(Selection.transforms, "Chaos Rotation");
foreach (Transform t in Selection.transforms)
{
Vector3 currentRot = t.localEulerAngles;
float randY = Random.Range(-maxRotationY, maxRotationY);
float randX = Random.Range(-maxTilt, maxTilt);
float randZ = Random.Range(-maxTilt, maxTilt);
t.localEulerAngles = new Vector3(currentRot.x + randX, currentRot.y + randY, currentRot.z + randZ);
}
Debug.Log($"<color=#FF8800><b>[LevelDecorator]</b></color> Rotation randomized for {Selection.transforms.Length} objects.");
}
private void ApplyScale()
{
if (Selection.transforms.Length == 0) return;
Undo.RecordObjects(Selection.transforms, "Chaos Scale");
foreach (Transform t in Selection.transforms)
{
if (uniformScale)
{
float uniformRnd = Random.Range(minScale.x, maxScale.x);
t.localScale = new Vector3(uniformRnd, uniformRnd, uniformRnd);
}
else
{
float rX = Random.Range(minScale.x, maxScale.x);
float rY = Random.Range(minScale.y, maxScale.y);
float rZ = Random.Range(minScale.z, maxScale.z);
t.localScale = new Vector3(rX, rY, rZ);
}
}
Debug.Log($"<color=#FF8800><b>[LevelDecorator]</b></color> Scale randomized for {Selection.transforms.Length} objects.");
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 85b906e8fc762834bab1190294ad2d15

View File

@@ -0,0 +1,149 @@
// ===============================================================================
// MeasureTool - Smart Scene Measurement & Analysis
//
// Creator: Scove
// Last Updated: 2024-05-08
// Version: 3.0
//
// Purpose:
// A professional measurement tool that visualizes distances between objects
// in the Scene View. Supports single distance, chain distance, and axis breakdown.
//
// Key Features:
// 1. Two-Point & Chain Mode: Select 2 or more objects to measure.
// 2. Axis Breakdown: Shows Delta X, Y, Z with Unity-standard colors.
// 3. Scene Overlay: On-screen toggle and settings directly in Scene View.
// 4. Readable UI: High-contrast labels with background for any lighting condition.
//
// How to Use:
// 1. Place in an 'Editor' folder.
// 2. Select 2 or more GameObjects in the Hierarchy.
// 3. Use the "MEASURE" overlay in the Scene View to toggle axis details.
// ===============================================================================
using UnityEditor;
using UnityEngine;
using System.Collections.Generic;
namespace Editor
{
[InitializeOnLoad]
public class MeasureTool
{
// Settings (Persisted via EditorPrefs)
private static bool IsEnabled = true;
private static bool ShowAxisBreakdown = true;
private static bool ShowTotalDistance = true;
static MeasureTool()
{
IsEnabled = EditorPrefs.GetBool("MeasureTool_Enabled", true);
ShowAxisBreakdown = EditorPrefs.GetBool("MeasureTool_Axis", true);
SceneView.duringSceneGui += OnSceneGUI;
}
private static void OnSceneGUI(SceneView view)
{
DrawOverlay(view);
if (!IsEnabled || Selection.transforms.Length < 2) return;
Transform[] selected = Selection.transforms;
// Draw measurement for each pair in the selection
for (int i = 0; i < selected.Length - 1; i++)
{
DrawDistance(selected[i].position, selected[i + 1].position);
}
}
private static void DrawDistance(Vector3 p1, Vector3 p2)
{
float distance = Vector3.Distance(p1, p2);
Vector3 midPoint = (p1 + p2) * 0.5f;
// 1. Draw the Main Dotted Line
Handles.color = Color.cyan;
Handles.DrawDottedLine(p1, p2, 4f);
// Draw small spheres at start/end points for clarity
Handles.SphereHandleCap(0, p1, Quaternion.identity, 0.1f, EventType.Repaint);
Handles.SphereHandleCap(0, p2, Quaternion.identity, 0.1f, EventType.Repaint);
// 2. Draw Main Label (Total Distance)
if (ShowTotalDistance)
{
GUIStyle labelStyle = GetLabelStyle(Color.white, new Color(0, 0, 0, 0.6f));
string labelText = $"Dist: {distance:F2}m";
Handles.Label(midPoint + (Vector3.up * 0.1f), labelText, labelStyle);
}
// 3. Draw Axis Breakdown (X, Y, Z Delta)
if (ShowAxisBreakdown)
{
Vector3 delta = new Vector3(Mathf.Abs(p1.x - p2.x), Mathf.Abs(p1.y - p2.y), Mathf.Abs(p1.z - p2.z));
// We only show axis delta if it's significant (> 0.01)
string axisText = "";
if (delta.x > 0.01f) axisText += $"<color=#FF5555>X: {delta.x:F2}</color> ";
if (delta.y > 0.01f) axisText += $"<color=#55FF55>Y: {delta.y:F2}</color> ";
if (delta.z > 0.01f) axisText += $"<color=#5555FF>Z: {delta.z:F2}</color>";
if (!string.IsNullOrEmpty(axisText))
{
GUIStyle axisStyle = GetLabelStyle(Color.white, new Color(0.1f, 0.1f, 0.1f, 0.8f));
axisStyle.richText = true;
axisStyle.fontSize = 11;
Handles.Label(midPoint - (Vector3.up * 0.3f), axisText, axisStyle);
}
}
}
private static void DrawOverlay(SceneView view)
{
Handles.BeginGUI();
// Positioning the overlay in the bottom right
float width = 140;
float height = 90;
Rect rect = new Rect(view.position.width - width - 10, view.position.height - height - 30, width, height);
GUILayout.BeginArea(rect, "MEASURE TOOL", GUI.skin.window);
EditorGUI.BeginChangeCheck();
IsEnabled = GUILayout.Toggle(IsEnabled, " Enable Tool");
ShowTotalDistance = GUILayout.Toggle(ShowTotalDistance, " Show Total");
ShowAxisBreakdown = GUILayout.Toggle(ShowAxisBreakdown, " Axis Breakdown");
if (EditorGUI.EndChangeCheck())
{
EditorPrefs.SetBool("MeasureTool_Enabled", IsEnabled);
EditorPrefs.SetBool("MeasureTool_Axis", ShowAxisBreakdown);
SceneView.RepaintAll();
}
GUILayout.EndArea();
Handles.EndGUI();
}
private static GUIStyle GetLabelStyle(Color textColor, Color bgColor)
{
GUIStyle style = new GUIStyle();
style.normal.textColor = textColor;
style.fontSize = 13;
style.fontStyle = FontStyle.Bold;
style.alignment = TextAnchor.MiddleCenter;
style.padding = new RectOffset(4, 4, 2, 2);
// Create a background texture dynamically
Texture2D tex = new Texture2D(1, 1);
tex.SetPixel(0, 0, bgColor);
tex.Apply();
style.normal.background = tex;
return style;
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 38e38ba189f79f34eb81984f39aeefaf

View File

@@ -0,0 +1,215 @@
// ===============================================================================
// PlayFromHereTool - Professional Instant Testing Workflow
//
// Creator: Scove
// Last Updated: 2026-03-03
// Version: 3.0
//
// Purpose:
// Speeds up level testing by instantly teleporting the Player to your current
// Scene View camera position. It allows you to teleport and optionally start
// Play Mode immediately with fully customizable hotkeys.
//
// Key Features:
// 1. Custom Hotkeys: User-definable modifiers and keys stored in EditorPrefs.
// 2. Smart Detection: Automatically finds the player using Selection -> Tag -> Name.
// 3. Two Modes: "Teleport Only" for setup, and "Play From Here" for testing.
// 4. UI Feedback: Displays notifications directly in the Scene View.
//
// How to Use:
// 1. Place this script in an 'Editor' folder.
// 2. Open Settings: Menu -> Tools -> Play From Here Settings.
// 3. Assign your preferred keys (e.g., Ctrl+Alt+P for Teleport).
// 4. In the Scene View, press your shortcut to move the player.
// ===============================================================================
using UnityEditor;
using UnityEngine;
namespace Editor
{
[InitializeOnLoad]
public class PlayFromHereTool : EditorWindow
{
// Customizable Shortcuts for Mode 1 (Teleport Only)
private static EventModifiers teleportModifier;
private static KeyCode teleportKey;
// Customizable Shortcuts for Mode 2 (Teleport & Play)
private static EventModifiers playModifier;
private static KeyCode playKey;
// Static constructor runs automatically when Unity loads or recompiles
static PlayFromHereTool()
{
LoadSettings();
// Subscribe to SceneView GUI to listen for keyboard inputs
SceneView.duringSceneGui += OnSceneGUI;
}
[MenuItem("Tools/Play From Here Settings")]
public static void ShowWindow()
{
GetWindow<PlayFromHereTool>("Play From Here");
}
private static void LoadSettings()
{
// Load settings from EditorPrefs, fallback to default values
teleportModifier = (EventModifiers)EditorPrefs.GetInt("PFH_TeleportMod", (int)(EventModifiers.Control | EventModifiers.Alt));
teleportKey = (KeyCode)EditorPrefs.GetInt("PFH_TeleportKey", (int)KeyCode.P);
playModifier = (EventModifiers)EditorPrefs.GetInt("PFH_PlayMod", (int)(EventModifiers.Control | EventModifiers.Alt | EventModifiers.Shift));
playKey = (KeyCode)EditorPrefs.GetInt("PFH_PlayKey", (int)KeyCode.P);
}
private static void SaveSettings()
{
EditorPrefs.SetInt("PFH_TeleportMod", (int)teleportModifier);
EditorPrefs.SetInt("PFH_TeleportKey", (int)teleportKey);
EditorPrefs.SetInt("PFH_PlayMod", (int)playModifier);
EditorPrefs.SetInt("PFH_PlayKey", (int)playKey);
}
private void OnGUI()
{
GUILayout.Label("Shortcut Configuration", EditorStyles.boldLabel);
EditorGUILayout.Space();
EditorGUI.BeginChangeCheck();
// Section for Mode 1
EditorGUILayout.BeginVertical("box");
GUILayout.Label("Mode 1: Teleport Only", EditorStyles.boldLabel);
teleportModifier = (EventModifiers)EditorGUILayout.EnumFlagsField("Modifier Keys", teleportModifier);
teleportKey = (KeyCode)EditorGUILayout.EnumPopup("Main Key", teleportKey);
EditorGUILayout.EndVertical();
EditorGUILayout.Space();
// Section for Mode 2
EditorGUILayout.BeginVertical("box");
GUILayout.Label("Mode 2: Play From Here (Teleport + Play)", EditorStyles.boldLabel);
playModifier = (EventModifiers)EditorGUILayout.EnumFlagsField("Modifier Keys", playModifier);
playKey = (KeyCode)EditorGUILayout.EnumPopup("Main Key", playKey);
EditorGUILayout.EndVertical();
if (EditorGUI.EndChangeCheck())
{
SaveSettings();
}
// Conflict warning
if (teleportModifier == playModifier && teleportKey == playKey)
{
EditorGUILayout.HelpBox("Conflict: Mode 1 and Mode 2 have the same hotkey!", MessageType.Error);
}
EditorGUILayout.Space();
EditorGUILayout.Space();
if (GUILayout.Button("Reset to Defaults", GUILayout.Height(30)))
{
teleportModifier = EventModifiers.Control | EventModifiers.Alt;
teleportKey = KeyCode.P;
playModifier = EventModifiers.Control | EventModifiers.Alt | EventModifiers.Shift;
playKey = KeyCode.P;
SaveSettings();
GUI.FocusControl(null);
}
}
private static void OnSceneGUI(SceneView view)
{
Event e = Event.current;
// Only listen for key down events
if (e.type == EventType.KeyDown && e.keyCode != KeyCode.None)
{
// Clean modifiers (ignore CapsLock, etc.)
EventModifiers currentMods = e.modifiers & (EventModifiers.Shift | EventModifiers.Control | EventModifiers.Alt | EventModifiers.Command);
// Try Mode 2 first (stricter modifiers)
if (e.keyCode == playKey && currentMods == playModifier)
{
ExecuteTeleport(true);
e.Use();
}
// Try Mode 1
else if (e.keyCode == teleportKey && currentMods == teleportModifier)
{
ExecuteTeleport(false);
e.Use();
}
}
}
private static void ExecuteTeleport(bool startPlayMode)
{
if (EditorApplication.isPlaying) return;
GameObject player = FindPlayerObject();
if (player == null)
{
Debug.LogWarning("<b>[PlayFromHere]</b> Player not found! Tag your object 'Player', name it 'Player', or select it manually.");
return;
}
if (SceneView.lastActiveSceneView == null || SceneView.lastActiveSceneView.camera == null) return;
Camera sceneCam = SceneView.lastActiveSceneView.camera;
// Record Undo
Undo.RecordObject(player.transform, "Teleport Player");
// Teleport
player.transform.position = sceneCam.transform.position;
// Rotate Y axis (Yaw) to match camera
Vector3 camRot = sceneCam.transform.rotation.eulerAngles;
player.transform.rotation = Quaternion.Euler(0, camRot.y, 0);
// Notify
string msg = startPlayMode ? "Teleported & Starting Play..." : "Player Teleported Here";
SceneView.lastActiveSceneView.ShowNotification(new GUIContent($"{msg}\nTarget: {player.name}"));
Debug.Log($"<color=#00FFFF><b>[PlayFromHere]</b></color> {msg}");
if (startPlayMode)
{
EditorApplication.isPlaying = true;
}
}
private static GameObject FindPlayerObject()
{
// 1. Check current selection (The user knows best)
if (Selection.activeGameObject != null) return Selection.activeGameObject;
// 2. Check Tag "Player"
try
{
GameObject tagPlayer = GameObject.FindGameObjectWithTag("Player");
if (tagPlayer != null) return tagPlayer;
}
catch { }
// 3. Check exact name "Player"
GameObject namePlayer = GameObject.Find("Player");
if (namePlayer != null) return namePlayer;
// 4. Search for objects containing common names
GameObject[] allObjects = Object.FindObjectsByType<GameObject>(FindObjectsSortMode.None);
foreach (var obj in allObjects)
{
string n = obj.name.ToLower();
if (n.Contains("player") || n.Contains("character") || n.Contains("controller"))
{
return obj;
}
}
return null;
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: c8d518311a665394bbaf16052dc48f1c

View File

@@ -0,0 +1,163 @@
// ===============================================================================
// ProjectAudioPreview - Instant Audio Preview in Project Window
//
// Creator: Scove
// Last Updated: 2026-03-03
// Version: 2.0
//
// Purpose:
// Allows users to quickly preview AudioClips directly from the Project window
// without selecting them or looking at the Inspector.
//
// Key Features:
// 1. Hover-to-Show: Play button only appears when hovering over an audio file.
// 2. Play/Stop Toggle: Single button to start and stop the preview.
// 3. High Performance: Cached reflection methods to prevent UI lag.
// 4. Integrated UI: Uses Unity's built-in editor icons for a native feel.
//
// How to Use:
// 1. Place this script in an 'Editor' folder.
// 2. Open the Project window in List View (Two-Column layout).
// 3. Hover over any AudioClip; a Play/Stop icon will appear on the right side.
// ===============================================================================
using UnityEditor;
using UnityEngine;
using System;
using System.Reflection;
namespace EditorTools
{
[InitializeOnLoad]
public class ProjectAudioPreview
{
// Reflection Cache
private static MethodInfo playPreviewMethod;
private static MethodInfo stopAllPreviewMethod;
private static MethodInfo isPreviewPlayingMethod;
private static AudioClip currentlyPlayingClip;
static ProjectAudioPreview()
{
// Initialize Reflection once to save performance
InitReflection();
// Subscribe to project window item GUI
EditorApplication.projectWindowItemOnGUI += OnProjectWindowGUI;
}
private static void InitReflection()
{
Assembly unityEditorAssembly = typeof(AudioImporter).Assembly;
Type audioUtilClass = unityEditorAssembly.GetType("UnityEditor.AudioUtil");
if (audioUtilClass != null)
{
playPreviewMethod = audioUtilClass.GetMethod("PlayPreviewClip", BindingFlags.Static | BindingFlags.Public);
stopAllPreviewMethod = audioUtilClass.GetMethod("StopAllPreviewClips", BindingFlags.Static | BindingFlags.Public);
isPreviewPlayingMethod = audioUtilClass.GetMethod("IsPreviewClipPlaying", BindingFlags.Static | BindingFlags.Public);
}
}
static void OnProjectWindowGUI(string guid, Rect selectionRect)
{
// Optimization: Only process if the row is wide enough (List View)
if (selectionRect.width < 50) return;
// Only show button if mouse is hovering over the current item
Event e = Event.current;
if (!selectionRect.Contains(e.mousePosition)) return;
// Check if asset is an AudioClip
string path = AssetDatabase.GUIDToAssetPath(guid);
AudioType audioType = GetAudioType(path);
if (audioType != AudioType.UNKNOWN)
{
DrawPreviewButton(selectionRect, path);
}
}
private static void DrawPreviewButton(Rect rect, string path)
{
// Calculate button position (Right-aligned)
Rect btnRect = new Rect(rect.xMax - 25, rect.y, 20, rect.height);
AudioClip clip = AssetDatabase.LoadAssetAtPath<AudioClip>(path);
if (clip == null) return;
bool isPlaying = IsClipPlaying(clip);
// Choose icon based on state
// "d_PlayButton" and "d_PreMatQuad" are internal Unity icons
GUIContent icon = isPlaying
? EditorGUIUtility.IconContent("d_PreMatQuad")
: EditorGUIUtility.IconContent("d_PlayButton");
// Styling the button
GUIStyle btnStyle = new GUIStyle(GUI.skin.button);
btnStyle.padding = new RectOffset(0, 0, 0, 0);
// FIX: backgroundColor is a property of GUI, not GUIStyle
Color prevColor = GUI.backgroundColor;
GUI.backgroundColor = isPlaying ? Color.cyan : Color.white;
if (GUI.Button(btnRect, icon, btnStyle))
{
if (isPlaying)
{
StopAllClips();
}
else
{
StopAllClips(); // Stop previous before playing new
PlayClip(clip);
}
}
// Restore original background color
GUI.backgroundColor = prevColor;
}
private static void PlayClip(AudioClip clip)
{
if (playPreviewMethod != null)
{
currentlyPlayingClip = clip;
playPreviewMethod.Invoke(null, new object[] { clip, 0, false });
}
}
private static void StopAllClips()
{
if (stopAllPreviewMethod != null)
{
stopAllPreviewMethod.Invoke(null, new object[] { });
currentlyPlayingClip = null;
}
}
private static bool IsClipPlaying(AudioClip clip)
{
if (isPreviewPlayingMethod != null && currentlyPlayingClip == clip)
{
return (bool)isPreviewPlayingMethod.Invoke(null, new object[] { });
}
return false;
}
private static AudioType GetAudioType(string path)
{
string ext = System.IO.Path.GetExtension(path).ToLower();
switch (ext)
{
case ".mp3": return AudioType.MPEG;
case ".wav": return AudioType.WAV;
case ".ogg": return AudioType.OGGVORBIS;
case ".aiff": return AudioType.AIFF;
default: return AudioType.UNKNOWN;
}
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 417cdb3b433688a419848d097f778a2b

View File

@@ -0,0 +1,226 @@
// ===============================================================================
// ProjectDashboardTool - Central Control Panel for Unity Projects
//
// Creator: Scove
// Last Updated: 2026-03-03
// Version: 2.0
//
// Purpose:
// A centralized dashboard to quickly navigate between scenes, start the game
// from the initialization (Boot) scene, and manage save data / PlayerPrefs.
//
// Key Features:
// 1. Dynamic Scene List: Automatically fetches scenes from Build Settings.
// 2. 1-Click Play: Instantly loads the Boot scene and enters Play Mode.
// 3. Data Management: Clear PlayerPrefs, delete save files, or open the Save folder.
// 4. Color-Coded UI: Prevents accidental data deletion with clear visual warnings.
//
// How to Use:
// 1. Place this script in an 'Editor' folder.
// 2. Open via: Menu -> Tools -> Project Dashboard.
// 3. Add your scenes to File -> Build Settings to see them in the list.
// ===============================================================================
using UnityEditor;
using UnityEngine;
using UnityEditor.SceneManagement;
using System.IO;
namespace Editor
{
public class ProjectDashboardTool : EditorWindow
{
private Vector2 sceneScrollPos;
[MenuItem("Tools/Project Dashboard")]
public static void ShowWindow()
{
// Create a window with a minimum size
ProjectDashboardTool window = GetWindow<ProjectDashboardTool>("Dashboard");
window.minSize = new Vector2(300, 450);
}
private void OnGUI()
{
EditorGUILayout.Space();
DrawPlaySection();
EditorGUILayout.Space(15);
DrawSceneNavigation();
EditorGUILayout.Space(15);
DrawDataManagement();
}
private void DrawPlaySection()
{
GUILayout.Label("QUICK PLAY", EditorStyles.boldLabel);
EditorGUILayout.BeginVertical("box");
// Green Play Button
Color oldColor = GUI.backgroundColor;
GUI.backgroundColor = new Color(0.4f, 1f, 0.4f); // Light Green
if (GUILayout.Button("▶ PLAY GAME (From Boot Scene)", GUILayout.Height(40)))
{
PlayFromBootScene();
}
GUI.backgroundColor = oldColor;
EditorGUILayout.HelpBox("Automatically saves your current scene, loads the first scene in Build Settings, and presses Play.", MessageType.Info);
EditorGUILayout.EndVertical();
}
private void DrawSceneNavigation()
{
GUILayout.Label("SCENE NAVIGATION (Build Settings)", EditorStyles.boldLabel);
EditorGUILayout.BeginVertical("box");
// Fetch scenes dynamically from Build Settings
EditorBuildSettingsScene[] scenes = EditorBuildSettings.scenes;
if (scenes.Length == 0)
{
EditorGUILayout.HelpBox("No scenes found in Build Settings! Please go to File -> Build Settings and add your scenes.", MessageType.Warning);
}
else
{
sceneScrollPos = EditorGUILayout.BeginScrollView(sceneScrollPos, GUILayout.MaxHeight(200));
for (int i = 0; i < scenes.Length; i++)
{
if (scenes[i].enabled)
{
string scenePath = scenes[i].path;
string sceneName = Path.GetFileNameWithoutExtension(scenePath);
EditorGUILayout.BeginHorizontal();
GUILayout.Label($"[{i}]", GUILayout.Width(25));
if (GUILayout.Button($"Load {sceneName}", GUILayout.Height(25)))
{
OpenScene(scenePath);
}
EditorGUILayout.EndHorizontal();
}
}
EditorGUILayout.EndScrollView();
}
EditorGUILayout.EndVertical();
}
private void DrawDataManagement()
{
GUILayout.Label("DATA MANAGEMENT", EditorStyles.boldLabel);
EditorGUILayout.BeginVertical("box");
if (GUILayout.Button("Open Save Folder (Explorer/Finder)", GUILayout.Height(30)))
{
EditorUtility.RevealInFinder(Application.persistentDataPath);
}
EditorGUILayout.Space(5);
// Red Delete Button
Color oldColor = GUI.backgroundColor;
GUI.backgroundColor = new Color(1f, 0.4f, 0.4f); // Light Red
if (GUILayout.Button("⚠ Clear PlayerPrefs & Save Data", GUILayout.Height(35)))
{
if (EditorUtility.DisplayDialog(
"Clear All Data?",
"Are you sure you want to delete all PlayerPrefs and JSON save files?\nThis action cannot be undone.",
"Yes, Delete Everything",
"Cancel"))
{
ClearAllData();
}
}
GUI.backgroundColor = oldColor;
EditorGUILayout.EndVertical();
}
private void PlayFromBootScene()
{
EditorBuildSettingsScene[] scenes = EditorBuildSettings.scenes;
if (scenes.Length == 0)
{
Debug.LogError("<b>[Dashboard]</b> Cannot Play: No scenes in Build Settings.");
return;
}
// Stop playing if currently playing
if (EditorApplication.isPlaying)
{
EditorApplication.isPlaying = false;
return;
}
// Save current scene and load Scene 0
if (EditorSceneManager.SaveCurrentModifiedScenesIfUserWantsTo())
{
EditorSceneManager.OpenScene(scenes[0].path);
EditorApplication.isPlaying = true;
}
}
private void OpenScene(string path)
{
if (EditorApplication.isPlaying)
{
Debug.LogWarning("<b>[Dashboard]</b> Cannot load scene while in Play Mode.");
return;
}
if (EditorSceneManager.SaveCurrentModifiedScenesIfUserWantsTo())
{
EditorSceneManager.OpenScene(path);
Debug.Log($"<b>[Dashboard]</b> Loaded scene: {Path.GetFileNameWithoutExtension(path)}");
}
}
private void ClearAllData()
{
// 1. Clear Unity PlayerPrefs
PlayerPrefs.DeleteAll();
PlayerPrefs.Save();
// 2. Clear common save files in persistentDataPath
string persistentPath = Application.persistentDataPath;
string[] filesToDelete = new string[]
{
"save_data.json",
"player_data.dat",
"settings.json"
};
int deletedCount = 0;
foreach (string file in filesToDelete)
{
string filePath = Path.Combine(persistentPath, file);
if (File.Exists(filePath))
{
File.Delete(filePath);
deletedCount++;
}
}
// Alternative: Delete ALL .json files in the folder (Uncomment if needed)
/*
string[] allJsonFiles = Directory.GetFiles(persistentPath, "*.json");
foreach (string file in allJsonFiles) { File.Delete(file); }
*/
Debug.Log($"<color=#FF5555><b>[Dashboard]</b></color> Cleared PlayerPrefs and {deletedCount} save file(s).");
// Show notification on the Editor Window
this.ShowNotification(new GUIContent("Data Cleared Successfully!"));
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 382b2c71505c85d488edc73f585e957a

View File

@@ -0,0 +1,95 @@
// ===============================================================================
// ProjectNavigation - Navigation History for Unity Project Window
//
// Creator: Scove
// Last Updated: 2026-03-05
// Version: 1.1
//
// Purpose:
// This tool provides a navigation history for the Unity Project window, allowing
// users to quickly jump back and forward between previously visited folders.
//
// Key Features:
// 1. Tracks folder selection history automatically.
// 2. Supports Back and Forward navigation via menu items and shortcuts.
// 3. Pings the selected folder for quick visual identification.
//
// How to Use:
// 1. Place this script in an 'Editor' folder in your project.
// 2. Use Alt + Left Arrow to navigate Back.
// 3. Use Alt + Right Arrow to navigate Forward.
// ===============================================================================
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;
namespace Editor
{
public class ProjectNavigation : EditorWindow
{
private static List<string> history = new List<string>();
private static int currentIndex = -1;
private static string lastPath = "";
// Store the current position when selecting a folder
[InitializeOnLoadMethod]
static void Init()
{
Selection.selectionChanged += OnSelectionChanged;
}
static void OnSelectionChanged()
{
if (Selection.activeObject != null)
{
string path = AssetDatabase.GetAssetPath(Selection.activeObject);
// Only track if it's a valid folder and different from the last recorded path
if (AssetDatabase.IsValidFolder(path) && path != lastPath)
{
// If we are in the middle of history and perform a new "direct jump",
// clear the forward history (browser-style navigation)
if (currentIndex < history.Count - 1)
{
history.RemoveRange(currentIndex + 1, history.Count - (currentIndex + 1));
}
history.Add(path);
currentIndex++;
lastPath = path;
}
}
}
// Shortcut Alt + Left Arrow (Back)
[MenuItem("Tools/Navigation/Back &LEFT")]
static void Back()
{
if (currentIndex > 0)
{
currentIndex--;
NavigateTo(history[currentIndex]);
}
}
// Shortcut Alt + Right Arrow (Forward)
[MenuItem("Tools/Navigation/Forward &RIGHT")]
static void Forward()
{
if (currentIndex < history.Count - 1)
{
currentIndex++;
NavigateTo(history[currentIndex]);
}
}
static void NavigateTo(string path)
{
lastPath = path;
Object obj = AssetDatabase.LoadAssetAtPath<Object>(path);
Selection.activeObject = obj;
EditorGUIUtility.PingObject(obj);
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 09e4547a2316bd340b1e7443f68858b9

View File

@@ -0,0 +1,161 @@
// ===============================================================================
// ReferenceFinderTool - Deep Dependency Analysis for Unity Assets
//
// Creator: Scove
// Last Updated: 2024-05-08
// Version: 2.0
//
// Purpose:
// Finds every asset in the project that references the selected item by
// scanning GUIDs inside Unity's YAML-based files (Scenes, Prefabs, Materials, etc.).
//
// Key Features:
// 1. Interactive Result List: Click any result to highlight the asset in Project window.
// 2. Wide Scope: Scans all text-based assets (Mat, PhysMat, Controller, Asset, etc.).
// 3. Optimized Search: Faster file reading and progress bar with Cancel support.
// 4. Clean UI: Integrated as a professional Editor Window.
//
// How to Use:
// 1. Right-click any asset in the Project window.
// 2. Select "Find References (Deep Scan)".
// 3. View the results in the pop-up window.
// ===============================================================================
using UnityEditor;
using UnityEngine;
using System.Collections.Generic;
using System.IO;
namespace Editor
{
public class ReferenceFinderTool : EditorWindow
{
private static List<string> foundPaths = new List<string>();
private static string targetAssetName = "";
private static string targetAssetGUID = "";
private Vector2 scrollPosition;
[MenuItem("Assets/Find References (Deep Scan)", false, 25)]
public static void FindReferences()
{
Object selected = Selection.activeObject;
if (selected == null) return;
string path = AssetDatabase.GetAssetPath(selected);
targetAssetGUID = AssetDatabase.AssetPathToGUID(path);
targetAssetName = selected.name;
if (string.IsNullOrEmpty(targetAssetGUID)) return;
PerformDeepScan();
// Open the results window
ReferenceFinderTool window = GetWindow<ReferenceFinderTool>("Reference Finder");
window.minSize = new Vector2(400, 300);
window.Show();
}
private static void PerformDeepScan()
{
foundPaths.Clear();
// We look for all assets that are usually saved in Text/YAML format
// Added: Materials, Animators, ScriptableObjects, etc.
string[] allGuids = AssetDatabase.FindAssets("t:Prefab t:Scene t:Material t:PhysicMaterial t:RuntimeAnimatorController t:ScriptableObject t:AnimatorOverrideController");
int total = allGuids.Length;
for (int i = 0; i < total; i++)
{
string assetPath = AssetDatabase.GUIDToAssetPath(allGuids[i]);
// Update Progress Bar
bool isCanceled = EditorUtility.DisplayCancelableProgressBar(
"Searching References",
$"Scanning: {Path.GetFileName(assetPath)}",
(float)i / total);
if (isCanceled) break;
// Check if the asset file contains the Target GUID
if (FileContainsGUID(assetPath, targetAssetGUID))
{
foundPaths.Add(assetPath);
}
}
EditorUtility.ClearProgressBar();
}
private static bool FileContainsGUID(string path, string guid)
{
if (!File.Exists(path)) return false;
// Using StreamReader is faster than File.ReadAllText for very large files
using (StreamReader reader = new StreamReader(path))
{
string line;
while ((line = reader.ReadLine()) != null)
{
if (line.Contains(guid)) return true;
}
}
return false;
}
private void OnGUI()
{
GUILayout.Label($"References for: {targetAssetName}", EditorStyles.boldLabel);
EditorGUILayout.LabelField($"GUID: {targetAssetGUID}", EditorStyles.miniLabel);
EditorGUILayout.Space();
if (foundPaths.Count == 0)
{
EditorGUILayout.HelpBox("No references found. Note: Ensure 'Asset Serialization' is set to 'Force Text' in Project Settings.", MessageType.Info);
}
else
{
GUILayout.Label($"Found {foundPaths.Count} items:", EditorStyles.label);
scrollPosition = EditorGUILayout.BeginScrollView(scrollPosition);
for (int i = 0; i < foundPaths.Count; i++)
{
DrawResultItem(foundPaths[i]);
}
EditorGUILayout.EndScrollView();
}
EditorGUILayout.Space();
if (GUILayout.Button("Rescan", GUILayout.Height(30)))
{
PerformDeepScan();
}
}
private void DrawResultItem(string path)
{
EditorGUILayout.BeginHorizontal("box");
// Get the asset icon
Texture icon = AssetDatabase.GetCachedIcon(path);
GUILayout.Label(icon, GUILayout.Width(16), GUILayout.Height(16));
// Display path
GUILayout.Label(path, EditorStyles.wordWrappedLabel);
GUILayout.FlexibleSpace();
// Ping Button
if (GUILayout.Button("Ping", GUILayout.Width(50)))
{
Object obj = AssetDatabase.LoadAssetAtPath<Object>(path);
EditorGUIUtility.PingObject(obj);
Selection.activeObject = obj;
}
EditorGUILayout.EndHorizontal();
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: e4aa815f17d76a34e8063c78c9821e5e

View File

@@ -0,0 +1,158 @@
// ===============================================================================
// SmartBootstrapper - Auto-Boot Scene Loader for Unity
//
// Creator: Scove
// Last Updated: 2026-03-03
// Version: 3.0 (Drag & Drop UI Added)
//
// Purpose:
// Forces the Unity Editor to always start from an initialization (Boot) scene
// when hitting Play, regardless of which scene is currently open.
//
// Key Features:
// 1. Drag & Drop UI: Easily assign your Boot Scene via a settings window.
// 2. Persistent Settings: Saves your configuration automatically via EditorPrefs.
// 3. Quick Toggle: Enable or disable the feature directly from the window.
// 4. Smart Play Mode: Gracefully returns to your working scene after testing.
//
// How to Use:
// 1. Place this script in an 'Editor' folder.
// 2. Open via: Menu -> Tools -> Smart Boot Settings.
// 3. Drag and drop your Boot Scene into the slot and enable the tool.
// ===============================================================================
using System.IO;
using UnityEditor;
using UnityEditor.SceneManagement;
using UnityEngine;
namespace Editor
{[InitializeOnLoad]
public class SmartBootstrapper : EditorWindow
{
// EditorPrefs Keys
private const string PREFS_TOGGLE_KEY = "SmartBoot_Enabled";
private const string PREFS_PATH_KEY = "SmartBoot_ScenePath";
private static bool IsEnabled
{
get => EditorPrefs.GetBool(PREFS_TOGGLE_KEY, false);
set => EditorPrefs.SetBool(PREFS_TOGGLE_KEY, value);
}
private static string BootScenePath
{
get => EditorPrefs.GetString(PREFS_PATH_KEY, "");
set => EditorPrefs.SetString(PREFS_PATH_KEY, value);
}[MenuItem("Tools/Smart Boot Settings")]
public static void ShowWindow()
{
SmartBootstrapper window = GetWindow<SmartBootstrapper>("Smart Boot");
window.minSize = new Vector2(350, 150);
window.maxSize = new Vector2(500, 160);
window.Show();
}
private void OnGUI()
{
GUILayout.Space(10);
EditorGUILayout.LabelField("Boot Scene Configuration", EditorStyles.boldLabel);
GUILayout.Space(5);
SceneAsset currentScene = null;
if (!string.IsNullOrEmpty(BootScenePath))
{
currentScene = AssetDatabase.LoadAssetAtPath<SceneAsset>(BootScenePath);
}
EditorGUI.BeginChangeCheck();
SceneAsset draggedScene = (SceneAsset)EditorGUILayout.ObjectField(
"Boot Scene",
currentScene,
typeof(SceneAsset),
false
);
// IF THE USER DRAGS AND DROPS A NEW SCENE
if (EditorGUI.EndChangeCheck())
{
if (draggedScene == null)
{
BootScenePath = "";
EditorSceneManager.playModeStartScene = null; // Clear immediately
}
else
{
BootScenePath = AssetDatabase.GetAssetPath(draggedScene);
EditorSceneManager.playModeStartScene = draggedScene; // Update immediately
}
}
GUILayout.Space(10);
EditorGUI.BeginChangeCheck();
bool newToggleState = EditorGUILayout.Toggle("Enable Auto-Boot", IsEnabled);
// IF THE USER TOGGLES THE SWITCH
if (EditorGUI.EndChangeCheck())
{
IsEnabled = newToggleState;
if (!IsEnabled)
EditorSceneManager.playModeStartScene = null; // Clear cache immediately when disabled
}
GUILayout.Space(15);
if (string.IsNullOrEmpty(BootScenePath))
{
EditorGUILayout.HelpBox("Please drag and drop a Boot Scene into the slot above.", MessageType.Warning);
}
else if (IsEnabled)
{
EditorGUILayout.HelpBox($"Ready! Hitting PLAY will always start from:\n{Path.GetFileName(BootScenePath)}", MessageType.Info);
}
else
{
EditorGUILayout.HelpBox("Auto-Boot is currently disabled. The active scene will play normally.", MessageType.None);
}
}
static SmartBootstrapper()
{
// Avoid registering the event multiple times on recompile
EditorApplication.playModeStateChanged -= OnPlayModeChanged;
EditorApplication.playModeStateChanged += OnPlayModeChanged;
}
private static void OnPlayModeChanged(PlayModeStateChange state)
{
if (state != PlayModeStateChange.ExitingEditMode) return;
// 1. Feature disabled or no scene assigned
if (!IsEnabled || string.IsNullOrEmpty(BootScenePath))
{
EditorSceneManager.playModeStartScene = null;
return;
}
// 2. Find Scene Asset
SceneAsset bootScene = AssetDatabase.LoadAssetAtPath<SceneAsset>(BootScenePath);
if (bootScene == null)
{
Debug.LogWarning($"<b>[SmartBoot]</b> Scene not found at saved path: <color=yellow>{BootScenePath}</color>. Please re-assign it.");
EditorSceneManager.playModeStartScene = null;
return;
}
// 3. ALWAYS override with the Boot Scene (fixes old Scene sticking)
EditorSceneManager.playModeStartScene = bootScene;
// 4. Only show Log if the open Scene is not the Boot Scene
string activeScenePath = EditorSceneManager.GetActiveScene().path;
if (activeScenePath != BootScenePath)
{
string currentSceneName = Path.GetFileNameWithoutExtension(activeScenePath);
Debug.Log($"<color=#00FF00><b>[SmartBoot]</b></color> Starting from Boot Scene... <i>(Will return context to {currentSceneName})</i>");
}
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: db3e1e6db7211d04cb2f994b539535be

View File

@@ -0,0 +1,151 @@
// ===============================================================================
// StickyNoteEditor - Custom Inspector & Scene Gizmo for U_StickyNote
//
// Creator: Scove
// Last Updated: 2026-03-03
// Version: 2.0 (Ergonomic UI & Scene Rendering)
//
// Purpose:
// Enhances the Unity Inspector and Scene View for the U_StickyNote component.
// Makes level designing and leaving notes in the scene intuitive, readable,
// and visually appealing.
//
// Key Features:
// 1. Beautiful Scene Gizmos: Renders a colored background pad with
// auto-contrasting text (black/white) for readability in any environment.
// 2. Pin-Line Indicator: Draws a visual pin connecting the floating note
// to its actual GameObject position.
// 3. Ergonomic Inspector: Large text area for multi-line notes instead of
// a cramped default single-line text field.
// 4. Quick Color Presets: 1-click pastel color buttons (Yellow, Blue, Green,
// Pink, White) for rapid and aesthetic note tagging.
//
// How to Use:
// 1. Place this script in an 'Editor' folder.
// 2. Attach your 'StickyNote' script to any GameObject in the scene.
// 3. Select the GameObject to see the upgraded Inspector and Scene View note!
// ===============================================================================
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;
namespace Editor
{
[CustomEditor(typeof(global::StickyNote))]
public class StickyNoteEditor : UnityEditor.Editor
{
private SerializedProperty noteText;
private SerializedProperty noteColor;
private SerializedProperty showAlways;
// Cache background texture for performance optimization
private static Dictionary<Color, Texture2D> backgroundCache = new Dictionary<Color, Texture2D>();
private void OnEnable()
{
// Link properties from the original script
noteText = serializedObject.FindProperty("noteText");
noteColor = serializedObject.FindProperty("noteColor");
showAlways = serializedObject.FindProperty("showAlways");
}
public override void OnInspectorGUI()
{
serializedObject.Update();
// 1. Stylish Header
EditorGUILayout.Space(5);
GUIStyle headerStyle = new GUIStyle(EditorStyles.boldLabel) { fontSize = 14, alignment = TextAnchor.MiddleCenter };
EditorGUILayout.LabelField("📝 STICKY NOTE PANEL", headerStyle);
EditorGUILayout.Space(5);
// 2. Large & Convenient Text Input Area
EditorGUILayout.LabelField("Note Content:", EditorStyles.boldLabel);
GUIStyle textAreaStyle = new GUIStyle(EditorStyles.textArea) { wordWrap = true, fontSize = 12, padding = new RectOffset(8, 8, 8, 8) };
noteText.stringValue = EditorGUILayout.TextArea(noteText.stringValue, textAreaStyle, GUILayout.Height(80));
EditorGUILayout.Space(5);
// 3. Quick Color Selection Buttons (Preset Colors - highly convenient)
EditorGUILayout.LabelField("Quick Colors:", EditorStyles.boldLabel);
GUILayout.BeginHorizontal();
DrawColorPresetButton("Yellow", new Color(1f, 0.92f, 0.53f)); // Pastel Yellow
DrawColorPresetButton("Blue", new Color(0.68f, 0.85f, 0.9f)); // Pastel Blue
DrawColorPresetButton("Green", new Color(0.67f, 0.88f, 0.69f)); // Pastel Green
DrawColorPresetButton("Pink", new Color(1f, 0.71f, 0.76f)); // Pastel Pink
DrawColorPresetButton("White", new Color(0.95f, 0.95f, 0.95f)); // Off-white
GUILayout.EndHorizontal();
// Custom color picker
EditorGUILayout.PropertyField(noteColor, new GUIContent("Custom Color"));
EditorGUILayout.Space(5);
// 4. Toggle Settings
EditorGUILayout.BeginVertical(EditorStyles.helpBox);
EditorGUILayout.PropertyField(showAlways, new GUIContent("📌 Show Always in Scene"));
EditorGUILayout.EndVertical();
serializedObject.ApplyModifiedProperties();
}
// Function to create a colored button
private void DrawColorPresetButton(string label, Color color)
{
Color oldColor = GUI.backgroundColor;
GUI.backgroundColor = color;
if (GUILayout.Button(label, GUILayout.Height(25)))
{
noteColor.colorValue = color;
}
GUI.backgroundColor = oldColor;
}
// =========================================================================
// SCENE VIEW RENDERING - VISUAL NOTE INTERFACE
// =========================================================================
[DrawGizmo(GizmoType.NonSelected | GizmoType.Selected | GizmoType.Active)]
static void DrawGizmo(StickyNote note, GizmoType gizmoType)
{
if (!note.showAlways && (gizmoType & GizmoType.Selected) == 0) return;
if (string.IsNullOrEmpty(note.noteText)) return;
Vector3 basePos = note.transform.position;
Vector3 labelPos = basePos + Vector3.up * 1.2f; // Height of the note board
// 1. Draw the "Pin" and connecting line
Gizmos.color = note.noteColor;
Gizmos.DrawLine(basePos, labelPos);
Gizmos.DrawSphere(basePos, 0.1f); // Pin base
Gizmos.DrawSphere(labelPos, 0.05f); // Pin top
// 2. Initialize Style for the Note Board
GUIStyle noteStyle = new GUIStyle(GUI.skin.label);
noteStyle.normal.background = GetBackgroundTexture(note.noteColor);
// Calculate text color for readability (If dark background -> white text, light background -> black text)
float luminance = (0.299f * note.noteColor.r) + (0.587f * note.noteColor.g) + (0.114f * note.noteColor.b);
noteStyle.normal.textColor = luminance > 0.6f ? Color.black : Color.white;
noteStyle.fontSize = 13;
noteStyle.fontStyle = FontStyle.Bold;
noteStyle.alignment = TextAnchor.MiddleCenter;
noteStyle.padding = new RectOffset(10, 10, 8, 8); // Text distance to border (Padding)
// 3. Draw Label with sharp background and padding
Handles.Label(labelPos + Vector3.up * 0.2f, note.noteText, noteStyle);
}
// Function to automatically create colored background Texture (Cache to prevent lag)
private static Texture2D GetBackgroundTexture(Color color)
{
// Make the color slightly transparent for a more professional look
Color bgColor = new Color(color.r, color.g, color.b, 0.9f);
if (!backgroundCache.TryGetValue(bgColor, out Texture2D tex) || tex == null)
{
tex = new Texture2D(1, 1);
tex.SetPixel(0, 0, bgColor);
tex.Apply();
backgroundCache[bgColor] = tex;
}
return tex;
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: be3283bc34f51f742aefd7d713f5c8fa

146
Assets/Editor/TimeLord.cs Normal file
View File

@@ -0,0 +1,146 @@
// ===============================================================================
// TimeLord - In-Scene Time Manipulation Tool
//
// Creator: Scove
// Last Updated: 2026-03-03
// Version: 2.0 (Sleek UI & Ergonomic Controls)
//
// Purpose:
// Provides a floating, interactive control panel inside the Scene View during
// Play Mode. Allows developers to easily manipulate game time (slow motion,
// fast forward, or pause) without constantly looking away from the action.
//
// Key Features:
// 1. Floating Dashboard: Clean, unobtrusive UI placed directly in the Scene View.
// 2. Dynamic Slider: Drag to fine-tune the time scale smoothly from 0x to 10x.
// 3. Smart Highlighting: Active speeds light up (Green), making it easy to read.
// 4. Auto-Resume: Clicking a speed preset while paused automatically resumes play.
// 5. Visual Pause State: Prominent pause button changes color when active.
//
// How to Use:
// 1. Place this script in an 'Editor' folder.
// 2. Hit PLAY in Unity.
// 3. Move your mouse to the Scene View and use the top-center Time Lord panel!
// ===============================================================================
using UnityEditor;
using UnityEngine;
namespace Editor
{
[InitializeOnLoad]
public class TimeLord
{
// Panel configuration
private const float PANEL_WIDTH = 340f;
private const float PANEL_HEIGHT = 105f;
static TimeLord()
{
// Unsubscribe first to prevent double-hooking upon script recompile
SceneView.duringSceneGui -= OnSceneGUI;
SceneView.duringSceneGui += OnSceneGUI;
}
private static void OnSceneGUI(SceneView sceneView)
{
// Only display the panel when the game is actually running
if (!Application.isPlaying) return;
Handles.BeginGUI();
// Calculate center-top position dynamically based on current Scene View size
float posX = (sceneView.position.width - PANEL_WIDTH) / 2f;
float posY = 15f;
Rect panelRect = new Rect(posX, posY, PANEL_WIDTH, PANEL_HEIGHT);
// Draw the main background box
GUI.Box(panelRect, GUIContent.none, EditorStyles.helpBox);
GUILayout.BeginArea(new Rect(posX + 10, posY + 10, PANEL_WIDTH - 20, PANEL_HEIGHT - 20));
// --- 1. HEADER (Title & Current Speed) ---
GUIStyle headerStyle = new GUIStyle(EditorStyles.boldLabel)
{
richText = true,
alignment = TextAnchor.MiddleCenter,
fontSize = 13
};
// Format the time scale to show 2 decimal places
string currentSpeedText = EditorApplication.isPaused ? "<color=#FF6B6B>PAUSED</color>" : $"<color=#4ECDC4>{Time.timeScale:F2}x</color>";
GUILayout.Label($"⏳ <b>TIME LORD</b> | Current: {currentSpeedText}", headerStyle);
GUILayout.Space(5);
// --- 2. TIME SLIDER (Fine-tune control) ---
EditorGUI.BeginChangeCheck();
float newTimeScale = GUILayout.HorizontalSlider(Time.timeScale, 0f, 10f);
if (EditorGUI.EndChangeCheck())
{
Time.timeScale = newTimeScale;
// Auto-resume if adjusting slider while paused
if (EditorApplication.isPaused && newTimeScale > 0f)
{
EditorApplication.isPaused = false;
}
}
GUILayout.Space(5);
// --- 3. PRESET SPEED BUTTONS ---
GUILayout.BeginHorizontal();
DrawSpeedButton("0.1x", 0.1f);
DrawSpeedButton("0.5x", 0.5f);
DrawSpeedButton("1x", 1f);
DrawSpeedButton("2x", 2f);
DrawSpeedButton("5x", 5f);
GUILayout.EndHorizontal();
GUILayout.Space(5);
// --- 4. PAUSE / RESUME BUTTON ---
Color oldBgColor = GUI.backgroundColor;
GUI.backgroundColor = EditorApplication.isPaused ? new Color(1f, 0.4f, 0.4f) : new Color(0.9f, 0.9f, 0.9f);
string pauseLabel = EditorApplication.isPaused ? "▶ RESUME GAME" : "⏸ PAUSE GAME";
GUIStyle pauseStyle = new GUIStyle(GUI.skin.button) { fontStyle = FontStyle.Bold };
if (GUILayout.Button(pauseLabel, pauseStyle, GUILayout.Height(25)))
{
EditorApplication.isPaused = !EditorApplication.isPaused;
}
GUI.backgroundColor = oldBgColor;
GUILayout.EndArea();
Handles.EndGUI();
}
/// <summary>
/// Draws a preset button that automatically highlights green if it matches the current time scale.
/// </summary>
private static void DrawSpeedButton(string label, float targetSpeed)
{
Color oldBgColor = GUI.backgroundColor;
// Highlight green if this is the active speed AND the game is not paused
bool isActive = Mathf.Approximately(Time.timeScale, targetSpeed) && !EditorApplication.isPaused;
if (isActive)
{
GUI.backgroundColor = new Color(0.4f, 1f, 0.4f); // Light Green
}
if (GUILayout.Button(label, GUILayout.Height(22)))
{
Time.timeScale = targetSpeed;
// Auto-resume if the player clicked a speed while paused
if (EditorApplication.isPaused)
{
EditorApplication.isPaused = false;
}
}
// Restore previous color
GUI.backgroundColor = oldBgColor;
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 66de86109a2db614797e172526afa8da