如何调整一个克隆的形状/尺寸以影响场景视图中的所有其他克隆

我想通过调整一个来更改/调整场景视图中几个克隆对象的形状/尺寸。 这个对象可以说是需要扩展的四元组或线条渲染器。 例如,当场景视图中的一个游戏对象线渲染器被扩展(使用鼠标)时,所有其他克隆都会受到影响。 我知道在克隆它之前调整一个对象的形状/尺寸要简单得多,也可以在预制件上进行此更改并应用于所有对象,但我需要看到动态更改,因为它们恰好使我的设计过程更多有效。 我还想在需要时打开和关闭此function。 请看看我是如何在这个问题中创建克隆的。

请注意,我不想在运行时实现此目的。

如何调整一个克隆的形状/尺寸以影响场景视图中的所有其他克隆

在对组件进行修改时同步属性。 要使用现有的代码库,我们还需要克隆修改后的对象,以确保在重新创建其他对象时不会销毁它。

这个对象可以说是需要扩展的四元组或线条渲染器

因此,我们想知道何时修改了任何组件的任何属性。 使用自定义脚本,使用OnValidate是微不足道的,但是使用像LineRenderer这样的密封组件,它有点棘手。 幸运的是,由于我们正在使用编辑器,因此我们可以访问它的一些核心function。

具体来说,我们可以挂钩编辑器的Undo.postprocessModifications事件,以便在对场景进行修改时获得回调。 该委托将为我们提供一个UndoPropertyModification数组,该数组将包含一个已修改的组件属性列表,我们可以使用这些属性通过EditorUtility.CopySerializedIfDifferent来同步其他对象。


CircleSpawn

 [ExecuteInEditMode] public class CircleSpawn : MonoBehaviour { public List Objects; public GameObject OriginalObject; public GameObject PreviousObject; public GameObject ActiveObject; public SpawnData Data; private void OnEnable () { if (Objects == null) Objects = new List(); // 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(); 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; } 

我稍微重构了代码,但在大多数情况下,变化很小