Уникальный id при дублировнии(клонировнии) объекта в Unity3d

В своём проекте на Unity3d пытаюсь вынести в ScriptableObject важные данные об объектах на сцене.

Объекты однотипные, но информация о каждом экземпляре в сцене должна храниться в List<T> в экземпляре SceneData : ScriptableObject. К этой информации объект сцены будет обращается в процессе игры.

Например есть класс с данными:

public class PersonData
{
    long id;
    string firstName;
    Gender gender;
    float cash;
}

Он сохраняется в List<PersonData> ScriptableObject и ссылка на экземпляр PersonData будет импортироваться в класс PersonView:

public class PersonView : MonoBehaviour
{
    long id;
    PersonData data;
}

Ссылка на PersonData будет добавятся PersonView при открытии сцены в редакторе и после загрузки сцены в игре. Загрузка сцены будет из assetBundle, скачиваемого через интернет, поэтому связывание данных должно быть по id, который будет генерироваться при добавлении объекта PersonView на сцену в редакторе, в методе OnEnable:

[CustomEditor(typeof(PersonView))]
public class PersonViewEditor : Editor
{
    void OnEnable()
    {
       var pv = this.target as PersonView;
       if(pv.id == 0)
       {
           pv.data = new PersonData();
           pv.data = pv.id = System.DateTime.Now.Ticks;
           EditorUtility.SetDirty(target);
       }
    }
}

При сохранении сцены, данные из PersonView сохраняются в PersonData.

Проблема: при дублировании объекта(Ctrl+d), id старого объекта копируется в новый, а мне нужно чтобы она создавалась заново.

Единственный вариант, который я вижу: это в методе OnEnable() искать дубликаты, и если объект с таким же id присутствует на сцене, генерировать новый id и PersonData:

[CustomEditor(typeof(PersonView))]
public class PersonViewEditor : Editor
{
    PersonView pv;
    void OnEnable()
    {
       pv = this.target as PersonView;
       
       if(pv.id == 0) { SetNewId(); return; }
       else {
           var views = FindObjectsOfType<PersonView>()
           foreach(var view in views)
           {
              if(pv.id == view.id) { SetNewId(); return; }
           }
       }
    }

    private void SetNewId()
    {
        pv.data = new PersonData();
        pv.data = pv.id = System.DateTime.Now.Ticks;
        EditorUtility.SetDirty(target);
    }
}

Ответы (1 шт):

Автор решения: Yaroslav

Если я правильно понял персоны записывают в SceneData свои дефолтные значения выставленные в инспекторе если их там нет или читают если они там уже есть. Все персоны неповторимы и загвоздка в том что-бы понять какие данные для какой персоны.

Вообще данные всегда разделяют на константы и изменяемые чтобы была возможность сохранять только изменяемые. То есть firstName и gender не меняются в отличие от cash которые нужно сохранять.

[Serializable]
public struct PersonData
{
    public PersonConstants Constants;
    public PersonalProperties Properties;
}

[Serializable]
public struct PersonConstants
{
    public string FirstName;
    public Gender Gender;
}

[Serializable]
public struct PersonalProperties
{
    public float Cash;
}

Сохранять нужно будить только PersonalProperties в обертке с неким id (int или string) в некой сущности с массивом через JsonUtility, а json хранить в PlayerPrefs или на сервере. В сохранении PersonConstants никакого смысла нет.

[Serializable]
public struct PersonalPropertiesSaveArray
{
    public PersonalPropertiesSave[] Persons;
}

[Serializable]
public struct PersonalPropertiesSave
{
    public string ID;
    public PersonalProperties Properties;
}

PersonalPropertiesSaveArray save;
string json = JsonUtility.ToJson(save);

PersonalPropertiesSaveArray load;
load = JsonUtility.FromJson<PersonalPropertiesSaveArray>(json);

Касаемо идентификации кто есть кто. Я не понял почему сущность называется SceneData, вроде к конкретной сцене это не привязано, скорее PersonalDataProvider. В каждой сцене должен быть объект имеющий ссылки на всех персон на сцене, некий ScenePersonals. Именно он и отвечает за идентификацию.

public class PersonalDataProvider : MonoBehaviour
{
    [SerializeField] private PersonalDataProvider _provider;
    [SerializeField] private Personal[] _personals;

    private void OnEnable ()
    {
        Load();
    }
    
    private void OnDisable ()
    {
        Save();
    }

    private void Load ()
    {
        _provider.Load();
        for (int i = 0; i < _personals.Length; i++)
        {
            string id = GetID(i);
            if (_provider.HasPersonal(id))
                _personals[i].Data.Properys = _provider.GetPropertys(id);
        }
    }

    private void Save ()
    {
        for (int i = 0; i < _personals.Length; i++)
        {
            string id = GetID(i);
            // по id провайдер сам разберется это новые данные которые
            // нужно добавить в текущий справочник или изменить имеющиеся 
            _provider.SetPropertys(id, _personals[i].Data.Properys);
        }
        _provider.Save();

    }

    private string GetID (int index)
    {
        // простой вариант, имя сцены + индекс в списке
        return gameObject.scene.name+index;
    }
}
→ Ссылка