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.
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With