using System; using System.Collections.Generic; using UnityEngine; #if UNITY_EDITOR using UnityEditor; #endif namespace Amanotes.Core { /// /// One row in a 's flat entry list. Either holds a renderer, /// or acts as a group separator ( = true) — separators are just /// another draggable row, so grouping renderers into the same depth is done by dragging /// them adjacent to each other with no separator between; dragging a separator between /// two renderers splits them into different depths. /// [Serializable] public class DepthSortEntry { public Renderer renderer; public bool isSeparator; } /// /// Stamps Renderer.sortingLayerID / sortingOrder on child renderers so a /// "closer" instance can draw on top of a "farther" one — the 2.5D depth-sort overlap /// trick, no 3D depth buffer needed. /// /// is one flat, drag-reorderable list. Consecutive renderer rows /// (no separator between them) share one sortingOrder; a separator row starts a new depth. /// /// Setup: add to the GameObject, right-click the component and run "Collect Child /// Renderers" — this populates entries with every child renderer, each in its own group /// (separator after each). Drag renderers together to merge them into one group, or drag /// in more separators to split further. /// /// Editor: changing or in the /// inspector re-applies immediately. Runtime: nothing is applied automatically — call /// or to change and re-apply. /// [AddComponentMenu("Widget2D/Depth Sort Group")] public class DepthSortGroup : MonoBehaviour { [Tooltip("Sorting layer applied to every renderer in this group. sortingOrder only competes within the same sortingLayer, so all renderers here must share one.")] [SerializeField] private int sortingLayerID; [Tooltip("Flat, drag-reorderable list. Consecutive renderer rows (no separator between) share one sortingOrder. Drag a separator row between two renderers to split them into different depths. Use the context menu to auto-populate from child renderers.")] [SerializeField] private List entries = new List(); [Tooltip("Base sortingOrder — the first group gets this value, each subsequent group -1 (farther groups get a lower sortingOrder so nearer/earlier groups draw on top).")] [SerializeField] private int sortIndex; public int sortingLayer => sortingLayerID; /// Number of distinct depth groups in entries (separators split groups; consecutive renderers with no separator share one group). This is how many consecutive sortIndex values this instance consumes. public int groupCount { get { var group = 0; var groupHasContent = false; for (var i = 0; i < entries.Count; i++) { var e = entries[i]; if (e.isSeparator) { if (groupHasContent) { group++; groupHasContent = false; } continue; } if (e.renderer != null) groupHasContent = true; } return groupHasContent ? group + 1 : group; } } /// Sets the base sortingOrder and re-applies. public void SetSortIndex(int value) { sortIndex = value; Apply(); } /// Sets the sorting layer (by SortingLayer.id) and re-applies. public void SetSortingLayer(int layerId) { sortingLayerID = layerId; Apply(); } /// Re-stamps sortingLayerID/sortingOrder on every renderer in entries. public void Apply() { var group = 0; var groupHasContent = false; for (var i = 0; i < entries.Count; i++) { var e = entries[i]; if (e.isSeparator) { if (groupHasContent) { group++; groupHasContent = false; } continue; } if (e.renderer == null) continue; e.renderer.sortingLayerID = sortingLayerID; e.renderer.sortingOrder = sortIndex - group; groupHasContent = true; } } /// /// Sort order for a renderer not tracked in (e.g. a trail or VFX /// spawned after Apply()), expressed as an offset from sortIndex (matches group 0..N /// each getting sortIndex - group). /// public int GetOrder(int offset) => sortIndex - offset; #if UNITY_EDITOR private void OnValidate() { if (PrefabUtility.IsPartOfPrefabAsset(gameObject)) return; #pragma warning disable UDR0005 EditorApplication.delayCall += ApplyIfAlive; #pragma warning restore UDR0005 } private void ApplyIfAlive() { if (this == null) return; Apply(); } /// /// Collects every Renderer under this GameObject into entries, each in its own group /// (a separator after every renderer). Drag renderers together afterward to merge /// groups. Overwrites the current entries list. /// [ContextMenu("Collect Child Renderers")] private void CollectChildRenderers() { var found = new List(); GetComponentsInChildren(true, found); entries.Clear(); for (var i = 0; i < found.Count; i++) { if (i > 0) entries.Add(new DepthSortEntry { isSeparator = true }); entries.Add(new DepthSortEntry { renderer = found[i] }); } EditorUtility.SetDirty(this); Debug.Log($"[DepthSortGroup] {name}: collected {found.Count} renderer(s)."); } [ContextMenu("Add Separator")] private void AddSeparator() { entries.Add(new DepthSortEntry { isSeparator = true }); EditorUtility.SetDirty(this); } #endif } }