diff --git a/Assets/Examples/Prefabs/TestOutlineLayers.asset b/Assets/Examples/Prefabs/TestOutlineLayers.asset index 4cb159e..4db7174 100644 --- a/Assets/Examples/Prefabs/TestOutlineLayers.asset +++ b/Assets/Examples/Prefabs/TestOutlineLayers.asset @@ -19,15 +19,18 @@ MonoBehaviour: _outlineWidth: 5 _outlineIntensity: 2 _outlineMode: 0 + _enabled: 1 - _settings: _outlineSettings: {fileID: 0} _outlineColor: {r: 1, g: 1, b: 0, a: 1} _outlineWidth: 15 _outlineIntensity: 2 _outlineMode: 1 + _enabled: 1 - _settings: _outlineSettings: {fileID: 0} _outlineColor: {r: 1, g: 0, b: 1, a: 1} _outlineWidth: 4 _outlineIntensity: 2 _outlineMode: 0 + _enabled: 1 diff --git a/Assets/Plugins/UnityFx.Outline/Editor/Scripts/OutlineBehaviourEditor.cs b/Assets/Plugins/UnityFx.Outline/Editor/Scripts/OutlineBehaviourEditor.cs index 4716161..ea92d3c 100644 --- a/Assets/Plugins/UnityFx.Outline/Editor/Scripts/OutlineBehaviourEditor.cs +++ b/Assets/Plugins/UnityFx.Outline/Editor/Scripts/OutlineBehaviourEditor.cs @@ -12,6 +12,7 @@ namespace UnityFx.Outline public class OutlineBehaviourEditor : Editor { private OutlineBehaviour _effect; + private bool _debugOpened; private bool _renderersOpened; private bool _camerasOpened; @@ -78,6 +79,18 @@ public override void OnInspectorGUI() EditorGUI.indentLevel -= 1; EditorGUI.EndDisabledGroup(); } + + // 4) Debug info. + _debugOpened = EditorGUILayout.Foldout(_debugOpened, "Debug", true); + + if (_debugOpened) + { + EditorGUI.BeginDisabledGroup(true); + EditorGUI.indentLevel += 1; + EditorGUILayout.IntField("Command buffer updates", _effect.NumberOfCommandBufferUpdates); + EditorGUI.indentLevel -= 1; + EditorGUI.EndDisabledGroup(); + } } } } diff --git a/Assets/Plugins/UnityFx.Outline/Editor/Scripts/OutlineEditorUtility.cs b/Assets/Plugins/UnityFx.Outline/Editor/Scripts/OutlineEditorUtility.cs index 6e04ee0..10ee383 100644 --- a/Assets/Plugins/UnityFx.Outline/Editor/Scripts/OutlineEditorUtility.cs +++ b/Assets/Plugins/UnityFx.Outline/Editor/Scripts/OutlineEditorUtility.cs @@ -106,8 +106,18 @@ public static void RenderPreview(OutlineLayer layer, int layerIndex, bool showOb EditorGUI.indentLevel += 1; EditorGUILayout.PrefixLabel("Layer #" + layerIndex.ToString()); EditorGUI.indentLevel -= 1; - EditorGUILayout.IntField(layer.OutlineWidth, GUILayout.MaxWidth(100)); - EditorGUILayout.ColorField(layer.OutlineColor, GUILayout.MinWidth(100)); + + if (layer.Enabled) + { + EditorGUILayout.LabelField(layer.OutlineMode == OutlineMode.Solid ? layer.OutlineMode.ToString() : string.Format("Blurred ({0})", layer.OutlineIntensity), GUILayout.MaxWidth(70)); + EditorGUILayout.IntField(layer.OutlineWidth, GUILayout.MaxWidth(100)); + EditorGUILayout.ColorField(layer.OutlineColor, GUILayout.MinWidth(100)); + } + else + { + EditorGUILayout.LabelField("Disabled."); + } + EditorGUILayout.EndHorizontal(); if (showObjects) diff --git a/Assets/Plugins/UnityFx.Outline/Editor/Scripts/OutlineEffectEditor.cs b/Assets/Plugins/UnityFx.Outline/Editor/Scripts/OutlineEffectEditor.cs index f6fb00d..464e492 100644 --- a/Assets/Plugins/UnityFx.Outline/Editor/Scripts/OutlineEffectEditor.cs +++ b/Assets/Plugins/UnityFx.Outline/Editor/Scripts/OutlineEffectEditor.cs @@ -5,7 +5,8 @@ using System.Collections; using System.Collections.Generic; using UnityEditor; -using UnityEngine; +using UnityEditor.SceneManagement; +using UnityEngine.Rendering; namespace UnityFx.Outline { @@ -13,6 +14,7 @@ namespace UnityFx.Outline public class OutlineEffectEditor : Editor { private OutlineEffect _effect; + private bool _debugOpened; private bool _previewOpened; private void OnEnable() @@ -24,6 +26,25 @@ public override void OnInspectorGUI() { base.OnInspectorGUI(); + EditorGUI.BeginChangeCheck(); + var e = (CameraEvent)EditorGUILayout.EnumPopup("Render Event", _effect.RenderEvent); + + if (e != _effect.RenderEvent) + { + Undo.RecordObject(_effect, "Set Render Event"); + _effect.RenderEvent = e; + } + + if (EditorGUI.EndChangeCheck()) + { + EditorUtility.SetDirty(_effect.gameObject); + + if (!EditorApplication.isPlayingOrWillChangePlaymode) + { + EditorSceneManager.MarkSceneDirty(_effect.gameObject.scene); + } + } + if (_effect.OutlineLayers.Count > 0) { _previewOpened = EditorGUILayout.Foldout(_previewOpened, "Preview", true); @@ -33,6 +54,17 @@ public override void OnInspectorGUI() OutlineEditorUtility.RenderPreview(_effect.OutlineLayers, true); } } + + _debugOpened = EditorGUILayout.Foldout(_debugOpened, "Debug", true); + + if (_debugOpened) + { + EditorGUI.BeginDisabledGroup(true); + EditorGUI.indentLevel += 1; + EditorGUILayout.IntField("Command buffer updates", _effect.NumberOfCommandBufferUpdates); + EditorGUI.indentLevel -= 1; + EditorGUI.EndDisabledGroup(); + } } } } diff --git a/Assets/Plugins/UnityFx.Outline/Editor/Scripts/OutlineLayerCollectionEditor.cs b/Assets/Plugins/UnityFx.Outline/Editor/Scripts/OutlineLayerCollectionEditor.cs index a3fc6bb..7e3615f 100644 --- a/Assets/Plugins/UnityFx.Outline/Editor/Scripts/OutlineLayerCollectionEditor.cs +++ b/Assets/Plugins/UnityFx.Outline/Editor/Scripts/OutlineLayerCollectionEditor.cs @@ -29,8 +29,23 @@ public override void OnInspectorGUI() for (var i = 0; i < _layers.Count; i++) { EditorGUILayout.Space(); + var rect = EditorGUILayout.BeginHorizontal(); - EditorGUILayout.PrefixLabel("Layer #" + i.ToString()); + var enabled = EditorGUILayout.ToggleLeft("Layer #" + i.ToString(), _layers[i].Enabled); + + if (enabled != _layers[i].Enabled) + { + if (enabled) + { + Undo.RecordObject(_layers, "Enable Layer"); + } + else + { + Undo.RecordObject(_layers, "Disable Layer"); + } + + _layers[i].Enabled = enabled; + } GUILayout.FlexibleSpace(); @@ -49,6 +64,22 @@ public override void OnInspectorGUI() GUI.Box(rect, GUIContent.none); + var name = EditorGUILayout.TextField("Layer Name", _layers[i].NameTag); + + if (name != _layers[i].NameTag) + { + Undo.RecordObject(_layers, "Set Layer Name"); + _layers[i].NameTag = name; + } + + var priority = EditorGUILayout.IntField("Layer Priority", _layers[i].Priority); + + if (priority != _layers[i].Priority) + { + Undo.RecordObject(_layers, "Set Layer Priority"); + _layers[i].Priority = priority; + } + OutlineEditorUtility.Render(_layers[i], _layers); } } diff --git a/Assets/Plugins/UnityFx.Outline/Runtime/Scripts/Helpers.meta b/Assets/Plugins/UnityFx.Outline/Runtime/Scripts/Helpers.meta new file mode 100644 index 0000000..f18352b --- /dev/null +++ b/Assets/Plugins/UnityFx.Outline/Runtime/Scripts/Helpers.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: de5ff1c474a17ba44a09339f7b56a80b +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Plugins/UnityFx.Outline/Runtime/Scripts/Helpers/AssemblyInfo.cs b/Assets/Plugins/UnityFx.Outline/Runtime/Scripts/Helpers/AssemblyInfo.cs new file mode 100644 index 0000000..041e387 --- /dev/null +++ b/Assets/Plugins/UnityFx.Outline/Runtime/Scripts/Helpers/AssemblyInfo.cs @@ -0,0 +1,32 @@ +// Copyright (C) 2019 Alexander Bogarsukov. All rights reserved. +// See the LICENSE.md file in the project root for more information. + +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("UnityFx.Outline")] +[assembly: AssemblyProduct("UnityFx.Outline")] +[assembly: AssemblyDescription("Screen-space outlines for Unity3d.")] +#if DEBUG +[assembly: AssemblyConfiguration("Debug")] +#else +[assembly: AssemblyConfiguration("Release")] +#endif +[assembly: AssemblyCompany("")] +[assembly: AssemblyCopyright("Copyright © Alexander Bogarsukov 2019")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM. +[assembly: Guid("1ace8625-97c5-4d37-a649-03975d187542")] + +// Make internals visible to the editor assembly. +[assembly: InternalsVisibleTo("UnityFx.Outline.Editor")] diff --git a/Assets/Plugins/UnityFx.Outline/Runtime/Scripts/Helpers/AssemblyInfo.cs.meta b/Assets/Plugins/UnityFx.Outline/Runtime/Scripts/Helpers/AssemblyInfo.cs.meta new file mode 100644 index 0000000..dd9f2bd --- /dev/null +++ b/Assets/Plugins/UnityFx.Outline/Runtime/Scripts/Helpers/AssemblyInfo.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 1613c034178676349be3282789167284 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Plugins/UnityFx.Outline/Runtime/Scripts/OutlineBehaviour.Renderers.cs b/Assets/Plugins/UnityFx.Outline/Runtime/Scripts/OutlineBehaviour.Renderers.cs index d2304d6..70989b9 100644 --- a/Assets/Plugins/UnityFx.Outline/Runtime/Scripts/OutlineBehaviour.Renderers.cs +++ b/Assets/Plugins/UnityFx.Outline/Runtime/Scripts/OutlineBehaviour.Renderers.cs @@ -35,7 +35,7 @@ private void OnWillRenderObject() } } - private sealed class RendererCollection : IList + private sealed class RendererCollection : ICollection { #region data @@ -73,58 +73,6 @@ public void Reset() #endregion - #region IList - - public Renderer this[int index] - { - get - { - return _renderers[index]; - } - set - { - if (index < 0 || index >= _renderers.Count) - { - throw new ArgumentOutOfRangeException("index"); - } - - Validate(value); - Release(_renderers[index]); - Init(value); - - _renderers[index] = value; - } - } - - public int IndexOf(Renderer renderer) - { - return _renderers.IndexOf(renderer); - } - - public void Insert(int index, Renderer renderer) - { - if (index < 0 || index >= _renderers.Count) - { - throw new ArgumentOutOfRangeException("index"); - } - - Validate(renderer); - Init(renderer); - - _renderers.Insert(index, renderer); - } - - public void RemoveAt(int index) - { - if (index >= 0 && index < _renderers.Count) - { - Release(_renderers[index]); - _renderers.RemoveAt(index); - } - } - - #endregion - #region ICollection public int Count diff --git a/Assets/Plugins/UnityFx.Outline/Runtime/Scripts/OutlineBehaviour.cs b/Assets/Plugins/UnityFx.Outline/Runtime/Scripts/OutlineBehaviour.cs index ad60cec..4f41843 100644 --- a/Assets/Plugins/UnityFx.Outline/Runtime/Scripts/OutlineBehaviour.cs +++ b/Assets/Plugins/UnityFx.Outline/Runtime/Scripts/OutlineBehaviour.cs @@ -34,10 +34,31 @@ public sealed partial class OutlineBehaviour : MonoBehaviour, IOutlineSettingsEx private Dictionary _cameraMap = new Dictionary(); private float _cameraMapUpdateTimer; +#if UNITY_EDITOR + + private int _commandBufferUpdateCounter; + +#endif + #endregion #region interface +#if UNITY_EDITOR + + /// + /// Gets number of the command buffer updates since its creation. Only available in editor. + /// + public int NumberOfCommandBufferUpdates + { + get + { + return _commandBufferUpdateCounter; + } + } + +#endif + /// /// Gets or sets resources used by the effect implementation. /// @@ -148,7 +169,7 @@ private void Update() #endif - if (_outlineResources != null && _renderers != null && _outlineSettings.IsChanged) + if (_outlineResources != null && _renderers != null && (_outlineSettings.IsChanged || _commandBuffer.sizeInBytes == 0)) { using (var renderer = new OutlineRenderer(_commandBuffer, BuiltinRenderTextureType.CameraTarget)) { @@ -156,6 +177,12 @@ private void Update() } _outlineSettings.AcceptChanges(); + +#if UNITY_EDITOR + + _commandBufferUpdateCounter++; + +#endif } } @@ -306,6 +333,12 @@ private void CreateCommandBufferIfNeeded() { _commandBuffer = new CommandBuffer(); _commandBuffer.name = string.Format("{0} - {1}", GetType().Name, name); + +#if UNITY_EDITOR + + _commandBufferUpdateCounter = 0; + +#endif } } diff --git a/Assets/Plugins/UnityFx.Outline/Runtime/Scripts/OutlineEffect.cs b/Assets/Plugins/UnityFx.Outline/Runtime/Scripts/OutlineEffect.cs index a5bfc3f..c5f9e75 100644 --- a/Assets/Plugins/UnityFx.Outline/Runtime/Scripts/OutlineEffect.cs +++ b/Assets/Plugins/UnityFx.Outline/Runtime/Scripts/OutlineEffect.cs @@ -2,7 +2,7 @@ // See the LICENSE.md file in the project root for more information. using System; -using System.Collections.Generic; +using System.Runtime.CompilerServices; using UnityEngine; using UnityEngine.Rendering; @@ -25,14 +25,37 @@ public sealed partial class OutlineEffect : MonoBehaviour private OutlineResources _outlineResources; [SerializeField] private OutlineLayerCollection _outlineLayers; + [SerializeField, HideInInspector] + private CameraEvent _cameraEvent = OutlineRenderer.RenderEvent; private CommandBuffer _commandBuffer; private bool _changed; +#if UNITY_EDITOR + + private int _commandBufferUpdateCounter; + +#endif + #endregion #region interface +#if UNITY_EDITOR + + /// + /// Gets number of the command buffer updates since its creation. Only available in editor. + /// + public int NumberOfCommandBufferUpdates + { + get + { + return _commandBufferUpdateCounter; + } + } + +#endif + /// /// Gets or sets resources used by the effect implementation. /// @@ -67,11 +90,7 @@ public OutlineLayerCollection OutlineLayers { get { - if (_outlineLayers == null) - { - _outlineLayers = ScriptableObject.CreateInstance(); - } - + CreateLayersIfNeeded(); return _outlineLayers; } set @@ -89,6 +108,35 @@ public OutlineLayerCollection OutlineLayers } } + /// + /// Gets or sets used to render the outlines. + /// + public CameraEvent RenderEvent + { + get + { + return _cameraEvent; + } + set + { + if (_cameraEvent != value) + { + if (_commandBuffer != null) + { + var camera = GetComponent(); + + if (camera) + { + camera.RemoveCommandBuffer(_cameraEvent, _commandBuffer); + camera.AddCommandBuffer(value, _commandBuffer); + } + } + + _cameraEvent = value; + } + } + } + /// /// Shares with another instance. /// @@ -98,10 +146,7 @@ public void ShareLayersWith(OutlineEffect other) { if (other) { - if (_outlineLayers == null) - { - _outlineLayers = ScriptableObject.CreateInstance(); - } + CreateLayersIfNeeded(); other._outlineLayers = _outlineLayers; other._changed = true; @@ -133,11 +178,20 @@ private void OnEnable() if (camera) { - _commandBuffer = new CommandBuffer(); - _commandBuffer.name = string.Format("{0} - {1}", GetType().Name, name); + _commandBuffer = new CommandBuffer + { + name = string.Format("{0} - {1}", GetType().Name, name) + }; + _changed = true; - camera.AddCommandBuffer(OutlineRenderer.RenderEvent, _commandBuffer); +#if UNITY_EDITOR + + _commandBufferUpdateCounter = 0; + +#endif + + camera.AddCommandBuffer(_cameraEvent, _commandBuffer); } } @@ -147,7 +201,7 @@ private void OnDisable() if (camera) { - camera.RemoveCommandBuffer(OutlineRenderer.RenderEvent, _commandBuffer); + camera.RemoveCommandBuffer(_cameraEvent, _commandBuffer); } if (_commandBuffer != null) @@ -182,6 +236,7 @@ private void LateUpdate() private void OnDestroy() { + // TODO: Find a way to do this once per OutlineLayerCollection instance. if (_outlineLayers) { _outlineLayers.Reset(); @@ -213,13 +268,7 @@ private void FillCommandBuffer() { using (var renderer = new OutlineRenderer(_commandBuffer, BuiltinRenderTextureType.CameraTarget)) { - for (var i = 0; i < _outlineLayers.Count; ++i) - { - if (_outlineLayers[i] != null) - { - _outlineLayers[i].Render(renderer, _outlineResources); - } - } + _outlineLayers.Render(renderer, _outlineResources); } } else @@ -228,11 +277,21 @@ private void FillCommandBuffer() } _changed = false; + +#if UNITY_EDITOR + + _commandBufferUpdateCounter++; + +#endif } - private void OnChanged(object sender, EventArgs args) + private void CreateLayersIfNeeded() { - _changed = true; + if (ReferenceEquals(_outlineLayers, null)) + { + _outlineLayers = ScriptableObject.CreateInstance(); + _outlineLayers.name = "OutlineLayers"; + } } #endregion diff --git a/Assets/Plugins/UnityFx.Outline/Runtime/Scripts/OutlineLayer.Renderers.cs b/Assets/Plugins/UnityFx.Outline/Runtime/Scripts/OutlineLayer.Renderers.cs new file mode 100644 index 0000000..79dbd9e --- /dev/null +++ b/Assets/Plugins/UnityFx.Outline/Runtime/Scripts/OutlineLayer.Renderers.cs @@ -0,0 +1,155 @@ +// Copyright (C) 2019 Alexander Bogarsukov. All rights reserved. +// See the LICENSE.md file in the project root for more information. + +using System; +using System.Collections; +using System.Collections.Generic; +using System.ComponentModel; +using UnityEngine; + +namespace UnityFx.Outline +{ + partial class OutlineLayer + { + #region interface + #endregion + + #region implementation + + private sealed class RendererCollection : ICollection + { + #region data + + private readonly List _renderers = new List(); + private readonly GameObject _go; + + #endregion + + #region interface + + internal RendererCollection(GameObject parent) + { + Debug.Assert(parent); + _go = parent; + } + + internal RendererCollection(GameObject parent, int ignoreMask) + { + Debug.Assert(parent); + + _go = parent; + Reset(ignoreMask); + } + + public void Reset(int ignoreLayerMask) + { + _renderers.Clear(); + + var renderers = _go.GetComponentsInChildren(); + + if (renderers != null) + { + if (ignoreLayerMask != 0) + { + foreach (var renderer in renderers) + { + if (((1 << renderer.gameObject.layer) & ignoreLayerMask) == 0) + { + _renderers.Add(renderer); + } + } + } + else + { + foreach (var renderer in renderers) + { + _renderers.Add(renderer); + } + } + } + } + + #endregion + + #region ICollection + + public int Count + { + get + { + return _renderers.Count; + } + } + + public bool IsReadOnly + { + get + { + return false; + } + } + + public void Add(Renderer renderer) + { + Validate(renderer); + + _renderers.Add(renderer); + } + + public bool Remove(Renderer renderer) + { + return _renderers.Remove(renderer); + } + + public void Clear() + { + _renderers.Clear(); + } + + public bool Contains(Renderer renderer) + { + return _renderers.Contains(renderer); + } + + public void CopyTo(Renderer[] array, int arrayIndex) + { + _renderers.CopyTo(array, arrayIndex); + } + + #endregion + + #region IEnumerable + + public IEnumerator GetEnumerator() + { + return _renderers.GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return _renderers.GetEnumerator(); + } + + #endregion + + #region implementation + + private void Validate(Renderer renderer) + { + if (renderer == null) + { + throw new ArgumentNullException("renderer"); + } + + if (!renderer.transform.IsChildOf(_go.transform)) + { + throw new ArgumentException(string.Format("Only children of the {0} are allowed.", _go.name), "renderer"); + } + } + + #endregion + } + + #endregion + } +} diff --git a/Assets/Plugins/UnityFx.Outline/Runtime/Scripts/OutlineLayer.Renderers.cs.meta b/Assets/Plugins/UnityFx.Outline/Runtime/Scripts/OutlineLayer.Renderers.cs.meta new file mode 100644 index 0000000..a2ecbf0 --- /dev/null +++ b/Assets/Plugins/UnityFx.Outline/Runtime/Scripts/OutlineLayer.Renderers.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: f98c44e515873c54180f1d3b1037f999 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Plugins/UnityFx.Outline/Runtime/Scripts/OutlineLayer.cs b/Assets/Plugins/UnityFx.Outline/Runtime/Scripts/OutlineLayer.cs index 5fb6bc3..acbcf47 100644 --- a/Assets/Plugins/UnityFx.Outline/Runtime/Scripts/OutlineLayer.cs +++ b/Assets/Plugins/UnityFx.Outline/Runtime/Scripts/OutlineLayer.cs @@ -5,6 +5,7 @@ using System.Collections; using System.Collections.Generic; using System.ComponentModel; +using System.Text; using UnityEngine; namespace UnityFx.Outline @@ -16,21 +17,105 @@ namespace UnityFx.Outline /// /// [Serializable] - public sealed class OutlineLayer : ICollection, IOutlineSettingsEx, IChangeTracking + public sealed partial class OutlineLayer : ICollection, IOutlineSettingsEx, IChangeTracking { #region data [SerializeField, HideInInspector] private OutlineSettingsInstance _settings = new OutlineSettingsInstance(); + [SerializeField, HideInInspector] + private string _name; + [SerializeField, HideInInspector] + private int _zOrder; + [SerializeField, HideInInspector] + private bool _enabled = true; private OutlineLayerCollection _parentCollection; - private Dictionary _outlineObjects = new Dictionary(); + private Dictionary _outlineObjects = new Dictionary(); private bool _changed; #endregion #region interface + /// + /// Gets the layer name. + /// + public string Name + { + get + { + if (string.IsNullOrEmpty(_name)) + { + return "OutlineLayer #" + Index.ToString(); + } + + return _name; + } + } + + /// + /// Gets or sets a value indicating whether the layer is enabled. + /// + /// + public bool Enabled + { + get + { + return _enabled; + } + set + { + if (_enabled != value) + { + _enabled = value; + _changed = true; + } + } + } + + /// + /// Gets or sets the layer priority. Layers with greater values are rendered on top of layers with lower priority. + /// Layers with equal priorities are rendered according to index in parent collection. + /// + /// + public int Priority + { + get + { + return _zOrder; + } + set + { + if (_zOrder != value) + { + if (_parentCollection != null) + { + _parentCollection.SetOrderChanged(); + } + + _zOrder = value; + _changed = true; + } + } + } + + /// + /// Gets index of the layer in parent collection. + /// + public int Index + { + get + { + if (_parentCollection != null) + { + return _parentCollection.IndexOf(this); + } + + return -1; + } + } + /// /// Initializes a new instance of the class. /// @@ -38,6 +123,14 @@ public OutlineLayer() { } + /// + /// Initializes a new instance of the class. + /// + public OutlineLayer(string name) + { + _name = name; + } + /// /// Initializes a new instance of the class. /// @@ -52,6 +145,21 @@ public OutlineLayer(OutlineSettings settings) _settings.OutlineSettings = settings; } + /// + /// Initializes a new instance of the class. + /// + /// Thrown if is . + public OutlineLayer(string name, OutlineSettings settings) + { + if (settings == null) + { + throw new ArgumentNullException("settings"); + } + + _name = name; + _settings.OutlineSettings = settings; + } + /// /// Adds a new object to the layer. /// @@ -65,39 +173,59 @@ public void Add(GameObject go, int ignoreLayerMask) if (!_outlineObjects.ContainsKey(go)) { - var renderers = go.GetComponentsInChildren(); + _outlineObjects.Add(go, new RendererCollection(go, ignoreLayerMask)); + _changed = true; + } + } - if (renderers != null) - { - if (renderers.Length > 0 && ignoreLayerMask != 0) - { - var filteredRenderers = new List(renderers.Length); + /// + /// Adds a new object to the layer. + /// + /// Thrown if is . + public void Add(GameObject go, string ignoreLayer) + { + Add(go, 1 << LayerMask.NameToLayer(ignoreLayer)); + } - for (var i = 0; i < renderers.Length; ++i) - { - if ((renderers[i].gameObject.layer & ignoreLayerMask) == 0) - { - filteredRenderers.Add(renderers[i]); - } - } + /// + /// Attempts to get renderers assosiated with the specified . + /// + /// Thrown if is . + public bool TryGetRenderers(GameObject go, out ICollection renderers) + { + if (go == null) + { + throw new ArgumentNullException("go"); + } - renderers = filteredRenderers.ToArray(); - } - } - else - { - renderers = new Renderer[0]; - } + RendererCollection result; - _outlineObjects.Add(go, renderers); - _changed = true; + if (_outlineObjects.TryGetValue(go, out result)) + { + renderers = result; + return true; } + + renderers = null; + return false; } #endregion #region internals + internal string NameTag + { + get + { + return _name; + } + set + { + _name = value; + } + } + internal OutlineLayerCollection ParentCollection { get @@ -131,13 +259,16 @@ internal void SetCollection(OutlineLayerCollection collection) internal void Render(OutlineRenderer renderer, OutlineResources resources) { - _settings.SetResources(resources); - - foreach (var kvp in _outlineObjects) + if (_enabled) { - if (kvp.Key) + _settings.SetResources(resources); + + foreach (var kvp in _outlineObjects) { - renderer.RenderSingleObject(kvp.Value, _settings.OutlineMaterials); + if (kvp.Key && kvp.Key.activeInHierarchy) + { + renderer.RenderSingleObject(kvp.Value, _settings.OutlineMaterials); + } } } } @@ -321,6 +452,52 @@ public void AcceptChanges() #endregion + #region Object + + public override string ToString() + { + var text = new StringBuilder(); + + if (string.IsNullOrEmpty(_name)) + { + text.Append("OutlineLayer"); + } + else + { + text.Append(_name); + } + + if (_parentCollection != null) + { + text.Append(" #"); + text.Append(_parentCollection.IndexOf(this)); + } + + if (_zOrder > 0) + { + text.Append(" z"); + text.Append(_zOrder); + } + + if (_outlineObjects.Count > 0) + { + text.Append(" ("); + + foreach (var go in _outlineObjects.Keys) + { + text.Append(go.name); + text.Append(", "); + } + + text.Remove(text.Length - 2, 2); + text.Append(")"); + } + + return string.Format("{0}", text); + } + + #endregion + #region implementation #endregion } diff --git a/Assets/Plugins/UnityFx.Outline/Runtime/Scripts/OutlineLayerCollection.cs b/Assets/Plugins/UnityFx.Outline/Runtime/Scripts/OutlineLayerCollection.cs index 9722da1..77b8a65 100644 --- a/Assets/Plugins/UnityFx.Outline/Runtime/Scripts/OutlineLayerCollection.cs +++ b/Assets/Plugins/UnityFx.Outline/Runtime/Scripts/OutlineLayerCollection.cs @@ -20,15 +20,48 @@ public sealed class OutlineLayerCollection : ScriptableObject, IList + { + public int Compare(OutlineLayer x, OutlineLayer y) + { + return x.Priority - y.Priority; + } + } + [SerializeField, HideInInspector] private List _layers = new List(); + private List _sortedLayers = new List(); + private OutlineLayerComparer _sortComparer = new OutlineLayerComparer(); + private bool _orderChanged = true; private bool _changed = true; #endregion #region interface + /// + /// Gets layers ordered by . + /// + public OutlineLayer[] SortedLayers + { + get + { + UpdateSortedLayersIdNeeded(); + return _sortedLayers.ToArray(); + } + } + + #endregion + + #region internals + + internal void SetOrderChanged() + { + _orderChanged = true; + _changed = true; + } + internal void Reset() { foreach (var layer in _layers) @@ -45,6 +78,16 @@ internal void UpdateChanged() } } + internal void Render(OutlineRenderer renderer, OutlineResources resources) + { + UpdateSortedLayersIdNeeded(); + + foreach (var layer in _sortedLayers) + { + layer.Render(renderer, resources); + } + } + #endregion #region ScriptableObject @@ -55,6 +98,8 @@ private void OnEnable() { layer.SetCollection(this); } + + _orderChanged = true; } #endregion @@ -86,6 +131,8 @@ public OutlineLayer this[int layerIndex] _layers[layerIndex].SetCollection(null); _layers[layerIndex] = value; + + _orderChanged = true; _changed = true; } } @@ -115,6 +162,8 @@ public void Insert(int index, OutlineLayer layer) layer.SetCollection(this); _layers.Insert(index, layer); + + _orderChanged = true; _changed = true; } } @@ -126,6 +175,8 @@ public void RemoveAt(int index) { _layers[index].SetCollection(null); _layers.RemoveAt(index); + + _orderChanged = true; _changed = true; } } @@ -165,6 +216,8 @@ public void Add(OutlineLayer layer) layer.SetCollection(this); _layers.Add(layer); + + _orderChanged = true; _changed = true; } } @@ -176,7 +229,9 @@ public bool Remove(OutlineLayer layer) { layer.SetCollection(null); + _sortedLayers.Remove(layer); _changed = true; + return true; } @@ -194,6 +249,7 @@ public void Clear() } _layers.Clear(); + _sortedLayers.Clear(); _changed = true; } } @@ -270,6 +326,18 @@ public void AcceptChanges() #endregion #region implementation + + private void UpdateSortedLayersIdNeeded() + { + if (_orderChanged) + { + _sortedLayers.Clear(); + _sortedLayers.AddRange(_layers); + _sortedLayers.Sort(_sortComparer); + _orderChanged = false; + } + } + #endregion } } diff --git a/Assets/Plugins/UnityFx.Outline/Runtime/Scripts/OutlineRenderer.cs b/Assets/Plugins/UnityFx.Outline/Runtime/Scripts/OutlineRenderer.cs index c0bc1e4..0866684 100644 --- a/Assets/Plugins/UnityFx.Outline/Runtime/Scripts/OutlineRenderer.cs +++ b/Assets/Plugins/UnityFx.Outline/Runtime/Scripts/OutlineRenderer.cs @@ -103,7 +103,7 @@ public OutlineRenderer(CommandBuffer commandBuffer, RenderTargetIdentifier dst) /// /// Adds commands for rendering single outline object. /// - public void RenderSingleObject(IList renderers, OutlineMaterialSet materials) + public void RenderSingleObject(IEnumerable renderers, OutlineMaterialSet materials) { if (renderers == null) { @@ -118,15 +118,13 @@ public void RenderSingleObject(IList renderers, OutlineMaterialSet mat _commandBuffer.SetRenderTarget(_maskRtId); _commandBuffer.ClearRenderTarget(false, true, Color.black); - for (var i = 0; i < renderers.Count; ++i) + foreach (var r in renderers) { - var renderer = renderers[i]; - - if (renderer && renderer.gameObject.activeInHierarchy && renderer.enabled) + if (r && r.enabled && r.gameObject.activeInHierarchy) { - for (var j = 0; j < renderer.sharedMaterials.Length; ++j) + for (var j = 0; j < r.sharedMaterials.Length; ++j) { - _commandBuffer.DrawRenderer(renderer, materials.RenderMaterial, j); + _commandBuffer.DrawRenderer(r, materials.RenderMaterial, j); } } } diff --git a/Assets/Plugins/UnityFx.Outline/Runtime/Scripts/OutlineSettingsInstance.cs b/Assets/Plugins/UnityFx.Outline/Runtime/Scripts/OutlineSettingsInstance.cs index cb982da..b8509da 100644 --- a/Assets/Plugins/UnityFx.Outline/Runtime/Scripts/OutlineSettingsInstance.cs +++ b/Assets/Plugins/UnityFx.Outline/Runtime/Scripts/OutlineSettingsInstance.cs @@ -131,6 +131,8 @@ public Color OutlineColor } set { + ThrowIfSettingsAssigned(); + if (_outlineColor != value) { _outlineColor = value; @@ -153,6 +155,8 @@ public int OutlineWidth } set { + ThrowIfSettingsAssigned(); + value = Mathf.Clamp(value, OutlineRenderer.MinWidth, OutlineRenderer.MaxWidth); if (_outlineWidth != value) @@ -177,6 +181,8 @@ public float OutlineIntensity } set { + ThrowIfSettingsAssigned(); + value = Mathf.Clamp(value, OutlineRenderer.MinIntensity, OutlineRenderer.MaxIntensity); if (_outlineIntensity != value) @@ -201,6 +207,8 @@ public OutlineMode OutlineMode } set { + ThrowIfSettingsAssigned(); + if (_outlineMode != value) { _outlineMode = value; @@ -249,6 +257,15 @@ public void Dispose() #endregion #region implementation + + private void ThrowIfSettingsAssigned() + { + if (_outlineSettings) + { + throw new InvalidOperationException("The outline parameters cannot be altered when OutlineSettings is set."); + } + } + #endregion } } diff --git a/Assets/Plugins/UnityFx.Outline/Tests/Editor/Scripts/Helpers/IOutlineSettingsExTests.cs b/Assets/Plugins/UnityFx.Outline/Tests/Editor/Scripts/Helpers/IOutlineSettingsExTests.cs index 0f38e19..c39e3bb 100644 --- a/Assets/Plugins/UnityFx.Outline/Tests/Editor/Scripts/Helpers/IOutlineSettingsExTests.cs +++ b/Assets/Plugins/UnityFx.Outline/Tests/Editor/Scripts/Helpers/IOutlineSettingsExTests.cs @@ -74,5 +74,25 @@ public void OutlineSettings_DoesNotSetsChangedOnSameValue() Assert.IsFalse(_changeTracking.IsChanged); } } + + [Test] + public void OutlineSettings_MakesOtherSettersThrow() + { + var settings = ScriptableObject.CreateInstance(); + + try + { + _settings.OutlineSettings = settings; + + Assert.Throws(() => _settings.OutlineColor = Color.blue); + Assert.Throws(() => _settings.OutlineWidth = 12); + Assert.Throws(() => _settings.OutlineMode = OutlineMode.Blurred); + Assert.Throws(() => _settings.OutlineIntensity = 17); + } + finally + { + UnityEngine.Object.DestroyImmediate(settings); + } + } } } diff --git a/Assets/Plugins/UnityFx.Outline/Tests/Editor/Scripts/OutlineLayerCollectionTests.cs b/Assets/Plugins/UnityFx.Outline/Tests/Editor/Scripts/OutlineLayerCollectionTests.cs index d3cc51e..78177c6 100644 --- a/Assets/Plugins/UnityFx.Outline/Tests/Editor/Scripts/OutlineLayerCollectionTests.cs +++ b/Assets/Plugins/UnityFx.Outline/Tests/Editor/Scripts/OutlineLayerCollectionTests.cs @@ -295,5 +295,25 @@ public void AcceptChanges_ResetsChanged() Assert.IsFalse(_layerCollection.IsChanged); } + + [Test] + public void SortedLayers_IsSortedByPriority() + { + var layer1 = new OutlineLayer() + { + Priority = 2 + }; + + var layer2 = new OutlineLayer(); + var layer3 = new OutlineLayer(); + + _layerCollection.Add(layer1); + _layerCollection.Add(layer2); + _layerCollection.Add(layer3); + + Assert.AreEqual(layer2, _layerCollection.SortedLayers[0]); + Assert.AreEqual(layer3, _layerCollection.SortedLayers[1]); + Assert.AreEqual(layer1, _layerCollection.SortedLayers[2]); + } } } diff --git a/Assets/Plugins/UnityFx.Outline/Tests/Editor/Scripts/OutlineLayerTests.cs b/Assets/Plugins/UnityFx.Outline/Tests/Editor/Scripts/OutlineLayerTests.cs index 0e9df7e..f965443 100644 --- a/Assets/Plugins/UnityFx.Outline/Tests/Editor/Scripts/OutlineLayerTests.cs +++ b/Assets/Plugins/UnityFx.Outline/Tests/Editor/Scripts/OutlineLayerTests.cs @@ -18,7 +18,7 @@ public class OutlineLayerTests : IOutlineSettingsExTests, IDisposable [SetUp] public void Init() { - _layer = new OutlineLayer(); + _layer = new OutlineLayer("TestLayer"); Init(_layer); } @@ -34,6 +34,8 @@ public void DefaultStateIsValid() Assert.IsFalse(_layer.IsReadOnly); Assert.IsEmpty(_layer); Assert.Zero(_layer.Count); + Assert.AreEqual("TestLayer", _layer.Name); + Assert.AreEqual(-1, _layer.Index); } [Test] @@ -75,6 +77,25 @@ public void Add_SetsChanged() Assert.IsTrue(_layer.IsChanged); } + [Test] + public void Add_FiltersRenderesByLayer() + { + var go = new GameObject("r1", typeof(MeshRenderer)); + var go2 = new GameObject("r2", typeof(MeshRenderer)); + + go2.layer = LayerMask.NameToLayer("TransparentFX"); + go2.transform.SetParent(go.transform, false); + + ICollection r; + + _layer.Add(go, "TransparentFX"); + _layer.TryGetRenderers(go, out r); + + Assert.AreEqual(1, r.Count); + Assert.IsTrue(r.Contains(go.GetComponent())); + Assert.IsFalse(r.Contains(go2.GetComponent())); + } + [Test] public void Remove_DoesNotThrowOnNullArgument() { diff --git a/CHANGELOG.md b/CHANGELOG.md index f485942..ffc89a3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,9 +3,27 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/); this project adheres to [Semantic Versioning](http://semver.org/). +## [0.6.0] - 2019.09.26 + +Quality of life improvements. + +### Added +- Added `OutlineLayer.Enabled`. +- Added `OutlineLayer.Name`. +- Added possibility to change render order of layers via `OutlineLayer.Priority`. +- Added possibility to edit renderers of an `OutlineLayer`. +- Added possibility to alter `CameraEvent` used to render `OutlineEffect`. +- Added more info to the `OutlineLayer` preview inspector. + +### Changed +- `IOutilneSettings` setters now throw if overriden. + +### Fixed +- Fixed `OutlineLayer.Add` not filtering renderers by the mask passed. + ## [0.5.0] - 2019.09.09 -Features editor UI and unit tests. +Editor UI improvements and unit tests. ### Added - Added `OutlineSettings`, that can be shared between dfferent `OutlineLayer` and `OutlineBehaviour` instances. @@ -18,7 +36,7 @@ Features editor UI and unit tests. ## [0.4.0] - 2019.08.31 -Features blurred otulines. +Blurred outlines. ### Added - Added Gauss blurring to outlines. diff --git a/README.md b/README.md index 4fe0304..6a6928d 100644 --- a/README.md +++ b/README.md @@ -54,7 +54,7 @@ Npm package is available at [npmjs.com](https://www.npmjs.com/package/com.unityf } ], "dependencies": { - "com.unityfx.outline": "0.5.0" + "com.unityfx.outline": "0.6.0" } } ``` @@ -71,7 +71,7 @@ using UnityFx.Outline; Add `OutlineEffect` script to a camera that should render outlines. Then add and configure as many layers as you need: ```csharp var outlineEffect = Camera.main.GetComponent(); -var layer = new OutlineLayer(); +var layer = new OutlineLayer("MyOutlines"); layer.OutlineColor = Color.red; layer.OutlineWidth = 7;