Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I adjust shape/dimensions of one clone to affect all other clones in the scene view

I would like to change/adjust the shape/dimensions of several cloned objects within the scene view by adjusting one. This object could be say a quad or a line renderer that needs to be extended. For example as one game objects line renderer is extended (using the mouse) in the scene view, all other clones are affected. I know that it's a lot simpler to adjust shape/dimensions of one object before cloning it, also this change can be made on a prefab and applied to all, but I need to see the dynamic changes as they happen to make my design process more effective. I would also like to turn this functionality on and off, when the need arises. Please see how I created my clones in this question.

Note that I don't want to achieve this at runtime.

like image 607
NSSwift Avatar asked May 04 '18 05:05

NSSwift


1 Answers

How can I adjust shape/dimensions of one clone to affect all other clones in the scene view

Synchronize properties when modifications to the component are made. To work with the existing code-base, we'll also need to clone the modified object to ensure that it's not destroyed when we re-create the other objects.

This object could be say a quad or a line renderer that needs to be extended

So we want to know when any property on any component has been modified. With custom scripts it's trivial with OnValidate, but with a sealed component such LineRenderer it's a little trickier. Fortunately, since we're working with the Editor we have access to some of its core features.

Specifically, we can hook into the Editor's Undo.postprocessModifications event to get a callback whenever modifications are made to the scene. This delegate will provide us with an UndoPropertyModification array that will contain a list of modified component properties which we can use to synchronize the other objects via EditorUtility.CopySerializedIfDifferent.


CircleSpawn

[ExecuteInEditMode]
public class CircleSpawn : MonoBehaviour
{
    public List<GameObject> Objects;
    public GameObject OriginalObject;
    public GameObject PreviousObject;
    public GameObject ActiveObject;
    public SpawnData Data;

    private void OnEnable ()
    {
        if (Objects == null) Objects = new List<GameObject>();

        // Register modification event
        Undo.postprocessModifications += OnPropertyModification;
    }

    private void OnDisable ()
    {
        // Deregister modification event
        Undo.postprocessModifications -= OnPropertyModification;
    }

    private UndoPropertyModification[] OnPropertyModification (
            UndoPropertyModification[] modifications)
    {
        // Iterate through modifications
        foreach (var mod in modifications)
        {
            var trg = mod.currentValue.target as Component;
            if (trg)
            {
                // Filter only those objects that we've created
                if (Objects.Contains(trg.gameObject))
                {
                    // Clone the object and make it 'active'
                    if (!ActiveObject.Equals(trg.gameObject))
                    {
                        SetActiveObj(Instantiate(trg.gameObject));
                        ActiveObject.name = OriginalObject.name;
                        ActiveObject.hideFlags =
                        HideFlags.DontSaveInBuild | HideFlags.HideInHierarchy;
                        ActiveObject.SetActive(false);
                    }

                    // Synchronize the other object properties
                    foreach (var obj in Objects)
                    {
                        var type = mod.currentValue.target.GetType();
                        var comp = obj.GetComponent(type);
                        if (comp == null)
                            comp = obj.AddComponent(type);

                        EditorUtility.CopySerializedIfDifferent(trg, comp);
                    }

                    UpdateTransforms();
                    break;
                }
            }
        }

        return modifications;
    }

    public void SetActiveObj (GameObject active)
    {
        // Destroy the active object
        if (!OriginalObject.Equals(ActiveObject) &&
             PreviousObject && !PreviousObject.Equals(ActiveObject))
             DestroyImmediate(ActiveObject);

        ActiveObject = active;
    }

    public void UpdateObjects ()
    {    
        // Destroy old objects
        foreach (var obj in Objects) DestroyImmediate(obj);

        Objects.Clear();

        var steps = 360.0f / Data.Count;
        var angle = 0f;

        // Instantiate new objects
        for (var i = 0; i < Data.Count; i++)
        {
            var rot = Quaternion.Euler(0f, 0f, Data.Angle + angle);
            var pos = rot * Vector3.right * Data.Radius;
            var obj = Instantiate(ActiveObject, transform.position + pos, rot);
            obj.SetActive(true);
            Objects.Add(obj);
            angle += steps;
        }
    }

    public void UpdateTransforms ()
    {
        var steps = 360.0f / Objects.Count;
        var angle = 0f;

        // Set transforms based on Angle and Radius
        for (var i = 0; i < Objects.Count; i++)
        {
            var rot = Quaternion.Euler(0f, 0f, Data.Angle + angle);
            var pos = rot * Vector3.right * Data.Radius;
            Objects[i].transform.position =
            transform.position + pos;
            Objects[i].transform.rotation = rot;
            angle += steps;
        }
    }
}

CircleSpawnEditor

[CustomEditor(typeof(CircleSpawn))]
public class CircleSpawnEditor : Editor
{
    public override void OnInspectorGUI ()
    {
        GUI.enabled = !EditorApplication.isPlaying;
        var spawner = (CircleSpawn)target;

        // Draw object field
        EditorGUILayout.LabelField("Object");
        spawner.OriginalObject = (GameObject)EditorGUILayout.ObjectField(
        spawner.OriginalObject, typeof(GameObject), true);
        if (!spawner.OriginalObject) return;

        // Restore original object
        if (GUILayout.Button("Revert") || !spawner.ActiveObject ||
           !spawner.OriginalObject.Equals(spawner.PreviousObject))
        {
            // Store data reference
            spawner.Data = spawner.OriginalObject.GetComponent<SpawnData>();
            if (!spawner.Data) return;

            spawner.SetActiveObj(spawner.OriginalObject);
            spawner.PreviousObject = spawner.OriginalObject;
            spawner.UpdateObjects();
        }

        // Draw numeric sliders
        EditorGUILayout.LabelField("Radius"); // Set as required
        spawner.Data.Radius = EditorGUILayout.Slider(spawner.Data.Radius, 0f, 100f);
        EditorGUILayout.LabelField("Angle"); // Set as required
        spawner.Data.Angle = EditorGUILayout.Slider(spawner.Data.Angle, 0f, 360f);
        EditorGUILayout.LabelField("Count"); // Set as required
        spawner.Data.Count = EditorGUILayout.IntSlider(spawner.Data.Count, 0, 36);  

        // Update objects on Count slider change
        if (spawner.Data.Count != spawner.Objects.Count)
            spawner.UpdateObjects();

        // Update transforms on Angle or Radius slider change
        if (!Mathf.Approximately(spawner.Data.Angle, spawner.Data.LastAngle) ||
            !Mathf.Approximately(spawner.Data.Radius, spawner.Data.LastRadius))
        {
            spawner.Data.LastAngle = spawner.Data.Angle;
            spawner.Data.LastRadius = spawner.Data.Radius;
            spawner.UpdateTransforms();
        }
    }
}

SpawnData

public class SpawnData : MonoBehaviour
{
    public int Count;
    public float Radius, LastRadius, Angle, LastAngle;
}

I've refactored the code a little, but for the most part, changes are minimal

like image 178
Lece Avatar answered Nov 02 '22 01:11

Lece