如何通过PrefabUtility.InstantiatePrefab实例化一个GameObject并改变其父物体?

3
通常你可以在脚本中使用以下方式实例化预设物的克隆:
var obj = (GameObject)Instantiate(....);

然后使用以下方法更改其父元素:

var obj.transform.SetParent(...);

我正在编写一个小的编辑器脚本,用于在菜单项上实例化prefabs并保持它们与prefab之间的链接。我将其放在一个静态类中,如下所示:

public static class EditorMenu
{
    // This is a reference that I already have and is not null
    privtae static Transform exampleParent;

    [MenuItem("Example/Create Clone")]
    private static void CreateClone()
    {
        var prefab = AssetDatabase.LoadAssetAtPath("Example/Path/myObject.prefab", typeof(GameObject));
        var obj = (GameObject)PrefabUtility.InstantiatePrefab(prefab);

        obj.transform.position = Vector3.zero;
        obj.transform.rotation = Quaternion.identity;

        Selection.activeGameObject = obj;
    }
}

我必须使用

(GameObject)PrefabUtility.InstantiatePrefab(prefab);

因为Instantiate()方法不会保存预制体的链接,而是创建了一个副本。

所以到目前为止,上面的代码已经很好地运作了,该对象被插入到场景中,就像我进行拖放一样。

现在我想要将这个新实例化的对象的父级更改为parent,因此我添加了以下代码:

obj.transform.SetParent(exampleParent, true);

我也尝试过

obj.transform.parent = exampleParent;

但是两者都会抛出异常:

设置存储在预制件中的变换的父项已被禁用以防止数据损坏。 UnityEngine.Transform:SetParent(Transform)

prefab 和通过它实例化的 obj 都是预制件的最上层 GameObject,因此我认为我正在设置整个实例化对象的父项,而不是任何预制件层次结构内的变换。

如何更改通过PrefabUtility.InstantiatePrefab实例化的 GameObject 的父项?


更新

我刚刚尝试的一个解决方法是实际使用Instantiate并执行以下操作:

var obj = Object.Instantiate(prefab, cancel.transform);

// remove the added "(clone)" suffix
obj.name = prefab.name;
obj.transform.SetParent(cancel.transform);

然而,正如之前提到的那样,这并不能完全保持prefb功能的完整性...因此,它只允许我还原,但不允许应用更改。作为解决方法,这可能已经足够了,因为在我的情况下,我将其用作实例化预制件的快捷方式,用户后来不会真正想要更改...


你确定 Instantiate() 只能从 MonoBehaviour 调用吗?这是一个静态调用 GameObject.Instantiate() - z3nth10n
@z3nth10n 不是的,我刚刚更新了那部分代码;) 我实际上再次尝试了一下,但它与使用 InstantiatePrefab 不同,因为 Instantiate 会破坏预制件的功能,例如 Apply 更改。 - derHugo
我发现了问题,请参见下面的帖子(https://dev59.com/iLHma4cB1Zd3GeqPGyNH#54203097)。错误消息表述不清,实际问题在于我没有提供的代码,因此我投票关闭了这个问题。 - derHugo
2个回答

2
抱歉,我刚发现了问题:
由于错误信息含混/表述不佳,所以我没有添加它。
我正在使用Resources.FindObjectsOfTypeAll来检查场景中是否存在类型为SomeTypeexampleParent
遵循Unity的示例,应该排除预制件。
// Validation for the Menu item
[MenuItem("Example/Create Clone", true]
private bool TargetAvailable()
{
    foreach (var target in (SomeType[])Resources.FindObjectsOfTypeAll(typeof(SomeType))
    { 
        if (target.hideFlags == HideFlags.NotEditable || target.hideFlags == HideFlags.HideAndDontSave)
            continue;

        if (!EditorUtility.IsPersistent(target.transform.root.gameObject))
            continue;

        exampleParent = target.transform;
        return true;
    }

    exampleParent = null;
    return false;
}

但是这似乎是错误的,不起作用,因为它总是从预制体中返回SomeType引用!(我已经发现这有点奇怪,他们做了什么)

!EditorUtility.IsPersistent(target.transform.root.gameObject))
    continue;

我不确定他们的示例代码中的那个 ! 是否是一个错误?!
所以看起来像是 "设置父级不被允许" 的错误实际上意味着并且应该说

将父级设置为位于预制件中的Transform是不被允许的...

如果我一开始就这样做,我可能会找到实际的问题。
因此,作为解决方法,直到我能够弄清楚那个 FindObjectsOfTypeAll 的事情,我转而使用 Object.FindObjectOfType,假设我的目标始终在场景中处于活动状态。现在,使用 SetParentPrefabUtility.InstantiatePrefab 一起工作了。

1

如何通过PrefabUtility.InstantiatePrefab实例化的GameObject更改其父对象?

这是正确的,但请确保cancel也是一个实例化的对象。

obj.transform.parent = cancel.transform;

正如之前所说,这就是我正在做的事情,但它会抛出“禁用将驻留在预制件中的变换的父级设置以防止数据损坏。UnityEngine.Transform:SetParent(Transform)”的错误信息。 - derHugo
1
您使用的Unity版本是哪个,您如何获取“cancel”对象? - shingo
2017年4月18日。但我找到了问题所在。实际上是我获取父变换的方式有误..错误信息只是表述得很糟糕。请参见我的帖子。无论如何,我还是给你点赞了,因为基本上你是对的 ;) - derHugo

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接