using UnityEditor; using UnityEditor.IMGUI.Controls; using UnityEditor.SceneManagement; using UnityEngine; using Ashwild.Harvesting; namespace Ashwild.EditorTools { [CustomEditor(typeof(ScatterZone))] public class ScatterZoneEditor : Editor { private enum EditMode { Resize, Brush } private EditMode mode = EditMode.Resize; // Outil Pinceau (layer) private int activeLayer; private bool brushDelete; // État de drag private Vector3 lastPaint; private bool hasLastPaint; private readonly BoxBoundsHandle boxHandle = new BoxBoundsHandle(); // ---------- Création via le menu ---------- [MenuItem("GameObject/Tools/Scatter Zone", false, 10)] private static void CreateZone(MenuCommand cmd) { GameObject go = new GameObject("ScatterZone"); go.AddComponent(); GameObjectUtility.SetParentAndAlign(go, cmd.context as GameObject); if (SceneView.lastActiveSceneView != null) go.transform.position = SceneView.lastActiveSceneView.pivot; Undo.RegisterCreatedObjectUndo(go, "Create Scatter Zone"); Selection.activeGameObject = go; } // ---------- Inspector ---------- public override void OnInspectorGUI() { ScatterZone zone = (ScatterZone)target; serializedObject.Update(); EditorGUILayout.PropertyField(serializedObject.FindProperty("size")); EditorGUILayout.PropertyField(serializedObject.FindProperty("layers"), true); serializedObject.ApplyModifiedProperties(); EditorGUILayout.Space(); DrawLayerActions(zone); EditorGUILayout.Space(); if (GUILayout.Button("Ajouter un volume d'exclusion")) AddExclusionVolume(zone); EditorGUILayout.Space(); DrawModeBar(); EditorGUILayout.Space(); if (mode == EditMode.Brush) DrawBrushControls(zone); else EditorGUILayout.HelpBox("Mode Déplacer/Redim : poignées de la boîte dans la Scene View.", MessageType.None); } private static string LayerLabel(ScatterZone.Layer layer, int index) => layer.profile != null ? layer.profile.name : $"Layer {index} (aucun profil)"; private void DrawLayerActions(ScatterZone zone) { EditorGUILayout.LabelField("Actions", EditorStyles.boldLabel); for (int i = 0; i < zone.layers.Count; i++) { ScatterZone.Layer layer = zone.layers[i]; using (new EditorGUILayout.HorizontalScope()) { EditorGUILayout.LabelField(LayerLabel(layer, i), GUILayout.MinWidth(60)); using (new EditorGUI.DisabledScope(layer.profile == null)) if (GUILayout.Button("Scatter", GUILayout.Width(70))) RunAndNotify(() => ScatterRunner.ScatterLayer(zone, i), "placées"); if (GUILayout.Button("Clear", GUILayout.Width(60))) RunAndNotify(() => ScatterRunner.ClearLayer(zone, i), "supprimées"); } } EditorGUILayout.Space(2); using (new EditorGUILayout.HorizontalScope()) { if (GUILayout.Button("Scatter tout", GUILayout.Height(26))) RunAndNotify(() => ScatterRunner.ScatterAll(zone), "placées"); GUI.backgroundColor = new Color(1f, 0.6f, 0.6f); if (GUILayout.Button("Clear tout", GUILayout.Height(26), GUILayout.Width(100))) RunAndNotify(() => ScatterRunner.ClearAll(zone), "supprimées"); GUI.backgroundColor = Color.white; } } private void AddExclusionVolume(ScatterZone zone) { GameObject go = new GameObject("Exclusion"); Undo.RegisterCreatedObjectUndo(go, "Add Exclusion Volume"); ScatterExclusionVolume vol = go.AddComponent(); Undo.SetTransformParent(go.transform, zone.transform, "Add Exclusion Volume"); go.transform.position = zone.transform.position; go.transform.localScale = Vector3.one; vol.size = new Vector3(10f, 10f, 10f); Selection.activeGameObject = go; } private void DrawModeBar() { EditMode newMode = (EditMode)GUILayout.Toolbar((int)mode, new[] { "Déplacer/Redim", "Pinceau" }); if (newMode != mode) { mode = newMode; hasLastPaint = false; SceneView.RepaintAll(); } } private void DrawBrushControls(ScatterZone zone) { if (zone.layers.Count == 0) { EditorGUILayout.HelpBox("Ajoute au moins un layer pour utiliser le pinceau.", MessageType.Warning); return; } string[] names = new string[zone.layers.Count]; for (int i = 0; i < names.Length; i++) names[i] = LayerLabel(zone.layers[i], i); activeLayer = Mathf.Clamp(activeLayer, 0, zone.layers.Count - 1); activeLayer = EditorGUILayout.Popup("Layer actif", activeLayer, names); brushDelete = GUILayout.Toolbar(brushDelete ? 1 : 0, new[] { "Add", "Delete" }) == 1; ScatterZone.Layer layer = zone.layers[activeLayer]; EditorGUI.BeginChangeCheck(); float r = EditorGUILayout.Slider("Rayon", layer.brushRadius, 0.5f, 50f); float s = EditorGUILayout.Slider("Densité", layer.brushStrength, 0.01f, 1f); if (EditorGUI.EndChangeCheck()) { Undo.RecordObject(zone, "Brush Settings"); layer.brushRadius = r; layer.brushStrength = s; } EditorGUILayout.HelpBox("Clique-glisse sur le terrain : Add pose, Delete supprime (dans le rayon).", MessageType.None); } // ---------- Scene View ---------- private void OnSceneGUI() { ScatterZone zone = (ScatterZone)target; if (mode == EditMode.Resize) { DrawBoxHandle(zone); return; } HandleBrush(zone); } private void DrawBoxHandle(ScatterZone zone) { boxHandle.center = zone.transform.position; boxHandle.size = zone.size; EditorGUI.BeginChangeCheck(); boxHandle.DrawHandle(); if (EditorGUI.EndChangeCheck()) { Undo.RecordObject(zone, "Resize Scatter Zone"); Undo.RecordObject(zone.transform, "Move Scatter Zone"); zone.transform.position = boxHandle.center; zone.size = new Vector3( Mathf.Max(1f, boxHandle.size.x), Mathf.Max(0.1f, boxHandle.size.y), Mathf.Max(1f, boxHandle.size.z)); } } private void HandleBrush(ScatterZone zone) { if (zone.layers.Count == 0) return; Event e = Event.current; int id = GUIUtility.GetControlID(FocusType.Passive); if (e.type == EventType.Layout) HandleUtility.AddDefaultControl(id); Ray ray = HandleUtility.GUIPointToWorldRay(e.mousePosition); if (!Physics.Raycast(ray, out RaycastHit hit, 5000f)) return; Vector3 point = hit.point; activeLayer = Mathf.Clamp(activeLayer, 0, zone.layers.Count - 1); float radius = zone.layers[activeLayer].brushRadius; Handles.color = brushDelete ? new Color(1f, 0.25f, 0.2f) : new Color(0.2f, 1f, 0.3f); Handles.DrawWireDisc(point, Vector3.up, radius); Color fill = Handles.color; fill.a = 0.08f; Handles.color = fill; Handles.DrawSolidDisc(point, Vector3.up, radius); bool paintEvent = (e.type == EventType.MouseDown || e.type == EventType.MouseDrag) && e.button == 0 && !e.alt; if (paintEvent) { float step = radius * 0.25f; if (!hasLastPaint || e.type == EventType.MouseDown || (point - lastPaint).sqrMagnitude >= step * step) { ApplyBrush(zone, point); lastPaint = point; hasLastPaint = true; } e.Use(); } if (e.type == EventType.MouseUp) hasLastPaint = false; SceneView.RepaintAll(); } private void ApplyBrush(ScatterZone zone, Vector3 point) { activeLayer = Mathf.Clamp(activeLayer, 0, zone.layers.Count - 1); ScatterZone.Layer layer = zone.layers[activeLayer]; Undo.IncrementCurrentGroup(); int group = Undo.GetCurrentGroup(); if (brushDelete) ScatterRunner.PaintDelete(zone, activeLayer, point, layer.brushRadius); else ScatterRunner.PaintAdd(zone, activeLayer, point, layer.brushRadius, layer.brushStrength); Undo.CollapseUndoOperations(group); MarkDirty(zone); } // ---------- Utilitaires ---------- private void RunAndNotify(System.Func action, string verb) { Undo.IncrementCurrentGroup(); int group = Undo.GetCurrentGroup(); int n = action(); Undo.CollapseUndoOperations(group); MarkDirty((ScatterZone)target); ShowNotification($"{n} instances {verb}"); } private static void ShowNotification(string msg) { if (SceneView.lastActiveSceneView != null) SceneView.lastActiveSceneView.ShowNotification(new GUIContent(msg)); } private static void MarkDirty(ScatterZone zone) { EditorUtility.SetDirty(zone); if (!Application.isPlaying) EditorSceneManager.MarkSceneDirty(zone.gameObject.scene); } } }