【问题标题】:Unique unchanged identifier for gameobject in unity统一游戏对象的唯一不变标识符
【发布时间】:2023-01-13 10:53:21
【问题描述】:

我必须在 excel 文件中存储有关每个对象(游戏对象/网格)的信息。就像我有一个球体对象一样,我正在获取它的 instanceId 并将其保存在 excel 中。在 excel 文件以及对象实例 ID 中,我正在保存其他内容,例如对象显示名称、类型。

  objInstanceId = transform.GetInstanceID();

我的问题是这是最安全的方法吗?我检查过 instanceID 是唯一的,但我不确定它是否被更改过?就像我更换我的模型一样,它会改变吗?我需要每个网格都有一个唯一且不变的标识符,这样我就可以将它保存在一个 excel 文件中,并将一些数据与标识符相关联!

【问题讨论】:

  • 我说你需要制作一个组件来生成自己的 guid,一旦有了它,它就会保留它
  • 是的,好主意,但我该如何坚持呢?假设如果我在层次结构中带回我的模型!然后它将创建 newguid,我用旧指南保存的数据将不再有效。
  • @MuhammadFaizanKhan 如何在序列化字段中为组件的每个实例保存一个唯一 ID -> 持久性......你能稍微扩展一下你的确切用例吗?场景中的所有这些对象是否从一开始就存在?你在运行时添加/删除它们吗?
  • 我不会在运行时添加/删除它,但在编辑器上,我们经常在更新后替换模型。就像如果 FBX 在建模工具中更新,然后我们在场景中替换它。以便所有建模更改都能正确反映在场景中。

标签: c# unity3d


【解决方案1】:

让我们看一下文档:

对象的实例 ID 始终是唯一的。 ID 在播放器运行时和编辑器会话之间发生变化。因此,ID 对于在编辑器和运行时会话之间执行操作是不可靠的,例如,从保存文件加载对象状态。

https://docs.unity3d.com/2021.1/Documentation/ScriptReference/Object.GetInstanceID.html

因此在运行时会话中,id 保证是唯一的。在会话之间,id 可能会改变。

换句话说,导出所有实例 ID,添加/更改值,然后再次导入它们将在运行时会话(当应用程序运行时)内工作。

当您关闭应用程序并启动另一个运行时会话时,它将不起作用,在这种情况下,任何创建的对象都将具有不同的 ID。

【讨论】:

  • 谢谢,但我正在寻找解决方案。
  • @MuhammadFaizanKhan 然后澄清你的最终目标是什么,并更加关注你的问题。
  • 我的目标是为每个对象保存数据。数据将是它的显示名称和类型。所以我想保存两个字符串,并且都可以更改。
  • 但是,如果您只在 Unity 之外更改 FBX,然后将其保存在相同的文件位置,然后返回到 Unity,那么 Unity 无论如何都会正确地保持引用,并且新的 Mesh 将在之前使用过的任何地方使用..
【解决方案2】:

如果您想要一个唯一的持久 ID,我会使用序列化字段并将其隐藏起来,例如

    #if UNITY_EDITOR
using UnityEditor;
#endif

using UnityEngine;
using System.Collections.Generic;
using System.Linq;

// See https://docs.unity3d.com/ScriptReference/ExecuteInEditMode.html
[ExecuteAlways]
public class UniqueID : MonoBehaviour
{
    // Serialize/saves the unique ID
    // but hide it in the Inspector -> we don't want to edit it manually
    // uint allows around 4 Million IDs that should be enough for most cases ;)
    [HideInInspector] private uint _id;
    private static List<uint> usedIds;

    // Public read-only accessor
    public uint ID => _id;

#if UNITY_EDITOR
    // Due to ExecuteAllways this is called once the component is created
    private void Awake()
    {
        if (!Application.isPlaying && _id == 0)
        {
            _id = GetFreeID();
            usedIDs.Add(_id);

            EditorUtility.SetDirty(this);
        }
    }
#endif

    // For runtime e.g. when spawning prefabs
    // in that case Start is delayed so you can right after instantiating also assign a specific ID
    private void Start()
    {
        if (Application.isPlaying && _id == 0) _id = GetFreeID();
    }

    // Allows to set a specific ID e.g. when instantiating on runtime
    public void SetSpecificID(uint id)
    {
        if (Application.isPlaying) _id = id;
        else Debug.LogWarning("Only use in play mode!", this);
    }

    // Stores all already used IDs
    private readonly static HashSet<uint> usedIDs = new HashSet<uint>();

    private static readonly System.Random random = new System.Random();

    // Get a random uint
    private static uint RandomUInt()
    {
        uint thirtyBits = (uint)random.Next(1 << 30);
        uint twoBits = (uint)random.Next(1 << 2);
        return (thirtyBits << 2) | twoBits;
    }

    // This is called ONCE when the project is opened in the editor and ONCE when the app is started
    // See https://docs.unity3d.com/ScriptReference/InitializeOnLoadMethodAttribute.html
    [InitializeOnLoadMethod]
    [RuntimeInitializeOnLoadMethod]
    private static void InitUsedIDs()
    {
        // Find all instances of this class in the scene
        var instances = FindObjectsOfType<UniqueID>(true);
        foreach (var instance in instances.Where(i => i._id != 0))
        {
            usedIds.Add(instance._id);
        }

        foreach (var instance in instances.Where(i => i._id == 0))
        {
            instance._id = GetFreeID();

#if UNITY_EDITOR
            // See https://docs.unity3d.com/ScriptReference/EditorUtility.SetDirty.html
            EditorUtility.SetDirty(instance);
#endif

            usedIDs.Add(instance._id);
        }
    }

    private static uint GetFreeID()
    {
        uint id = 0;

        do
        {
            id = RandomUInt();
        }
        while (id == 0 || usedIDs.Contains(id));

        return id;
    }
}

注意:在智能手机上打字,但我希望思路清晰。

可能会有一些边缘情况,但我希望这能让你开始。


作为替代方案,您也可以简单地使用两个 SerializedDictionary 并直接在任何类型(如 GameObjectMesh)和唯一键(如 uint)之间来回引用。

【讨论】:

  • 我已经更新了脚本,但还没有检查如果我重新附加脚本它将如何工作。
  • @MuhammadFaizanKhan 重新连接是什么意思?这个脚本的每个实例都有一个唯一的 ID ..它没有绑定到它所附加的 GameObject ;) 你当然可以使用 SerializedDictionary 添加它并直接引用 GameObjects 和 ID ..
  • 我必须将此脚本附加到每个对象才能分配 ID?如果我删除脚本并将其再次附加到对象,会发生什么情况?那么id会扩孔一样吗?或者它生成新的ID?
  • 是的。如前所述,它将生成一个新的,除非在附加它之后立即调用 SetSpecificID 并使用特定的 ID ...这只是一个起点 .. 确保您必须将它应用于您的特定用例;)
  • 每次我玩的时候 ID 都会改变。
【解决方案3】:

这是我为自己的项目提出的解决方案。
它是一个 MonoBehaviour 派生类,因此它旨在用作需要持久唯一 ID 的组件的基类,但它可以修改为按原样工作。
您的派生脚本应该是[ExecuteAlways]

这种方法区分新创建的对象和实例化的对象。它通过查询 instanceID 来做到这一点。

当用于调用原始对象时,此方法返回一个正值。当用于调用实例对象时,该方法返回一个负值。

Unity GetInstanceID()

由于 guid 被序列化,它保持持久。
为同一脚本创建新 guid 的唯一时间是在组件检查器上下文菜单中单击重置按钮时。

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

namespace MyNamespace {
    public abstract class UniqueMonoBehaviour : MonoBehaviour {
        [HideInInspector][SerializeField] protected int instanceID = 0;
        [HideInInspector][SerializeField] protected string guid = null;
        private Action callbackStart;

        protected void Awake() {
            var res = Validate();
            if (res.Item1 || res.Item2)
                OnCreatedNewGUIDAwake(res.Item2);
        }

        protected void Start() {
            callbackStart?.Invoke();
        }

        protected void Reset() {
            var res = Validate();
            if (res.Item1 || res.Item2)
                OnCreatedNewGUIDReset();
        }

        (bool, bool) Validate() {
            //check if this is a fresh object
            if (guid == null || instanceID == 0) {
                RegenerateGUID();
                instanceID = GetInstanceID();
                callbackStart = () => OnCreatedNewGUIDStart(false);
                return (true, false);
            }

            //check for new instanceid
            else if (instanceID != GetInstanceID()) {
                instanceID = GetInstanceID();
                //check if this is an instance of another gameobject
                if (instanceID < 0) {
                    RegenerateGUID();
                    callbackStart = () => OnCreatedNewGUIDStart(true);
                    return (false, true);
                }
            }
            return (false, false);
        }
        private void RegenerateGUID() {
            guid = System.Guid.NewGuid().ToString();
        }

        protected abstract void OnCreatedNewGUIDAwake(bool isInstance);
        protected abstract void OnCreatedNewGUIDStart(bool isInstance);
        protected abstract void OnCreatedNewGUIDReset();
    }
}

【讨论】:

    猜你喜欢
    • 2010-10-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多