unity3d Unity中游戏对象的唯一不变标识符

slhcrj9b  于 2023-01-13  发布在  其他
关注(0)|答案(3)|浏览(493)

我必须在excel文件中存储每个对象(游戏对象/网格)的信息。就像我有一个球体对象一样,我获取它的示例ID并将其保存在excel中。在excel文件中,随着对象示例ID,我保存了其他东西,如对象显示名称,类型。

objInstanceId = transform.GetInstanceID();

我的问题是这是最安全的方法吗?我检查了instanceID是唯一的,但我不确定它是否会改变?比如如果我更换了我的模型,它会改变吗?我需要一个唯一的和不变的标识符为每一个网格,以便我可以将其保存在Excel文件中,并将一些数据与标识符相关联!

368yc8dk

368yc8dk1#

我们来看看文档:
对象的示例ID始终是唯一的。ID在播放器运行时和编辑器会话之间变化。因此,在编辑器和运行时会话之间执行操作时,ID不可靠,例如,从保存文件加载对象状态。
https://docs.unity3d.com/2021.1/Documentation/ScriptReference/Object.GetInstanceID.html
所以在一个运行时会话中,id是唯一的,在会话之间,id可能会改变。
换句话说,导出所有示例ID、添加/更改值然后再次导入它们将在运行时会话中工作(当应用运行时)。
当您关闭应用程序并启动另一个运行时会话时,它将不起作用,在这种情况下,任何创建的对象都将具有不同的id。

oxiaedzo

oxiaedzo2#

如果你想要一个唯一的持久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)之间来回引用。

whhtz7ly

whhtz7ly3#

这是我为自己的项目想出的一个解决方案。
它是一个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();
    }
}

这种方法的一个缺点是在预置中使用时会失败,因为对象始终是一个示例,永远不会转换为源对象。

相关问题