Karena belum ada dukungan untuk membuat serial ObservableCollection C# di Unity, saya menggunakan skrip yang memperluas List<> dan membuat kelas Serializable yang disebut ObservableList seperti yang disebutkan dalam jawaban forum unity ini Daftar yang Dapat Diobservasi. Berikut ini adalah kode yang sama:

[Serializable]
 public class ObservedList<T> : List<T>
 {
     public event Action<int> Changed = delegate { };
     public event Action Updated = delegate { };
     public new void Add(T item)
     {
         base.Add(item);
         Updated();
     }
     public new void Remove(T item)
     {
         base.Remove(item);
         Updated();
     }
     public new void AddRange(IEnumerable<T> collection)
     {
         base.AddRange(collection);
         Updated();
     }
     public new void RemoveRange(int index, int count)
     {
         base.RemoveRange(index, count);
         Updated();
     }
     public new void Clear()
     {
         base.Clear();
         Updated();
     }
     public new void Insert(int index, T item)
     {
         base.Insert(index, item);
         Updated();
     }
     public new void InsertRange(int index, IEnumerable<T> collection)
     {
         base.InsertRange(index, collection);
         Updated();
     }
     public new void RemoveAll(Predicate<T> match)
     {
         base.RemoveAll(match);
         Updated();
     }


     public new T this[int index]
     {
         get
         {
             return base[index];
         }
         set
         {
             base[index] = value;
             Changed(index);
         }
     }
 }

Tetap saja itu tidak diserialkan dan saya tidak bisa melihatnya di editor Unity. Bantuan apa pun akan sangat dihargai!

EDIT #1

Kasus Penggunaan yang Dimaksudkan:

public class Initialize : MonoBehaviour
{
    public ObservedList<int> percentageSlider = new ObservedList<int>();
    
    void Start()
    {
        percentageSlider.Changed += ValueUpdateHandler;
    }
    
    void Update()
    {
    }
    
    private void ValueUpdateHandler(int index)
    {
        // Some index specific action
        Debug.Log($"{index} was updated");
    }
}

Saya melampirkan skrip ini sebagai komponen ke GameObject sehingga saya dapat memasukkan size dari daftar ini, bermain-main dengan nilai-nilai (seperti yang dapat saya lakukan dengan List) dan melakukan beberapa tindakan yang hanya dipecat ketika beberapa nilai di dalam ObservableList diperbarui.

Apa yang ingin saya lihat?

Inspector

Apa yang saya lihat?

Inspector2

0
hulkinBrain 18 Mei 2021, 03:36

2 jawaban

Jawaban Terbaik

Jadi inilah yang saya lakukan

Alih-alih mewarisi dari List<T> dan mencoba memperbaiki Inspektur entah bagaimana dengan skrip editor, Anda cukup menggunakan "backend" List<T> yang Unity sudah menyediakan serialisasi dan membiarkan Anda kelas mengimplementasikan IList<T> seperti misalnya

[Serializable]
public class ObservedList<T> : IList<T>
{
    public delegate void ChangedDelegate(int index, T oldValue, T newValue);

    [SerializeField] private List<T> _list = new List<T>();

    // NOTE: I changed the signature to provide a bit more information
    // now it returns index, oldValue, newValue
    public event ChangedDelegate Changed;

    public event Action Updated;

    public IEnumerator<T> GetEnumerator()
    {
        return _list.GetEnumerator();
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return GetEnumerator();
    }

    public void Add(T item)
    {
        _list.Add(item);
        Updated?.Invoke();
    }

    public void Clear()
    {
        _list.Clear();
        Updated?.Invoke();
    }

    public bool Contains(T item)
    {
        return _list.Contains(item);
    }

    public void CopyTo(T[] array, int arrayIndex)
    {
        _list.CopyTo(array, arrayIndex);
    }

    public bool Remove(T item)
    {
        var output = _list.Remove(item);
        Updated?.Invoke();

        return output;
    }

    public int Count => _list.Count;
    public bool IsReadOnly => false;

    public int IndexOf(T item)
    {
        return _list.IndexOf(item);
    }

    public void Insert(int index, T item)
    {
        _list.Insert(index, item);
        Updated?.Invoke();
    }

    public void RemoveAt(int index)
    {
        _list.RemoveAt(index);
        Updated?.Invoke();
    }

    public void AddRange(IEnumerable<T> collection)
    {
        _list.AddRange( collection);
        Updated?.Invoke();
    }

    public void RemoveAll(Predicate<T> predicate)
    {
        _list.RemoveAll(predicate);
        Updated?.Invoke();
    }

    public void InsertRange(int index, IEnumerable<T> collection)
    {
        _list.InsertRange(index, collection);
        Updated?.Invoke();
    }

    public void RemoveRange(int index, int count)
    {
        _list.RemoveRange(index, count);
        Updated?.Invoke();
    }

    public T this[int index]
    {
        get { return _list[index]; }
        set
        {
            var oldValue = _list[index];
            _list[index] = value;
            Changed?.Invoke(index, oldValue, value);
            // I would also call the generic one here
            Updated?.Invoke();
        }
    }
}

Dalam penggunaan dari kode sama sekali tidak ada perubahan (kecuali seperti yang disebutkan satu tanda tangan acara):

public class Example : MonoBehaviour
{
    public ObservedList<int> percentageSlider = new ObservedList<int>();

    private void Start()
    {
        percentageSlider.Changed += ValueUpdateHandler;

        // Just as an example
        percentageSlider[3] = 42;
    }

    private void ValueUpdateHandler(int index, int oldValue, int newValue)
    {
        // Some index specific action
        Debug.Log($"Element at index {index} was updated from {oldValue} to {newValue}");
    }
}

Tapi Inspektur sekarang terlihat seperti ini

enter image description here

enter image description here


Kemudian jika Anda benar-benar perlu karena Anda benci tampilannya di editor ^^ Anda bisa menggunakan sedikit peretasan kotor dan menimpanya seperti

Miliki kelas dasar non-generik karena editor khusus tidak berfungsi dengan obat generik

public abstract class ObservedList { }

Kemudian mewarisi dari itu

[Serializable]
public class ObservedList<T> : ObservedList, IList<T>
{
   ...
}

Kemudian kita dapat menerapkan laci khusus yang sebenarnya menyediakan tipe dasar tetapi itu akan diterapkan ke pewaris

[CustomPropertyDrawer(typeof(ObservedList), true)]
public class ObservedListDrawer : PropertyDrawer
{
    public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
    {
        // NOTE: Now here is the dirty hack
        // even though the ObservedList itself doesn't have that property
        // we can still look for the field called "_list" which only the 
        // ObservedList<T> has
        var list = property.FindPropertyRelative("_list");
        EditorGUI.PropertyField(position, list, label, true);
    }

    public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
    {
        var list = property.FindPropertyRelative("_list");

        return EditorGUI.GetPropertyHeight(list, label, true);
    }
}

Sekarang terlihat seperti ini

enter image description here


Unity 2019 dan yang lebih lama

Di Unity 2019 dan yang lebih lama, serialisasi obat generik tidak didukung sama sekali.

Di Serialisasi Skrip 2020 ini ditambahkan:

Jenis bidang generik juga dapat langsung diserialisasi, tanpa perlu mendeklarasikan subkelas non-generik.

Ini tidak terjadi di Serialisasi Skrip 2019 dan yang lebih lama.

Jadi di sini Anda perlu memiliki implementasi eksplisit seperti

[Serializable]
public class ObservedListInt : ObservedList<int>{ }

public class Example : MonoBehaviour
{
    public ObservedListInt percentageSlider = new ObservedListInt();

    private void Start()
    {
        percentageSlider.Changed += ValueUpdateHandler;

        // Just as an example
        percentageSlider[3] = 42;
    }

    private void ValueUpdateHandler(int index, int oldValue, int newValue)
    {
        // Some index specific action
        Debug.Log($"Element at index {index} was updated from {oldValue} to {newValue}");
    }
}

Tanpa laci khusus

enter image description here

Dengan laci khusus

enter image description here

1
derHugo 18 Mei 2021, 12:33
  1. Jika Anda ingin tindakan diserialisasi, Anda harus menggunakan UnityEvents (yang terhubung ke sistem acara serial Unity).

  2. Pastikan Anda menggunakan atribut [SerializedField] sebelum semua jenis yang ingin Anda serialkan.

  3. Masalah utama kemungkinan berasal dari pewarisan, yang tidak cocok dengan sistem serialisasi Unity. Secara umum, saya tidak akan pernah mewarisi dari Daftar lagian (berikut beberapa alasannya) Saya akan membuat jenis Anda yang dapat diamati sebagai pembungkus untuk Daftar dengan beberapa fitur tambahan (Daftar akan menjadi anggota dalam Dapat Diamati).

Sunting 1

Menambahkan contoh kode yang diuji. Alih-alih mewarisi, sebuah ObservedList bertindak sebagai pembungkus untuk Daftar. Anda akan dapat berlangganan ke acara yang Diubah dan Diperbarui, tetapi acara tersebut tidak diserialkan. Jika Anda ingin mengakses fungsionalitas daftar lainnya, Anda hanya perlu menambahkan metode publik di kelas ObservedList yang bertindak sebagai pembungkus. Beri tahu saya jika Anda memiliki masalah lain.

[Serializable]
public class ObservedList<T>
{
    public event Action<int> Changed;
    public event Action Updated;
    [SerializeField] private List<T> _value;
    
    public T this[int index]
    {
        get
        {
            return _value[index];
        }
        set
        {
            //you might want to add a check here to only call changed
            //if _value[index] != value
            _value[index] = value;
            Changed?.Invoke(index);
        }
    }
    
    public void Add(T item)
    {
        _value.Add(item);
        Updated?.Invoke();
    }
    public void Remove(T item)
    {
        _value.Remove(item);
        Updated?.Invoke();
    }
    public void AddRange(IEnumerable<T> collection)
    {
        _value.AddRange(collection);
        Updated?.Invoke();
    }
    public void RemoveRange(int index, int count)
    {
        _value.RemoveRange(index, count);
        Updated?.Invoke();
    }
    public void Clear()
    {
        _value.Clear();
        Updated?.Invoke();
    }
    public void Insert(int index, T item)
    {
        _value.Insert(index, item);
        Updated?.Invoke();
    }
    public void InsertRange(int index, IEnumerable<T> collection)
    {
        _value.InsertRange(index, collection);
        Updated?.Invoke();
    }
    public void RemoveAll(Predicate<T> match)
    {
        _value.RemoveAll(match);
        Updated?.Invoke();
    }
}
1
Charly 18 Mei 2021, 08:56