// =============================================================================== // 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; } } }