diff --git a/Assets/TutorialInfo.meta b/Assets/Editor.meta similarity index 77% rename from Assets/TutorialInfo.meta rename to Assets/Editor.meta index a700bca4..e617f1e5 100644 --- a/Assets/TutorialInfo.meta +++ b/Assets/Editor.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: ba062aa6c92b140379dbc06b43dd3b9b +guid: bfcf56e2246d90745bf402b615e6b7be folderAsset: yes DefaultImporter: externalObjects: {} diff --git a/Assets/Editor/AddStickyNoteContextMenu.cs b/Assets/Editor/AddStickyNoteContextMenu.cs new file mode 100644 index 00000000..0c901b36 --- /dev/null +++ b/Assets/Editor/AddStickyNoteContextMenu.cs @@ -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() != null) + { + Debug.LogWarning($"StickyNote component already exists on '{selectedGameObject.name}'."); + return; + } + + // Add the StickyNote component + StickyNote stickyNote = selectedGameObject.AddComponent(); + 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() == null; + } + } +} \ No newline at end of file diff --git a/Assets/Editor/AddStickyNoteContextMenu.cs.meta b/Assets/Editor/AddStickyNoteContextMenu.cs.meta new file mode 100644 index 00000000..5da4db05 --- /dev/null +++ b/Assets/Editor/AddStickyNoteContextMenu.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 7ffe544b9fe35744cba464108a3b4203 \ No newline at end of file diff --git a/Assets/Editor/AutoSaveTool.cs b/Assets/Editor/AutoSaveTool.cs new file mode 100644 index 00000000..ffc8a0ce --- /dev/null +++ b/Assets/Editor/AutoSaveTool.cs @@ -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("Auto Save"); + } + + private void OnGUI() + { + GUILayout.Label("Auto Save Configuration (Runs in Background)", EditorStyles.boldLabel); + EditorGUILayout.Space(); + + // Begin tracking changes on the GUI + EditorGUI.BeginChangeCheck(); + + isAutoSaveEnabled = EditorGUILayout.Toggle("Enable Auto Save", isAutoSaveEnabled); + saveIntervalMinutes = EditorGUILayout.FloatField("Interval (Minutes)", saveIntervalMinutes); + showDebugLog = EditorGUILayout.Toggle("Show Debug Log", showDebugLog); + + // If any setting changed, save it immediately to EditorPrefs + if (EditorGUI.EndChangeCheck()) + { + if (saveIntervalMinutes < 0.5f) saveIntervalMinutes = 0.5f; // Minimum 30 seconds + + EditorPrefs.SetBool("AutoSave_Enabled", isAutoSaveEnabled); + EditorPrefs.SetFloat("AutoSave_Interval", saveIntervalMinutes); + EditorPrefs.SetBool("AutoSave_Log", showDebugLog); + + ResetTimer(); // Reset timer based on new settings + } + + EditorGUILayout.Space(); + + if (isAutoSaveEnabled) + { + TimeSpan timeRemaining = nextSaveTime - DateTime.Now; + if (timeRemaining.TotalSeconds < 0) timeRemaining = TimeSpan.Zero; + + // Display warning if Play Mode or Compiling + if (EditorApplication.isPlaying || EditorApplication.isCompiling) + { + EditorGUILayout.HelpBox("Currently in Play Mode or Compiling. Saving is temporarily paused to prevent errors.", MessageType.Warning); + } + else + { + string timeStr = string.Format("{0:00}:{1:00}", timeRemaining.Minutes, timeRemaining.Seconds); + EditorGUILayout.HelpBox($"System is running in background.\nAuto-saving in: {timeStr}", MessageType.Info); + } + + if (GUILayout.Button("Save Now", GUILayout.Height(30))) + { + SaveNow(); + } + } + else + { + EditorGUILayout.HelpBox("Auto Save System is currently DISABLED.", MessageType.Error); + } + } + + // This function runs continuously in the background + private static void OnEditorUpdate() + { + if (!isAutoSaveEnabled) return; + + // If playing or compiling -> PAUSE the countdown (do not reset) + if (EditorApplication.isPlaying || EditorApplication.isCompiling) + { + // Add delta time to nextSaveTime to compensate for time elapsed while paused + nextSaveTime = nextSaveTime.AddSeconds(Time.unscaledDeltaTime); + return; + } + + // Check if it's time to save + if (DateTime.Now >= nextSaveTime) + { + SaveNow(); + } + + // Repaint the GUI if the window is open to keep the countdown smooth + if (HasOpenInstances()) + { + GetWindow().Repaint(); + } + } + + private static void ResetTimer() + { + nextSaveTime = DateTime.Now.AddMinutes(saveIntervalMinutes); + } + + private static void SaveNow() + { + var currentScene = EditorSceneManager.GetActiveScene(); + + bool isSaved = false; + + // 1. Save Current Scene if it is dirty AND has a path (not Untitled) + if (currentScene.isDirty && !string.IsNullOrEmpty(currentScene.path)) + { + EditorSceneManager.SaveOpenScenes(); + isSaved = true; + } + + // 2. Always save Assets (Prefabs, ScriptableObjects, etc.) + AssetDatabase.SaveAssets(); + isSaved = true; + + if (isSaved && showDebugLog) + { + Debug.Log($"[AutoSave] Project saved automatically at {DateTime.Now.ToString("HH:mm:ss")}"); + } + + ResetTimer(); + } + } +} \ No newline at end of file diff --git a/Assets/Editor/AutoSaveTool.cs.meta b/Assets/Editor/AutoSaveTool.cs.meta new file mode 100644 index 00000000..440f5f50 --- /dev/null +++ b/Assets/Editor/AutoSaveTool.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 5a4d73f0e418e4d4f92db04c0d6acc25 \ No newline at end of file diff --git a/Assets/Editor/CameraBookmarksTool.cs b/Assets/Editor/CameraBookmarksTool.cs new file mode 100644 index 00000000..192d9b1f --- /dev/null +++ b/Assets/Editor/CameraBookmarksTool.cs @@ -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("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($"[CameraBookmarks] {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($"[CameraBookmarks] {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($"[CameraBookmarks] {message}"); + } + catch (Exception ex) + { + Debug.LogError($"[CameraBookmarks] Failed to parse data for Slot {slotIndex}. Error: {ex.Message}"); + } + } + } + } +} \ No newline at end of file diff --git a/Assets/Editor/CameraBookmarksTool.cs.meta b/Assets/Editor/CameraBookmarksTool.cs.meta new file mode 100644 index 00000000..8b8905ed --- /dev/null +++ b/Assets/Editor/CameraBookmarksTool.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 5044058a94d2d014bb3bd8a1bd1e5707 \ No newline at end of file diff --git a/Assets/Editor/DistributeTool.cs b/Assets/Editor/DistributeTool.cs new file mode 100644 index 00000000..abf519cc --- /dev/null +++ b/Assets/Editor/DistributeTool.cs @@ -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("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($"[DistributeTool] Distributed {sorted.Count} objects along the {(axis == 0 ? "X" : axis == 1 ? "Y" : "Z")} axis."); + } + } +} \ No newline at end of file diff --git a/Assets/Editor/DistributeTool.cs.meta b/Assets/Editor/DistributeTool.cs.meta new file mode 100644 index 00000000..1e05f5a0 --- /dev/null +++ b/Assets/Editor/DistributeTool.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 18664781a6d758c42a67a8d1c895c3dd \ No newline at end of file diff --git a/Assets/Editor/HierarchyEnhancer.cs b/Assets/Editor/HierarchyEnhancer.cs new file mode 100644 index 00000000..edde70ed --- /dev/null +++ b/Assets/Editor/HierarchyEnhancer.cs @@ -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); + } + } + } + } +} \ No newline at end of file diff --git a/Assets/Editor/HierarchyEnhancer.cs.meta b/Assets/Editor/HierarchyEnhancer.cs.meta new file mode 100644 index 00000000..b4679694 --- /dev/null +++ b/Assets/Editor/HierarchyEnhancer.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 5a86ff34ff4b799498a6e0e250dfbd52 \ No newline at end of file diff --git a/Assets/Editor/HierarchySeparators.cs b/Assets/Editor/HierarchySeparators.cs new file mode 100644 index 00000000..8b231596 --- /dev/null +++ b/Assets/Editor/HierarchySeparators.cs @@ -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; + } + + } + } + } +} \ No newline at end of file diff --git a/Assets/Editor/HierarchySeparators.cs.meta b/Assets/Editor/HierarchySeparators.cs.meta new file mode 100644 index 00000000..7010f20f --- /dev/null +++ b/Assets/Editor/HierarchySeparators.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 840e668e5bda80441802a7b8ffef62f9 \ No newline at end of file diff --git a/Assets/Editor/LevelDecorator.cs b/Assets/Editor/LevelDecorator.cs new file mode 100644 index 00000000..db96f286 --- /dev/null +++ b/Assets/Editor/LevelDecorator.cs @@ -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("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($"[LevelDecorator] 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($"[LevelDecorator] Scale randomized for {Selection.transforms.Length} objects."); + } + } +} \ No newline at end of file diff --git a/Assets/Editor/LevelDecorator.cs.meta b/Assets/Editor/LevelDecorator.cs.meta new file mode 100644 index 00000000..a521ac7b --- /dev/null +++ b/Assets/Editor/LevelDecorator.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 85b906e8fc762834bab1190294ad2d15 \ No newline at end of file diff --git a/Assets/Editor/MeasureTool.cs b/Assets/Editor/MeasureTool.cs new file mode 100644 index 00000000..12c4e9cf --- /dev/null +++ b/Assets/Editor/MeasureTool.cs @@ -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 += $"X: {delta.x:F2} "; + if (delta.y > 0.01f) axisText += $"Y: {delta.y:F2} "; + if (delta.z > 0.01f) axisText += $"Z: {delta.z:F2}"; + + 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; + } + } +} \ No newline at end of file diff --git a/Assets/Editor/MeasureTool.cs.meta b/Assets/Editor/MeasureTool.cs.meta new file mode 100644 index 00000000..07c032a8 --- /dev/null +++ b/Assets/Editor/MeasureTool.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 38e38ba189f79f34eb81984f39aeefaf \ No newline at end of file diff --git a/Assets/Editor/PlayFromHereTool.cs b/Assets/Editor/PlayFromHereTool.cs new file mode 100644 index 00000000..fdce9f2c --- /dev/null +++ b/Assets/Editor/PlayFromHereTool.cs @@ -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("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("[PlayFromHere] 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($"[PlayFromHere] {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(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; + } + } +} \ No newline at end of file diff --git a/Assets/Editor/PlayFromHereTool.cs.meta b/Assets/Editor/PlayFromHereTool.cs.meta new file mode 100644 index 00000000..41c54621 --- /dev/null +++ b/Assets/Editor/PlayFromHereTool.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: c8d518311a665394bbaf16052dc48f1c \ No newline at end of file diff --git a/Assets/Editor/ProjectAudioPreview.cs b/Assets/Editor/ProjectAudioPreview.cs new file mode 100644 index 00000000..1a8123d8 --- /dev/null +++ b/Assets/Editor/ProjectAudioPreview.cs @@ -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(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; + } + } + } +} \ No newline at end of file diff --git a/Assets/Editor/ProjectAudioPreview.cs.meta b/Assets/Editor/ProjectAudioPreview.cs.meta new file mode 100644 index 00000000..2d1a0e0f --- /dev/null +++ b/Assets/Editor/ProjectAudioPreview.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 417cdb3b433688a419848d097f778a2b \ No newline at end of file diff --git a/Assets/Editor/ProjectDashboardTool.cs b/Assets/Editor/ProjectDashboardTool.cs new file mode 100644 index 00000000..a8958b8c --- /dev/null +++ b/Assets/Editor/ProjectDashboardTool.cs @@ -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("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("[Dashboard] 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("[Dashboard] Cannot load scene while in Play Mode."); + return; + } + + if (EditorSceneManager.SaveCurrentModifiedScenesIfUserWantsTo()) + { + EditorSceneManager.OpenScene(path); + Debug.Log($"[Dashboard] 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($"[Dashboard] Cleared PlayerPrefs and {deletedCount} save file(s)."); + + // Show notification on the Editor Window + this.ShowNotification(new GUIContent("Data Cleared Successfully!")); + } + } +} \ No newline at end of file diff --git a/Assets/Editor/ProjectDashboardTool.cs.meta b/Assets/Editor/ProjectDashboardTool.cs.meta new file mode 100644 index 00000000..82dd20c0 --- /dev/null +++ b/Assets/Editor/ProjectDashboardTool.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 382b2c71505c85d488edc73f585e957a \ No newline at end of file diff --git a/Assets/Editor/ProjectNavigation.cs b/Assets/Editor/ProjectNavigation.cs new file mode 100644 index 00000000..92843324 --- /dev/null +++ b/Assets/Editor/ProjectNavigation.cs @@ -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 history = new List(); + 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(path); + Selection.activeObject = obj; + EditorGUIUtility.PingObject(obj); + } + } +} diff --git a/Assets/Editor/ProjectNavigation.cs.meta b/Assets/Editor/ProjectNavigation.cs.meta new file mode 100644 index 00000000..3eca9bcd --- /dev/null +++ b/Assets/Editor/ProjectNavigation.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 09e4547a2316bd340b1e7443f68858b9 \ No newline at end of file diff --git a/Assets/Editor/ReferenceFinderTool.cs b/Assets/Editor/ReferenceFinderTool.cs new file mode 100644 index 00000000..530a656b --- /dev/null +++ b/Assets/Editor/ReferenceFinderTool.cs @@ -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 foundPaths = new List(); + 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("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(path); + EditorGUIUtility.PingObject(obj); + Selection.activeObject = obj; + } + + EditorGUILayout.EndHorizontal(); + } + } +} \ No newline at end of file diff --git a/Assets/Editor/ReferenceFinderTool.cs.meta b/Assets/Editor/ReferenceFinderTool.cs.meta new file mode 100644 index 00000000..d7cb3af4 --- /dev/null +++ b/Assets/Editor/ReferenceFinderTool.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: e4aa815f17d76a34e8063c78c9821e5e \ No newline at end of file diff --git a/Assets/Editor/SmartBootstrapper.cs b/Assets/Editor/SmartBootstrapper.cs new file mode 100644 index 00000000..2a9b98c2 --- /dev/null +++ b/Assets/Editor/SmartBootstrapper.cs @@ -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("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(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(BootScenePath); + if (bootScene == null) + { + Debug.LogWarning($"[SmartBoot] Scene not found at saved path: {BootScenePath}. 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($"[SmartBoot] Starting from Boot Scene... (Will return context to {currentSceneName})"); + } + } + } +} \ No newline at end of file diff --git a/Assets/Editor/SmartBootstrapper.cs.meta b/Assets/Editor/SmartBootstrapper.cs.meta new file mode 100644 index 00000000..9f073c0f --- /dev/null +++ b/Assets/Editor/SmartBootstrapper.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: db3e1e6db7211d04cb2f994b539535be \ No newline at end of file diff --git a/Assets/Editor/StickyNoteEditor.cs b/Assets/Editor/StickyNoteEditor.cs new file mode 100644 index 00000000..e9c560f1 --- /dev/null +++ b/Assets/Editor/StickyNoteEditor.cs @@ -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 backgroundCache = new Dictionary(); + + 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; + } + } +} \ No newline at end of file diff --git a/Assets/Editor/StickyNoteEditor.cs.meta b/Assets/Editor/StickyNoteEditor.cs.meta new file mode 100644 index 00000000..8c31d107 --- /dev/null +++ b/Assets/Editor/StickyNoteEditor.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: be3283bc34f51f742aefd7d713f5c8fa \ No newline at end of file diff --git a/Assets/Editor/TimeLord.cs b/Assets/Editor/TimeLord.cs new file mode 100644 index 00000000..1d1901a2 --- /dev/null +++ b/Assets/Editor/TimeLord.cs @@ -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 ? "PAUSED" : $"{Time.timeScale:F2}x"; + GUILayout.Label($"⏳ TIME LORD | 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(); + } + + /// + /// Draws a preset button that automatically highlights green if it matches the current time scale. + /// + 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; + } + } +} \ No newline at end of file diff --git a/Assets/Editor/TimeLord.cs.meta b/Assets/Editor/TimeLord.cs.meta new file mode 100644 index 00000000..7ceb477f --- /dev/null +++ b/Assets/Editor/TimeLord.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 66de86109a2db614797e172526afa8da \ No newline at end of file diff --git a/Assets/Readme.asset b/Assets/Readme.asset deleted file mode 100644 index 77c2f83c..00000000 --- a/Assets/Readme.asset +++ /dev/null @@ -1,34 +0,0 @@ -%YAML 1.1 -%TAG !u! tag:unity3d.com,2011: ---- !u!114 &11400000 -MonoBehaviour: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 0} - m_Enabled: 1 - m_EditorHideFlags: 0 - m_Script: {fileID: 11500000, guid: fcf7219bab7fe46a1ad266029b2fee19, type: 3} - m_Name: Readme - m_EditorClassIdentifier: - icon: {fileID: 2800000, guid: 727a75301c3d24613a3ebcec4a24c2c8, type: 3} - title: URP Empty Template - sections: - - heading: Welcome to the Universal Render Pipeline - text: This template includes the settings and assets you need to start creating with the Universal Render Pipeline. - linkText: - url: - - heading: URP Documentation - text: - linkText: Read more about URP - url: https://docs.unity3d.com/Packages/com.unity.render-pipelines.universal@latest - - heading: Forums - text: - linkText: Get answers and support - url: https://forum.unity.com/forums/universal-render-pipeline.383/ - - heading: Report bugs - text: - linkText: Submit a report - url: https://unity3d.com/unity/qa/bug-reporting - loadedLayout: 1 diff --git a/Assets/Readme.asset.meta b/Assets/Readme.asset.meta deleted file mode 100644 index ab3ad453..00000000 --- a/Assets/Readme.asset.meta +++ /dev/null @@ -1,8 +0,0 @@ -fileFormatVersion: 2 -guid: 8105016687592461f977c054a80ce2f2 -NativeFormatImporter: - externalObjects: {} - mainObjectFileID: 0 - userData: - assetBundleName: - assetBundleVariant: diff --git a/Assets/TutorialInfo/Scripts.meta b/Assets/Scripts.meta similarity index 57% rename from Assets/TutorialInfo/Scripts.meta rename to Assets/Scripts.meta index 02da605b..002d3c25 100644 --- a/Assets/TutorialInfo/Scripts.meta +++ b/Assets/Scripts.meta @@ -1,9 +1,8 @@ fileFormatVersion: 2 -guid: 5a9bcd70e6a4b4b05badaa72e827d8e0 +guid: 8cdbb6650bb138a45a32b6f7f2517667 folderAsset: yes -timeCreated: 1475835190 -licenseType: Store DefaultImporter: + externalObjects: {} userData: assetBundleName: assetBundleVariant: diff --git a/Assets/TutorialInfo/Scripts/Editor.meta b/Assets/Scripts/Camera Controller.meta similarity index 57% rename from Assets/TutorialInfo/Scripts/Editor.meta rename to Assets/Scripts/Camera Controller.meta index f59f0996..86f0f3d9 100644 --- a/Assets/TutorialInfo/Scripts/Editor.meta +++ b/Assets/Scripts/Camera Controller.meta @@ -1,9 +1,8 @@ fileFormatVersion: 2 -guid: 3ad9b87dffba344c89909c6d1b1c17e1 +guid: 962e9e0d2b8d78d4fbb25fb03224f618 folderAsset: yes -timeCreated: 1475593892 -licenseType: Store DefaultImporter: + externalObjects: {} userData: assetBundleName: assetBundleVariant: diff --git a/Assets/Scripts/Camera Controller/CameraController.cs b/Assets/Scripts/Camera Controller/CameraController.cs new file mode 100644 index 00000000..451f068c --- /dev/null +++ b/Assets/Scripts/Camera Controller/CameraController.cs @@ -0,0 +1,287 @@ +using System; +using UnityEngine; + +namespace OnlyScove.Scripts +{ + public class CameraController : MonoBehaviour + { + [SerializeField] InputReader inputReader; // Kéo thả Object chứa InputReader vào đây + [SerializeField] Transform followTarget; + [SerializeField] float distance = 5; + [SerializeField] float minDistance = 2f; + [SerializeField] float maxDistance = 15f; + [SerializeField] float zoomSensitivity = 1f; + [SerializeField] float sensitivity = 0.1f; // Độ nhạy (chỉnh trong Inspector) + [SerializeField] LayerMask collisionLayers; + [SerializeField] float cameraRadius = 0.2f; + [SerializeField] float positionSmoothTime = 0.12f; // Độ trễ đuổi theo nhân vật + [SerializeField] float rotationSmoothTime = 5f; // Tốc độ làm mượt vòng xoay chuột + + [Header("Auto Rotation")] + [SerializeField] bool useAutoRotation = true; + [SerializeField] float autoRotateDelay = 2.5f; // Sau bao lâu không chạm chuột thì xoay + [SerializeField] float autoRotateSpeed = 2f; // Tốc độ xoay về sau lưng + + [Header("Occlusion Transparency")] + [SerializeField] bool useTransparency = true; + [SerializeField] LayerMask transparencyLayers; + [SerializeField] float fadeAlpha = 0.3f; // Độ trong suốt (0 là biến mất, 1 là hiện rõ) + + [Header("Dynamic FOV")] + [SerializeField] bool useDynamicFOV = true; + [SerializeField] float baseFOV = 60f; + [SerializeField] float sprintFOV = 70f; + [SerializeField] float fovSmoothTime = 5f; + + [Header("Character Fading")] + [SerializeField] bool useCharacterFading = true; + [SerializeField] float minVisibleDistance = 1.2f; // Khoảng cách bắt đầu mờ + [SerializeField] float fullyHiddenDistance = 0.6f; // Khoảng cách biến mất hẳn + [SerializeField] Renderer[] characterRenderers; // Kéo các Mesh của nhân vật vào đây + + [Header("Side Bias")] + [SerializeField] bool useSideBias = true; + [SerializeField] float horizontalBiasAmount = 0.5f; // Độ lệch sang trái/phải + [SerializeField] float biasSmoothTime = 3f; // Tốc độ chuyển đổi độ lệch + + [Header("Camera Shake")] + [SerializeField] bool useShake = true; + private float shakeIntensity = 0f; + private float shakeDuration = 0f; + private float shakeTimer = 0f; + private Vector3 shakeOffset; + + [SerializeField] float minVerticalAngle = -45f; + [SerializeField] float maxVerticalAngle = 45f; + [SerializeField] Vector2 framingOffset; + [SerializeField] private bool invertX; + [SerializeField] private bool invertY; + + private float rotationX; + private float rotationY; + + private float invertXVal; + private float invertYVal; + + private float lastInputTime; + private Vector3 currentVelocity; + private Quaternion currentRotation; + + private Camera cam; + private Renderer lastFadedRenderer; + private Color originalColor; + + private float currentSideBias; + + private void Start() + { + cam = GetComponent(); + Cursor.visible = false; + Cursor.lockState = CursorLockMode.Locked; + + // Khởi tạo vòng xoay hiện tại + rotationX = transform.eulerAngles.x; + rotationY = transform.eulerAngles.y; + currentRotation = transform.rotation; + lastInputTime = Time.time; + } + + private void Update() + { + if (inputReader != null) + { + // Kiểm tra xem có input xoay chuột không + if (inputReader.LookInput.magnitude > 0.01f) + { + lastInputTime = Time.time; + } + + invertXVal = (invertX) ? -1 : 1; + invertYVal = (invertY) ? -1 : 1; + + rotationX -= inputReader.LookInput.y * invertYVal * sensitivity * Time.deltaTime; + rotationX = Mathf.Clamp(rotationX, minVerticalAngle, maxVerticalAngle); + + rotationY += inputReader.LookInput.x * invertXVal * sensitivity * Time.deltaTime; + + // Logic Side Bias (Lệch khung hình khi di chuyển) + if (useSideBias) + { + float targetBias = -inputReader.MoveInput.x * horizontalBiasAmount; + currentSideBias = Mathf.Lerp(currentSideBias, targetBias, biasSmoothTime * Time.deltaTime); + } + else + { + currentSideBias = 0; + } + + // Logic Tự động xoay sau lưng (Auto-Correction) + if (useAutoRotation && Time.time - lastInputTime > autoRotateDelay) + { + // Chỉ xoay khi nhân vật đang di chuyển + if (inputReader.MoveInput.magnitude > 0.1f) + { + // Lấy hướng nhân vật đang nhìn (Yaw) + float targetYaw = followTarget.eulerAngles.y; + // Dùng LerpAngle để xoay mượt mà về hướng đó + rotationY = Mathf.LerpAngle(rotationY, targetYaw, autoRotateSpeed * Time.deltaTime); + } + } + + float scrollDelta = inputReader.ScrollInput.y; + if (Mathf.Abs(scrollDelta) > 0.1f) + { + distance -= scrollDelta * zoomSensitivity * Time.deltaTime; + distance = Mathf.Clamp(distance, minDistance, maxDistance); + } + } + + // Xoay chuột: Làm mượt bằng Slerp + Quaternion targetRotation = Quaternion.Euler(rotationX, rotationY, 0f); + currentRotation = Quaternion.Slerp(currentRotation, targetRotation, rotationSmoothTime * Time.deltaTime); + + // Vị trí mục tiêu: Áp dụng offset + Side Bias + Vector3 focusPosition = followTarget.position + currentRotation * new Vector3(framingOffset.x + currentSideBias, framingOffset.y, 0); + + // Collision Logic (Dùng currentRotation đã được làm mượt) + float targetDistance = distance; + RaycastHit hit; + Vector3 rayStart = focusPosition; + Vector3 rayDirection = currentRotation * Vector3.back; + + if (Physics.SphereCast(rayStart, cameraRadius, rayDirection, out hit, distance, collisionLayers)) + { + targetDistance = Mathf.Max(minDistance, hit.distance - 0.1f); + } + + // Logic Làm mờ nhân vật (Character Fading) + if (useCharacterFading && characterRenderers != null && characterRenderers.Length > 0) + { + HandleCharacterFading(targetDistance); + } + + // Vị trí cuối cùng: Làm mượt bằng SmoothDamp để tạo độ trễ đuổi theo + Vector3 targetPosition = focusPosition - currentRotation * new Vector3(0, 0, targetDistance); + + // Xử lý Camera Shake + if (useShake && shakeTimer > 0) + { + HandleShake(); + } + else + { + shakeOffset = Vector3.zero; + } + + transform.position = Vector3.SmoothDamp(transform.position, targetPosition, ref currentVelocity, positionSmoothTime) + shakeOffset; + transform.rotation = currentRotation; + + // Logic Làm trong suốt vật cản (Occlusion Transparency) + if (useTransparency) + { + HandleTransparency(focusPosition); + } + + // Logic FOV linh hoạt + if (useDynamicFOV && cam != null) + { + HandleDynamicFOV(); + } + } + + private void HandleDynamicFOV() + { + float targetFOV = baseFOV; + + // Nếu đang di chuyển và nhấn giữ nút Sprint + if (inputReader.MoveInput.magnitude > 0.1f && inputReader.IsSprintHeld) + { + targetFOV = sprintFOV; + } + + // Làm mượt quá trình thay đổi FOV + cam.fieldOfView = Mathf.Lerp(cam.fieldOfView, targetFOV, fovSmoothTime * Time.deltaTime); + } + + private void HandleShake() + { + shakeTimer -= Time.deltaTime; + + // Cường độ rung giảm dần theo thời gian + float currentIntensity = (shakeTimer / shakeDuration) * shakeIntensity; + + // Dùng Perlin Noise để tạo rung động mượt mà + float shakeX = (Mathf.PerlinNoise(Time.time * 25f, 0f) - 0.5f) * 2f; + float shakeY = (Mathf.PerlinNoise(0f, Time.time * 25f) - 0.5f) * 2f; + float shakeZ = (Mathf.PerlinNoise(Time.time * 25f, Time.time * 25f) - 0.5f) * 2f; + + shakeOffset = new Vector3(shakeX, shakeY, shakeZ) * currentIntensity; + } + + public void Shake(float intensity, float duration) + { + shakeIntensity = intensity; + shakeDuration = duration; + shakeTimer = duration; + } + + private void HandleCharacterFading(float currentDistance) + { + // Tính độ mờ dựa trên khoảng cách + float alpha = Mathf.InverseLerp(fullyHiddenDistance, minVisibleDistance, currentDistance); + + foreach (var renderer in characterRenderers) + { + if (renderer != null) + { + Color color = renderer.material.color; + color.a = alpha; + renderer.material.color = color; + } + } + } + + private void HandleTransparency(Vector3 focusPosition) + { + Vector3 direction = focusPosition - transform.position; + float distanceToPlayer = direction.magnitude; + RaycastHit hit; + + // Bắn một tia từ Camera đến Nhân vật + if (Physics.Raycast(transform.position, direction.normalized, out hit, distanceToPlayer, transparencyLayers)) + { + Renderer renderer = hit.collider.GetComponent(); + if (renderer != null && renderer != lastFadedRenderer) + { + // Nếu chạm vật mới, khôi phục vật cũ + ResetLastRenderer(); + + // Lưu thông tin vật mới và làm mờ + lastFadedRenderer = renderer; + originalColor = renderer.material.color; + Color fadedColor = originalColor; + fadedColor.a = fadeAlpha; + + // Lưu ý: Material cần hỗ trợ Transparency (Surface Type: Transparent trong URP) + renderer.material.color = fadedColor; + } + } + else + { + // Nếu không chạm gì, khôi phục vật cũ + ResetLastRenderer(); + } + } + + private void ResetLastRenderer() + { + if (lastFadedRenderer != null) + { + lastFadedRenderer.material.color = originalColor; + lastFadedRenderer = null; + } + } + + public Quaternion PlanarRotation => Quaternion.Euler(0f, rotationY, 0f); + } +} \ No newline at end of file diff --git a/Assets/Scripts/Camera Controller/CameraController.cs.meta b/Assets/Scripts/Camera Controller/CameraController.cs.meta new file mode 100644 index 00000000..18f3a536 --- /dev/null +++ b/Assets/Scripts/Camera Controller/CameraController.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: d3a2f40f0d755824f91dfa62616cd6fc \ No newline at end of file diff --git a/Assets/Scripts/Debug.meta b/Assets/Scripts/Debug.meta new file mode 100644 index 00000000..6fd5b9dc --- /dev/null +++ b/Assets/Scripts/Debug.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 9477ecbb64ef4d9c8863fb16d2c4bc96 +timeCreated: 1773383891 \ No newline at end of file diff --git a/Assets/Scripts/Debug/PlayerDebugProvider.cs b/Assets/Scripts/Debug/PlayerDebugProvider.cs new file mode 100644 index 00000000..0a97a137 --- /dev/null +++ b/Assets/Scripts/Debug/PlayerDebugProvider.cs @@ -0,0 +1,76 @@ +using UnityEngine; +using UnityEngine.InputSystem; + +namespace OnlyScove.Scripts +{ + public class PlayerDebugProvider : MonoBehaviour + { + [Header("References")] + [SerializeField] private PlayerStateMachine stateMachine; + [Tooltip("Kéo cái Canvas (World Space) bạn đã thiết kế vào đây")] + [SerializeField] private GameObject debugCanvas; + + [Header("Follow Settings")] + [SerializeField] private Vector3 followOffset = new Vector3(1.5f, 2f, 0f); + [SerializeField] private float smoothTime = 0.15f; + [SerializeField] private bool lookAtCamera = true; + + // Các thuộc tính Public để bạn truy cập từ Script UI của bạn + public string CurrentState => stateMachine != null ? stateMachine.CurrentStateName : "N/A"; + public string GroundedStatus => (stateMachine != null && stateMachine.IsGrounded) ? "YES" : "NO"; + public float HorizontalSpeed => stateMachine != null ? new Vector3(stateMachine.Controller.velocity.x, 0, stateMachine.Controller.velocity.z).magnitude : 0f; + public float VerticalSpeed => stateMachine != null ? stateMachine.VelocityY : 0f; + public Vector2 MoveInput => stateMachine != null ? stateMachine.Input.MoveInput : Vector2.zero; + public bool IsSprinting => stateMachine != null ? stateMachine.Input.IsSprintHeld : false; + public string TargetInteractable => stateMachine != null ? (stateMachine.GetInteractable()?.InteractionPrompt ?? "None") : "N/A"; + + private Vector3 currentVelocity; + private Transform cameraTransform; + private bool isVisible = true; + + private void Awake() + { + if (stateMachine == null) stateMachine = GetComponent(); + cameraTransform = Camera.main?.transform; + + if (debugCanvas != null) debugCanvas.SetActive(isVisible); + } + + private void Update() + { + // Toggle Visibility (Ctrl + Shift + B) sử dụng New Input System + if (Keyboard.current != null) + { + bool ctrl = Keyboard.current.leftCtrlKey.isPressed || Keyboard.current.rightCtrlKey.isPressed; + bool shift = Keyboard.current.leftShiftKey.isPressed || Keyboard.current.rightShiftKey.isPressed; + bool bDown = Keyboard.current.bKey.wasPressedThisFrame; + + if (ctrl && shift && bDown) + { + isVisible = !isVisible; + if (debugCanvas != null) debugCanvas.SetActive(isVisible); + } + } + } + + private void LateUpdate() + { + if (!isVisible || debugCanvas == null) return; + + // 1. Damping Follow: UI đuổi theo Player + Vector3 targetPos = transform.position + followOffset; + debugCanvas.transform.position = Vector3.SmoothDamp( + debugCanvas.transform.position, + targetPos, + ref currentVelocity, + smoothTime + ); + + // 2. Billboard: Luôn nhìn về Camera + if (lookAtCamera && cameraTransform != null) + { + debugCanvas.transform.LookAt(debugCanvas.transform.position + cameraTransform.forward); + } + } + } +} \ No newline at end of file diff --git a/Assets/Scripts/Debug/PlayerDebugProvider.cs.meta b/Assets/Scripts/Debug/PlayerDebugProvider.cs.meta new file mode 100644 index 00000000..12bbe1b8 --- /dev/null +++ b/Assets/Scripts/Debug/PlayerDebugProvider.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: bf6aff0b7e11d41439ac80f4963a0795 \ No newline at end of file diff --git a/Assets/Scripts/EnvironmentScanner.cs b/Assets/Scripts/EnvironmentScanner.cs new file mode 100644 index 00000000..2fe5d56c --- /dev/null +++ b/Assets/Scripts/EnvironmentScanner.cs @@ -0,0 +1,48 @@ +using UnityEngine; + +namespace OnlyScove.Scripts +{ + public class EnvironmentScanner : MonoBehaviour + { + [SerializeField] private Vector3 forwardRayOffset = new Vector3(0, 2.5f, 0); + [SerializeField] float forwardRayLength = 10f; + [SerializeField] LayerMask obstacleLayer; + [SerializeField] float heightRayLength; + + public ObstacleHitInfo ObstacleCheck() + { + var hitData = new ObstacleHitInfo(); + + var forwardOrigin = transform.position + forwardRayOffset; + + hitData.forwardHitFound = Physics.Raycast(transform.position + forwardRayOffset, + transform.forward, + out hitData.forwardHit, + forwardRayLength, obstacleLayer + ); + + Debug.DrawRay(forwardOrigin, transform.forward * forwardRayLength, (hitData.forwardHitFound) ? Color.red : Color.green); + + if (hitData.forwardHitFound) + { + var heightOrigin = hitData.forwardHit.point + Vector3.up * heightRayLength; + hitData.heightHitFound = Physics.Raycast(heightOrigin, Vector3.down, + out hitData.heightHit, + heightRayLength, obstacleLayer); + + Debug.DrawRay(heightOrigin, Vector3.down * heightRayLength, (hitData.heightHitFound) ? Color.red : Color.green); + + } + return hitData; + } + } +} + + +public struct ObstacleHitInfo +{ + public RaycastHit forwardHit; + public RaycastHit heightHit; + public bool forwardHitFound; + public bool heightHitFound; +} \ No newline at end of file diff --git a/Assets/Scripts/EnvironmentScanner.cs.meta b/Assets/Scripts/EnvironmentScanner.cs.meta new file mode 100644 index 00000000..c36eb832 --- /dev/null +++ b/Assets/Scripts/EnvironmentScanner.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 169e37d35fede30409266070c88b118f \ No newline at end of file diff --git a/Assets/TutorialInfo/Icons.meta b/Assets/Scripts/GameSetup.meta similarity index 57% rename from Assets/TutorialInfo/Icons.meta rename to Assets/Scripts/GameSetup.meta index 1d19fb99..6fa146e3 100644 --- a/Assets/TutorialInfo/Icons.meta +++ b/Assets/Scripts/GameSetup.meta @@ -1,9 +1,8 @@ fileFormatVersion: 2 -guid: 8a0c9218a650547d98138cd835033977 +guid: b562adae77c550e4db1edcabf68b0530 folderAsset: yes -timeCreated: 1484670163 -licenseType: Store DefaultImporter: + externalObjects: {} userData: assetBundleName: assetBundleVariant: diff --git a/Assets/Scripts/GameSetup/CharacterAutoSetup.cs b/Assets/Scripts/GameSetup/CharacterAutoSetup.cs new file mode 100644 index 00000000..aa8da5ab --- /dev/null +++ b/Assets/Scripts/GameSetup/CharacterAutoSetup.cs @@ -0,0 +1,170 @@ +using UnityEngine; +using System.Text; + +namespace OnlyScove.Scripts.GameSetup +{ + [RequireComponent(typeof(CharacterController))] + public class CharacterAutoSetup : MonoBehaviour + { + [Header("Manual Overrides (If Detection Fails)")] + [SerializeField] private float defaultHeight = 1.8f; + [SerializeField] private float defaultShoulderWidth = 0.4f; + + [Header("Settings")] + [SerializeField] private Transform modelRoot; + [SerializeField] private bool autoDetectOnStart = true; + [SerializeField] private float zCenterOffset = 0.05f; + + private void Start() + { + if (autoDetectOnStart) + { + ApplyAutoSetup(); + } + } + + [ContextMenu("Apply Auto Setup")] + public void ApplyAutoSetup() + { + CharacterController controller = GetComponent(); + PlayerStateMachine stateMachine = GetComponent(); + Animator animator = GetComponentInChildren(); + StringBuilder sb = new StringBuilder(); + + sb.AppendLine($"[AUTO-SETUP REPORT] Character: {gameObject.name}"); + sb.AppendLine("------------------------------------------------------------"); + + // 1. HEIGHT DETECTION + float finalHeight = defaultHeight; + string heightMethod = "Default Fallback"; + + if (animator != null && animator.GetBoneTransform(HumanBodyBones.Head) != null) + { + heightMethod = "Humanoid Bones (Feet to Head)"; + Transform head = animator.GetBoneTransform(HumanBodyBones.Head); + // We measure from the local Y=0 (feet) to the head bone and add 10% for the skull/hair + float headHeight = transform.InverseTransformPoint(head.position).y; + finalHeight = headHeight * 1.12f; // 12% extra for the top of the skull + } + else + { + heightMethod = "Local Mesh Bounds"; + Bounds localBounds = GetRelativeBounds(); + if (localBounds.size.y > 0) finalHeight = localBounds.size.y; + } + + sb.AppendLine(string.Format("1. HEIGHT: {0:F3}m ({1}) ➔ {2:F3}m", finalHeight, heightMethod, finalHeight)); + + // 2. RADIUS DETECTION + float shoulderWidth = defaultShoulderWidth; + string radiusMethod = "Default Fallback"; + + if (animator != null && animator.GetBoneTransform(HumanBodyBones.Hips) != null) + { + radiusMethod = "Humanoid Bones (Shoulders)"; + Transform leftArm = animator.GetBoneTransform(HumanBodyBones.LeftUpperArm); + Transform rightArm = animator.GetBoneTransform(HumanBodyBones.RightUpperArm); + + if (leftArm != null && rightArm != null) + { + float distance = Vector3.Distance(leftArm.position, rightArm.position); + shoulderWidth = distance * 1.2f; // Add 20% for arm/shoulder thickness + } + } + else + { + radiusMethod = "Bounds Width Fallback"; + Bounds b = GetRelativeBounds(); + shoulderWidth = b.size.x * 0.25f; + } + + float finalRadius = shoulderWidth / 2f; + sb.AppendLine(string.Format("2. RADIUS: {0:F3}m ({1}) [/ 2] ➔ {2:F3}m", shoulderWidth, radiusMethod, finalRadius)); + + // 3. COLLISION PRECISION + float skinWidth = finalRadius * 0.10f; + sb.AppendLine(string.Format("3. SKIN WIDTH: {0:F3}m (Radius) [x 0.10] ➔ {1:F3}m", finalRadius, skinWidth)); + + // 4. CAPSULE CENTER + // Center Y = (Height / 2) + SkinWidth + float centerY = (finalHeight / 2f) + skinWidth; + sb.AppendLine(string.Format("4. CENTER Y: ({0:F3}m / 2) + {1:F3}m (Skin) ➔ {2:F3}m", finalHeight, skinWidth, centerY)); + sb.AppendLine(string.Format("5. CENTER Z: [Fixed Offset] ➔ {0:F3}m", zCenterOffset)); + + // 5. MOVEMENT CONSTRAINTS + float stepOffset = finalHeight * 0.15f; + sb.AppendLine(string.Format("6. STEP OFFSET: {0:F3}m (Height) [x 0.15] ➔ {1:F3}m", finalHeight, stepOffset)); + + // 6. GROUND CHECK (STATE MACHINE) + sb.AppendLine("------------------------------------------------------------"); + if (stateMachine != null) + { + float groundCheckRadius = finalRadius * 0.6f; + float groundCheckOffsetY = finalHeight / 24f; + Vector3 groundCheckOffset = new Vector3(0, groundCheckOffsetY, zCenterOffset); + + stateMachine.SetGroundCheck(groundCheckRadius, groundCheckOffset); + + sb.AppendLine("7. GROUND CHECK SETUP:"); + sb.AppendLine(string.Format(" - Radius: {0:F3}m (Radius) [x 0.60] ➔ {1:F3}m", finalRadius, groundCheckRadius)); + sb.AppendLine(string.Format(" - Offset Y: {0:F3}m (Height) [/ 24] ➔ {1:F3}m", finalHeight, groundCheckOffsetY)); + sb.AppendLine(string.Format(" - Offset Z: [Sync Center Z] ➔ {0:F3}m", zCenterOffset)); + } + + sb.AppendLine("------------------------------------------------------------"); + + // Apply to Controller + controller.height = finalHeight; + controller.radius = finalRadius; + controller.skinWidth = skinWidth; + controller.center = new Vector3(0, centerY, zCenterOffset); + controller.slopeLimit = 45f; + controller.stepOffset = stepOffset; + controller.minMoveDistance = 0.001f; + + Debug.Log(sb.ToString()); + } + + private Bounds GetRelativeBounds() + { + Transform targetRoot = modelRoot != null ? modelRoot : transform; + Renderer[] renderers = targetRoot.GetComponentsInChildren(); + + if (renderers.Length == 0) return new Bounds(Vector3.zero, Vector3.zero); + + // Using local bounds of SkinnedMeshRenderers for better accuracy on animated characters + Bounds combinedLocalBounds = new Bounds(); + bool first = true; + + foreach (Renderer renderer in renderers) + { + if (renderer is ParticleSystemRenderer) continue; + + Bounds localB; + if (renderer is SkinnedMeshRenderer smr) + { + localB = smr.localBounds; + } + else + { + // For static meshes, convert world bounds back to local root space + Vector3 min = transform.InverseTransformPoint(renderer.bounds.min); + Vector3 max = transform.InverseTransformPoint(renderer.bounds.max); + localB = new Bounds((min + max) / 2f, max - min); + } + + if (first) + { + combinedLocalBounds = localB; + first = false; + } + else + { + combinedLocalBounds.Encapsulate(localB); + } + } + + return combinedLocalBounds; + } + } +} \ No newline at end of file diff --git a/Assets/Scripts/GameSetup/CharacterAutoSetup.cs.meta b/Assets/Scripts/GameSetup/CharacterAutoSetup.cs.meta new file mode 100644 index 00000000..7dcb2af1 --- /dev/null +++ b/Assets/Scripts/GameSetup/CharacterAutoSetup.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: e16a6690e589f0449ad89a6bf508ab62 \ No newline at end of file diff --git a/Assets/Scripts/GameSetup/CharacterSetupSettings.cs b/Assets/Scripts/GameSetup/CharacterSetupSettings.cs new file mode 100644 index 00000000..e917be03 --- /dev/null +++ b/Assets/Scripts/GameSetup/CharacterSetupSettings.cs @@ -0,0 +1,26 @@ +using UnityEngine; + +namespace OnlyScove.Scripts.GameSetup +{ + [CreateAssetMenu(fileName = "CharacterSetupSettings", menuName = "Setup/Character Setup Settings")] + public class CharacterSetupSettings : ScriptableObject + { + [Header("Movement Constraints")] + public float slopeLimit = 45f; + [Range(0.01f, 0.5f)] public float stepHeightRatio = 0.15f; // Step offset as % of height + + [Header("Precision & Collision")] + public float skinWidthRatio = 0.1f; // Skin width as % of radius + public float minMoveDistance = 0.001f; + + [Header("Dimension Multipliers")] + [Tooltip("Multiplies the detected shoulder width to define Radius.")] + public float radiusMultiplier = 0.8f; + [Tooltip("Multiplies the detected bounding box height.")] + public float heightMultiplier = 1.0f; + + [Header("Center Offset")] + [Tooltip("Y-axis offset for the center of the capsule (0.5 means exact middle).")] + public float centerYRatio = 0.5f; + } +} \ No newline at end of file diff --git a/Assets/Scripts/GameSetup/CharacterSetupSettings.cs.meta b/Assets/Scripts/GameSetup/CharacterSetupSettings.cs.meta new file mode 100644 index 00000000..498b476d --- /dev/null +++ b/Assets/Scripts/GameSetup/CharacterSetupSettings.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 0d44cb4bd45c0e24bb3d8196a137db00 \ No newline at end of file diff --git a/Assets/Scripts/Interface.meta b/Assets/Scripts/Interface.meta new file mode 100644 index 00000000..83f7d629 --- /dev/null +++ b/Assets/Scripts/Interface.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: f1398e4608a9406cb68d769d6bc9a3ec +timeCreated: 1773397974 \ No newline at end of file diff --git a/Assets/Scripts/Interface/IInteractable.cs b/Assets/Scripts/Interface/IInteractable.cs new file mode 100644 index 00000000..4107663c --- /dev/null +++ b/Assets/Scripts/Interface/IInteractable.cs @@ -0,0 +1,8 @@ +namespace OnlyScove.Scripts +{ + public interface IInteractable + { + string InteractionPrompt { get; } + void OnInteract(PlayerStateMachine player); + } +} \ No newline at end of file diff --git a/Assets/Scripts/Interface/IInteractable.cs.meta b/Assets/Scripts/Interface/IInteractable.cs.meta new file mode 100644 index 00000000..bd9f3fde --- /dev/null +++ b/Assets/Scripts/Interface/IInteractable.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: f001a3bb0fe5e954ea724ded3158209d \ No newline at end of file diff --git a/Assets/Scripts/Optimization.meta b/Assets/Scripts/Optimization.meta new file mode 100644 index 00000000..b420068a --- /dev/null +++ b/Assets/Scripts/Optimization.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: e6d134b82fd19fc4aa38896d5e45efed +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/Optimization/AutoPlayerStateMachine.cs b/Assets/Scripts/Optimization/AutoPlayerStateMachine.cs new file mode 100644 index 00000000..6d782cc5 --- /dev/null +++ b/Assets/Scripts/Optimization/AutoPlayerStateMachine.cs @@ -0,0 +1,49 @@ +using UnityEngine; + +namespace OnlyScove.Scripts +{ + /// + /// A version of PlayerStateMachine that simulates constant forward or backward input. + /// + public class AutoPlayerStateMachine : PlayerStateMachine + { + [Header("Auto Pilot Settings")] + public bool alwaysMoveForward = true; + public bool alwaysRun = true; + public bool isRed = false; // New property to differentiate movement direction + + private class FakeInputReader : InputReader + { + public Vector2 ForcedMove { get; set; } + public bool ForcedSprint { get; set; } + + public override Vector2 MoveInput => ForcedMove; + public override bool IsSprintHeld => ForcedSprint; + } + + private FakeInputReader fakeInput; + + public override InputReader Input => fakeInput; + + protected override void Awake() + { + fakeInput = gameObject.AddComponent(); + base.Awake(); + } + + protected override void Update() + { + if (fakeInput != null) + { + // Logic updated: isRed moves backward (Vector2.down), others move forward (Vector2.up) + fakeInput.ForcedMove = (isRed) + ? (alwaysMoveForward ? Vector2.down : Vector2.zero) + : (alwaysMoveForward ? Vector2.up : Vector2.zero); + + fakeInput.ForcedSprint = alwaysRun; + } + + base.Update(); + } + } +} diff --git a/Assets/Scripts/Optimization/AutoPlayerStateMachine.cs.meta b/Assets/Scripts/Optimization/AutoPlayerStateMachine.cs.meta new file mode 100644 index 00000000..b00381b9 --- /dev/null +++ b/Assets/Scripts/Optimization/AutoPlayerStateMachine.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: dbe8532b2e328a249bf61ae957c92486 \ No newline at end of file diff --git a/Assets/Scripts/Optimization/JobsMovementManager.cs b/Assets/Scripts/Optimization/JobsMovementManager.cs new file mode 100644 index 00000000..e7f76e92 --- /dev/null +++ b/Assets/Scripts/Optimization/JobsMovementManager.cs @@ -0,0 +1,129 @@ +using UnityEngine; +using Unity.Collections; +using Unity.Jobs; +using UnityEngine.Jobs; +using Unity.Burst; + +namespace Elbyss.Optimization +{ + /// + /// Manages 10,000+ objects using C# Job System and Burst Compiler. + /// This avoids the overhead of 10,000 individual Update() calls. + /// + public class JobsMovementManager : MonoBehaviour + { + [Header("Spawn Settings")] + public GameObject prefab; + public int objectCount = 10000; + public float spacing = 1.5f; + + [Header("Movement Settings")] + public float speed = 5f; + + private TransformAccessArray transformAccessArray; + private NativeArray directions; + private bool isInitialized = false; + + private void Start() + { + // Optional: Start automatically or via Context Menu + // Setup(objectCount); + } + + [ContextMenu("Setup 10k Objects")] + public void InitialSetup() + { + Setup(objectCount); + } + + public void Setup(int count) + { + if (isInitialized) Cleanup(); + + objectCount = count; + Transform[] transforms = new Transform[objectCount]; + directions = new NativeArray(objectCount, Allocator.Persistent); + + int rowSize = Mathf.CeilToInt(Mathf.Sqrt(objectCount)); + + for (int i = 0; i < objectCount; i++) + { + float x = (i % rowSize) * spacing; + float z = (i / rowSize) * spacing; + Vector3 pos = transform.position + new Vector3(x, 0, z); + + GameObject go = Instantiate(prefab, pos, Quaternion.identity, this.transform); + transforms[i] = go.transform; + + // Set alternating directions + directions[i] = (i % 2 == 0) ? Vector3.forward : Vector3.back; + + // CRITICAL OPTIMIZATION: Disable components that are too heavy for 10k objects + if (go.TryGetComponent(out var anim)) anim.enabled = false; + if (go.TryGetComponent(out var cc)) cc.enabled = false; + + // Disable all other custom scripts + MonoBehaviour[] scripts = go.GetComponents(); + foreach (var s in scripts) + { + if (s != this) s.enabled = false; + } + } + + transformAccessArray = new TransformAccessArray(transforms); + isInitialized = true; + Debug.Log($"Initialized {objectCount} objects with Job System."); + } + + private void Update() + { + if (!isInitialized) return; + + // Create the movement job + var job = new MovementJob + { + DeltaTime = Time.deltaTime, + Speed = speed, + Directions = directions + }; + + // Schedule the job to run in parallel on all available CPU cores + // transformAccessArray allows the job to modify Transform data directly + JobHandle handle = job.Schedule(transformAccessArray); + + // This ensures the job is finished before the frame ends + // In a real scenario, you might want to call Complete() in LateUpdate or next frame + // but for simple movement, scheduling and completing in Update is fine. + handle.Complete(); + } + + private void OnDestroy() + { + Cleanup(); + } + + private void Cleanup() + { + if (isInitialized) + { + if (transformAccessArray.isCreated) transformAccessArray.Dispose(); + if (directions.IsCreated) directions.Dispose(); + isInitialized = false; + } + } + + [BurstCompile] // This attribute tells the Burst compiler to optimize this job into machine code + struct MovementJob : IJobParallelForTransform + { + public float DeltaTime; + public float Speed; + [ReadOnly] public NativeArray Directions; + + public void Execute(int index, TransformAccess transform) + { + // Directly modify the transform position in parallel + transform.position += Directions[index] * Speed * DeltaTime; + } + } + } +} diff --git a/Assets/Scripts/Optimization/JobsMovementManager.cs.meta b/Assets/Scripts/Optimization/JobsMovementManager.cs.meta new file mode 100644 index 00000000..2c3a9a82 --- /dev/null +++ b/Assets/Scripts/Optimization/JobsMovementManager.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 41899df442467714dbee462ae451773a \ No newline at end of file diff --git a/Assets/Scripts/Optimization/MassiveSpawner.cs b/Assets/Scripts/Optimization/MassiveSpawner.cs new file mode 100644 index 00000000..f757a4e1 --- /dev/null +++ b/Assets/Scripts/Optimization/MassiveSpawner.cs @@ -0,0 +1,153 @@ +// using GPUInstancerPro; +// using Unity.Burst; +// using Unity.Collections; +// using Unity.Jobs; +// using Unity.Mathematics; +// using UnityEngine; +// +// namespace Elbyss.Optimization +// { +// public class MassiveSpawner : MonoBehaviour +// { +// [Header("Spawn Settings")] +// public GameObject prefab; +// public GPUIProfile profile; +// public int instanceCount = 100000; +// public float spacing = 1.5f; +// +// [Header("Update Settings")] +// public bool runUpdate = true; +// public float movementSpeed = 1.0f; +// public float amplitude = 2.0f; +// +// private int _rendererKey; +// private NativeArray _matrices; +// private JobHandle _jobHandle; +// private bool _isInitialized; +// +// private void OnEnable() +// { +// Initialize(); +// } +// +// private void OnDisable() +// { +// Dispose(); +// } +// +// private void OnValidate() +// { +// if (Application.isPlaying && _isInitialized) +// { +// // Re-initialize if count changes during play (optional, but good for testing) +// Initialize(); +// } +// } +// +// public void Initialize() +// { +// Dispose(); +// if (prefab == null) return; +// +// if (GPUICoreAPI.RegisterRenderer(this, prefab, profile, out _rendererKey)) +// { +// _matrices = new NativeArray(instanceCount, Allocator.Persistent); +// +// // Initial generation +// GenerateMatrices(0); +// _jobHandle.Complete(); +// GPUICoreAPI.SetTransformBufferData(_rendererKey, _matrices); +// +// _isInitialized = true; +// Debug.Log($"[MassiveSpawner] Registered {instanceCount} instances with key {_rendererKey}"); +// } +// else +// { +// Debug.LogError("[MassiveSpawner] Failed to register renderer!"); +// } +// } +// +// private void Update() +// { +// if (!_isInitialized || _rendererKey == 0) return; +// +// if (runUpdate) +// { +// // Complete previous frame's work if any +// _jobHandle.Complete(); +// +// // Apply updated matrices to GPUI +// GPUICoreAPI.SetTransformBufferData(_rendererKey, _matrices); +// +// // Schedule next frame's work +// GenerateMatrices(Time.time); +// } +// } +// +// private void GenerateMatrices(float time) +// { +// int side = Mathf.CeilToInt(Mathf.Sqrt(instanceCount)); +// +// var job = new MatrixUpdateJob +// { +// matrices = _matrices, +// side = side, +// spacing = spacing, +// time = time, +// speed = movementSpeed, +// amplitude = amplitude, +// origin = transform.position +// }; +// +// _jobHandle = job.Schedule(instanceCount, 64); +// } +// +// public void Dispose() +// { +// _jobHandle.Complete(); +// if (_rendererKey != 0) +// { +// GPUICoreAPI.DisposeRenderer(_rendererKey); +// _rendererKey = 0; +// } +// +// if (_matrices.IsCreated) +// { +// _matrices.Dispose(); +// } +// _isInitialized = false; +// } +// +// [BurstCompile] +// struct MatrixUpdateJob : IJobParallelFor +// { +// public NativeArray matrices; +// public int side; +// public float spacing; +// public float time; +// public float speed; +// public float amplitude; +// public Vector3 origin; +// +// public void Execute(int index) +// { +// int x = index % side; +// int z = index / side; +// +// float xPos = x * spacing; +// float zPos = z * spacing; +// +// // Add some animation to prove it's updating +// float yPos = math.sin(time * speed + (xPos + zPos) * 0.1f) * amplitude; +// +// Vector3 pos = origin + new Vector3(xPos, yPos, zPos); +// +// // Simple rotation based on time +// float angle = (time * speed * 10f + index) % 360f; +// Quaternion rot = Quaternion.Euler(0, angle, 0); +// +// matrices[index] = Matrix4x4.TRS(pos, rot, Vector3.one); +// } +// } +// } +// } diff --git a/Assets/Scripts/Optimization/MassiveSpawner.cs.meta b/Assets/Scripts/Optimization/MassiveSpawner.cs.meta new file mode 100644 index 00000000..334b7683 --- /dev/null +++ b/Assets/Scripts/Optimization/MassiveSpawner.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 50831f537cbccac4c9bf4067b6b158c7 \ No newline at end of file diff --git a/Assets/Scripts/Optimization/PerformanceHUD.cs b/Assets/Scripts/Optimization/PerformanceHUD.cs new file mode 100644 index 00000000..a25d63f5 --- /dev/null +++ b/Assets/Scripts/Optimization/PerformanceHUD.cs @@ -0,0 +1,34 @@ +using UnityEngine; +using TMPro; + +namespace Elbyss.Optimization +{ + public class PerformanceHUD : MonoBehaviour + { + private float deltaTime = 0.0f; + private string displayFormat = "{0:0.0} ms ({1:0.} fps)"; + + private void Update() + { + deltaTime += (Time.unscaledDeltaTime - deltaTime) * 0.1f; + } + + private void OnGUI() + { + int w = Screen.width, h = Screen.height; + + GUIStyle style = new GUIStyle(); + + Rect rect = new Rect(10, 10, w, h * 2 / 100); + style.alignment = TextAnchor.UpperLeft; + style.fontSize = h * 2 / 50; + style.normal.textColor = new Color(0.0f, 1.0f, 0.5f, 1.0f); + + float msec = deltaTime * 1000.0f; + float fps = 1.0f / deltaTime; + string text = string.Format(displayFormat, msec, fps); + + GUI.Label(rect, text, style); + } + } +} diff --git a/Assets/Scripts/Optimization/PerformanceHUD.cs.meta b/Assets/Scripts/Optimization/PerformanceHUD.cs.meta new file mode 100644 index 00000000..031cdef0 --- /dev/null +++ b/Assets/Scripts/Optimization/PerformanceHUD.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 9c9a374a3ab089d41a6784b1ffad3b6f \ No newline at end of file diff --git a/Assets/Scripts/Optimization/StressTestSpawner.cs b/Assets/Scripts/Optimization/StressTestSpawner.cs new file mode 100644 index 00000000..72178969 --- /dev/null +++ b/Assets/Scripts/Optimization/StressTestSpawner.cs @@ -0,0 +1,85 @@ +using System.Collections.Generic; +using UnityEngine; +using OnlyScove.Scripts; + +namespace Elbyss.Optimization +{ + public class StressTestSpawner : MonoBehaviour + { + [Header("Spawn Settings")] + public GameObject prefabToSpawn; + public int spawnLimit = 1000; + public int spawnsPerFrame = 10; + public float spacing = 2.0f; + + [Header("Testing Mode")] + public bool useAutoStateMachine = true; + + [Header("Optimization Options")] + public bool stripHeavyComponents = false; + + private List spawnedObjects = new List(); + private int currentSpawnCount = 0; + private bool isSpawning = false; + + private void Update() + { + if (isSpawning && currentSpawnCount < spawnLimit) + { + for (int i = 0; i < spawnsPerFrame && currentSpawnCount < spawnLimit; i++) + { + SpawnObject(); + } + } + } + + [ContextMenu("Start Stress Test")] + public void StartStressTest() => isSpawning = true; + + [ContextMenu("Clear All")] + public void ClearAll() + { + foreach (var obj in spawnedObjects) if (obj != null) Destroy(obj); + spawnedObjects.Clear(); + currentSpawnCount = 0; + isSpawning = false; + } + + private void SpawnObject() + { + if (prefabToSpawn == null) return; + + int rowSize = Mathf.CeilToInt(Mathf.Sqrt(spawnLimit)); + float x = (currentSpawnCount % rowSize) * spacing; + float z = (currentSpawnCount / rowSize) * spacing; + Vector3 spawnPos = transform.position + new Vector3(x, 0, z); + + GameObject newObj = Instantiate(prefabToSpawn, spawnPos, transform.rotation); + + if (useAutoStateMachine) + { + var realInput = newObj.GetComponent(); + if (realInput != null) realInput.enabled = false; + + var originalSM = newObj.GetComponent(); + if (originalSM != null) + { + DestroyImmediate(originalSM); + var autoSM = newObj.AddComponent(); + autoSM.alwaysMoveForward = true; + autoSM.alwaysRun = true; + } + } + + if (stripHeavyComponents) + { + if (newObj.TryGetComponent(out var anim)) anim.enabled = false; + if (newObj.TryGetComponent(out var cc)) cc.enabled = false; + if (newObj.TryGetComponent(out var sm)) sm.enabled = false; + } + + spawnedObjects.Add(newObj); + currentSpawnCount++; + } + } +} diff --git a/Assets/Scripts/Optimization/StressTestSpawner.cs.meta b/Assets/Scripts/Optimization/StressTestSpawner.cs.meta new file mode 100644 index 00000000..a49cd7b1 --- /dev/null +++ b/Assets/Scripts/Optimization/StressTestSpawner.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 4a7c5ef310b7f354685dc6706be2d530 \ No newline at end of file diff --git a/Assets/Scripts/Player Controller.meta b/Assets/Scripts/Player Controller.meta new file mode 100644 index 00000000..f878e1bf --- /dev/null +++ b/Assets/Scripts/Player Controller.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 8e0fd09e39d1b90458b7097e555a9f3f +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/Player Controller/InputReader.cs b/Assets/Scripts/Player Controller/InputReader.cs new file mode 100644 index 00000000..f62a3b3d --- /dev/null +++ b/Assets/Scripts/Player Controller/InputReader.cs @@ -0,0 +1,89 @@ +using System; +using UnityEngine; +using UnityEngine.InputSystem; + +namespace OnlyScove.Scripts +{ + public class InputReader : MonoBehaviour + { + // Continuous Inputs + public virtual Vector2 MoveInput { get; protected set; } + public virtual Vector2 LookInput { get; protected set; } + public virtual Vector2 ScrollInput { get; protected set; } + public virtual bool IsSprintHeld { get; protected set; } // Left Shift + public virtual bool IsAttackHeld { get; protected set; } // Left Mouse Button + + // One-shot Events + public event Action OnJumpEvent; // Space + public event Action OnDodgeEvent; // Right Mouse Button (RMB) + public event Action OnAttackEvent; // Left Mouse Button (LMB) + public event Action OnCrouchEvent; // C Key + public event Action OnInteractEvent; // E Key + public event Action OnNextInteractEvent; // R Key + public event Action OnPreviousInteractEvent; // Q Key + + public void OnAttack(InputAction.CallbackContext context) + { + if (context.performed) + { + OnAttackEvent?.Invoke(); + IsAttackHeld = true; + } + if (context.canceled) + { + IsAttackHeld = false; + } + } + + public void OnMove(InputAction.CallbackContext context) + { + MoveInput = context.ReadValue(); + } + + public void OnLook(InputAction.CallbackContext context) + { + LookInput = context.ReadValue(); + } + + public void OnScroll(InputAction.CallbackContext context) + { + ScrollInput = context.ReadValue(); + } + + public void OnSprint(InputAction.CallbackContext context) + { + if (context.performed) IsSprintHeld = true; + if (context.canceled) IsSprintHeld = false; + } + + public void OnJump(InputAction.CallbackContext context) + { + if (context.performed) OnJumpEvent?.Invoke(); + } + + public void OnDodgeOrThrust(InputAction.CallbackContext context) + { + if (context.performed) OnDodgeEvent?.Invoke(); + } + + public void OnCrouch(InputAction.CallbackContext context) + { + if (context.performed) OnCrouchEvent?.Invoke(); + } + + public void OnInteract(InputAction.CallbackContext context) + { + if (context.performed) OnInteractEvent?.Invoke(); + } + + public void OnNext(InputAction.CallbackContext context) + { + if (context.performed) OnNextInteractEvent?.Invoke(); + } + + public void OnPrevious(InputAction.CallbackContext context) + { + if (context.performed) OnPreviousInteractEvent?.Invoke(); + } + } +} \ No newline at end of file diff --git a/Assets/Scripts/Player Controller/InputReader.cs.meta b/Assets/Scripts/Player Controller/InputReader.cs.meta new file mode 100644 index 00000000..84788156 --- /dev/null +++ b/Assets/Scripts/Player Controller/InputReader.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 5962d8f2c8e40e240a4a4907c7b539fa \ No newline at end of file diff --git a/Assets/Scripts/Player Controller/ParkourAction.cs b/Assets/Scripts/Player Controller/ParkourAction.cs new file mode 100644 index 00000000..02ae6bdb --- /dev/null +++ b/Assets/Scripts/Player Controller/ParkourAction.cs @@ -0,0 +1,12 @@ +using UnityEngine; + +namespace OnlyScove.Game_Definition +{ + [CreateAssetMenu(fileName = "ParkourAction", menuName = "Parkour System/New Parkour Action")] + public class ParkourAction : ScriptableObject + { + [SerializeField] string animationName; + [SerializeField] private float minHeight; + [SerializeField] private float maxHeight; + } +} diff --git a/Assets/Scripts/Player Controller/ParkourAction.cs.meta b/Assets/Scripts/Player Controller/ParkourAction.cs.meta new file mode 100644 index 00000000..23e0398c --- /dev/null +++ b/Assets/Scripts/Player Controller/ParkourAction.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 985468dedaf32f44191b2cc29c813c8c \ No newline at end of file diff --git a/Assets/Scripts/Player Controller/ParkourController.cs b/Assets/Scripts/Player Controller/ParkourController.cs new file mode 100644 index 00000000..3f515a00 --- /dev/null +++ b/Assets/Scripts/Player Controller/ParkourController.cs @@ -0,0 +1,59 @@ +using System; +using System.Collections; +using UnityEngine; + +namespace OnlyScove.Scripts +{ + public class ParkourController : MonoBehaviour + { + [SerializeField] private InputReader inputReader; + EnvironmentScanner environmentScanner; + Animator animator; + PlayerController playerController; + + bool inAction; + private void Awake() + { + inputReader = GetComponent(); + environmentScanner = GetComponent(); + animator = GetComponent(); + playerController = GetComponent(); + } + + private void OnEnable() + { + inputReader.OnJumpEvent += HandleParkour; + } + + private void OnDisable() + { + inputReader.OnJumpEvent -= HandleParkour; + } + + private void HandleParkour() + { + var hitData = environmentScanner.ObstacleCheck(); + + if (hitData.forwardHitFound) + { + StartCoroutine(DoParkourAction()); + } + } + + IEnumerator DoParkourAction() + { + inAction = true; + playerController.SetControl(false); + + animator.CrossFade("Step Up", 0.1f); + yield return null; + + var animationState = animator.GetNextAnimatorStateInfo(0); + + yield return new WaitForSeconds(animationState.length); + + playerController.SetControl(true); + inAction = false; + } + } +} diff --git a/Assets/Scripts/Player Controller/ParkourController.cs.meta b/Assets/Scripts/Player Controller/ParkourController.cs.meta new file mode 100644 index 00000000..72f45c02 --- /dev/null +++ b/Assets/Scripts/Player Controller/ParkourController.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 0407ea3fbe445ac43b4f1ca3077ce283 \ No newline at end of file diff --git a/Assets/Scripts/Player Controller/PlayerAirDashState.cs b/Assets/Scripts/Player Controller/PlayerAirDashState.cs new file mode 100644 index 00000000..261fb982 --- /dev/null +++ b/Assets/Scripts/Player Controller/PlayerAirDashState.cs @@ -0,0 +1,58 @@ +using UnityEngine; + +namespace OnlyScove.Scripts +{ + public class PlayerAirDashState : PlayerBaseState + { + private readonly int airDashHash = Animator.StringToHash("AirDash"); + private float dashDuration = 0.2f; + private float dashTimer; + private Vector3 dashDirection; + + public PlayerAirDashState(PlayerStateMachine stateMachine) : base(stateMachine) {} + + public override void Enter() + { + dashTimer = dashDuration; + stateMachine.Anim.SetTrigger(airDashHash); + + // Reset vertical velocity so we don't carry falling momentum into the dash + stateMachine.VelocityY = 0f; + + // Determine dash direction + Vector2 input = stateMachine.Input.MoveInput; + if (input != Vector2.zero) + { + dashDirection = new Vector3(input.x, 0f, input.y).normalized; + + if (stateMachine.Cam != null) + { + dashDirection = stateMachine.Cam.PlanarRotation * dashDirection; + } + + stateMachine.transform.rotation = Quaternion.LookRotation(dashDirection); + } + else + { + dashDirection = stateMachine.transform.forward; + } + } + + public override void Tick(float deltaTime) + { + dashTimer -= deltaTime; + + // Move horizontally, ignoring gravity for this brief moment + stateMachine.Controller.Move(dashDirection * stateMachine.DashForce * deltaTime); + + // When the air dash ends, return to falling + if (dashTimer <= 0f) + { + stateMachine.SwitchState(new PlayerFallState(stateMachine)); + } + } + + public override void PhysicsTick(float fixedDeltaTime) {} + public override void Exit() {} + } +} \ No newline at end of file diff --git a/Assets/Scripts/Player Controller/PlayerAirDashState.cs.meta b/Assets/Scripts/Player Controller/PlayerAirDashState.cs.meta new file mode 100644 index 00000000..4dde67d3 --- /dev/null +++ b/Assets/Scripts/Player Controller/PlayerAirDashState.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 43c3e5e400fea604fb432f84d5dd9ce1 \ No newline at end of file diff --git a/Assets/Scripts/Player Controller/PlayerBaseState.cs b/Assets/Scripts/Player Controller/PlayerBaseState.cs new file mode 100644 index 00000000..b7fc648f --- /dev/null +++ b/Assets/Scripts/Player Controller/PlayerBaseState.cs @@ -0,0 +1,25 @@ +namespace OnlyScove.Scripts +{ + public abstract class PlayerBaseState + { + protected PlayerStateMachine stateMachine; + + // Constructor to pass the state machine reference + public PlayerBaseState(PlayerStateMachine stateMachine) + { + this.stateMachine = stateMachine; + } + + // Called once when entering the state + public abstract void Enter(); + + // Called every frame (equivalent to Update) + public abstract void Tick(float deltaTime); + + // Called every physics frame (equivalent to FixedUpdate) + public abstract void PhysicsTick(float fixedDeltaTime); + + // Called once before switching to a new state + public abstract void Exit(); + } +} \ No newline at end of file diff --git a/Assets/Scripts/Player Controller/PlayerBaseState.cs.meta b/Assets/Scripts/Player Controller/PlayerBaseState.cs.meta new file mode 100644 index 00000000..d67fe415 --- /dev/null +++ b/Assets/Scripts/Player Controller/PlayerBaseState.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: af622221f1750284885366b02b8bfbba \ No newline at end of file diff --git a/Assets/Scripts/Player Controller/PlayerController.cs b/Assets/Scripts/Player Controller/PlayerController.cs new file mode 100644 index 00000000..903f8add --- /dev/null +++ b/Assets/Scripts/Player Controller/PlayerController.cs @@ -0,0 +1,134 @@ +using System; +using Unity.VisualScripting; +using UnityEngine; + +namespace OnlyScove.Scripts +{ + public class PlayerController : MonoBehaviour + { + private static readonly int MoveAmount = Animator.StringToHash("moveAmount"); + + [SerializeField] private InputReader inputReader; + [SerializeField] private float rotationSpeed = 500f; + [SerializeField] private float moveSpeed = 10f; + [SerializeField] private float jumpHeight = 2f; + [SerializeField] private float animationDamping = 0.2f; + [SerializeField] private float groundCheckRadius = 0.2f; + [SerializeField] private Vector3 groundCheckOffset; + [SerializeField] private LayerMask groundMask; + + CameraController cameraController; + Animator animator; + private CharacterController characterController; + + Quaternion targetRotation; + private float horizontal; + private float vertical; + bool isGrounded; + private bool wasGrounded; + private bool hasControl = true; + private float ySpeed; + + private void Awake() + { + if (Camera.main != null) cameraController = Camera.main.GetComponent(); + animator = GetComponent(); + characterController = GetComponent(); + } + + private void OnEnable() + { + inputReader.OnJumpEvent += HandleJump; + } + + private void OnDisable() + { + inputReader.OnJumpEvent -= HandleJump; + } + + private void HandleJump() + { + if (isGrounded && hasControl) + { + // Công thức tính vận tốc nhảy: v = sqrt(h * -2 * g) + ySpeed = Mathf.Sqrt(jumpHeight * -2f * Physics.gravity.y); + } + } + + private void Update() + { + horizontal = inputReader.MoveInput.x; + vertical = inputReader.MoveInput.y; + float moveAmount = Mathf.Clamp01(Math.Abs(horizontal) + Math.Abs(vertical)); + + var moveInput = (new Vector3(horizontal, 0, vertical)).normalized; + + var moveDirection = cameraController.PlanarRotation * moveInput; + + if (!hasControl) + return; + + wasGrounded = isGrounded; + GroundCheck(); + + // Phát hiện tiếp đất (Landing) + if (isGrounded && !wasGrounded && ySpeed < -1f) + { + // Rung camera khi tiếp đất mạnh + if (cameraController != null) + { + cameraController.Shake(0.2f, 0.15f); + } + } + + if (isGrounded && ySpeed < 0) + { + ySpeed = -2f; // Giữ nhân vật dính xuống mặt đất + } + else + { + ySpeed += Physics.gravity.y * Time.deltaTime; + } + + var velocity = moveDirection * moveSpeed; + velocity.y = ySpeed; + + characterController.Move(velocity * Time.deltaTime); + + if (moveAmount > 0) + { + targetRotation = Quaternion.LookRotation(moveDirection); + + } + + transform.rotation = Quaternion.RotateTowards(transform.rotation, targetRotation, + Time.deltaTime * rotationSpeed); + + animator.SetFloat(MoveAmount, moveAmount, animationDamping, Time.deltaTime); + } + + void GroundCheck() + { + isGrounded = + Physics.CheckSphere(transform.TransformPoint(groundCheckOffset), groundCheckRadius, groundMask); + } + + public void SetControl(bool control) + { + this.hasControl = control; + characterController.enabled = hasControl; + + if (!hasControl) + { + animator.SetFloat(MoveAmount, 0f); + targetRotation = transform.rotation; + } + } + + private void OnDrawGizmosSelected() + { + Gizmos.color = new Color(0, 1, 0, 0.5f); + Gizmos.DrawSphere(transform.TransformPoint(groundCheckOffset), groundCheckRadius); + } + } +} diff --git a/Assets/Scripts/Player Controller/PlayerController.cs.meta b/Assets/Scripts/Player Controller/PlayerController.cs.meta new file mode 100644 index 00000000..791e6827 --- /dev/null +++ b/Assets/Scripts/Player Controller/PlayerController.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: cd897e8bcaabdc8408bb6aaf7c037537 \ No newline at end of file diff --git a/Assets/Scripts/Player Controller/PlayerCrouchState.cs b/Assets/Scripts/Player Controller/PlayerCrouchState.cs new file mode 100644 index 00000000..bb17cdc0 --- /dev/null +++ b/Assets/Scripts/Player Controller/PlayerCrouchState.cs @@ -0,0 +1,97 @@ +using UnityEngine; + +namespace OnlyScove.Scripts +{ + public class PlayerCrouchState : PlayerBaseState + { + private readonly int isCrouchingHash = Animator.StringToHash("IsCrouching"); + private readonly int crouchSpeedHash = Animator.StringToHash("CrouchSpeed"); + private const float AnimatorDampTime = 0.1f; + + public PlayerCrouchState(PlayerStateMachine stateMachine) : base(stateMachine) {} + + public override void Enter() + { + stateMachine.Anim.SetBool(isCrouchingHash, true); + stateMachine.Input.OnCrouchEvent += OnCrouchToggle; + stateMachine.Input.OnDodgeEvent += OnDodge; + } + + public override void Tick(float deltaTime) + { + Vector2 moveInput = stateMachine.Input.MoveInput; + float currentSpeed = 0f; + float animValue = 0f; + + if (moveInput == Vector2.zero) + { + animValue = 0f; + } + else + { + if (stateMachine.Input.IsSprintHeld) + { + currentSpeed = stateMachine.SneakSpeed; + animValue = 0.5f; + } + else + { + currentSpeed = stateMachine.WalkSpeed; + animValue = 1.0f; + } + + Vector3 moveDirection = new Vector3(moveInput.x, 0f, moveInput.y).normalized; + + if (stateMachine.Cam != null) + { + moveDirection = stateMachine.Cam.PlanarRotation * moveDirection; + } + + // Apply horizontal movement + Vector3 movement = moveDirection * currentSpeed; + + // Apply gravity + if (stateMachine.Controller.isGrounded) + { + stateMachine.VelocityY = -2f; + } + else + { + stateMachine.VelocityY += stateMachine.Gravity * deltaTime; + } + + movement.y = stateMachine.VelocityY; + + stateMachine.Controller.Move(movement * deltaTime); + + Quaternion targetRotation = Quaternion.LookRotation(moveDirection); + stateMachine.transform.rotation = Quaternion.Slerp( + stateMachine.transform.rotation, + targetRotation, + deltaTime * 10f + ); + } + + stateMachine.Anim.SetFloat(crouchSpeedHash, animValue, AnimatorDampTime, deltaTime); + } + + public override void PhysicsTick(float fixedDeltaTime) {} + + public override void Exit() + { + stateMachine.Anim.SetBool(isCrouchingHash, false); + stateMachine.Input.OnCrouchEvent -= OnCrouchToggle; + stateMachine.Input.OnDodgeEvent -= OnDodge; + } + + private void OnCrouchToggle() + { + if (stateMachine.Input.MoveInput == Vector2.zero) + stateMachine.SwitchState(new PlayerIdleState(stateMachine)); + else + stateMachine.SwitchState(new PlayerMoveState(stateMachine)); + } + + private void OnDodge() => stateMachine.SwitchState(new PlayerDodgeState(stateMachine)); + } +} \ No newline at end of file diff --git a/Assets/Scripts/Player Controller/PlayerCrouchState.cs.meta b/Assets/Scripts/Player Controller/PlayerCrouchState.cs.meta new file mode 100644 index 00000000..269e98e5 --- /dev/null +++ b/Assets/Scripts/Player Controller/PlayerCrouchState.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 82b04241d720e444a937973caed8eb42 \ No newline at end of file diff --git a/Assets/Scripts/Player Controller/PlayerDashState.cs b/Assets/Scripts/Player Controller/PlayerDashState.cs new file mode 100644 index 00000000..03891e85 --- /dev/null +++ b/Assets/Scripts/Player Controller/PlayerDashState.cs @@ -0,0 +1,86 @@ +using UnityEngine; + +namespace OnlyScove.Scripts +{ + public class PlayerDashState : PlayerBaseState + { + private readonly int dashHash = Animator.StringToHash("Dash"); // Trigger parameter + private float dashDuration = 0.25f; // How long the burst lasts (tweak as needed) + private float dashTimer; + private Vector3 dashDirection; + + public PlayerDashState(PlayerStateMachine stateMachine) : base(stateMachine) {} + + public override void Enter() + { + dashTimer = dashDuration; + + // Fire the Dash animation trigger + stateMachine.Anim.SetTrigger(dashHash); + + stateMachine.Input.OnJumpEvent += OnJump; + + // Determine dash direction based on input, or default to forward if no input + Vector2 input = stateMachine.Input.MoveInput; + if (input != Vector2.zero) + { + dashDirection = new Vector3(input.x, 0f, input.y).normalized; + + if (stateMachine.Cam != null) + { + dashDirection = stateMachine.Cam.PlanarRotation * dashDirection; + } + + // Instantly snap rotation to face the dash direction + stateMachine.transform.rotation = Quaternion.LookRotation(dashDirection); + } + else + { + dashDirection = stateMachine.transform.forward; + } + } + + public override void Tick(float deltaTime) + { + dashTimer -= deltaTime; + + // Apply high speed for the dash (Burst speed) + stateMachine.Controller.Move(dashDirection * stateMachine.SprintSpeed * deltaTime); + + // When the dash finishes, decide the next state + if (dashTimer <= 0f) + { + if (stateMachine.Input.IsSprintHeld && stateMachine.Input.MoveInput != Vector2.zero) + { + // Kept holding Shift -> Go to Sprint + stateMachine.SwitchState(new PlayerRunState(stateMachine)); + } + else if (stateMachine.Input.MoveInput != Vector2.zero) + { + // Released Shift but still moving -> Go to Walk + stateMachine.SwitchState(new PlayerMoveState(stateMachine)); + } + else + { + // Stopped moving -> Go to Idle + stateMachine.SwitchState(new PlayerIdleState(stateMachine)); + } + } + } + + public override void PhysicsTick(float fixedDeltaTime) {} + + public override void Exit() + { + stateMachine.Input.OnJumpEvent -= OnJump; + } + + private void OnJump() + { + if (stateMachine.IsGrounded) + { + stateMachine.SwitchState(new PlayerJumpState(stateMachine, stateMachine.SprintSpeed)); + } + } + } +} \ No newline at end of file diff --git a/Assets/Scripts/Player Controller/PlayerDashState.cs.meta b/Assets/Scripts/Player Controller/PlayerDashState.cs.meta new file mode 100644 index 00000000..9e689d92 --- /dev/null +++ b/Assets/Scripts/Player Controller/PlayerDashState.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 03721c0a319be8c4093b616e2f46afe8 \ No newline at end of file diff --git a/Assets/Scripts/Player Controller/PlayerDodgeState.cs b/Assets/Scripts/Player Controller/PlayerDodgeState.cs new file mode 100644 index 00000000..1a1adc18 --- /dev/null +++ b/Assets/Scripts/Player Controller/PlayerDodgeState.cs @@ -0,0 +1,65 @@ +using UnityEngine; + +namespace OnlyScove.Scripts +{ + public class PlayerDodgeState : PlayerBaseState + { + private readonly int dodgeHash = Animator.StringToHash("Dodge"); + private float dodgeDuration = 0.4f; // Adjust based on your dodge animation length + private float dodgeTimer; + private Vector3 dodgeDirection; + + public PlayerDodgeState(PlayerStateMachine stateMachine) : base(stateMachine) {} + + public override void Enter() + { + dodgeTimer = dodgeDuration; + stateMachine.Anim.SetTrigger(dodgeHash); + + // Calculate dodge direction based on current input + Vector2 input = stateMachine.Input.MoveInput; + if (input != Vector2.zero) + { + // Dodge in the input direction (Left, Right, Back, Forward) + dodgeDirection = new Vector3(input.x, 0f, input.y).normalized; + + if (stateMachine.Cam != null) + { + dodgeDirection = stateMachine.Cam.PlanarRotation * dodgeDirection; + } + + // Instantly rotate the player to face the dodge direction + stateMachine.transform.rotation = Quaternion.LookRotation(dodgeDirection); + } + else + { + // If no input, just roll straight forward + dodgeDirection = stateMachine.transform.forward; + } + } + + public override void Tick(float deltaTime) + { + dodgeTimer -= deltaTime; + + // Apply movement force for the dodge (usually slightly slower than a Dash) + stateMachine.Controller.Move(dodgeDirection * (stateMachine.DashForce * 0.8f) * deltaTime); + + // Once the roll finishes, transition back to Idle or Move + if (dodgeTimer <= 0f) + { + if (stateMachine.Input.MoveInput != Vector2.zero) + { + stateMachine.SwitchState(new PlayerMoveState(stateMachine)); + } + else + { + stateMachine.SwitchState(new PlayerIdleState(stateMachine)); + } + } + } + + public override void PhysicsTick(float fixedDeltaTime) {} + public override void Exit() {} + } +} \ No newline at end of file diff --git a/Assets/Scripts/Player Controller/PlayerDodgeState.cs.meta b/Assets/Scripts/Player Controller/PlayerDodgeState.cs.meta new file mode 100644 index 00000000..bb7be8b9 --- /dev/null +++ b/Assets/Scripts/Player Controller/PlayerDodgeState.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 71bce3b0373df774c8b1598487bcd4ee \ No newline at end of file diff --git a/Assets/Scripts/Player Controller/PlayerFallState.cs b/Assets/Scripts/Player Controller/PlayerFallState.cs new file mode 100644 index 00000000..396de4bd --- /dev/null +++ b/Assets/Scripts/Player Controller/PlayerFallState.cs @@ -0,0 +1,82 @@ +using UnityEngine; + +namespace OnlyScove.Scripts +{ + public class PlayerFallState : PlayerBaseState + { + private readonly int fallHash = Animator.StringToHash("Fall"); + private float fallSpeed; + + public PlayerFallState(PlayerStateMachine stateMachine, float fallSpeed = -1f) : base(stateMachine) + { + if (fallSpeed < 0) + { + this.fallSpeed = stateMachine.WalkSpeed; + } + else + { + this.fallSpeed = fallSpeed; + } + } + + public override void Enter() + { + stateMachine.Anim.SetTrigger(fallHash); + stateMachine.Input.OnDodgeEvent += OnThrustPressed; + } + + public override void Tick(float deltaTime) + { + stateMachine.VelocityY += Physics.gravity.y * deltaTime; + + Vector2 input = stateMachine.Input.MoveInput; + Vector3 inputDir = new Vector3(input.x, 0, input.y).normalized; + Vector3 moveDirection = stateMachine.Cam != null ? stateMachine.Cam.PlanarRotation * inputDir : inputDir; + + Vector3 velocity = moveDirection * fallSpeed; + velocity.y = stateMachine.VelocityY; + + stateMachine.Controller.Move(velocity * deltaTime); + + if (stateMachine.Input.IsSprintHeld) + { + stateMachine.SwitchState(new PlayerAirDashState(stateMachine)); + return; + } + + if (stateMachine.IsGrounded) + { + // Landing Shake from PlayerController.cs + if (!stateMachine.WasGrounded && stateMachine.VelocityY < -1f) + { + if (stateMachine.Cam != null) + { + stateMachine.Cam.Shake(0.2f, 0.15f); + } + } + + stateMachine.VelocityY = -2f; + + if (input == Vector2.zero) + stateMachine.SwitchState(new PlayerIdleState(stateMachine)); + else + { + // Return to the appropriate movement state based on sprint input + if (stateMachine.Input.IsSprintHeld) + stateMachine.SwitchState(new PlayerRunState(stateMachine)); + else + stateMachine.SwitchState(new PlayerMoveState(stateMachine)); + } + } + } + + private void OnThrustPressed() => stateMachine.SwitchState(new PlayerThrustState(stateMachine)); + + public override void PhysicsTick(float fixedDeltaTime) {} + + public override void Exit() + { + stateMachine.Input.OnDodgeEvent -= OnThrustPressed; + } + } +} \ No newline at end of file diff --git a/Assets/Scripts/Player Controller/PlayerFallState.cs.meta b/Assets/Scripts/Player Controller/PlayerFallState.cs.meta new file mode 100644 index 00000000..fb45b846 --- /dev/null +++ b/Assets/Scripts/Player Controller/PlayerFallState.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: e2b239715e32b6a4791f677e0159b862 \ No newline at end of file diff --git a/Assets/Scripts/Player Controller/PlayerIdleState.cs b/Assets/Scripts/Player Controller/PlayerIdleState.cs new file mode 100644 index 00000000..509df688 --- /dev/null +++ b/Assets/Scripts/Player Controller/PlayerIdleState.cs @@ -0,0 +1,71 @@ +using UnityEngine; + +namespace OnlyScove.Scripts +{ + public class PlayerIdleState : PlayerBaseState + { + private readonly int speedHash = Animator.StringToHash("Speed"); + + public PlayerIdleState(PlayerStateMachine stateMachine) : base(stateMachine) {} + + public override void Enter() + { + stateMachine.Input.OnJumpEvent += OnJump; + stateMachine.Input.OnDodgeEvent += OnDodge; + stateMachine.Input.OnCrouchEvent += OnCrouch; + stateMachine.Input.OnInteractEvent += OnInteract; + } + + public override void Tick(float deltaTime) + { + stateMachine.Anim.SetFloat(speedHash, 0f, stateMachine.AnimationDamping, deltaTime); + + if (stateMachine.Input.MoveInput != Vector2.zero) + { + stateMachine.SwitchState(new PlayerMoveState(stateMachine)); + return; + } + + if (stateMachine.IsGrounded && stateMachine.VelocityY < 0) + { + stateMachine.VelocityY = -2f; + } + else + { + stateMachine.VelocityY += Physics.gravity.y * deltaTime; + } + + stateMachine.Controller.Move(new Vector3(0, stateMachine.VelocityY, 0) * deltaTime); + } + + public override void PhysicsTick(float fixedDeltaTime) {} + + public override void Exit() + { + stateMachine.Input.OnJumpEvent -= OnJump; + stateMachine.Input.OnDodgeEvent -= OnDodge; + stateMachine.Input.OnCrouchEvent -= OnCrouch; + stateMachine.Input.OnInteractEvent -= OnInteract; + } + + private void OnJump() + { + if (stateMachine.IsGrounded) + { + if (stateMachine.Scanner != null) + { + var hitData = stateMachine.Scanner.ObstacleCheck(); + if (hitData.forwardHitFound) + { + stateMachine.SwitchState(new PlayerParkourState(stateMachine)); + return; + } + } + stateMachine.SwitchState(new PlayerJumpState(stateMachine, stateMachine.WalkSpeed)); + } + } + private void OnDodge() => stateMachine.SwitchState(new PlayerDodgeState(stateMachine)); + private void OnCrouch() => stateMachine.SwitchState(new PlayerCrouchState(stateMachine)); + private void OnInteract() => stateMachine.SwitchState(new PlayerInteractState(stateMachine)); + } +} \ No newline at end of file diff --git a/Assets/Scripts/Player Controller/PlayerIdleState.cs.meta b/Assets/Scripts/Player Controller/PlayerIdleState.cs.meta new file mode 100644 index 00000000..313a184c --- /dev/null +++ b/Assets/Scripts/Player Controller/PlayerIdleState.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: fd83e34f11c7aaa4ab144ee59db04e8d \ No newline at end of file diff --git a/Assets/Scripts/Player Controller/PlayerInteractState.cs b/Assets/Scripts/Player Controller/PlayerInteractState.cs new file mode 100644 index 00000000..9617ebb7 --- /dev/null +++ b/Assets/Scripts/Player Controller/PlayerInteractState.cs @@ -0,0 +1,38 @@ +using UnityEngine; + +namespace OnlyScove.Scripts +{ + public class PlayerInteractState : PlayerBaseState + { + public PlayerInteractState(PlayerStateMachine stateMachine) : base(stateMachine) {} + + public override void Enter() + { + // Lấy vật thể đang được chọn (Index hiện tại) + IInteractable interactable = stateMachine.GetInteractable(); + + if (interactable != null) + { + Debug.Log($"[Interaction] Interacting with: {interactable.InteractionPrompt}"); + interactable.OnInteract(stateMachine); + + // Bạn có thể phát animation tương tác ở đây + // stateMachine.Anim.CrossFadeInFixedTime("Interact", 0.1f); + } + + // Chuyển về trạng thái di chuyển hoặc đứng yên ngay lập tức + if (stateMachine.Input.MoveInput == Vector2.zero) + { + stateMachine.SwitchState(new PlayerIdleState(stateMachine)); + } + else + { + stateMachine.SwitchState(new PlayerMoveState(stateMachine)); + } + } + + public override void Tick(float deltaTime) { } + public override void PhysicsTick(float fixedDeltaTime) { } + public override void Exit() { } + } +} \ No newline at end of file diff --git a/Assets/Scripts/Player Controller/PlayerInteractState.cs.meta b/Assets/Scripts/Player Controller/PlayerInteractState.cs.meta new file mode 100644 index 00000000..cfabf0df --- /dev/null +++ b/Assets/Scripts/Player Controller/PlayerInteractState.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 0b772d1c0c26c634fad5e71b43db9385 \ No newline at end of file diff --git a/Assets/Scripts/Player Controller/PlayerJumpState.cs b/Assets/Scripts/Player Controller/PlayerJumpState.cs new file mode 100644 index 00000000..9f579b69 --- /dev/null +++ b/Assets/Scripts/Player Controller/PlayerJumpState.cs @@ -0,0 +1,52 @@ +using UnityEngine; + +namespace OnlyScove.Scripts +{ + public class PlayerJumpState : PlayerBaseState + { + private readonly int jumpHash = Animator.StringToHash("Jump"); + private float jumpSpeed; + + public PlayerJumpState(PlayerStateMachine stateMachine, float jumpSpeed = -1f) : base(stateMachine) + { + if (jumpSpeed < 0) + { + this.jumpSpeed = stateMachine.WalkSpeed; + } + else + { + this.jumpSpeed = jumpSpeed; + } + } + + public override void Enter() + { + stateMachine.Anim.SetTrigger(jumpHash); + + // Physic formula: v = sqrt(h * -2 * g) + stateMachine.VelocityY = Mathf.Sqrt(stateMachine.JumpHeight * -2f * Physics.gravity.y); + } + + public override void Tick(float deltaTime) + { + stateMachine.VelocityY += Physics.gravity.y * deltaTime; + + Vector2 input = stateMachine.Input.MoveInput; + Vector3 inputDir = new Vector3(input.x, 0, input.y).normalized; + Vector3 moveDirection = stateMachine.Cam != null ? stateMachine.Cam.PlanarRotation * inputDir : inputDir; + + Vector3 velocity = moveDirection * jumpSpeed; + velocity.y = stateMachine.VelocityY; + + stateMachine.Controller.Move(velocity * deltaTime); + + if (stateMachine.VelocityY <= 0f) + { + stateMachine.SwitchState(new PlayerFallState(stateMachine, jumpSpeed)); + } + } + + public override void PhysicsTick(float fixedDeltaTime) {} + public override void Exit() {} + } +} \ No newline at end of file diff --git a/Assets/Scripts/Player Controller/PlayerJumpState.cs.meta b/Assets/Scripts/Player Controller/PlayerJumpState.cs.meta new file mode 100644 index 00000000..1e1df8e3 --- /dev/null +++ b/Assets/Scripts/Player Controller/PlayerJumpState.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 7ab5bb519df10fe45a5af5f45111ed4d \ No newline at end of file diff --git a/Assets/Scripts/Player Controller/PlayerMoveState.cs b/Assets/Scripts/Player Controller/PlayerMoveState.cs new file mode 100644 index 00000000..01d8d195 --- /dev/null +++ b/Assets/Scripts/Player Controller/PlayerMoveState.cs @@ -0,0 +1,97 @@ +using UnityEngine; + +namespace OnlyScove.Scripts +{ + public class PlayerMoveState : PlayerBaseState + { + private readonly int speedHash = Animator.StringToHash("Speed"); + + public PlayerMoveState(PlayerStateMachine stateMachine) : base(stateMachine) {} + + public override void Enter() + { + stateMachine.Input.OnJumpEvent += OnJump; + stateMachine.Input.OnDodgeEvent += OnDodge; + stateMachine.Input.OnCrouchEvent += OnCrouch; + stateMachine.Input.OnInteractEvent += OnInteract; + } + + public override void Tick(float deltaTime) + { + Vector2 input = stateMachine.Input.MoveInput; + float moveAmount = Mathf.Clamp01(Mathf.Abs(input.x) + Mathf.Abs(input.y)); + + if (moveAmount <= 0.01f) + { + stateMachine.SwitchState(new PlayerIdleState(stateMachine)); + return; + } + + if (stateMachine.Input.IsSprintHeld) + { + stateMachine.SwitchState(new PlayerDashState(stateMachine)); + return; + } + + Vector3 inputDir = new Vector3(input.x, 0, input.y).normalized; + Vector3 moveDirection = stateMachine.Cam != null ? stateMachine.Cam.PlanarRotation * inputDir : inputDir; + + Vector3 velocity = moveDirection * stateMachine.RunSpeed; + + if (stateMachine.IsGrounded && stateMachine.VelocityY < 0) + { + stateMachine.VelocityY = -2f; + } + else + { + stateMachine.VelocityY += Physics.gravity.y * deltaTime; + } + velocity.y = stateMachine.VelocityY; + + stateMachine.Controller.Move(velocity * deltaTime); + + if (moveDirection != Vector3.zero) + { + Quaternion targetRot = Quaternion.LookRotation(moveDirection); + stateMachine.transform.rotation = Quaternion.RotateTowards( + stateMachine.transform.rotation, + targetRot, + stateMachine.RotationSpeed * deltaTime + ); + } + + stateMachine.Anim.SetFloat(speedHash, 0.7f, stateMachine.AnimationDamping, deltaTime); + } + + public override void PhysicsTick(float fixedDeltaTime) {} + + public override void Exit() + { + stateMachine.Input.OnJumpEvent -= OnJump; + stateMachine.Input.OnDodgeEvent -= OnDodge; + stateMachine.Input.OnCrouchEvent -= OnCrouch; + stateMachine.Input.OnInteractEvent -= OnInteract; + } + + private void OnJump() + { + if (stateMachine.IsGrounded) + { + if (stateMachine.Scanner != null) + { + var hitData = stateMachine.Scanner.ObstacleCheck(); + if (hitData.forwardHitFound) + { + stateMachine.SwitchState(new PlayerParkourState(stateMachine)); + return; + } + } + stateMachine.SwitchState(new PlayerJumpState(stateMachine, stateMachine.RunSpeed)); + } + } + + private void OnDodge() => stateMachine.SwitchState(new PlayerDodgeState(stateMachine)); + private void OnCrouch() => stateMachine.SwitchState(new PlayerCrouchState(stateMachine)); + private void OnInteract() => stateMachine.SwitchState(new PlayerInteractState(stateMachine)); + } +} \ No newline at end of file diff --git a/Assets/Scripts/Player Controller/PlayerMoveState.cs.meta b/Assets/Scripts/Player Controller/PlayerMoveState.cs.meta new file mode 100644 index 00000000..b0e20926 --- /dev/null +++ b/Assets/Scripts/Player Controller/PlayerMoveState.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: a11b0f0549f3dbd45bbdfd8114586a43 \ No newline at end of file diff --git a/Assets/Scripts/Player Controller/PlayerParkourState.cs b/Assets/Scripts/Player Controller/PlayerParkourState.cs new file mode 100644 index 00000000..6e7b8a42 --- /dev/null +++ b/Assets/Scripts/Player Controller/PlayerParkourState.cs @@ -0,0 +1,53 @@ +using UnityEngine; + +namespace OnlyScove.Scripts +{ + public class PlayerParkourState : PlayerBaseState + { + private readonly int parkourHash = Animator.StringToHash("Step Up"); + private float animationDuration; + private float timer; + + public PlayerParkourState(PlayerStateMachine stateMachine) : base(stateMachine) {} + + public override void Enter() + { + // Play the Parkour animation (Step Up) + stateMachine.Anim.CrossFadeInFixedTime(parkourHash, 0.1f); + + // We'll wait for the animation to finish. + // In a real project, you might get the exact duration from the Animator. + // For now, we'll assume a fixed duration or check state. + timer = 0f; + } + + public override void Tick(float deltaTime) + { + timer += deltaTime; + + // Simple way to wait for animation: check normalized time of the current state + var stateInfo = stateMachine.Anim.GetCurrentAnimatorStateInfo(0); + if (stateInfo.shortNameHash == parkourHash && stateInfo.normalizedTime >= 1f) + { + if (stateMachine.Input.MoveInput == Vector2.zero) + stateMachine.SwitchState(new PlayerIdleState(stateMachine)); + else + stateMachine.SwitchState(new PlayerMoveState(stateMachine)); + } + + // Safety timeout if animation doesn't play or something + if (timer > 2f) + { + stateMachine.SwitchState(new PlayerIdleState(stateMachine)); + } + } + + public override void PhysicsTick(float fixedDeltaTime) + { + // Usually during parkour we disable gravity or handle it specially + stateMachine.VelocityY = 0; + } + + public override void Exit() {} + } +} \ No newline at end of file diff --git a/Assets/Scripts/Player Controller/PlayerParkourState.cs.meta b/Assets/Scripts/Player Controller/PlayerParkourState.cs.meta new file mode 100644 index 00000000..c23eec07 --- /dev/null +++ b/Assets/Scripts/Player Controller/PlayerParkourState.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: d9b73ee252f157e49a08f8f7566dc6ac \ No newline at end of file diff --git a/Assets/Scripts/Player Controller/PlayerRunState.cs b/Assets/Scripts/Player Controller/PlayerRunState.cs new file mode 100644 index 00000000..51616543 --- /dev/null +++ b/Assets/Scripts/Player Controller/PlayerRunState.cs @@ -0,0 +1,94 @@ +using UnityEngine; + +namespace OnlyScove.Scripts +{ + public class PlayerRunState : PlayerBaseState + { + private readonly int speedHash = Animator.StringToHash("Speed"); + + public PlayerRunState(PlayerStateMachine stateMachine) : base(stateMachine) {} + + public override void Enter() + { + stateMachine.Input.OnJumpEvent += OnJump; + stateMachine.Input.OnDodgeEvent += OnDodge; + stateMachine.Input.OnCrouchEvent += OnCrouch; + } + + public override void Tick(float deltaTime) + { + Vector2 input = stateMachine.Input.MoveInput; + float moveAmount = Mathf.Clamp01(Mathf.Abs(input.x) + Mathf.Abs(input.y)); + + if (moveAmount <= 0.01f) + { + stateMachine.SwitchState(new PlayerIdleState(stateMachine)); + return; + } + + if (!stateMachine.Input.IsSprintHeld) + { + stateMachine.SwitchState(new PlayerMoveState(stateMachine)); + return; + } + + Vector3 inputDir = new Vector3(input.x, 0, input.y).normalized; + Vector3 moveDirection = stateMachine.Cam != null ? stateMachine.Cam.PlanarRotation * inputDir : inputDir; + + Vector3 velocity = moveDirection * stateMachine.SprintSpeed; + + if (stateMachine.IsGrounded && stateMachine.VelocityY < 0) + { + stateMachine.VelocityY = -2f; + } + else + { + stateMachine.VelocityY += Physics.gravity.y * deltaTime; + } + velocity.y = stateMachine.VelocityY; + + stateMachine.Controller.Move(velocity * deltaTime); + + if (moveDirection != Vector3.zero) + { + Quaternion targetRot = Quaternion.LookRotation(moveDirection); + stateMachine.transform.rotation = Quaternion.RotateTowards( + stateMachine.transform.rotation, + targetRot, + stateMachine.RotationSpeed * deltaTime + ); + } + + stateMachine.Anim.SetFloat(speedHash, 1f, stateMachine.AnimationDamping, deltaTime); + } + + public override void PhysicsTick(float fixedDeltaTime) {} + + public override void Exit() + { + stateMachine.Input.OnJumpEvent -= OnJump; + stateMachine.Input.OnDodgeEvent -= OnDodge; + stateMachine.Input.OnCrouchEvent -= OnCrouch; + } + + private void OnJump() + { + if (stateMachine.IsGrounded) + { + if (stateMachine.Scanner != null) + { + var hitData = stateMachine.Scanner.ObstacleCheck(); + if (hitData.forwardHitFound) + { + stateMachine.SwitchState(new PlayerParkourState(stateMachine)); + return; + } + } + stateMachine.SwitchState(new PlayerJumpState(stateMachine, stateMachine.SprintSpeed)); + } + } + + private void OnDodge() => stateMachine.SwitchState(new PlayerDodgeState(stateMachine)); + private void OnCrouch() => stateMachine.SwitchState(new PlayerCrouchState(stateMachine)); + } +} \ No newline at end of file diff --git a/Assets/Scripts/Player Controller/PlayerRunState.cs.meta b/Assets/Scripts/Player Controller/PlayerRunState.cs.meta new file mode 100644 index 00000000..c6fa4e62 --- /dev/null +++ b/Assets/Scripts/Player Controller/PlayerRunState.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: ef096feea261a8d449384a68f71470b3 \ No newline at end of file diff --git a/Assets/Scripts/Player Controller/PlayerStateMachine.cs b/Assets/Scripts/Player Controller/PlayerStateMachine.cs new file mode 100644 index 00000000..19a2ee9b --- /dev/null +++ b/Assets/Scripts/Player Controller/PlayerStateMachine.cs @@ -0,0 +1,177 @@ +using System.Collections.Generic; +using UnityEngine; + +namespace OnlyScove.Scripts +{ + [RequireComponent(typeof(CharacterController), typeof(InputReader), typeof(Animator))] + public class PlayerStateMachine : MonoBehaviour + { + [field: Header("References")] + [field: SerializeField] public CharacterController Controller { get; private set; } + [field: SerializeField] public virtual InputReader Input { get; private set; } + [field: SerializeField] public Animator Anim { get; private set; } + [field: SerializeField] public EnvironmentScanner Scanner { get; private set; } + public CameraController Cam { get; private set; } + + [field: Header("Movement Settings")] + [field: SerializeField] public float WalkSpeed { get; private set; } = 3f; + [field: SerializeField] public float RunSpeed { get; private set; } = 6f; + [field: SerializeField] public float SprintSpeed { get; private set; } = 9f; // 150% of RunSpeed + [field: SerializeField] public float SneakSpeed { get; private set; } = 1.5f; + [field: SerializeField] public float DashForce { get; private set; } = 10f; + [field: SerializeField] public float RotationSpeed { get; private set; } = 500f; + [field: SerializeField] public float AnimationDamping { get; private set; } = 0.2f; + + [field: Header("Airborne Settings")] + [field: SerializeField] public float JumpHeight { get; private set; } = 2f; + [field: SerializeField] public float Gravity { get; private set; } = -9.81f; + [field: SerializeField] public float ThrustDownwardForce { get; private set; } = -20f; + + [field: Header("Ground Check")] + [field: SerializeField] public float GroundCheckRadius { get; private set; } = 0.2f; + [field: SerializeField] public Vector3 GroundCheckOffset { get; private set; } + [field: SerializeField] public LayerMask GroundMask { get; private set; } + + [field: Header("Interaction")] + [field: SerializeField] public float InteractionRange { get; private set; } = 2f; + [field: SerializeField] public LayerMask InteractionMask { get; private set; } + + public float VelocityY { get; set; } + public bool IsGrounded { get; private set; } + public bool WasGrounded { get; private set; } + + // Interaction system variables + private List interactablesNearby = new List(); + private int currentInteractableIndex = 0; + + public string CurrentStateName => currentState != null ? currentState.GetType().Name : "None"; + + private PlayerBaseState currentState; + private bool hasControl = true; + + protected virtual void Awake() + { + Controller = GetComponent(); + Input = GetComponent(); + Anim = GetComponentInChildren(); + Scanner = GetComponent(); + Cam = Camera.main?.GetComponent(); + } + + private void Start() + { + SwitchState(new PlayerIdleState(this)); + + // Subscribe to cycle events + Input.OnNextInteractEvent += OnNextInteract; + Input.OnPreviousInteractEvent += OnPreviousInteract; + } + + private void OnDestroy() + { + if (Input != null) + { + Input.OnNextInteractEvent -= OnNextInteract; + Input.OnPreviousInteractEvent -= OnPreviousInteract; + } + } + + protected virtual void Update() + { + if (!hasControl) return; + + WasGrounded = IsGrounded; + CheckGround(); + UpdateInteractablesList(); + + currentState?.Tick(Time.deltaTime); + } + + private void FixedUpdate() + { + if (!hasControl) return; + currentState?.PhysicsTick(Time.fixedDeltaTime); + } + + private void CheckGround() + { + IsGrounded = Physics.CheckSphere(transform.TransformPoint(GroundCheckOffset), GroundCheckRadius, GroundMask); + } + + private void UpdateInteractablesList() + { + interactablesNearby.Clear(); + Collider[] colliders = Physics.OverlapSphere(transform.position + transform.forward * (InteractionRange / 2), InteractionRange, InteractionMask); + foreach (var col in colliders) + { + if (col.TryGetComponent(out IInteractable interactable)) + { + if (!interactablesNearby.Contains(interactable)) + interactablesNearby.Add(interactable); + } + } + + if (interactablesNearby.Count == 0) + { + currentInteractableIndex = 0; + } + else if (currentInteractableIndex >= interactablesNearby.Count) + { + currentInteractableIndex = interactablesNearby.Count - 1; + } + } + + private void OnNextInteract() + { + if (interactablesNearby.Count <= 1) return; + currentInteractableIndex = (currentInteractableIndex + 1) % interactablesNearby.Count; + Debug.Log($"[Interaction] Switched to: {interactablesNearby[currentInteractableIndex].InteractionPrompt}"); + } + + private void OnPreviousInteract() + { + if (interactablesNearby.Count <= 1) return; + currentInteractableIndex--; + if (currentInteractableIndex < 0) currentInteractableIndex = interactablesNearby.Count - 1; + Debug.Log($"[Interaction] Switched to: {interactablesNearby[currentInteractableIndex].InteractionPrompt}"); + } + + public IInteractable GetInteractable() + { + if (interactablesNearby.Count == 0) return null; + return interactablesNearby[currentInteractableIndex]; + } + + public void SetGroundCheck(float radius, Vector3 offset) + { + GroundCheckRadius = radius; + GroundCheckOffset = offset; + } + + public void SwitchState(PlayerBaseState newState) + { + currentState?.Exit(); + currentState = newState; + currentState?.Enter(); + } + + public void SetControl(bool control) + { + hasControl = control; + Controller.enabled = control; + if (!control) + { + Anim.SetFloat("Speed", 0f); + } + } + + private void OnDrawGizmosSelected() + { + Gizmos.color = new Color(0, 1, 0, 0.5f); + Gizmos.DrawSphere(transform.TransformPoint(GroundCheckOffset), GroundCheckRadius); + + Gizmos.color = Color.blue; + Gizmos.DrawWireSphere(transform.position + transform.forward * (InteractionRange / 2), InteractionRange); + } + } +} \ No newline at end of file diff --git a/Assets/Scripts/Player Controller/PlayerStateMachine.cs.meta b/Assets/Scripts/Player Controller/PlayerStateMachine.cs.meta new file mode 100644 index 00000000..c0aab493 --- /dev/null +++ b/Assets/Scripts/Player Controller/PlayerStateMachine.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 848ad6fdeb60b254497391392419b063 \ No newline at end of file diff --git a/Assets/Scripts/Player Controller/PlayerThrustState.cs b/Assets/Scripts/Player Controller/PlayerThrustState.cs new file mode 100644 index 00000000..0896f740 --- /dev/null +++ b/Assets/Scripts/Player Controller/PlayerThrustState.cs @@ -0,0 +1,43 @@ +using UnityEngine; + +namespace OnlyScove.Scripts +{ + public class PlayerThrustState : PlayerBaseState + { + private readonly int thrustHash = Animator.StringToHash("Thrust"); + + public PlayerThrustState(PlayerStateMachine stateMachine) : base(stateMachine) {} + + public override void Enter() + { + stateMachine.Anim.SetTrigger(thrustHash); + + // Immediately set a massive downward velocity + stateMachine.VelocityY = stateMachine.ThrustDownwardForce; + } + + public override void Tick(float deltaTime) + { + // Keep applying heavy gravity just in case + stateMachine.VelocityY += (stateMachine.Gravity * 2f) * deltaTime; + + // Move the player straight down (no horizontal movement allowed during thrust) + Vector3 fallMovement = new Vector3(0f, stateMachine.VelocityY, 0f); + stateMachine.Controller.Move(fallMovement * deltaTime); + + // When we smash into the ground... + if (stateMachine.Controller.isGrounded) + { + stateMachine.VelocityY = -2f; + + // TODO: Add impact effects, screen shake, or damage area here! + + // Return to idle after landing + stateMachine.SwitchState(new PlayerIdleState(stateMachine)); + } + } + + public override void PhysicsTick(float fixedDeltaTime) {} + public override void Exit() {} + } +} \ No newline at end of file diff --git a/Assets/Scripts/Player Controller/PlayerThrustState.cs.meta b/Assets/Scripts/Player Controller/PlayerThrustState.cs.meta new file mode 100644 index 00000000..a77c6f95 --- /dev/null +++ b/Assets/Scripts/Player Controller/PlayerThrustState.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 4e76cfd208cc6f44b8b954147ee1defb \ No newline at end of file diff --git a/Assets/Scripts/SpineProxy.cs b/Assets/Scripts/SpineProxy.cs new file mode 100644 index 00000000..41c853c8 --- /dev/null +++ b/Assets/Scripts/SpineProxy.cs @@ -0,0 +1,65 @@ +// -- SPINE PROXY 1.0 | Kevin Iglesias -- +// This script ensures correct animation display when mixing upper and lower body animations using Unity Avatar Masks. +// Attach this script to the 'B-spineProxy' transform, which is a sibling of the 'B-hips' bone. +// In the 'originalSpine' field, assign the 'B-spine' bone (child of 'B-hips' and parent of 'B-chest'). +// By default it will automatically find the 'B-spine' and assign it to the 'originalSpine' field (OnValidate). +// When using a different character rig, manually assign the corresponding spine bone to the 'originalSpine' field and recreate +// 'Rig > B-root > B-spine' structure in your character hierarchy with empty GameObjects. + +// More information: https://www.keviniglesias.com/spine-proxy.html +// Contact Support: support@keviniglesias.com + +using UnityEngine; + +namespace KevinIglesias +{ + public class SpineProxy : MonoBehaviour + { + //Assign 'B-spine' (or equivalent) here: + [SerializeField] private Transform originalSpine; + + private Quaternion rotationOffset = Quaternion.identity; + +#if UNITY_EDITOR + //Attempting to find the original spine bone. + void OnValidate() + { + if(originalSpine == null) + { + Transform parent = transform.parent; + if(parent != null) + { + Transform hips = parent.Find("B-hips"); + if(hips != null) + { + Transform spine = hips.Find("B-spine"); + if(spine != null) + { + originalSpine = spine; + } + } + } + } + } +#endif + + //Match correct orientation on different character rigs + void Awake() + { + if(originalSpine != null) + {//originalSpine.rotation must be the default rotation in your character T-pose when this happens: + rotationOffset = Quaternion.Inverse(transform.rotation) * originalSpine.rotation; + } + } + + //Copy rotations from spine proxy bone to the original spine bone. + void LateUpdate() + { + if(originalSpine == null) + { + return; + } + originalSpine.rotation = transform.rotation * rotationOffset; + } + } +} diff --git a/Assets/Scripts/SpineProxy.cs.meta b/Assets/Scripts/SpineProxy.cs.meta new file mode 100644 index 00000000..b8262516 --- /dev/null +++ b/Assets/Scripts/SpineProxy.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 682245d9ac89ba4409aa3a92f17f5c6c \ No newline at end of file diff --git a/Assets/Scripts/StickyNote.cs b/Assets/Scripts/StickyNote.cs new file mode 100644 index 00000000..91162616 --- /dev/null +++ b/Assets/Scripts/StickyNote.cs @@ -0,0 +1,8 @@ +using UnityEngine; + +public class StickyNote : MonoBehaviour +{ + [TextArea] public string noteText = "Enter note here..."; + public Color noteColor = Color.yellow; + public bool showAlways = true; // Show even when not selected +} \ No newline at end of file diff --git a/Assets/Scripts/StickyNote.cs.meta b/Assets/Scripts/StickyNote.cs.meta new file mode 100644 index 00000000..60347617 --- /dev/null +++ b/Assets/Scripts/StickyNote.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 20733ff6cdef89e408acddf8ce51503d \ No newline at end of file diff --git a/Assets/Scripts/UI.meta b/Assets/Scripts/UI.meta new file mode 100644 index 00000000..5df265da --- /dev/null +++ b/Assets/Scripts/UI.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 91b95b0bf23143b68483f912b558e6f0 +timeCreated: 1773383929 \ No newline at end of file diff --git a/Assets/Scripts/UI/MyUIDisplay.cs b/Assets/Scripts/UI/MyUIDisplay.cs new file mode 100644 index 00000000..e1da65dc --- /dev/null +++ b/Assets/Scripts/UI/MyUIDisplay.cs @@ -0,0 +1,42 @@ +using OnlyScove.Scripts; +using UnityEngine; +using TMPro; + +namespace UI +{ + public class MyUIDisplay : MonoBehaviour + { + public PlayerDebugProvider playerDebugProvider; + + [Header("Text Fields")] + public TextMeshProUGUI stateText; + public TextMeshProUGUI groundedStatusText; + public TextMeshProUGUI horizontalSpeedText; + public TextMeshProUGUI verticaSpeedText; + public TextMeshProUGUI moveInputText; + public TextMeshProUGUI isSprintingText; + + private void Update() + { + if (playerDebugProvider == null) return; + + if (stateText != null) + stateText.text = "State: " + playerDebugProvider.CurrentState; + + if (groundedStatusText != null) + groundedStatusText.text = "Grounded: " + playerDebugProvider.GroundedStatus; + + if (horizontalSpeedText != null) + horizontalSpeedText.text = "Speed (H): " + playerDebugProvider.HorizontalSpeed.ToString("F2") + " m/s"; + + if (verticaSpeedText != null) + verticaSpeedText.text = "Speed (V): " + playerDebugProvider.VerticalSpeed.ToString("F2") + " m/s"; + + if (moveInputText != null) + moveInputText.text = "Input: " + playerDebugProvider.MoveInput.ToString(); + + if (isSprintingText != null) + isSprintingText.text = "Sprinting: " + (playerDebugProvider.IsSprinting ? "YES" : "NO"); + } + } +} \ No newline at end of file diff --git a/Assets/Scripts/UI/MyUIDisplay.cs.meta b/Assets/Scripts/UI/MyUIDisplay.cs.meta new file mode 100644 index 00000000..cdb74b2a --- /dev/null +++ b/Assets/Scripts/UI/MyUIDisplay.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: cd13c5c96000414397dd7d41a73edd62 +timeCreated: 1773383951 \ No newline at end of file diff --git a/Assets/Scripts/VFX.meta b/Assets/Scripts/VFX.meta new file mode 100644 index 00000000..cf5580d9 --- /dev/null +++ b/Assets/Scripts/VFX.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 793d95c58fcd4034f8b3152f2317a9e5 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/VFX/SlashMeshGenerator.cs b/Assets/Scripts/VFX/SlashMeshGenerator.cs new file mode 100644 index 00000000..4ba81871 --- /dev/null +++ b/Assets/Scripts/VFX/SlashMeshGenerator.cs @@ -0,0 +1,71 @@ +using UnityEngine; + +[RequireComponent(typeof(MeshFilter), typeof(MeshRenderer))] +public class SlashMeshGenerator : MonoBehaviour +{ + [Header("Mesh Settings")] + public int segments = 10; // Số lượng phân đoạn (càng cao càng mượt) + public float length = 5f; // Chiều dài vệt chém + public float width = 0.5f; // Chiều rộng ở giữa + public float curviness = 1f; // Độ cong của nhát chém + + [ContextMenu("Generate Slash Mesh")] + public void GenerateMesh() + { + Mesh mesh = new Mesh(); + mesh.name = "SukunaSlashMesh"; + + int vertexCount = (segments + 1) * 2; + Vector3[] vertices = new Vector3[vertexCount]; + Vector2[] uvs = new Vector2[vertexCount]; + int[] triangles = new int[segments * 6]; + + for (int i = 0; i <= segments; i++) + { + float t = (float)i / segments; // Tiến trình từ 0 đến 1 + + // Tính toán vị trí X (chiều dài) + float x = (t - 0.5f) * length; + + // Tính toán độ nhọn (Width Taper): Nhỏ ở 2 đầu, to ở giữa + // Dùng hàm Sin để tạo độ mượt hoặc (1 - |2t-1|) + float currentWidth = Mathf.Sin(t * Mathf.PI) * width; + + // Tính toán độ cong (Y Offset) + float yOffset = Mathf.Pow((t - 0.5f) * 2f, 2f) * curviness; + + // Tạo 2 đỉnh (trên và dưới) cho mỗi phân đoạn + vertices[i * 2] = new Vector3(x, yOffset + currentWidth / 2f, 0); + vertices[i * 2 + 1] = new Vector3(x, yOffset - currentWidth / 2f, 0); + + // Gán UV (để Shader chạy đúng) + uvs[i * 2] = new Vector2(t, 1); + uvs[i * 2 + 1] = new Vector2(t, 0); + + // Tạo tam giác (trừ phân đoạn cuối) + if (i < segments) + { + int start = i * 2; + triangles[i * 6] = start; + triangles[i * 6 + 1] = start + 2; + triangles[i * 6 + 2] = start + 1; + triangles[i * 6 + 3] = start + 1; + triangles[i * 6 + 4] = start + 2; + triangles[i * 6 + 5] = start + 3; + } + } + + mesh.vertices = vertices; + mesh.uv = uvs; + mesh.triangles = triangles; + mesh.RecalculateNormals(); + mesh.RecalculateBounds(); + + GetComponent().mesh = mesh; + } + + void Awake() + { + GenerateMesh(); + } +} diff --git a/Assets/Scripts/VFX/SlashMeshGenerator.cs.meta b/Assets/Scripts/VFX/SlashMeshGenerator.cs.meta new file mode 100644 index 00000000..c7c5165c --- /dev/null +++ b/Assets/Scripts/VFX/SlashMeshGenerator.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 78fcc45270373164cadbaa6681bab73b \ No newline at end of file diff --git a/Assets/Scripts/VFX/SukunaAbilityController.cs b/Assets/Scripts/VFX/SukunaAbilityController.cs new file mode 100644 index 00000000..526227ea --- /dev/null +++ b/Assets/Scripts/VFX/SukunaAbilityController.cs @@ -0,0 +1,69 @@ +using UnityEngine; +using OnlyScove.Scripts; + +public class SukunaAbilityController : MonoBehaviour +{ + [Header("Dependencies")] + [SerializeField] private InputReader inputReader; + + [Header("VFX Projectiles")] + public GameObject blackProjectilePrefab; + public GameObject redProjectilePrefab; + + [Header("Settings")] + public float attackRate = 0.15f; + public float forwardOffset = 1.5f; + public float verticalOffset = 1.0f; + + [Header("Random Rotation Ranges")] + public Vector2 rangeX = new Vector2(-360f, 360f); + public Vector2 rangeY = new Vector2(-10f, 10f); + public Vector2 rangeZ = new Vector2(50f, 120f); + + private float lastAttackTime = 0f; + + private void Update() + { + if (inputReader != null && inputReader.IsAttackHeld) + { + if (Time.time - lastAttackTime >= attackRate) + { + PerformDismantle(); + lastAttackTime = Time.time; + } + } + } + + private void PerformDismantle() + { + GameObject selectedPrefab = GetRandomSlashVariant(); + if (selectedPrefab == null) return; + + // Vị trí spawn trước mặt Player + Vector3 spawnPos = transform.position + transform.forward * forwardOffset + Vector3.up * verticalOffset; + + // Tạo góc xoay ngẫu nhiên theo yêu cầu của bạn + float randX = Random.Range(rangeX.x, rangeX.y); + float randY = Random.Range(rangeY.x, rangeY.y); + float randZ = Random.Range(rangeZ.x, rangeZ.y); + + // Kết hợp với hướng của Player + Quaternion spawnRot = transform.rotation * Quaternion.Euler(randX, randY, randZ); + + // Tạo đạn + GameObject projectile = Instantiate(selectedPrefab, spawnPos, spawnRot); + + // Bắt đạn bay về phía trước (hướng nhìn của Player) + if (projectile.TryGetComponent(out var projScript)) + { + projScript.SetDirection(transform.forward); + } + } + + private GameObject GetRandomSlashVariant() + { + float chance = Random.Range(0f, 100f); + if (chance <= 20f) return redProjectilePrefab != null ? redProjectilePrefab : blackProjectilePrefab; + return blackProjectilePrefab; + } +} diff --git a/Assets/Scripts/VFX/SukunaAbilityController.cs.meta b/Assets/Scripts/VFX/SukunaAbilityController.cs.meta new file mode 100644 index 00000000..5d60d5e0 --- /dev/null +++ b/Assets/Scripts/VFX/SukunaAbilityController.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 1630760c9d97a5f4eb1bc179549c95cd \ No newline at end of file diff --git a/Assets/Scripts/VFX/SukunaDomainController.cs b/Assets/Scripts/VFX/SukunaDomainController.cs new file mode 100644 index 00000000..082a8529 --- /dev/null +++ b/Assets/Scripts/VFX/SukunaDomainController.cs @@ -0,0 +1,229 @@ +using UnityEngine; +using UnityEngine.Rendering; +using UnityEngine.Rendering.Universal; +using System.Collections; +using System.Collections.Generic; + +namespace OnlyScove.Scripts +{ + public class SukunaDomainController : MonoBehaviour + { + [Header("References")] + public PlayerStateMachine playerStateMachine; + public GameObject slashPrefab; + public GameObject shrinePrefab; + public VolumeProfile domainVolumeProfile; + public Transform cinematicCameraPoint; + + [Header("Domain Settings")] + public float domainRadius = 15f; + public float domainDuration = 10f; + + [Tooltip("Số lượng vệt chém tạo ra mỗi giây")] + public float slashRate = 15f; + + public float shrineRiseHeight = 7f; // Độ sâu bắt đầu của miếu + + [Tooltip("Khoảng cách từ Pivot của Miếu đến mặt sàn nơi Player đứng")] + public float shrineFloorOffset = 0.5f; + + public float camMoveSpeed = 4f; + + private bool isActive = false; + private List activeSlashes = new List(); + private Volume localVolume; + private GameObject spawnedShrine; + + private void Start() + { + if (playerStateMachine == null) + playerStateMachine = GetComponent(); + + if (playerStateMachine != null && playerStateMachine.Input != null) + { + playerStateMachine.Input.OnPreviousInteractEvent += HandleDomainExpansion; + Debug.Log("[Sukuna] Sẵn sàng. slashRate: " + slashRate + ""); + } + } + + private void OnDestroy() + { + if (playerStateMachine != null && playerStateMachine.Input != null) + { + playerStateMachine.Input.OnPreviousInteractEvent -= HandleDomainExpansion; + } + } + + private void HandleDomainExpansion() + { + if (isActive) return; + Debug.Log("[Sukuna] RYŌIKI TENKAI: FUKUMA MIZUZUSHI!"); + StartCoroutine(DomainSequence()); + } + + private IEnumerator DomainSequence() + { + isActive = true; + + // Lưu vị trí ban đầu của Player (Vị trí thực tế trên mặt đất) + Vector3 playerStartPos = playerStateMachine.transform.position; + playerStateMachine.SetControl(false); + + CameraController camController = playerStateMachine.Cam; + bool originalCamEnabled = true; + Transform mainCam = Camera.main.transform; + + if (camController != null) + { + originalCamEnabled = camController.enabled; + camController.enabled = false; + } + + // 1. Tạo Volume (Bóng bong lãnh địa) + GameObject volumeObj = new GameObject("SukunaDomainVolume"); + volumeObj.transform.position = playerStartPos; + localVolume = volumeObj.AddComponent(); + localVolume.isGlobal = false; + localVolume.priority = 100; + localVolume.profile = domainVolumeProfile; + SphereCollider volumeCollider = volumeObj.AddComponent(); + volumeCollider.isTrigger = true; + volumeCollider.radius = 0.1f; + + // 2. Mọc miếu và đẩy Player + if (shrinePrefab != null) + { + // Spawn miếu ở vị trí rất sâu dưới chân Player + Vector3 shrineSpawnPos = playerStartPos - Vector3.up * shrineRiseHeight; + spawnedShrine = Instantiate(shrinePrefab, shrineSpawnPos, playerStateMachine.transform.rotation); + + float riseDuration = 2.0f; + float elapsed = 0; + while (elapsed < riseDuration) + { + elapsed += Time.deltaTime; + float t = Mathf.SmoothStep(0, 1, elapsed / riseDuration); + + // Di chuyển miếu lên dần dần + Vector3 currentShrinePos = Vector3.Lerp(shrineSpawnPos, playerStartPos, t); + spawnedShrine.transform.position = currentShrinePos; + + // Logic đẩy Player: + // floorY là độ cao mặt sàn của miếu tại khung hình hiện tại + float floorY = currentShrinePos.y + shrineFloorOffset; + + // Nếu mặt sàn của miếu đã trồi lên cao hơn vị trí chân Player ban đầu + if (floorY > playerStartPos.y) + { + // Player đi theo miếu + playerStateMachine.transform.position = new Vector3(playerStartPos.x, floorY, playerStartPos.z); + } + else + { + // Player đứng yên trên mặt đất ban đầu, chờ miếu trồi lên đỡ + playerStateMachine.transform.position = playerStartPos; + } + + // Mở rộng bán kính Volume + volumeCollider.radius = Mathf.Lerp(0.1f, domainRadius, t); + + // Lia Camera mượt mà + if (cinematicCameraPoint != null) + { + mainCam.position = Vector3.Lerp(mainCam.position, cinematicCameraPoint.position, Time.deltaTime * camMoveSpeed); + mainCam.rotation = Quaternion.Slerp(mainCam.rotation, Quaternion.LookRotation((playerStateMachine.transform.position + Vector3.up * 2f) - mainCam.position), Time.deltaTime * camMoveSpeed); + } + yield return null; + } + } + + // 3. Thực thi chém liên tục dựa trên slashRate + float timer = 0; + float slashCooldown = 1f / slashRate; + float lastSlashTime = 0; + + while (timer < domainDuration) + { + timer += Time.deltaTime; + + if (timer - lastSlashTime >= slashCooldown) + { + SpawnRandomSlash(playerStateMachine.transform.position); + lastSlashTime = timer; + } + + // Camera luôn theo dõi Player trên đỉnh miếu + if (cinematicCameraPoint != null) + { + mainCam.position = Vector3.Lerp(mainCam.position, cinematicCameraPoint.position, Time.deltaTime * camMoveSpeed * 0.5f); + mainCam.LookAt(playerStateMachine.transform.position + Vector3.up * 2f); + } + yield return null; + } + + // 4. Miếu sụp xuống (Player đứng lại vị trí Y ban đầu) + if (spawnedShrine != null) + { + float sinkTime = 1f; + float elapsed = 0; + Vector3 currentShrinePos = spawnedShrine.transform.position; + Vector3 targetSinkPos = currentShrinePos - Vector3.up * shrineRiseHeight; + while (elapsed < sinkTime) + { + elapsed += Time.deltaTime; + float t = elapsed / sinkTime; + spawnedShrine.transform.position = Vector3.Lerp(currentShrinePos, targetSinkPos, t); + + // Player từ từ hạ xuống sàn ban đầu + float floorY = spawnedShrine.transform.position.y + shrineFloorOffset; + if (floorY > playerStartPos.y) + playerStateMachine.transform.position = new Vector3(playerStartPos.x, floorY, playerStartPos.z); + else + playerStateMachine.transform.position = playerStartPos; + + yield return null; + } + Destroy(spawnedShrine); + } + + // 5. Thu nhỏ Volume + float shrinkDuration = 0.5f; + float sElapsed = 0; + while (sElapsed < shrinkDuration) + { + sElapsed += Time.deltaTime; + volumeCollider.radius = Mathf.Lerp(domainRadius, 0.1f, sElapsed / shrinkDuration); + yield return null; + } + Destroy(volumeObj); + + // Dọn dẹp + foreach (var s in activeSlashes) if (s != null) Destroy(s); + activeSlashes.Clear(); + + if (camController != null) camController.enabled = originalCamEnabled; + playerStateMachine.SetControl(true); + isActive = false; + } + + private void SpawnRandomSlash(Vector3 center) + { + Vector2 randCircle = Random.insideUnitCircle * domainRadius; + Vector3 spawnPos = center + new Vector3(randCircle.x, Random.Range(1f, 6f), randCircle.y); + + if (slashPrefab != null) + { + GameObject slash = Instantiate(slashPrefab, spawnPos, Random.rotation); + slash.transform.localScale *= Random.Range(0.6f, 2.5f); + activeSlashes.Add(slash); + StartCoroutine(DestroySlashAfterTime(slash, 0.7f)); + } + } + + private IEnumerator DestroySlashAfterTime(GameObject slash, float time) + { + yield return new WaitForSeconds(time); + if (activeSlashes != null && activeSlashes.Contains(slash)) activeSlashes.Remove(slash); + } + } +} diff --git a/Assets/Scripts/VFX/SukunaDomainController.cs.meta b/Assets/Scripts/VFX/SukunaDomainController.cs.meta new file mode 100644 index 00000000..1aa125f5 --- /dev/null +++ b/Assets/Scripts/VFX/SukunaDomainController.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 271dd39a46bad974485107bb1a070e0a \ No newline at end of file diff --git a/Assets/Scripts/VFX/SukunaProjectile.cs b/Assets/Scripts/VFX/SukunaProjectile.cs new file mode 100644 index 00000000..c557f6cf --- /dev/null +++ b/Assets/Scripts/VFX/SukunaProjectile.cs @@ -0,0 +1,27 @@ +using UnityEngine; + +public class SukunaProjectile : MonoBehaviour +{ + [Header("Movement")] + public float speed = 50f; + public float lifetime = 3f; + + private Vector3 moveDirection; + + public void SetDirection(Vector3 direction) + { + // Nhận hướng bay từ Player (luôn là hướng phía trước) + moveDirection = direction.normalized; + } + + void Start() + { + Destroy(gameObject, lifetime); + } + + void Update() + { + // Di chuyển đạn theo hướng đã gán, bất kể góc xoay hiển thị của nó là gì + transform.position += moveDirection * speed * Time.deltaTime; + } +} diff --git a/Assets/Scripts/VFX/SukunaProjectile.cs.meta b/Assets/Scripts/VFX/SukunaProjectile.cs.meta new file mode 100644 index 00000000..f7d48e16 --- /dev/null +++ b/Assets/Scripts/VFX/SukunaProjectile.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: b9b0aad9f1697954a9f8710b4e8f3f2e \ No newline at end of file diff --git a/Assets/Scripts/VFX/SukunaSlashEffect.cs b/Assets/Scripts/VFX/SukunaSlashEffect.cs new file mode 100644 index 00000000..a11fd61e --- /dev/null +++ b/Assets/Scripts/VFX/SukunaSlashEffect.cs @@ -0,0 +1,53 @@ +using UnityEngine; + +public class SukunaSlashEffect : MonoBehaviour +{ + [Header("Settings")] + public float duration = 0.2f; + public float maxScale = 5f; + public AnimationCurve scaleCurve = AnimationCurve.EaseInOut(0, 0, 1, 1); + + [Header("Visuals")] + private MeshRenderer meshRenderer; + private MaterialPropertyBlock propBlock; + private float timer = 0f; + private static readonly int DissolveId = Shader.PropertyToID("_Dissolve"); + + void Awake() + { + meshRenderer = GetComponent(); + propBlock = new MaterialPropertyBlock(); + } + + void Start() + { + // Reset scale ban đầu + transform.localScale = new Vector3(maxScale, 0.1f, 0.1f); + } + + void Update() + { + timer += Time.deltaTime; + float normalizedTime = timer / duration; + + if (normalizedTime <= 1.0f) + { + // Mở rộng vệt chém theo chiều ngang (Y hoặc Z tùy mesh) + float currentScaleY = scaleCurve.Evaluate(normalizedTime) * maxScale; + transform.localScale = new Vector3(maxScale, currentScaleY, 1f); + + // Điều khiển Shader bằng MaterialPropertyBlock + if (meshRenderer != null) + { + meshRenderer.GetPropertyBlock(propBlock); + propBlock.SetFloat(DissolveId, normalizedTime); + meshRenderer.SetPropertyBlock(propBlock); + } + } + else + { + // Tự hủy sau khi xong + Destroy(gameObject); + } + } +} diff --git a/Assets/Scripts/VFX/SukunaSlashEffect.cs.meta b/Assets/Scripts/VFX/SukunaSlashEffect.cs.meta new file mode 100644 index 00000000..cda3e9e8 --- /dev/null +++ b/Assets/Scripts/VFX/SukunaSlashEffect.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 66ad11f71e7aac841be73f4b03cf0d83 \ No newline at end of file diff --git a/Assets/Settings/PC_RPAsset.asset b/Assets/Settings/PC_RPAsset.asset index 8b30a060..9b2b0467 100644 --- a/Assets/Settings/PC_RPAsset.asset +++ b/Assets/Settings/PC_RPAsset.asset @@ -12,8 +12,8 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: bf2edee5c58d82540a51f03df9d42094, type: 3} m_Name: PC_RPAsset m_EditorClassIdentifier: - k_AssetVersion: 12 - k_AssetPreviousVersion: 12 + k_AssetVersion: 13 + k_AssetPreviousVersion: 13 m_RendererType: 1 m_RendererData: {fileID: 0} m_RendererDataList: @@ -53,6 +53,7 @@ MonoBehaviour: m_AdditionalLightsShadowResolutionTierHigh: 1024 m_ReflectionProbeBlending: 1 m_ReflectionProbeBoxProjection: 1 + m_ReflectionProbeAtlas: 1 m_ShadowDistance: 50 m_ShadowCascadeCount: 4 m_Cascade2Split: 0.25 @@ -78,11 +79,11 @@ MonoBehaviour: m_UseAdaptivePerformance: 1 m_ColorGradingMode: 0 m_ColorGradingLutSize: 32 + m_AllowPostProcessAlphaOutput: 0 m_UseFastSRGBLinearConversion: 0 m_SupportDataDrivenLensFlare: 1 m_SupportScreenSpaceLensFlare: 1 m_GPUResidentDrawerMode: 0 - m_UseLegacyLightmaps: 0 m_SmallMeshScreenPercentage: 0 m_GPUResidentDrawerEnableOcclusionCullingInCameras: 0 m_ShadowType: 1 @@ -109,6 +110,7 @@ MonoBehaviour: m_PrefilterDebugKeywords: 1 m_PrefilterWriteRenderingLayers: 0 m_PrefilterHDROutput: 1 + m_PrefilterAlphaOutput: 0 m_PrefilterSSAODepthNormals: 0 m_PrefilterSSAOSourceDepthLow: 1 m_PrefilterSSAOSourceDepthMedium: 1 @@ -126,8 +128,14 @@ MonoBehaviour: m_PrefilterSoftShadowsQualityHigh: 0 m_PrefilterSoftShadows: 0 m_PrefilterScreenCoord: 1 + m_PrefilterScreenSpaceIrradiance: 0 m_PrefilterNativeRenderPass: 1 m_PrefilterUseLegacyLightmaps: 0 + m_PrefilterBicubicLightmapSampling: 0 + m_PrefilterReflectionProbeRotation: 0 + m_PrefilterReflectionProbeBlending: 0 + m_PrefilterReflectionProbeBoxProjection: 0 + m_PrefilterReflectionProbeAtlas: 0 m_ShaderVariantLogLevel: 0 m_ShadowCascades: 0 m_Textures: diff --git a/Assets/TutorialInfo/Icons/URP.png b/Assets/TutorialInfo/Icons/URP.png deleted file mode 100644 index 6194a807..00000000 Binary files a/Assets/TutorialInfo/Icons/URP.png and /dev/null differ diff --git a/Assets/TutorialInfo/Icons/URP.png.meta b/Assets/TutorialInfo/Icons/URP.png.meta deleted file mode 100644 index 0f2cab05..00000000 --- a/Assets/TutorialInfo/Icons/URP.png.meta +++ /dev/null @@ -1,134 +0,0 @@ -fileFormatVersion: 2 -guid: 727a75301c3d24613a3ebcec4a24c2c8 -TextureImporter: - internalIDToNameTable: [] - externalObjects: {} - serializedVersion: 11 - mipmaps: - mipMapMode: 0 - enableMipMap: 0 - sRGBTexture: 1 - linearTexture: 0 - fadeOut: 0 - borderMipMap: 0 - mipMapsPreserveCoverage: 0 - alphaTestReferenceValue: 0.5 - mipMapFadeDistanceStart: 1 - mipMapFadeDistanceEnd: 3 - bumpmap: - convertToNormalMap: 0 - externalNormalMap: 0 - heightScale: 0.25 - normalMapFilter: 0 - isReadable: 0 - streamingMipmaps: 0 - streamingMipmapsPriority: 0 - vTOnly: 0 - ignoreMasterTextureLimit: 0 - grayScaleToAlpha: 0 - generateCubemap: 6 - cubemapConvolution: 0 - seamlessCubemap: 0 - textureFormat: 1 - maxTextureSize: 2048 - textureSettings: - serializedVersion: 2 - filterMode: 0 - aniso: 1 - mipBias: 0 - wrapU: 1 - wrapV: 1 - wrapW: 0 - nPOTScale: 0 - lightmap: 0 - compressionQuality: 50 - spriteMode: 0 - spriteExtrude: 1 - spriteMeshType: 1 - alignment: 0 - spritePivot: {x: 0.5, y: 0.5} - spritePixelsToUnits: 100 - spriteBorder: {x: 0, y: 0, z: 0, w: 0} - spriteGenerateFallbackPhysicsShape: 1 - alphaUsage: 1 - alphaIsTransparency: 1 - spriteTessellationDetail: -1 - textureType: 2 - textureShape: 1 - singleChannelComponent: 0 - flipbookRows: 1 - flipbookColumns: 1 - maxTextureSizeSet: 0 - compressionQualitySet: 0 - textureFormatSet: 0 - ignorePngGamma: 0 - applyGammaDecoding: 0 - platformSettings: - - serializedVersion: 3 - buildTarget: DefaultTexturePlatform - maxTextureSize: 2048 - resizeAlgorithm: 0 - textureFormat: -1 - textureCompression: 0 - compressionQuality: 50 - crunchedCompression: 0 - allowsAlphaSplitting: 0 - overridden: 0 - androidETC2FallbackOverride: 0 - forceMaximumCompressionQuality_BC6H_BC7: 0 - - serializedVersion: 3 - buildTarget: Standalone - maxTextureSize: 2048 - resizeAlgorithm: 0 - textureFormat: -1 - textureCompression: 1 - compressionQuality: 50 - crunchedCompression: 0 - allowsAlphaSplitting: 0 - overridden: 0 - androidETC2FallbackOverride: 0 - forceMaximumCompressionQuality_BC6H_BC7: 0 - - serializedVersion: 3 - buildTarget: Android - maxTextureSize: 2048 - resizeAlgorithm: 0 - textureFormat: -1 - textureCompression: 1 - compressionQuality: 50 - crunchedCompression: 0 - allowsAlphaSplitting: 0 - overridden: 0 - androidETC2FallbackOverride: 0 - forceMaximumCompressionQuality_BC6H_BC7: 0 - - serializedVersion: 3 - buildTarget: iPhone - maxTextureSize: 2048 - resizeAlgorithm: 0 - textureFormat: -1 - textureCompression: 1 - compressionQuality: 50 - crunchedCompression: 0 - allowsAlphaSplitting: 0 - overridden: 0 - androidETC2FallbackOverride: 0 - forceMaximumCompressionQuality_BC6H_BC7: 0 - spriteSheet: - serializedVersion: 2 - sprites: [] - outline: [] - physicsShape: [] - bones: [] - spriteID: - internalID: 0 - vertices: [] - indices: - edges: [] - weights: [] - secondaryTextures: [] - nameFileIdTable: {} - spritePackingTag: - pSDRemoveMatte: 0 - pSDShowRemoveMatteOption: 0 - userData: - assetBundleName: - assetBundleVariant: diff --git a/Assets/TutorialInfo/Layout.wlt b/Assets/TutorialInfo/Layout.wlt deleted file mode 100644 index 7b50a252..00000000 --- a/Assets/TutorialInfo/Layout.wlt +++ /dev/null @@ -1,654 +0,0 @@ -%YAML 1.1 -%TAG !u! tag:unity3d.com,2011: ---- !u!114 &1 -MonoBehaviour: - m_ObjectHideFlags: 52 - m_PrefabParentObject: {fileID: 0} - m_PrefabInternal: {fileID: 0} - m_GameObject: {fileID: 0} - m_Enabled: 1 - m_EditorHideFlags: 1 - m_Script: {fileID: 12004, guid: 0000000000000000e000000000000000, type: 0} - m_Name: - m_EditorClassIdentifier: - m_PixelRect: - serializedVersion: 2 - x: 0 - y: 45 - width: 1666 - height: 958 - m_ShowMode: 4 - m_Title: - m_RootView: {fileID: 6} - m_MinSize: {x: 950, y: 542} - m_MaxSize: {x: 10000, y: 10000} ---- !u!114 &2 -MonoBehaviour: - m_ObjectHideFlags: 52 - m_PrefabParentObject: {fileID: 0} - m_PrefabInternal: {fileID: 0} - m_GameObject: {fileID: 0} - m_Enabled: 1 - m_EditorHideFlags: 1 - m_Script: {fileID: 12006, guid: 0000000000000000e000000000000000, type: 0} - m_Name: - m_EditorClassIdentifier: - m_Children: [] - m_Position: - serializedVersion: 2 - x: 0 - y: 466 - width: 290 - height: 442 - m_MinSize: {x: 234, y: 271} - m_MaxSize: {x: 10004, y: 10021} - m_ActualView: {fileID: 14} - m_Panes: - - {fileID: 14} - m_Selected: 0 - m_LastSelected: 0 ---- !u!114 &3 -MonoBehaviour: - m_ObjectHideFlags: 52 - m_PrefabParentObject: {fileID: 0} - m_PrefabInternal: {fileID: 0} - m_GameObject: {fileID: 0} - m_Enabled: 1 - m_EditorHideFlags: 1 - m_Script: {fileID: 12010, guid: 0000000000000000e000000000000000, type: 0} - m_Name: - m_EditorClassIdentifier: - m_Children: - - {fileID: 4} - - {fileID: 2} - m_Position: - serializedVersion: 2 - x: 973 - y: 0 - width: 290 - height: 908 - m_MinSize: {x: 234, y: 492} - m_MaxSize: {x: 10004, y: 14042} - vertical: 1 - controlID: 226 ---- !u!114 &4 -MonoBehaviour: - m_ObjectHideFlags: 52 - m_PrefabParentObject: {fileID: 0} - m_PrefabInternal: {fileID: 0} - m_GameObject: {fileID: 0} - m_Enabled: 1 - m_EditorHideFlags: 1 - m_Script: {fileID: 12006, guid: 0000000000000000e000000000000000, type: 0} - m_Name: - m_EditorClassIdentifier: - m_Children: [] - m_Position: - serializedVersion: 2 - x: 0 - y: 0 - width: 290 - height: 466 - m_MinSize: {x: 204, y: 221} - m_MaxSize: {x: 4004, y: 4021} - m_ActualView: {fileID: 17} - m_Panes: - - {fileID: 17} - m_Selected: 0 - m_LastSelected: 0 ---- !u!114 &5 -MonoBehaviour: - m_ObjectHideFlags: 52 - m_PrefabParentObject: {fileID: 0} - m_PrefabInternal: {fileID: 0} - m_GameObject: {fileID: 0} - m_Enabled: 1 - m_EditorHideFlags: 1 - m_Script: {fileID: 12006, guid: 0000000000000000e000000000000000, type: 0} - m_Name: - m_EditorClassIdentifier: - m_Children: [] - m_Position: - serializedVersion: 2 - x: 0 - y: 466 - width: 973 - height: 442 - m_MinSize: {x: 202, y: 221} - m_MaxSize: {x: 4002, y: 4021} - m_ActualView: {fileID: 15} - m_Panes: - - {fileID: 15} - m_Selected: 0 - m_LastSelected: 0 ---- !u!114 &6 -MonoBehaviour: - m_ObjectHideFlags: 52 - m_PrefabParentObject: {fileID: 0} - m_PrefabInternal: {fileID: 0} - m_GameObject: {fileID: 0} - m_Enabled: 1 - m_EditorHideFlags: 1 - m_Script: {fileID: 12008, guid: 0000000000000000e000000000000000, type: 0} - m_Name: - m_EditorClassIdentifier: - m_Children: - - {fileID: 7} - - {fileID: 8} - - {fileID: 9} - m_Position: - serializedVersion: 2 - x: 0 - y: 0 - width: 1666 - height: 958 - m_MinSize: {x: 950, y: 542} - m_MaxSize: {x: 10000, y: 10000} ---- !u!114 &7 -MonoBehaviour: - m_ObjectHideFlags: 52 - m_PrefabParentObject: {fileID: 0} - m_PrefabInternal: {fileID: 0} - m_GameObject: {fileID: 0} - m_Enabled: 1 - m_EditorHideFlags: 1 - m_Script: {fileID: 12011, guid: 0000000000000000e000000000000000, type: 0} - m_Name: - m_EditorClassIdentifier: - m_Children: [] - m_Position: - serializedVersion: 2 - x: 0 - y: 0 - width: 1666 - height: 30 - m_MinSize: {x: 0, y: 0} - m_MaxSize: {x: 0, y: 0} - m_LastLoadedLayoutName: Tutorial ---- !u!114 &8 -MonoBehaviour: - m_ObjectHideFlags: 52 - m_PrefabParentObject: {fileID: 0} - m_PrefabInternal: {fileID: 0} - m_GameObject: {fileID: 0} - m_Enabled: 1 - m_EditorHideFlags: 1 - m_Script: {fileID: 12010, guid: 0000000000000000e000000000000000, type: 0} - m_Name: - m_EditorClassIdentifier: - m_Children: - - {fileID: 10} - - {fileID: 3} - - {fileID: 11} - m_Position: - serializedVersion: 2 - x: 0 - y: 30 - width: 1666 - height: 908 - m_MinSize: {x: 713, y: 492} - m_MaxSize: {x: 18008, y: 14042} - vertical: 0 - controlID: 74 ---- !u!114 &9 -MonoBehaviour: - m_ObjectHideFlags: 52 - m_PrefabParentObject: {fileID: 0} - m_PrefabInternal: {fileID: 0} - m_GameObject: {fileID: 0} - m_Enabled: 1 - m_EditorHideFlags: 1 - m_Script: {fileID: 12042, guid: 0000000000000000e000000000000000, type: 0} - m_Name: - m_EditorClassIdentifier: - m_Children: [] - m_Position: - serializedVersion: 2 - x: 0 - y: 938 - width: 1666 - height: 20 - m_MinSize: {x: 0, y: 0} - m_MaxSize: {x: 0, y: 0} ---- !u!114 &10 -MonoBehaviour: - m_ObjectHideFlags: 52 - m_PrefabParentObject: {fileID: 0} - m_PrefabInternal: {fileID: 0} - m_GameObject: {fileID: 0} - m_Enabled: 1 - m_EditorHideFlags: 1 - m_Script: {fileID: 12010, guid: 0000000000000000e000000000000000, type: 0} - m_Name: - m_EditorClassIdentifier: - m_Children: - - {fileID: 12} - - {fileID: 5} - m_Position: - serializedVersion: 2 - x: 0 - y: 0 - width: 973 - height: 908 - m_MinSize: {x: 202, y: 442} - m_MaxSize: {x: 4002, y: 8042} - vertical: 1 - controlID: 75 ---- !u!114 &11 -MonoBehaviour: - m_ObjectHideFlags: 52 - m_PrefabParentObject: {fileID: 0} - m_PrefabInternal: {fileID: 0} - m_GameObject: {fileID: 0} - m_Enabled: 1 - m_EditorHideFlags: 1 - m_Script: {fileID: 12006, guid: 0000000000000000e000000000000000, type: 0} - m_Name: - m_EditorClassIdentifier: - m_Children: [] - m_Position: - serializedVersion: 2 - x: 1263 - y: 0 - width: 403 - height: 908 - m_MinSize: {x: 277, y: 71} - m_MaxSize: {x: 4002, y: 4021} - m_ActualView: {fileID: 13} - m_Panes: - - {fileID: 13} - m_Selected: 0 - m_LastSelected: 0 ---- !u!114 &12 -MonoBehaviour: - m_ObjectHideFlags: 52 - m_PrefabParentObject: {fileID: 0} - m_PrefabInternal: {fileID: 0} - m_GameObject: {fileID: 0} - m_Enabled: 1 - m_EditorHideFlags: 1 - m_Script: {fileID: 12006, guid: 0000000000000000e000000000000000, type: 0} - m_Name: - m_EditorClassIdentifier: - m_Children: [] - m_Position: - serializedVersion: 2 - x: 0 - y: 0 - width: 973 - height: 466 - m_MinSize: {x: 202, y: 221} - m_MaxSize: {x: 4002, y: 4021} - m_ActualView: {fileID: 16} - m_Panes: - - {fileID: 16} - m_Selected: 0 - m_LastSelected: 0 ---- !u!114 &13 -MonoBehaviour: - m_ObjectHideFlags: 52 - m_PrefabParentObject: {fileID: 0} - m_PrefabInternal: {fileID: 0} - m_GameObject: {fileID: 0} - m_Enabled: 1 - m_EditorHideFlags: 1 - m_Script: {fileID: 12019, guid: 0000000000000000e000000000000000, type: 0} - m_Name: - m_EditorClassIdentifier: - m_AutoRepaintOnSceneChange: 0 - m_MinSize: {x: 275, y: 50} - m_MaxSize: {x: 4000, y: 4000} - m_TitleContent: - m_Text: Inspector - m_Image: {fileID: -6905738622615590433, guid: 0000000000000000d000000000000000, - type: 0} - m_Tooltip: - m_DepthBufferBits: 0 - m_Pos: - serializedVersion: 2 - x: 2 - y: 19 - width: 401 - height: 887 - m_ScrollPosition: {x: 0, y: 0} - m_InspectorMode: 0 - m_PreviewResizer: - m_CachedPref: -160 - m_ControlHash: -371814159 - m_PrefName: Preview_InspectorPreview - m_PreviewWindow: {fileID: 0} ---- !u!114 &14 -MonoBehaviour: - m_ObjectHideFlags: 52 - m_PrefabParentObject: {fileID: 0} - m_PrefabInternal: {fileID: 0} - m_GameObject: {fileID: 0} - m_Enabled: 1 - m_EditorHideFlags: 1 - m_Script: {fileID: 12014, guid: 0000000000000000e000000000000000, type: 0} - m_Name: - m_EditorClassIdentifier: - m_AutoRepaintOnSceneChange: 0 - m_MinSize: {x: 230, y: 250} - m_MaxSize: {x: 10000, y: 10000} - m_TitleContent: - m_Text: Project - m_Image: {fileID: -7501376956915960154, guid: 0000000000000000d000000000000000, - type: 0} - m_Tooltip: - m_DepthBufferBits: 0 - m_Pos: - serializedVersion: 2 - x: 2 - y: 19 - width: 286 - height: 421 - m_SearchFilter: - m_NameFilter: - m_ClassNames: [] - m_AssetLabels: [] - m_AssetBundleNames: [] - m_VersionControlStates: [] - m_ReferencingInstanceIDs: - m_ScenePaths: [] - m_ShowAllHits: 0 - m_SearchArea: 0 - m_Folders: - - Assets - m_ViewMode: 0 - m_StartGridSize: 64 - m_LastFolders: - - Assets - m_LastFoldersGridSize: -1 - m_LastProjectPath: /Users/danielbrauer/Unity Projects/New Unity Project 47 - m_IsLocked: 0 - m_FolderTreeState: - scrollPos: {x: 0, y: 0} - m_SelectedIDs: ee240000 - m_LastClickedID: 9454 - m_ExpandedIDs: ee24000000ca9a3bffffff7f - m_RenameOverlay: - m_UserAcceptedRename: 0 - m_Name: - m_OriginalName: - m_EditFieldRect: - serializedVersion: 2 - x: 0 - y: 0 - width: 0 - height: 0 - m_UserData: 0 - m_IsWaitingForDelay: 0 - m_IsRenaming: 0 - m_OriginalEventType: 11 - m_IsRenamingFilename: 1 - m_ClientGUIView: {fileID: 0} - m_SearchString: - m_CreateAssetUtility: - m_EndAction: {fileID: 0} - m_InstanceID: 0 - m_Path: - m_Icon: {fileID: 0} - m_ResourceFile: - m_AssetTreeState: - scrollPos: {x: 0, y: 0} - m_SelectedIDs: 68fbffff - m_LastClickedID: 0 - m_ExpandedIDs: ee240000 - m_RenameOverlay: - m_UserAcceptedRename: 0 - m_Name: - m_OriginalName: - m_EditFieldRect: - serializedVersion: 2 - x: 0 - y: 0 - width: 0 - height: 0 - m_UserData: 0 - m_IsWaitingForDelay: 0 - m_IsRenaming: 0 - m_OriginalEventType: 11 - m_IsRenamingFilename: 1 - m_ClientGUIView: {fileID: 0} - m_SearchString: - m_CreateAssetUtility: - m_EndAction: {fileID: 0} - m_InstanceID: 0 - m_Path: - m_Icon: {fileID: 0} - m_ResourceFile: - m_ListAreaState: - m_SelectedInstanceIDs: 68fbffff - m_LastClickedInstanceID: -1176 - m_HadKeyboardFocusLastEvent: 0 - m_ExpandedInstanceIDs: c6230000 - m_RenameOverlay: - m_UserAcceptedRename: 0 - m_Name: - m_OriginalName: - m_EditFieldRect: - serializedVersion: 2 - x: 0 - y: 0 - width: 0 - height: 0 - m_UserData: 0 - m_IsWaitingForDelay: 0 - m_IsRenaming: 0 - m_OriginalEventType: 11 - m_IsRenamingFilename: 1 - m_ClientGUIView: {fileID: 0} - m_CreateAssetUtility: - m_EndAction: {fileID: 0} - m_InstanceID: 0 - m_Path: - m_Icon: {fileID: 0} - m_ResourceFile: - m_NewAssetIndexInList: -1 - m_ScrollPosition: {x: 0, y: 0} - m_GridSize: 64 - m_DirectoriesAreaWidth: 110 ---- !u!114 &15 -MonoBehaviour: - m_ObjectHideFlags: 52 - m_PrefabParentObject: {fileID: 0} - m_PrefabInternal: {fileID: 0} - m_GameObject: {fileID: 0} - m_Enabled: 1 - m_EditorHideFlags: 1 - m_Script: {fileID: 12015, guid: 0000000000000000e000000000000000, type: 0} - m_Name: - m_EditorClassIdentifier: - m_AutoRepaintOnSceneChange: 1 - m_MinSize: {x: 200, y: 200} - m_MaxSize: {x: 4000, y: 4000} - m_TitleContent: - m_Text: Game - m_Image: {fileID: -2087823869225018852, guid: 0000000000000000d000000000000000, - type: 0} - m_Tooltip: - m_DepthBufferBits: 32 - m_Pos: - serializedVersion: 2 - x: 0 - y: 19 - width: 971 - height: 421 - m_MaximizeOnPlay: 0 - m_Gizmos: 0 - m_Stats: 0 - m_SelectedSizes: 00000000000000000000000000000000000000000000000000000000000000000000000000000000 - m_TargetDisplay: 0 - m_ZoomArea: - m_HRangeLocked: 0 - m_VRangeLocked: 0 - m_HBaseRangeMin: -242.75 - m_HBaseRangeMax: 242.75 - m_VBaseRangeMin: -101 - m_VBaseRangeMax: 101 - m_HAllowExceedBaseRangeMin: 1 - m_HAllowExceedBaseRangeMax: 1 - m_VAllowExceedBaseRangeMin: 1 - m_VAllowExceedBaseRangeMax: 1 - m_ScaleWithWindow: 0 - m_HSlider: 0 - m_VSlider: 0 - m_IgnoreScrollWheelUntilClicked: 0 - m_EnableMouseInput: 1 - m_EnableSliderZoom: 0 - m_UniformScale: 1 - m_UpDirection: 1 - m_DrawArea: - serializedVersion: 2 - x: 0 - y: 17 - width: 971 - height: 404 - m_Scale: {x: 2, y: 2} - m_Translation: {x: 485.5, y: 202} - m_MarginLeft: 0 - m_MarginRight: 0 - m_MarginTop: 0 - m_MarginBottom: 0 - m_LastShownAreaInsideMargins: - serializedVersion: 2 - x: -242.75 - y: -101 - width: 485.5 - height: 202 - m_MinimalGUI: 1 - m_defaultScale: 2 - m_TargetTexture: {fileID: 0} - m_CurrentColorSpace: 0 - m_LastWindowPixelSize: {x: 1942, y: 842} - m_ClearInEditMode: 1 - m_NoCameraWarning: 1 - m_LowResolutionForAspectRatios: 01000000000100000100 ---- !u!114 &16 -MonoBehaviour: - m_ObjectHideFlags: 52 - m_PrefabParentObject: {fileID: 0} - m_PrefabInternal: {fileID: 0} - m_GameObject: {fileID: 0} - m_Enabled: 1 - m_EditorHideFlags: 1 - m_Script: {fileID: 12013, guid: 0000000000000000e000000000000000, type: 0} - m_Name: - m_EditorClassIdentifier: - m_AutoRepaintOnSceneChange: 1 - m_MinSize: {x: 200, y: 200} - m_MaxSize: {x: 4000, y: 4000} - m_TitleContent: - m_Text: Scene - m_Image: {fileID: 2318424515335265636, guid: 0000000000000000d000000000000000, - type: 0} - m_Tooltip: - m_DepthBufferBits: 32 - m_Pos: - serializedVersion: 2 - x: 0 - y: 19 - width: 971 - height: 445 - m_SceneLighting: 1 - lastFramingTime: 0 - m_2DMode: 0 - m_isRotationLocked: 0 - m_AudioPlay: 0 - m_Position: - m_Target: {x: 0, y: 0, z: 0} - speed: 2 - m_Value: {x: 0, y: 0, z: 0} - m_RenderMode: 0 - m_ValidateTrueMetals: 0 - m_SceneViewState: - showFog: 1 - showMaterialUpdate: 0 - showSkybox: 1 - showFlares: 1 - showImageEffects: 1 - grid: - xGrid: - m_Target: 0 - speed: 2 - m_Value: 0 - yGrid: - m_Target: 1 - speed: 2 - m_Value: 1 - zGrid: - m_Target: 0 - speed: 2 - m_Value: 0 - m_Rotation: - m_Target: {x: -0.08717229, y: 0.89959055, z: -0.21045254, w: -0.3726226} - speed: 2 - m_Value: {x: -0.08717229, y: 0.89959055, z: -0.21045254, w: -0.3726226} - m_Size: - m_Target: 10 - speed: 2 - m_Value: 10 - m_Ortho: - m_Target: 0 - speed: 2 - m_Value: 0 - m_LastSceneViewRotation: {x: 0, y: 0, z: 0, w: 0} - m_LastSceneViewOrtho: 0 - m_ReplacementShader: {fileID: 0} - m_ReplacementString: - m_LastLockedObject: {fileID: 0} - m_ViewIsLockedToObject: 0 ---- !u!114 &17 -MonoBehaviour: - m_ObjectHideFlags: 52 - m_PrefabParentObject: {fileID: 0} - m_PrefabInternal: {fileID: 0} - m_GameObject: {fileID: 0} - m_Enabled: 1 - m_EditorHideFlags: 1 - m_Script: {fileID: 12061, guid: 0000000000000000e000000000000000, type: 0} - m_Name: - m_EditorClassIdentifier: - m_AutoRepaintOnSceneChange: 0 - m_MinSize: {x: 200, y: 200} - m_MaxSize: {x: 4000, y: 4000} - m_TitleContent: - m_Text: Hierarchy - m_Image: {fileID: -590624980919486359, guid: 0000000000000000d000000000000000, - type: 0} - m_Tooltip: - m_DepthBufferBits: 0 - m_Pos: - serializedVersion: 2 - x: 2 - y: 19 - width: 286 - height: 445 - m_TreeViewState: - scrollPos: {x: 0, y: 0} - m_SelectedIDs: 68fbffff - m_LastClickedID: -1176 - m_ExpandedIDs: 7efbffff00000000 - m_RenameOverlay: - m_UserAcceptedRename: 0 - m_Name: - m_OriginalName: - m_EditFieldRect: - serializedVersion: 2 - x: 0 - y: 0 - width: 0 - height: 0 - m_UserData: 0 - m_IsWaitingForDelay: 0 - m_IsRenaming: 0 - m_OriginalEventType: 11 - m_IsRenamingFilename: 0 - m_ClientGUIView: {fileID: 0} - m_SearchString: - m_ExpandedScenes: - - - m_CurrenRootInstanceID: 0 - m_Locked: 0 - m_CurrentSortingName: TransformSorting diff --git a/Assets/TutorialInfo/Layout.wlt.meta b/Assets/TutorialInfo/Layout.wlt.meta deleted file mode 100644 index c0c8c773..00000000 --- a/Assets/TutorialInfo/Layout.wlt.meta +++ /dev/null @@ -1,8 +0,0 @@ -fileFormatVersion: 2 -guid: eabc9546105bf4accac1fd62a63e88e6 -timeCreated: 1487337779 -licenseType: Store -DefaultImporter: - userData: - assetBundleName: - assetBundleVariant: diff --git a/Assets/TutorialInfo/Scripts/Editor/ReadmeEditor.cs b/Assets/TutorialInfo/Scripts/Editor/ReadmeEditor.cs deleted file mode 100644 index ad55ecaa..00000000 --- a/Assets/TutorialInfo/Scripts/Editor/ReadmeEditor.cs +++ /dev/null @@ -1,242 +0,0 @@ -using System.Collections; -using System.Collections.Generic; -using UnityEngine; -using UnityEditor; -using System; -using System.IO; -using System.Reflection; - -[CustomEditor(typeof(Readme))] -[InitializeOnLoad] -public class ReadmeEditor : Editor -{ - static string s_ShowedReadmeSessionStateName = "ReadmeEditor.showedReadme"; - - static string s_ReadmeSourceDirectory = "Assets/TutorialInfo"; - - const float k_Space = 16f; - - static ReadmeEditor() - { - EditorApplication.delayCall += SelectReadmeAutomatically; - } - - static void RemoveTutorial() - { - if (EditorUtility.DisplayDialog("Remove Readme Assets", - - $"All contents under {s_ReadmeSourceDirectory} will be removed, are you sure you want to proceed?", - "Proceed", - "Cancel")) - { - if (Directory.Exists(s_ReadmeSourceDirectory)) - { - FileUtil.DeleteFileOrDirectory(s_ReadmeSourceDirectory); - FileUtil.DeleteFileOrDirectory(s_ReadmeSourceDirectory + ".meta"); - } - else - { - Debug.Log($"Could not find the Readme folder at {s_ReadmeSourceDirectory}"); - } - - var readmeAsset = SelectReadme(); - if (readmeAsset != null) - { - var path = AssetDatabase.GetAssetPath(readmeAsset); - FileUtil.DeleteFileOrDirectory(path + ".meta"); - FileUtil.DeleteFileOrDirectory(path); - } - - AssetDatabase.Refresh(); - } - } - - static void SelectReadmeAutomatically() - { - if (!SessionState.GetBool(s_ShowedReadmeSessionStateName, false)) - { - var readme = SelectReadme(); - SessionState.SetBool(s_ShowedReadmeSessionStateName, true); - - if (readme && !readme.loadedLayout) - { - LoadLayout(); - readme.loadedLayout = true; - } - } - } - - static void LoadLayout() - { - var assembly = typeof(EditorApplication).Assembly; - var windowLayoutType = assembly.GetType("UnityEditor.WindowLayout", true); - var method = windowLayoutType.GetMethod("LoadWindowLayout", BindingFlags.Public | BindingFlags.Static); - method.Invoke(null, new object[] { Path.Combine(Application.dataPath, "TutorialInfo/Layout.wlt"), false }); - } - - static Readme SelectReadme() - { - var ids = AssetDatabase.FindAssets("Readme t:Readme"); - if (ids.Length == 1) - { - var readmeObject = AssetDatabase.LoadMainAssetAtPath(AssetDatabase.GUIDToAssetPath(ids[0])); - - Selection.objects = new UnityEngine.Object[] { readmeObject }; - - return (Readme)readmeObject; - } - else - { - Debug.Log("Couldn't find a readme"); - return null; - } - } - - protected override void OnHeaderGUI() - { - var readme = (Readme)target; - Init(); - - var iconWidth = Mathf.Min(EditorGUIUtility.currentViewWidth / 3f - 20f, 128f); - - GUILayout.BeginHorizontal("In BigTitle"); - { - if (readme.icon != null) - { - GUILayout.Space(k_Space); - GUILayout.Label(readme.icon, GUILayout.Width(iconWidth), GUILayout.Height(iconWidth)); - } - GUILayout.Space(k_Space); - GUILayout.BeginVertical(); - { - - GUILayout.FlexibleSpace(); - GUILayout.Label(readme.title, TitleStyle); - GUILayout.FlexibleSpace(); - } - GUILayout.EndVertical(); - GUILayout.FlexibleSpace(); - } - GUILayout.EndHorizontal(); - } - - public override void OnInspectorGUI() - { - var readme = (Readme)target; - Init(); - - foreach (var section in readme.sections) - { - if (!string.IsNullOrEmpty(section.heading)) - { - GUILayout.Label(section.heading, HeadingStyle); - } - - if (!string.IsNullOrEmpty(section.text)) - { - GUILayout.Label(section.text, BodyStyle); - } - - if (!string.IsNullOrEmpty(section.linkText)) - { - if (LinkLabel(new GUIContent(section.linkText))) - { - Application.OpenURL(section.url); - } - } - - GUILayout.Space(k_Space); - } - - if (GUILayout.Button("Remove Readme Assets", ButtonStyle)) - { - RemoveTutorial(); - } - } - - bool m_Initialized; - - GUIStyle LinkStyle - { - get { return m_LinkStyle; } - } - - [SerializeField] - GUIStyle m_LinkStyle; - - GUIStyle TitleStyle - { - get { return m_TitleStyle; } - } - - [SerializeField] - GUIStyle m_TitleStyle; - - GUIStyle HeadingStyle - { - get { return m_HeadingStyle; } - } - - [SerializeField] - GUIStyle m_HeadingStyle; - - GUIStyle BodyStyle - { - get { return m_BodyStyle; } - } - - [SerializeField] - GUIStyle m_BodyStyle; - - GUIStyle ButtonStyle - { - get { return m_ButtonStyle; } - } - - [SerializeField] - GUIStyle m_ButtonStyle; - - void Init() - { - if (m_Initialized) - return; - m_BodyStyle = new GUIStyle(EditorStyles.label); - m_BodyStyle.wordWrap = true; - m_BodyStyle.fontSize = 14; - m_BodyStyle.richText = true; - - m_TitleStyle = new GUIStyle(m_BodyStyle); - m_TitleStyle.fontSize = 26; - - m_HeadingStyle = new GUIStyle(m_BodyStyle); - m_HeadingStyle.fontStyle = FontStyle.Bold; - m_HeadingStyle.fontSize = 18; - - m_LinkStyle = new GUIStyle(m_BodyStyle); - m_LinkStyle.wordWrap = false; - - // Match selection color which works nicely for both light and dark skins - m_LinkStyle.normal.textColor = new Color(0x00 / 255f, 0x78 / 255f, 0xDA / 255f, 1f); - m_LinkStyle.stretchWidth = false; - - m_ButtonStyle = new GUIStyle(EditorStyles.miniButton); - m_ButtonStyle.fontStyle = FontStyle.Bold; - - m_Initialized = true; - } - - bool LinkLabel(GUIContent label, params GUILayoutOption[] options) - { - var position = GUILayoutUtility.GetRect(label, LinkStyle, options); - - Handles.BeginGUI(); - Handles.color = LinkStyle.normal.textColor; - Handles.DrawLine(new Vector3(position.xMin, position.yMax), new Vector3(position.xMax, position.yMax)); - Handles.color = Color.white; - Handles.EndGUI(); - - EditorGUIUtility.AddCursorRect(position, MouseCursor.Link); - - return GUI.Button(position, label, LinkStyle); - } -} diff --git a/Assets/TutorialInfo/Scripts/Editor/ReadmeEditor.cs.meta b/Assets/TutorialInfo/Scripts/Editor/ReadmeEditor.cs.meta deleted file mode 100644 index f0386181..00000000 --- a/Assets/TutorialInfo/Scripts/Editor/ReadmeEditor.cs.meta +++ /dev/null @@ -1,12 +0,0 @@ -fileFormatVersion: 2 -guid: 476cc7d7cd9874016adc216baab94a0a -timeCreated: 1484146680 -licenseType: Store -MonoImporter: - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Assets/TutorialInfo/Scripts/Readme.cs b/Assets/TutorialInfo/Scripts/Readme.cs deleted file mode 100644 index 95f62693..00000000 --- a/Assets/TutorialInfo/Scripts/Readme.cs +++ /dev/null @@ -1,16 +0,0 @@ -using System; -using UnityEngine; - -public class Readme : ScriptableObject -{ - public Texture2D icon; - public string title; - public Section[] sections; - public bool loadedLayout; - - [Serializable] - public class Section - { - public string heading, text, linkText, url; - } -} diff --git a/Assets/TutorialInfo/Scripts/Readme.cs.meta b/Assets/TutorialInfo/Scripts/Readme.cs.meta deleted file mode 100644 index 935153ff..00000000 --- a/Assets/TutorialInfo/Scripts/Readme.cs.meta +++ /dev/null @@ -1,12 +0,0 @@ -fileFormatVersion: 2 -guid: fcf7219bab7fe46a1ad266029b2fee19 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: - - icon: {instanceID: 0} - executionOrder: 0 - icon: {fileID: 2800000, guid: a186f8a87ca4f4d3aa864638ad5dfb65, type: 3} - userData: - assetBundleName: - assetBundleVariant: diff --git a/ProjectSettings/ShaderGraphSettings.asset b/ProjectSettings/ShaderGraphSettings.asset index e66042a7..ce8c2432 100644 --- a/ProjectSettings/ShaderGraphSettings.asset +++ b/ProjectSettings/ShaderGraphSettings.asset @@ -13,6 +13,7 @@ MonoBehaviour: m_Name: m_EditorClassIdentifier: shaderVariantLimit: 128 + overrideShaderVariantLimit: 0 customInterpolatorErrorThreshold: 32 customInterpolatorWarningThreshold: 16 customHeatmapValues: {fileID: 0} diff --git a/ProjectSettings/URPProjectSettings.asset b/ProjectSettings/URPProjectSettings.asset index 08faf033..6ad56318 100644 --- a/ProjectSettings/URPProjectSettings.asset +++ b/ProjectSettings/URPProjectSettings.asset @@ -12,4 +12,5 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: 247994e1f5a72c2419c26a37e9334c01, type: 3} m_Name: m_EditorClassIdentifier: - m_LastMaterialVersion: 9 + m_LastMaterialVersion: 10 + m_ProjectSettingFolderPath: URPDefaultResources