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