【问题标题】:How can I adjust shape/dimensions of one clone to affect all other clones in the scene view如何调整一个克隆的形状/尺寸以影响场景视图中的所有其他克隆
【发布时间】:2018-05-04 05:40:34
【问题描述】:

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

请注意,我不想在运行时实现这一点。

【问题讨论】:

  • 它是运行时的吗?那你不能。否则,它们是预制件的实例吗?将更改应用到预制件。
  • @Draco18s 实际上你可以在运行时并且不需要像 OP 想要的那样修改预制件。
  • @Draco18s 我不想在运行时这样做。现在,我实际上是在克隆一个游戏对象,而不是一个预制件。虽然我猜想将它实现为一个游戏对象和一个预制件不会有太大的不同。我在问题中说我需要看到动态变化。例如,当我增加四边形的大小时,我需要看到所有克隆的大小同时增长,或者我应该说是实时的。将更改应用到预制件意味着我必须增加一个克隆,然后点击应用按钮以查看所有其他克隆的更改。这不是我需要的。
  • @Programmer 真的吗?而且我的意思不是“存储对象列表并将相同的更改应用于整个列表”的方式,这很明显。 NSSwift:我认为没有内置的方法可以做到这一点。您必须编写某种自定义编辑器脚本(我可能建议仍然使用预制件)。
  • @Draco18s 是的,我想为此编写一个自定义编辑器脚本

标签: c# unity3d unity-editor


【解决方案1】:

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

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

这个对象可以说是一个四边形或线渲染器,需要 扩展

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

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


圈子

[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();
        }
    }
}

生成数据

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

我已经对代码进行了一些重构,但在大多数情况下,变化很小

【讨论】:

  • @Lece 谢谢,这正是我想要实现的目标。
  • @NSSwift 太棒了!我刚刚调整了它,所以请抓住更新。它现在支持对象切换并更好地处理播放模式。
  • @Lece 再次感谢。我一直在尝试寻找一种合适的方法来克隆多个游戏对象。有什么合适的方法来解决这个问题?此外,我要克隆的原始对象具有通过其自定义编辑器中的按钮触发的参数。这些按钮按下只能被选定的克隆对象识别,而不是所有克隆。
  • 我刚刚问了另一个问题stackoverflow.com/questions/51089098/…。我通过在CircleSpawn 类中为其他对象添加重复方法来复制行为,但这对于几个游戏对象来说绝对是不切实际的。如果你能看一下这个真的很感激,因为它是密切相关的。
  • @Lece NSSwift 的赏金将在不到 24 小时内结束 :)
猜你喜欢
  • 1970-01-01
  • 2017-06-08
  • 2020-10-10
  • 1970-01-01
  • 2011-05-08
  • 2010-10-17
  • 1970-01-01
相关资源
最近更新 更多