Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. Have a look at our Games Focus blog post series which will show what Unity is doing for all game developers – now, next year, and in the future.
    Dismiss Notice

Feature Request Target sorting layers as assets or editor configurations

Discussion in '2D Experimental Preview' started by ThundThund, Apr 18, 2021.

  1. ThundThund

    ThundThund

    Joined:
    Feb 7, 2017
    Posts:
    287
    I come from here: https://forum.unity.com/threads/2d-lights-proposal-target-sorting-layers-as-an-asset.1024630/

    Finally I decided to dedicate some time to this and I've come up with a solution, at least until I find a better one.

    The problem, summarizing:
    Target sorting layers is a property used by Light2Ds and ShadowCaster2Ds, which determine which sorting layers will be affected by them. The content of this property may by the same for hundreds of lights/shadow casters in a videogame project, some of them stored in prefabs and some of them stored in scenes. Real case: Imagine you have 3 sorting layers, Walls, Characters and Background. You also have 60 scenes, 200 lights in total. 150 of them have Characters and Background layers enabled and Walls disabled. Then you think that it would be good to add another sorting layer, Items, that should be affected by all those 150 lights. GOOD LUCK.

    "But you could use a prefab for lights to avoid having to change them one by one!" NOPE. Imagine you have many different lights of different types and with different settings, having 50 prefabs is not practical.
    Besides, what happens if there are light instances in the scene that override the Target sorting layers property of the prefab? You cannot affect that value anymore by changing the prefab.
    It would not be rare, also, that the Target sorting layers in some shadow casters are necesarily the same as the lights'.

    The solution, so far: If you could store just a few predefined Target sorting layers configurations, and reference them from every light prefab or instance, then you would only need to change those assets to affect all the lights in your project.

    That's what I've implemented and share with you in this post. Please feel free to propose improvements or report bugs. Beware of Unity changing how they call the internal variables of their classes, my code makes some assumptions.

    You can find other of my shared implementations here:
    You can follow me (and my game) on Twitter: @JailbrokenGame

    How it works:
    1. Create a TargetSortingLayers asset.
    2. Enable / disable the desired sorting layers.
    3. Add a TargetSortingLayersSetter component to every light and every shadow caster in your project (I recommend to create a prefab that already includes the component).
    4. Fill the property that references the asset.
    5. When the scene is loaded, in the Awake function of the light, the Target sorting layers will replaced.
    Code (CSharp):
    1. // Copyright 2021 Alejandro Villalba Avila
    2. //
    3. // Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"),
    4. // to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
    5. // and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
    6. //
    7. // The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
    8. //
    9. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    10. // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    11. // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
    12. // IN THE SOFTWARE.
    13.  
    14. #if ODIN_INSPECTOR
    15. using Sirenix.OdinInspector;
    16. #endif
    17. using UnityEngine;
    18. using UnityEngine.Experimental.Rendering.Universal;
    19.  
    20. namespace Game.Utils
    21. {
    22.     /// <summary>
    23.     /// A component that just replaced the Target sorting layers with those stored in an asset as soon as the game object awakes.
    24.     /// </summary>
    25.     /// <remarks>
    26.     /// By using this component in every light or shadow caster, you only need to define a set of TargetSortingLayers assets and reference them.
    27.     /// If you need to add / remove sorting layers, or change whether they are enabled / disabled for a group of lights, you just need to change the asset
    28.     /// and all those lights will be affected when the scene is loaded. Otherwise, you would have to visit light by light, in every scene, and change its
    29.     /// Target sorting layers property by hand.
    30.     /// It's highly recommended that you create a prefab per type of light and shadow caster, which have this component already added along with a default asset.
    31.     /// </remarks>
    32.     public class TargetSortingLayersSetter : MonoBehaviour
    33.     {
    34.         [Tooltip("The asset that contains the sorting layers configuration that will replace the one of the target component, when awaking.")]
    35.         [SerializeField]
    36.         protected TargetSortingLayers m_TargetSortingLayers;
    37.  
    38.         [Tooltip("The target component whose uses a property to store the Target sorting layers. It may be a Light2D or a ShadowCaster2D, for example.")]
    39.         [SerializeField]
    40.         protected MonoBehaviour m_TargetComponent;
    41.  
    42.         private void Reset()
    43.         {
    44.             // Automatically gets the component that uses Target sorting layers property, although it can be set manually afterwards
    45.             Light2D lightComponent;
    46.             ShadowCaster2D shadowCasterComponent;
    47.  
    48.             if (TryGetComponent(out lightComponent))
    49.             {
    50.                 m_TargetComponent = lightComponent;
    51.             }
    52.             else if(TryGetComponent(out shadowCasterComponent))
    53.             {
    54.                 m_TargetComponent = shadowCasterComponent;
    55.             }
    56.             else
    57.             {
    58.                 Debug.LogError("There is no component in the game object '" + name + "' that uses Target Sorting Layers.");
    59.             }
    60.         }
    61.  
    62.         private void Awake()
    63.         {
    64.             SetTargetSortingLayers();
    65.         }
    66.  
    67.         /// <summary>
    68.         /// Replaces the content of the Target sorting layers property of the target component with the content of the asset.
    69.         /// </summary>
    70. #if ODIN_INSPECTOR
    71.         [GUIColor(0.0f, 1.0f, 0.0f)]
    72.         [Button("Set Target sorting layers")]
    73. #endif
    74.         public void SetTargetSortingLayers()
    75.         {
    76.             if(m_TargetSortingLayers == null)
    77.             {
    78.                 Debug.LogError("There is no TargetSortingLayers asset in the game object '" + name + "'.");
    79.             }
    80.             else
    81.             {
    82.                 if (m_TargetComponent is Light2D)
    83.                 {
    84.                     (m_TargetComponent as Light2D).SetTargetSortingLayers(m_TargetSortingLayers.SortingLayers.ToArray());
    85.                 }
    86.                 else if (m_TargetComponent is ShadowCaster2D)
    87.                 {
    88.                     (m_TargetComponent as ShadowCaster2D).SetTargetSortingLayers(m_TargetSortingLayers.SortingLayers.ToArray());
    89.                 }
    90.                 else
    91.                 {
    92.                     Debug.LogError("There is no component in the game object '" + name + "' that uses Target Sorting Layers.");
    93.                 }
    94.             }
    95.         }
    96.     }
    97. }
    Code (CSharp):
    1. // Copyright 2021 Alejandro Villalba Avila
    2. //
    3. // Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"),
    4. // to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
    5. // and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
    6. //
    7. // The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
    8. //
    9. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    10. // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    11. // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
    12. // IN THE SOFTWARE.
    13.  
    14. using UnityEngine;
    15. using System.Collections.Generic;
    16. using UnityEngine.UIElements;
    17.  
    18. #if UNITY_EDITOR
    19. using UnityEditor;
    20. #endif
    21.  
    22. namespace Game.Utils
    23. {
    24.     /// <summary>
    25.     /// Stores a list of enabled and disabled sorting layers.
    26.     /// </summary>
    27.     [CreateAssetMenu(fileName = "NewTargetSortingLayers", menuName = "Game/Utils/Target sorting layers")]
    28.     public class TargetSortingLayers : ScriptableObject
    29.     {
    30.         /// <summary>
    31.         /// A list of enabled sorting layer IDs.
    32.         /// </summary>
    33.         public List<int> SortingLayers = new List<int>();
    34.  
    35.         // Just for debugging and tracking code repository changes easier
    36.         protected List<string> m_SortingLayerNames = new List<string>();
    37.  
    38. #if UNITY_EDITOR
    39.  
    40.         [CustomEditor(typeof(TargetSortingLayers))]
    41.         protected class CustomTargetSortingLayersInspector : Editor
    42.         {
    43.             private bool[] m_enabledSortingLayers;
    44.             private SortingLayer[] m_editorSortingLayers;
    45.             private TargetSortingLayers m_target;
    46.  
    47.             public override VisualElement CreateInspectorGUI()
    48.             {
    49.                 m_target = target as TargetSortingLayers;
    50.                 m_editorSortingLayers = SortingLayer.layers;
    51.                 m_enabledSortingLayers = new bool[m_editorSortingLayers.Length];
    52.  
    53.                 for (int i = 0; i < m_enabledSortingLayers.Length; ++i)
    54.                 {
    55.                     m_enabledSortingLayers[i] = m_target.SortingLayers.Contains(m_editorSortingLayers[i].id);
    56.                 }
    57.  
    58.                 return null;
    59.             }
    60.          
    61.             public override void OnInspectorGUI()
    62.             {
    63.                 bool hasSomethingChanged = false;
    64.  
    65.                 // Draws the list of sorting layer checkboxes
    66.                 EditorGUILayout.BeginVertical();
    67.                 {
    68.                     for(int i = 0; i < m_editorSortingLayers.Length; ++i)
    69.                     {
    70.                         EditorGUILayout.BeginHorizontal();
    71.                         {
    72.                             EditorGUI.BeginChangeCheck();
    73.                             {
    74.                                 m_enabledSortingLayers[i] = EditorGUILayout.ToggleLeft(new GUIContent(m_editorSortingLayers[i].name), m_enabledSortingLayers[i]);
    75.                             }
    76.                             if (EditorGUI.EndChangeCheck())
    77.                             {
    78.                                 hasSomethingChanged = true;
    79.                             }
    80.                         }
    81.                         EditorGUILayout.EndHorizontal();
    82.                     }
    83.                 }
    84.                 EditorGUILayout.EndVertical();
    85.  
    86.                 // Draws some selection utility buttons
    87.                 if(GUILayout.Button("All"))
    88.                 {
    89.                     for (int i = 0; i < m_editorSortingLayers.Length; ++i)
    90.                     {
    91.                         hasSomethingChanged = true;
    92.                         m_enabledSortingLayers[i] = true;
    93.                     }
    94.                 }
    95.  
    96.                 if(GUILayout.Button("None"))
    97.                 {
    98.                     for (int i = 0; i < m_editorSortingLayers.Length; ++i)
    99.                     {
    100.                         hasSomethingChanged = true;
    101.                         m_enabledSortingLayers[i] = false;
    102.                     }
    103.                 }
    104.  
    105.                 // Serializes if necessary
    106.                 if(hasSomethingChanged)
    107.                 {
    108.                     m_target.SortingLayers.Clear();
    109.                     m_target.m_SortingLayerNames.Clear();
    110.                     SerializedProperty sortingLayersProp = serializedObject.FindProperty("SortingLayers");
    111.                     SerializedProperty sortingLayerNamesProp = serializedObject.FindProperty("SortingLayerNames");
    112.                     sortingLayersProp.ClearArray();
    113.                     sortingLayerNamesProp.ClearArray();
    114.  
    115.                     for (int i = 0; i < m_enabledSortingLayers.Length; ++i)
    116.                     {
    117.                         if (m_enabledSortingLayers[i])
    118.                         {
    119.                             sortingLayersProp.InsertArrayElementAtIndex(0);
    120.                             sortingLayersProp.GetArrayElementAtIndex(0).intValue = m_editorSortingLayers[i].id;
    121.                             sortingLayerNamesProp.InsertArrayElementAtIndex(0);
    122.                             sortingLayerNamesProp.GetArrayElementAtIndex(0).stringValue = m_editorSortingLayers[i].name;
    123.  
    124.                             m_target.SortingLayers.Add(m_editorSortingLayers[i].id);
    125.                             m_target.m_SortingLayerNames.Add(m_editorSortingLayers[i].name);
    126.                         }
    127.                     }
    128.  
    129.                     serializedObject.ApplyModifiedProperties();
    130.                 }
    131.             }
    132.         }
    133.  
    134. #endif
    135.  
    136.     }
    137.  
    138. }
    Code (CSharp):
    1. // Copyright 2021 Alejandro Villalba Avila
    2. //
    3. // Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"),
    4. // to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
    5. // and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
    6. //
    7. // The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
    8. //
    9. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    10. // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    11. // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
    12. // IN THE SOFTWARE.
    13.  
    14. using System.Reflection;
    15. using UnityEngine.Experimental.Rendering.Universal;
    16.  
    17. namespace Game.Utils
    18. {
    19.     /// <summary>
    20.     /// It extends the Light2D class in order to be able to modify some private data members.
    21.     /// </summary>
    22.     public static class Light2DExtensions
    23.     {
    24.         /// <summary>
    25.         /// Replaces the target sorting layers of the component.
    26.         /// </summary>
    27.         /// <param name="light">The object to modify.</param>
    28.         /// <param name="sortingLayers">A list of sorting layer IDs to enable. Sorting layers not included in the list will be disabled.</param>
    29.         public static void SetTargetSortingLayers(this Light2D light, int[] sortingLayers)
    30.         {
    31.             FieldInfo targetSortingLayersField = typeof(Light2D).GetField("m_ApplyToSortingLayers",
    32.                                                                         BindingFlags.NonPublic |
    33.                                                                         BindingFlags.Instance);
    34.             targetSortingLayersField.SetValue(light, sortingLayers);
    35.         }
    36.     }
    37.  
    38. }
    Code (CSharp):
    1. // Copyright 2020 Alejandro Villalba Avila
    2. //
    3. // Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"),
    4. // to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
    5. // and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
    6. //
    7. // The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
    8. //
    9. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    10. // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    11. // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
    12. // IN THE SOFTWARE.
    13.  
    14. using System.Reflection;
    15. using UnityEngine.Experimental.Rendering.Universal;
    16.  
    17. namespace Game.Utils
    18. {
    19.  
    20.     /// <summary>
    21.     /// It extends the ShadowCaster2D class in order to be able to modify some private data members.
    22.     /// </summary>
    23.     public static class ShadowCaster2DExtensions
    24.     {
    25.         /// <summary>
    26.         /// Replaces the target sorting layers of the component.
    27.         /// </summary>
    28.         /// <param name="shadowCaster">The object to modify.</param>
    29.         /// <param name="sortingLayers">A list of sorting layer IDs to enable. Sorting layers not included in the list will be disabled.</param>
    30.         public static void SetTargetSortingLayers(this ShadowCaster2D shadowCaster, int[] sortingLayers)
    31.         {
    32.             FieldInfo targetSortingLayersField = typeof(ShadowCaster2D).GetField("m_ApplyToSortingLayers",
    33.                                                                         BindingFlags.NonPublic |
    34.                                                                         BindingFlags.Instance);
    35.             targetSortingLayersField.SetValue(shadowCaster, sortingLayers);
    36.         }
    37. }
    38.  
     
    Last edited: Jul 1, 2022
    atrivedi7, AzureMasters and NotaNaN like this.