using UnityEngine; using UnityEngine.Rendering; using UnityEditor; using UnityEngine.SceneManagement; using UnityEditor.SceneManagement; using System.Collections; using System.Collections.Generic; using System; using System.Linq; namespace Obi { [CustomEditor(typeof(ObiActorBlueprint), true)] public class ObiActorBlueprintEditor : Editor, IObiSelectableParticleProvider { protected IEnumerator routine; public List tools = new List(); public int currentToolIndex = 0; protected List properties = new List(); public int currentPropertyIndex = 0; protected List renderModes = new List(); public int renderModeFlags = 0; BooleanPreference showRenderModes; public bool editMode = false; public bool isEditing = false; protected List m_SceneStates; protected SceneSetup[] oldSetup; protected UnityEngine.Object oldSelection; //Additional status info for all particles: public float dotRadiusScale = 1; public int selectedCount = 0; public int activeParticle = -1; public bool[] selectionStatus = new bool[0]; public bool[] visible = new bool[0]; public Color[] tint = new Color[0]; protected float[] sqrDistanceToCamera = new float[0]; public int[] sortedIndices = new int[0]; public ObiActorBlueprint blueprint { get { return target as ObiActorBlueprint; } } public ObiBlueprintPropertyBase currentProperty { get { return GetProperty(currentPropertyIndex); } } public ObiBlueprintEditorTool currentTool { get { return GetTool(currentToolIndex); } } public override bool UseDefaultMargins() { return false; } public ObiBlueprintPropertyBase GetProperty(int index) { return (properties.Count > index && index >= 0) ? properties[index] : null; } public ObiBlueprintEditorTool GetTool(int index) { return (tools.Count > index && index >= 0) ? tools[index] : null; } #if (UNITY_2019_1_OR_NEWER) System.Action renderCallback; #endif public virtual void OnEnable() { properties.Add(new ObiBlueprintMass(this)); properties.Add(new ObiBlueprintRadius(this)); properties.Add(new ObiBlueprintFilterCategory(this)); properties.Add(new ObiBlueprintFilterMask(this)); renderModes.Add(new ObiBlueprintRenderModeParticles(this)); showRenderModes = new BooleanPreference($"{target.GetType()}.showRenderModes", false); #if (UNITY_2019_1_OR_NEWER) renderCallback = new System.Action((cntxt, cam) => { DrawWithCamera(cam); }); RenderPipelineManager.beginCameraRendering -= renderCallback; RenderPipelineManager.beginCameraRendering += renderCallback; #endif Camera.onPreCull -= DrawWithCamera; Camera.onPreCull += DrawWithCamera; } public virtual void OnDisable() { ExitBlueprintEditMode(); #if (UNITY_2019_1_OR_NEWER) RenderPipelineManager.beginCameraRendering -= renderCallback; #endif Camera.onPreCull -= DrawWithCamera; ObiParticleEditorDrawing.DestroyParticlesMesh(); foreach (var tool in tools) { tool.OnDisable(); tool.OnDestroy(); } foreach (var renderMode in renderModes) { renderMode.OnDestroy(); } properties.Clear(); renderModes.Clear(); } protected void Generate() { if (blueprint.empty) { EditorUtility.SetDirty(target); CoroutineJob job = new CoroutineJob(); routine = job.Start(blueprint.Generate()); EditorCoroutine.ShowCoroutineProgressBar("Generating blueprint...", ref routine); Refresh(); EditorGUIUtility.ExitGUI(); } else { if (EditorUtility.DisplayDialog("Blueprint generation", "This blueprint already contains data. Are you sure you want to re-generate this blueprint from scratch?", "Ok", "Cancel")) { EditorUtility.SetDirty(target); CoroutineJob job = new CoroutineJob(); routine = job.Start(blueprint.Generate()); EditorCoroutine.ShowCoroutineProgressBar("Generating blueprint...", ref routine); Refresh(); EditorGUIUtility.ExitGUI(); } } } protected virtual bool ValidateBlueprint() { return true; } public override void OnInspectorGUI() { serializedObject.UpdateIfRequiredOrScript(); EditorGUILayout.BeginVertical(EditorStyles.inspectorDefaultMargins); DrawBlueprintProperties(); GUILayout.Space(10); GUI.enabled = ValidateBlueprint(); if (GUILayout.Button("Generate", GUI.skin.FindStyle("LargeButton"), GUILayout.Height(32))) Generate(); GUI.enabled = (blueprint != null && !blueprint.empty && !Application.isPlaying); EditorGUI.BeginChangeCheck(); editMode = GUILayout.Toggle(editMode, editMode ? "Done" : "Edit", "Button"); if (EditorGUI.EndChangeCheck()) { if (editMode) EditorApplication.delayCall += EnterBlueprintEditMode; else EditorApplication.delayCall += ExitBlueprintEditMode; } EditorGUILayout.EndVertical(); GUI.enabled = true; if (isEditing) DrawTools(); if (GUI.changed) { serializedObject.ApplyModifiedProperties(); // There might be blueprint editing operations that have no undo entry, so do this to // ensure changes are serialized to disk by Unity. EditorUtility.SetDirty(target); } } protected virtual void DrawBlueprintProperties() { Editor.DrawPropertiesExcluding(serializedObject, "m_Script"); } private void DrawWithCamera(Camera camera) { if (editMode) { for (int i = 0; i < renderModes.Count; ++i) { if ((1 << i & renderModeFlags) != 0) renderModes[i].DrawWithCamera(camera); } } } [System.Serializable] protected class SceneStateCache { public SceneView view; public SceneView.SceneViewState state; } void EnterBlueprintEditMode() { if (!isEditing) { #if (UNITY_2019_1_OR_NEWER) SceneView.duringSceneGui -= this.OnSceneGUI; SceneView.duringSceneGui += this.OnSceneGUI; #else SceneView.onSceneGUIDelegate -= this.OnSceneGUI; SceneView.onSceneGUIDelegate += this.OnSceneGUI; #endif oldSelection = Selection.activeObject; if (EditorSceneManager.SaveCurrentModifiedScenesIfUserWantsTo()) { ActiveEditorTracker.sharedTracker.isLocked = true; oldSetup = EditorSceneManager.GetSceneManagerSetup(); EditorSceneManager.NewScene(NewSceneSetup.EmptyScene); // Set properties for all scene views: m_SceneStates = new List(); foreach (SceneView s in SceneView.sceneViews) { m_SceneStates.Add(new SceneStateCache { state = new SceneView.SceneViewState(s.sceneViewState), view = s }); s.sceneViewState.showFlares = false; s.sceneViewState.alwaysRefresh = false; s.sceneViewState.showFog = false; s.sceneViewState.showSkybox = false; s.sceneViewState.showImageEffects = false; s.sceneViewState.showParticleSystems = false; s.Frame(blueprint.bounds); } isEditing = true; Repaint(); } } } void ExitBlueprintEditMode() { if (isEditing) { isEditing = false; AssetDatabase.SaveAssets(); // Reset all scene views: foreach (var state in m_SceneStates) { if (state.view == null) continue; state.view.sceneViewState.showFog = state.state.showFog; state.view.sceneViewState.showFlares = state.state.showFlares; state.view.sceneViewState.alwaysRefresh = state.state.alwaysRefresh; state.view.sceneViewState.showSkybox = state.state.showSkybox; state.view.sceneViewState.showImageEffects = state.state.showImageEffects; state.view.sceneViewState.showParticleSystems = state.state.showParticleSystems; } ActiveEditorTracker.sharedTracker.isLocked = false; if (SceneManager.GetActiveScene().path.Length <= 0) { if (oldSetup != null && oldSetup.Length > 0) { EditorSceneManager.RestoreSceneManagerSetup(oldSetup); oldSetup = null; } else { EditorSceneManager.NewScene(NewSceneSetup.DefaultGameObjects); } } Selection.activeObject = oldSelection; #if (UNITY_2019_1_OR_NEWER) SceneView.duringSceneGui -= this.OnSceneGUI; #else SceneView.onSceneGUIDelegate -= this.OnSceneGUI; #endif Repaint(); } } public virtual void OnSceneGUI(SceneView sceneView) { if (!isEditing || sceneView.camera == null) return; ResizeParticleArrays(); Event e = Event.current; if (e.type == EventType.Repaint) { // Update camera facing status and world space positions array: UpdateParticleVisibility(); // Generate sorted indices for back-to-front rendering: for (int i = 0; i < sortedIndices.Length; i++) sortedIndices[i] = i; Array.Sort(sortedIndices, (a, b) => sqrDistanceToCamera[b].CompareTo(sqrDistanceToCamera[a])); // render modes OnSceneRepaint: for (int i = 0; i < renderModes.Count; ++i) { if ((1 << i & renderModeFlags) != 0) renderModes[i].OnSceneRepaint(sceneView); } // property OnSceneRepaint: currentProperty.OnSceneRepaint(); // update particle color based on visiblity, etc. UpdateTintColor(); // Draw particle handles: ObiParticleEditorDrawing.DrawParticles(sceneView.camera, blueprint, activeParticle, visible, tint, sortedIndices, dotRadiusScale); } if (currentTool != null) currentTool.OnSceneGUI(sceneView); } protected virtual void UpdateTintColor() { Color regularColor = ObiEditorSettings.GetOrCreateSettings().particleColor; Color selectedColor = ObiEditorSettings.GetOrCreateSettings().selectedParticleColor; Color activeColor = ObiEditorSettings.GetOrCreateSettings().activeParticleColor; for (int i = 0; i < blueprint.positions.Length; i++) { // get particle color: if (activeParticle == i) tint[i] = activeColor; else tint[i] = selectionStatus[i] ? selectedColor : regularColor; tint[i].a = visible[i] ? 1 : 0.15f; } } protected void ResizeParticleArrays() { if (blueprint.positions != null) { Array.Resize(ref selectionStatus, blueprint.positions.Length); Array.Resize(ref visible, blueprint.positions.Length); Array.Resize(ref tint, blueprint.positions.Length); Array.Resize(ref sqrDistanceToCamera, blueprint.positions.Length); Array.Resize(ref sortedIndices, blueprint.positions.Length); } } public int PropertySelector(int propertyIndex, string label = "Property") { // get all particle properties: string[] propertyNames = new string[properties.Count]; for (int i = 0; i < properties.Count; ++i) propertyNames[i] = properties[i].name; // Draw a selection dropdown: return EditorGUILayout.Popup(label, propertyIndex, propertyNames); } public virtual void RenderModeSelector() { showRenderModes.value = EditorGUILayout.BeginFoldoutHeaderGroup(showRenderModes, "Render modes"); if (showRenderModes) { EditorGUI.BeginChangeCheck(); for (int i = 0; i < renderModes.Count; ++i) { int value = 1 << i; if (EditorGUILayout.Toggle(renderModes[i].name, (value & renderModeFlags) != 0)) renderModeFlags |= value; else renderModeFlags &= ~value; } if (EditorGUI.EndChangeCheck()) Refresh(); } EditorGUILayout.EndFoldoutHeaderGroup(); } public void Refresh() { currentProperty.RecalculateMinMax(); // refresh render modes: for (int i = 0; i < renderModes.Count; ++i) { if ((1 << i & renderModeFlags) != 0) renderModes[i].Refresh(); } SceneView.RepaintAll(); } public virtual void UpdateParticleVisibility() { for (int i = 0; i < blueprint.positions.Length; i++) { if (blueprint.IsParticleActive(i)) { visible[i] = true; if (Camera.current != null) { Vector3 camToParticle = Camera.current.transform.position - blueprint.positions[i]; sqrDistanceToCamera[i] = camToParticle.sqrMagnitude; } } } if ((renderModeFlags & 1) != 0) Refresh(); } protected void DrawTools() { GUIContent[] contents = new GUIContent[tools.Count]; for (int i = 0; i < tools.Count; ++i) contents[i] = new GUIContent(tools[i].icon, tools[i].name); EditorGUILayout.Space(); GUILayout.Box(GUIContent.none, ObiEditorUtils.GetSeparatorLineStyle()); EditorGUILayout.Space(); EditorGUILayout.BeginVertical(EditorStyles.inspectorDefaultMargins); EditorGUI.BeginChangeCheck(); int newSelectedTool = ObiEditorUtils.DoToolBar(currentToolIndex, contents); EditorGUILayout.EndVertical(); if (EditorGUI.EndChangeCheck()) { if (currentTool != null) currentTool.OnDisable(); currentToolIndex = newSelectedTool; if (currentTool != null) currentTool.OnEnable(); SceneView.RepaintAll(); } if (currentTool != null) { EditorGUILayout.BeginVertical(EditorStyles.inspectorDefaultMargins); EditorGUILayout.LabelField(currentTool.name, EditorStyles.boldLabel); string help = currentTool.GetHelpString(); if (!help.Equals(string.Empty)) EditorGUILayout.LabelField(help, EditorStyles.helpBox); EditorGUILayout.EndVertical(); currentTool.OnInspectorGUI(); } } public void SetSelected(int particleIndex, bool selected) { selectionStatus[particleIndex] = selected; } public bool IsSelected(int particleIndex) { return selectionStatus[particleIndex]; } public bool Editable(int particleIndex) { return currentTool.Editable(particleIndex) && blueprint.IsParticleActive(particleIndex); } } }