2026-03-24 11:39:01 +08:00

537 lines
14 KiB
C#

#if UNITY_EDITOR
using UnityEditor;
using UnityEngine;
using UnityEngine.SceneManagement;
using System;
using Object = UnityEngine.Object;
namespace Framework
{
namespace Utils
{
namespace Editor
{
public class UnityPlayModeSaverWindow : EditorWindow
{
#region Constants
private const float kClearButtonWidth = 24f;
private const float kClearAllButtonWidth = 220f;
private const float kSaveModeWidth = 102f;
private const float kSnapshotButtonWidth = 116f;
private const float kDefaultNameWidth = 280f;
private const float kMinNameWidth = 50f;
private const string kWindowTitle = "Play Mode Saver";
private static readonly GUIContent kObjectLabel = new GUIContent("Saved Object");
private static readonly GUIContent kObjectPathLabel = new GUIContent("Object Path");
private static readonly GUIContent kNoObjectsLabel = new GUIContent("Either right click on any Game Object or Component and click 'Save Play Mode Changes'\nor drag any Game Object or Component into this window.");
private static readonly GUIContent kObjectsDetailsLabel = new GUIContent("These objects will have their values saved upon leaving Play Mode.\nIf an object has a snapshot saved it will restore to that, otherwise it will keep the values it had upon exiting Play Mode.");
private static readonly GUIContent kNotInEditModeLabel = new GUIContent("Not in Play Mode.");
private static readonly string[] kSaveModeOptions = new string[] { "Save on Exit", "Snapshot" };
private const string kDeletedObj = " <i>(Deleted)</i>";
private const string kSceneNotLoadedObj = " <i>(Scene not loaded)</i>";
private const string kSaveSnapshot = " Save Snapshot";
private const string kClearAllButton = "Clear All Saved Objects";
private static readonly float kResizerWidth = 6.0f;
private static readonly float kTextFieldSpace = 6.0f;
#endregion
#region Private Data
private Vector2 _scrollPosition = Vector2.zero;
private float _nameWidth = kDefaultNameWidth;
private Rect _nameResizerRect;
private bool _needsRepaint;
private enum ResizingState
{
NotResizing,
ResizingName,
}
private ResizingState _resizing;
private int _controlID;
private float _resizingOffset;
private GUIStyle _bottomBarInfoStyle;
private GUIStyle _bottomBarInfoTextStyle;
private GUIStyle _headerStyle;
private GUIStyle _itemNameStyle;
private GUIStyle _itemButtonStyle;
private GUIStyle _itemPathStyle;
private GUIStyle _itemSpaceStyle;
private GUIStyle _noObjectsStyle;
private GUIContent _clearButtonContent;
private GUIContent _clearAllButtonContent;
private GUIContent _saveSnapshotContent;
private Texture _scriptIcon;
#endregion
#region Internal Interface
internal static UnityPlayModeSaverWindow Open(bool focus)
{
UnityPlayModeSaverWindow window = (UnityPlayModeSaverWindow)GetWindow(typeof(UnityPlayModeSaverWindow), false, kWindowTitle, focus);
if (!focus)
window.Repaint();
return window;
}
#endregion
#region Unity Messages
private void OnGUI()
{
Initialize();
_needsRepaint = false;
if (UnityPlayModeSaver.CurrentPlayModeState == PlayModeStateChange.EnteredPlayMode)
{
EditorGUILayout.BeginVertical();
{
DrawTitleBar();
DrawTable();
DrawBottomButton();
}
EditorGUILayout.EndVertical();
HandleInput();
}
else
{
EditorGUILayout.LabelField(kNotInEditModeLabel, _noObjectsStyle, GUILayout.ExpandHeight(true));
}
if (_needsRepaint)
Repaint();
}
#endregion
#region Private Functions
private void Initialize()
{
if (_headerStyle == null)
{
_headerStyle = new GUIStyle(EditorStyles.toolbarButton)
{
alignment = TextAnchor.MiddleLeft,
padding = new RectOffset(8, 8, 0, 0),
fontStyle = FontStyle.Italic,
};
}
if (_itemNameStyle == null)
{
_itemNameStyle = new GUIStyle(EditorStyles.toolbarTextField)
{
alignment = TextAnchor.MiddleLeft,
margin = new RectOffset(0, 0, 2, 3),
padding = new RectOffset(8, 8, 0, 0),
fixedHeight = 0,
stretchHeight = true,
stretchWidth = true,
richText = true,
};
}
if (_itemButtonStyle == null)
{
_itemButtonStyle = new GUIStyle(EditorStyles.toolbarButton)
{
alignment = TextAnchor.MiddleLeft,
};
}
if (_itemPathStyle == null)
{
_itemPathStyle = new GUIStyle(EditorStyles.toolbarSearchField)
{
fontSize = 12,
margin = new RectOffset(0, 0, 2, 3),
padding = new RectOffset(16, 8, 0, 0),
fixedHeight = 0,
stretchHeight = true,
stretchWidth = true,
};
}
if (_itemSpaceStyle == null)
{
_itemSpaceStyle = new GUIStyle(EditorStyles.label)
{
margin = new RectOffset(0, 0, 0, 0),
padding = new RectOffset(0, 0, 0, 0),
};
}
if (_noObjectsStyle == null)
{
_noObjectsStyle = new GUIStyle(EditorStyles.label)
{
alignment = TextAnchor.MiddleCenter,
stretchWidth = true,
stretchHeight = true
};
}
if (_bottomBarInfoStyle == null)
{
_bottomBarInfoStyle = new GUIStyle(EditorStyles.toolbar)
{
fixedHeight = 42,
};
}
if (_bottomBarInfoTextStyle == null)
{
_bottomBarInfoTextStyle = new GUIStyle(EditorStyles.toolbarButton)
{
alignment = TextAnchor.MiddleCenter,
fixedHeight = 40,
fontStyle = FontStyle.Italic
};
}
if (_saveSnapshotContent == null)
{
titleContent = new GUIContent(kWindowTitle, EditorGUIUtility.IconContent("SaveAs").image);
minSize = new Vector2(kDefaultNameWidth, kDefaultNameWidth);
_saveSnapshotContent = new GUIContent(kSaveSnapshot, EditorGUIUtility.IconContent("SaveAs").image);
}
if (_clearButtonContent == null)
{
_clearButtonContent = EditorGUIUtility.IconContent("d_winbtn_win_close");
}
if (_clearAllButtonContent == null)
{
_clearAllButtonContent = new GUIContent(kClearAllButton);
}
if (_scriptIcon == null)
{
_scriptIcon = EditorGUIUtility.IconContent("cs Script Icon").image;
}
}
private void DrawTitleBar()
{
EditorGUILayout.BeginHorizontal(EditorStyles.toolbar);
{
//Clear Button
EditorGUILayout.Space(kClearButtonWidth, false);
//Object name
GUILayout.Label(kObjectLabel, _headerStyle, GUILayout.Width(_nameWidth - _scrollPosition.x));
//Name Resizer
RenderResizer(ref _nameResizerRect);
//Save Mode
GUILayout.Label("Save Mode", _headerStyle, GUILayout.Width(kSaveModeWidth));
//Snapshot buttons
GUILayout.Label(GUIContent.none, _headerStyle, GUILayout.Width(kSnapshotButtonWidth));
//Object Path
GUILayout.Label(kObjectPathLabel, _headerStyle);
}
EditorGUILayout.EndHorizontal();
}
private void DrawTable()
{
bool origGUIenabled = GUI.enabled;
_scrollPosition = EditorGUILayout.BeginScrollView(_scrollPosition, false, false);
{
for (int i=0; i< UnityPlayModeSaver.SavedObjects.Count;)
{
bool itemRemoved = false;
UnityPlayModeSaver.SavedObject savedObject = UnityPlayModeSaver.SavedObjects[i];
EditorGUILayout.BeginHorizontal(EditorStyles.toolbar);
{
//Clear object button
if (GUILayout.Button(_clearButtonContent, EditorStyles.toolbarButton, GUILayout.Width(kClearButtonWidth)))
{
itemRemoved = true;
}
//Spacer
GUILayout.Label(GUIContent.none, _itemSpaceStyle, GUILayout.Width(kTextFieldSpace));
// Object button
{
string name = " " + GetObjectName(savedObject);
if (savedObject._object == null)
{
GUI.enabled = false;
Scene scene = SceneManager.GetSceneByPath(savedObject._scenePath);
if (!scene.IsValid() || !scene.isLoaded)
{
name += kSceneNotLoadedObj;
}
else
{
name += kDeletedObj;
}
}
Texture icon = EditorGUIUtility.ObjectContent(null, savedObject._type).image;
if (icon == null)
{
icon = _scriptIcon;
}
GUIContent buttonContent = new GUIContent(name, icon);
if (GUILayout.Button(buttonContent, _itemNameStyle, GUILayout.Width(_nameWidth - kResizerWidth)))
{
FocusOnObject(savedObject._object);
}
GUI.enabled = origGUIenabled;
}
//Spacer
GUILayout.Label(GUIContent.none, _itemSpaceStyle, GUILayout.Width(kResizerWidth));
//Save Mode
{
int selected = savedObject._hasSnapshot ? 1 : 0;
int newSelected = EditorGUILayout.Popup(selected, kSaveModeOptions, EditorStyles.toolbarDropDown, GUILayout.Width(kSaveModeWidth));
if (selected != newSelected)
{
if (newSelected == 1)
{
UnityPlayModeSaver.SaveSnapshot(i);
}
else
{
UnityPlayModeSaver.ClearSnapshot(i);
//Check objects scene is still loaded, if not remove object
Scene scene = SceneManager.GetSceneByPath(savedObject._scenePath);
if (!scene.IsValid() || !scene.isLoaded)
{
itemRemoved = true;
}
}
}
}
//Snap shot button
{
if (savedObject._object == null)
{
GUI.enabled = false;
}
if (GUILayout.Button(_saveSnapshotContent, _itemButtonStyle, GUILayout.Width(kSnapshotButtonWidth)))
{
UnityPlayModeSaver.SaveSnapshot(i);
}
GUI.enabled = origGUIenabled;
}
//Spacer
GUILayout.Label(GUIContent.none, _itemSpaceStyle, GUILayout.Width(kTextFieldSpace));
//Object path
if (GUILayout.Button(savedObject._path + GetObjectName(savedObject), _itemPathStyle, GUILayout.ExpandWidth(true)))
{
FocusOnObject(savedObject._object);
}
GUILayout.Label(GUIContent.none, _itemSpaceStyle, GUILayout.Width(kTextFieldSpace));
}
EditorGUILayout.EndHorizontal();
if (itemRemoved)
{
UnityPlayModeSaver.ClearSavedObject(i);
_needsRepaint = true;
}
else
{
i++;
}
}
if (UnityPlayModeSaver.SavedObjects.Count == 0)
{
EditorGUILayout.LabelField(kNoObjectsLabel, _noObjectsStyle, GUILayout.ExpandHeight(true));
}
}
EditorGUILayout.EndScrollView();
}
private void DrawBottomButton()
{
EditorGUILayout.BeginHorizontal(_bottomBarInfoStyle);
{
GUILayout.Label(kObjectsDetailsLabel, _bottomBarInfoTextStyle);
}
EditorGUILayout.EndHorizontal();
EditorGUILayout.BeginHorizontal(EditorStyles.toolbar);
{
GUILayout.FlexibleSpace();
if (GUILayout.Button(_clearAllButtonContent, GUILayout.Width(kClearAllButtonWidth)))
{
UnityPlayModeSaver.ClearCache();
_needsRepaint = true;
}
GUILayout.FlexibleSpace();
}
EditorGUILayout.EndHorizontal();
}
private void HandleInput()
{
Event inputEvent = Event.current;
if (inputEvent == null)
return;
EventType controlEventType = inputEvent.GetTypeForControl(_controlID);
if (_resizing != ResizingState.NotResizing && inputEvent.rawType == EventType.MouseUp)
{
_resizing = ResizingState.NotResizing;
_needsRepaint = true;
}
switch (controlEventType)
{
case EventType.MouseDown:
{
if (inputEvent.button == 0)
{
if (_nameResizerRect.Contains(inputEvent.mousePosition))
{
_resizing = ResizingState.ResizingName;
}
else
{
_resizing = ResizingState.NotResizing;
}
if (_resizing != ResizingState.NotResizing)
{
inputEvent.Use();
_resizingOffset = inputEvent.mousePosition.x;
}
}
}
break;
case EventType.MouseUp:
{
if (_resizing != ResizingState.NotResizing)
{
inputEvent.Use();
_resizing = ResizingState.NotResizing;
}
}
break;
case EventType.MouseDrag:
{
if (_resizing != ResizingState.NotResizing)
{
if (_resizing == ResizingState.ResizingName)
{
_nameWidth += (inputEvent.mousePosition.x - _resizingOffset);
_nameWidth = Math.Max(_nameWidth, kMinNameWidth);
}
_resizingOffset = inputEvent.mousePosition.x;
_needsRepaint = true;
}
}
break;
case EventType.DragUpdated:
{
bool objectsAreAllowed = true;
foreach (Object obj in DragAndDrop.objectReferences)
{
if (!(obj is GameObject) && !(obj is Component))
{
objectsAreAllowed = false;
break;
}
}
DragAndDrop.visualMode = objectsAreAllowed ? DragAndDropVisualMode.Copy : DragAndDropVisualMode.Rejected;
}
break;
case EventType.DragPerform:
{
foreach (Object obj in DragAndDrop.objectReferences)
{
if (!UnityPlayModeSaver.IsObjectRegistered(obj, out _))
{
if (obj is GameObject gameObject)
{
UnityPlayModeSaver.RegisterSavedObject(gameObject);
}
else if (obj is Component component)
{
UnityPlayModeSaver.RegisterSavedObject(component);
}
}
}
DragAndDrop.AcceptDrag();
}
break;
}
}
private void RenderResizer(ref Rect rect)
{
GUILayout.Box(GUIContent.none, EditorStyles.toolbar, GUILayout.Width(kResizerWidth), GUILayout.ExpandHeight(true));
rect = GUILayoutUtility.GetLastRect();
EditorGUIUtility.AddCursorRect(rect, MouseCursor.SplitResizeLeftRight);
}
private static void FocusOnObject(Object obj)
{
Selection.activeObject = obj;
SceneView.FrameLastActiveSceneView();
EditorGUIUtility.PingObject(obj);
}
private string GetObjectName(UnityPlayModeSaver.SavedObject savedObject)
{
if (savedObject._object == null)
{
return savedObject._name;
}
else
{
return UnityPlayModeSaver.GetObjectName(savedObject._object);
}
}
#endregion
}
}
}
}
#endif