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
}
}