Commit 1
This commit is contained in:
45
Assets/Editor/AddStickyNoteContextMenu.cs
Normal file
45
Assets/Editor/AddStickyNoteContextMenu.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
2
Assets/Editor/AddStickyNoteContextMenu.cs.meta
Normal file
2
Assets/Editor/AddStickyNoteContextMenu.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7ffe544b9fe35744cba464108a3b4203
|
||||
175
Assets/Editor/AutoSaveTool.cs
Normal file
175
Assets/Editor/AutoSaveTool.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
2
Assets/Editor/AutoSaveTool.cs.meta
Normal file
2
Assets/Editor/AutoSaveTool.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5a4d73f0e418e4d4f92db04c0d6acc25
|
||||
226
Assets/Editor/CameraBookmarksTool.cs
Normal file
226
Assets/Editor/CameraBookmarksTool.cs
Normal 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}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
2
Assets/Editor/CameraBookmarksTool.cs.meta
Normal file
2
Assets/Editor/CameraBookmarksTool.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5044058a94d2d014bb3bd8a1bd1e5707
|
||||
131
Assets/Editor/DistributeTool.cs
Normal file
131
Assets/Editor/DistributeTool.cs
Normal 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.");
|
||||
}
|
||||
}
|
||||
}
|
||||
2
Assets/Editor/DistributeTool.cs.meta
Normal file
2
Assets/Editor/DistributeTool.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 18664781a6d758c42a67a8d1c895c3dd
|
||||
81
Assets/Editor/HierarchyEnhancer.cs
Normal file
81
Assets/Editor/HierarchyEnhancer.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
2
Assets/Editor/HierarchyEnhancer.cs.meta
Normal file
2
Assets/Editor/HierarchyEnhancer.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5a86ff34ff4b799498a6e0e250dfbd52
|
||||
84
Assets/Editor/HierarchySeparators.cs
Normal file
84
Assets/Editor/HierarchySeparators.cs
Normal 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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
2
Assets/Editor/HierarchySeparators.cs.meta
Normal file
2
Assets/Editor/HierarchySeparators.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 840e668e5bda80441802a7b8ffef62f9
|
||||
189
Assets/Editor/LevelDecorator.cs
Normal file
189
Assets/Editor/LevelDecorator.cs
Normal 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.");
|
||||
}
|
||||
}
|
||||
}
|
||||
2
Assets/Editor/LevelDecorator.cs.meta
Normal file
2
Assets/Editor/LevelDecorator.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 85b906e8fc762834bab1190294ad2d15
|
||||
149
Assets/Editor/MeasureTool.cs
Normal file
149
Assets/Editor/MeasureTool.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
2
Assets/Editor/MeasureTool.cs.meta
Normal file
2
Assets/Editor/MeasureTool.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 38e38ba189f79f34eb81984f39aeefaf
|
||||
215
Assets/Editor/PlayFromHereTool.cs
Normal file
215
Assets/Editor/PlayFromHereTool.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
2
Assets/Editor/PlayFromHereTool.cs.meta
Normal file
2
Assets/Editor/PlayFromHereTool.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c8d518311a665394bbaf16052dc48f1c
|
||||
163
Assets/Editor/ProjectAudioPreview.cs
Normal file
163
Assets/Editor/ProjectAudioPreview.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
2
Assets/Editor/ProjectAudioPreview.cs.meta
Normal file
2
Assets/Editor/ProjectAudioPreview.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 417cdb3b433688a419848d097f778a2b
|
||||
226
Assets/Editor/ProjectDashboardTool.cs
Normal file
226
Assets/Editor/ProjectDashboardTool.cs
Normal 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!"));
|
||||
}
|
||||
}
|
||||
}
|
||||
2
Assets/Editor/ProjectDashboardTool.cs.meta
Normal file
2
Assets/Editor/ProjectDashboardTool.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 382b2c71505c85d488edc73f585e957a
|
||||
95
Assets/Editor/ProjectNavigation.cs
Normal file
95
Assets/Editor/ProjectNavigation.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
2
Assets/Editor/ProjectNavigation.cs.meta
Normal file
2
Assets/Editor/ProjectNavigation.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 09e4547a2316bd340b1e7443f68858b9
|
||||
161
Assets/Editor/ReferenceFinderTool.cs
Normal file
161
Assets/Editor/ReferenceFinderTool.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
2
Assets/Editor/ReferenceFinderTool.cs.meta
Normal file
2
Assets/Editor/ReferenceFinderTool.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e4aa815f17d76a34e8063c78c9821e5e
|
||||
158
Assets/Editor/SmartBootstrapper.cs
Normal file
158
Assets/Editor/SmartBootstrapper.cs
Normal 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>");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
2
Assets/Editor/SmartBootstrapper.cs.meta
Normal file
2
Assets/Editor/SmartBootstrapper.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: db3e1e6db7211d04cb2f994b539535be
|
||||
151
Assets/Editor/StickyNoteEditor.cs
Normal file
151
Assets/Editor/StickyNoteEditor.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
2
Assets/Editor/StickyNoteEditor.cs.meta
Normal file
2
Assets/Editor/StickyNoteEditor.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: be3283bc34f51f742aefd7d713f5c8fa
|
||||
146
Assets/Editor/TimeLord.cs
Normal file
146
Assets/Editor/TimeLord.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
2
Assets/Editor/TimeLord.cs.meta
Normal file
2
Assets/Editor/TimeLord.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 66de86109a2db614797e172526afa8da
|
||||
Reference in New Issue
Block a user