Files
BABA_YAGA/Assets/Editor/PlayFromHereTool.cs

215 lines
8.2 KiB
C#
Raw Normal View History

2026-03-26 20:27:19 +07:00
// ===============================================================================
// 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;
}
}
}