Search Unity

How to easy Deep Copy?

Discussion in 'Scripting' started by leegod, Apr 22, 2021.

  1. leegod

    leegod

    Joined:
    May 5, 2010
    Posts:
    2,476
    How to easily deep copy some class?

    Memberwiseclone not work because it is shallow copy.

    Then manual copy each variables inside class is the way?

    Serialize->Deserialize is too expensive way.
     
  2. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    38,697
    It is approximately 3.

    This is the most maintenance-intensive way, but probably can be made the most performant way, assuming solid engineering.
     
    Joe-Censored likes this.
  3. Adrian

    Adrian

    Joined:
    Apr 5, 2008
    Posts:
    1,065
    If you're fine with Unity's serialization system, Object.Instantiate can clone anything that's derived from
    UnityEngine.Object
    . If your objects aren't derived from Unity's Object, you could stick them into a scriptable object and instantiate that.

    Otherwise, implement
    ICloneable
    and do it manually? There's really no shortcut that doesn't affect performance in some way.
     
  4. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,531
    In theory you could reflect all the fields and create a copy of the class that way, then for any non-primitive members loop its fields, and recurse until done.

    THING IS... there's a lot of implications that arise here. And it's why there is no 'deep-copy' method that just exists out there (and is why memberwiseclone is shallow).

    For example... what if you have some sort of circular reference in your fields:
    Code (csharp):
    1. public class Foo
    2. {
    3.     public Bar BarRef;
    4. }
    5.  
    6. public class Bar
    7. {
    8.     public Foo FooRef;
    9. }
    What do you do here?
    (note that the circular reference could occur several classes apart... A refs B refs C refs D refs B refs C refs D refs B refs C refs D...)

    ...

    What if there is a singleton somewhere in the reference hierarchy?

    What if there are UnityEngine.Object's somewhere in the reference hierarchy?

    What if any number of edge cases in regards to how the object expects its encapsulated state to represent something that this clone will break?

    ...

    This is why generally .net has the ICloneable interface which allows the class to control how its encapsulated state will be cloned and resolve all these edge cases accordingly to how IT needs to.

    ...

    But in the end what your question leads me to is...

    Why do you need a deep copy of an object?

    I'm not trying to suggest there isn't ever a need for a deep copy. It's just that when I hear someone needs one... odds are there may be a better approach to solving/coding the task they're attempting to solve by using a deep copy.

    So what is your end goal here?

    What is this going to be used for?
     
    Kurt-Dekker likes this.
  5. Bunny83

    Bunny83

    Joined:
    Oct 18, 2010
    Posts:
    3,993
    Right, those are the main questions because most people would not realise what an actual generic deep copy would mean. It would mean that you would need to clone literally everything that the object may reference. This would also cause countless of issues as soon as any UnityEngine.Object comes into the mix. Just for example: Cloning a MonoBehaviour instance means: also cloning the gameobject. Which means also cloning all other components. The renderer component may have a reference to a Matrial instance, so we clone that as well. The material may reference a Texture2D, so we clone that as well. A Material also has a Shader reference, clone that as well. The MeshFilter component references a Mesh asset, so we clone that as well. This would be a total mess.

    A generic deep copy / cloning method would be possible to implement with some reflection work. However this would only make sense for some simple pure data classes. As soon as you throw custom classes in the mix you run into all sorts of issues. What if a class doesn't have a public constructor because it was constructed through a factory method? What if there's no default constructor?

    Generally I would not recommend using any such generic approach. It makes much more sense to go with an ICloneable approach. A class know best what it is made of. Keep in mind if you may use ICloneable already for a shallow copy, you are free to invent your own interface (and so "IDeepCloneable" is born).

    I think the deep copy example provided in the MSDN documentation for MemberwiseClone is probably the most flexible approach. So you use MemberwiseClone to just get a copy of the object, and after that you take care of things which may need special attention. So this takes care of all the primitive data and you just have to handle the references.
     
    eaglechaowanin and PraetorBlue like this.
  6. MDADigital

    MDADigital

    Joined:
    Apr 18, 2020
    Posts:
    2,198
    What's your use case for deep clone? Often there is better solutions to the problem.

    Edit: not the first to point that out :)
     
  7. leegod

    leegod

    Joined:
    May 5, 2010
    Posts:
    2,476
    @lordofduct
    Why do you need a deep copy of an object?
    -> When I change scene, some class's variable(script)'s become null because scene destroyed.
    But I need to preserve it for Save & Load function, so I want to copy it.
     
  8. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,531
    If you want to save/load it... that implies it being written to disk. That would require serialization.

    As for clearing between scenes... if it's a unity object, well it's getting destroyed. You can't really clone that due to how unity objects work (there is a engine side unmanaged object that pairs with the .net side object). If you just mean some of the fields of said object... why clone it? Just hold onto a reference to it somewhere that doesn't get destroyed (like say in your gamemanager or something), it'll survive just fine.
     
    Last edited: Apr 23, 2021
    Bunny83 and PraetorBlue like this.
  9. leegod

    leegod

    Joined:
    May 5, 2010
    Posts:
    2,476
    @lordofduct
    So more specific, in scene A, there are many buildings (gameobject) on the field hexagon map tiles, on those each building has script on their own (but same class) and it manage its building's data.

    I need to keep those whole script's data before leaving scene A, cuz when I return to A after gone to scene B, that script's values should distribute to each same building's data so that each building's status set same with when left.

    So I referenced those scripts on to the DDS (dontdestroy) manager script, but when I return, it become null because destroyed.

    So I thought deep copy method.

    Other way then?
     
  10. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,531
    You can't "copy" those because they're Components. Components are UnityEngine.Objects (inherit from). You can't just make copies of those because of how they're integrated with the Unity engine.

    Basically any GameObject/Component/ScriptableObject has a C# side object and a C++ side object. When you "destroy" them the C++ side object is removed from unmanaged memory on the C++ side and the C# side object doesn't actually become null BUT evaluates as null due to Unity's overload of the == operator and if you access its members that interop with the internal C++ side it'll throw an error.

    If you cloned these objects... they just wouldn't work. At the very least they'd still be teathered to a destroyed object and evaluate as "null" just like the one you held... OR you'd get worst problems.

    ...

    What you need to do is capture the state of the fields in those components. This could be as simple as just returning some other token of that script. Something like so:

    Code (csharp):
    1. public class MyScript : MonoBehaviour
    2. {
    3.  
    4.     public float SomeNumber;
    5.     public string SomeString;
    6.     public Vector3 SomeVector;
    7.     public SomeDataClassType SomeData; //in this example 'SomeDataClassType' is a pure data class with no potential gotchas like refs to UnityEngine.Objects. Instead it just holds basic fields.
    8.  
    9.     public MyScriptToken GetToken()
    10.     {
    11.         return new MyScriptToken()
    12.         {
    13.             SomeNumber = this.SomeNumber,
    14.             SomeString = this.SomeString,
    15.             SomeVector = this.SomeVector,
    16.             SomeData = this.SomeData
    17.         };
    18.     }
    19.  
    20.     public void SetFromToken(MyScriptToken token)
    21.     {
    22.         SomeNumber = token.SomeNumber;
    23.         SomeString = token.SomeString;
    24.         SomeVector = token.SomeVector;
    25.         SomeData = token.SomeData;
    26.     }
    27.  
    28.     public struct MyScriptToken
    29.     {
    30.         public float SomeNumber;
    31.         public string SomeString;
    32.         public Vector3 SomeVector;
    33.         public SomeDataClassType SomeData;
    34.     }
    35. }
    Note, this is technically how I resolve issues like saving as well. I use tokens to get the state of objects and then serialize them. I can also store the tokens between scenes.

    Here you can see that:
    All of these implement an "ITokenizable" interface to generalize the process. This way my save code can just loop over all things that can be tokenized, get the token, and then stick that in a dictionary (the dictionary holds an id so I know what gets it... getting into how that id is done gets into a completely different issue/topic).

    A medium complexity example like my generic one above:
    Code (csharp):
    1. using UnityEngine;
    2. using System.Collections.Generic;
    3.  
    4. using com.spacepuppy.Dynamic;
    5.  
    6. namespace com.mansion.Entities.Actors
    7. {
    8.  
    9.     public class RecruitAttributes : MonoBehaviour, ITokenizable
    10.     {
    11.         public BodyMeshes BodyMesh;
    12.         public string Name;
    13.         public string Surname;
    14.         public int Face;
    15.         public int HeadAcc;
    16.         [Range(1, 99)]
    17.         public int Rank;
    18.         public Classes Class;
    19.         public Traits Trait1;
    20.         public Traits Trait2;
    21.         public Abilities Ability;
    22.         public Personalities Personality;
    23.         [Range(0, 9999)]
    24.         public int Cash;
    25.  
    26.  
    27.         #region ITokenizable Interface
    28.  
    29.         public object CreateStateToken()
    30.         {
    31.             return new Token()
    32.             {
    33.                 BodyMesh = this.BodyMesh,
    34.                 Name = this.Name,
    35.                 Surname = this.Surname,
    36.                 Face = this.Face,
    37.                 HeadAcc = this.HeadAcc,
    38.                 Rank = this.Rank,
    39.                 Class = this.Class,
    40.                 Trait1 = this.Trait1,
    41.                 Trait2 = this.Trait2,
    42.                 Ability = this.Ability,
    43.                 Personality = this.Personality,
    44.                 Cash = this.Cash
    45.             };
    46.         }
    47.  
    48.         public void RestoreFromStateToken(object token)
    49.         {
    50.             var tok = token as Token;
    51.             if (tok == null) return;
    52.  
    53.             this.BodyMesh = tok.BodyMesh;
    54.             this.Name = tok.Name;
    55.             this.Surname = tok.Surname;
    56.             this.Face = tok.Face;
    57.             this.HeadAcc = tok.HeadAcc;
    58.             this.Rank = tok.Rank;
    59.             this.Class = tok.Class;
    60.             this.Trait1 = tok.Trait1;
    61.             this.Trait2 = tok.Trait2;
    62.             this.Ability = tok.Ability;
    63.             this.Personality = tok.Personality;
    64.             this.Cash = tok.Cash;
    65.         }
    66.  
    67.         #endregion
    68.  
    69.         #region Special Types
    70.  
    71.         public enum BodyMeshes
    72.         {
    73.             DevActor = 0,
    74.             RookieAverageMale = 1,
    75.             RookieAverageFemale = 2,
    76.             K9UnitMaleAverage = 3,
    77.             K9UnitFemaleAverage = 4
    78.         }
    79.  
    80.         public enum Classes
    81.         {
    82.             Rookie = 0,
    83.             DeskJockey = 1,
    84.             Officer = 2,
    85.             BeatCop = 3,
    86.             K9Unit = 4,
    87.             SWAT = 5,
    88.             Detective = 6,
    89.             Sergeant = 7,
    90.             Undercover = 8,
    91.             SecretAgent = 9,
    92.             ChiefOfPolice = 10,
    93.             ParkRanger = 11,
    94.             Fireman = 12,
    95.             Informant = 13,
    96.             EscapedConvict = 14,
    97.             AnActualChild = 15
    98.         }
    99.  
    100.         public enum Traits
    101.         {
    102.             None = 0,
    103.             Acrobatic = 1,
    104.             Violent = 2,
    105.             Bignerd = 3,
    106.             Deadeye = 4,
    107.             DoorBasher = 5,
    108.             Feeble = 6,
    109.             FlatFooted = 7,
    110.             GunNut = 8,
    111.             Leadbelly = 9,
    112.             Lightweight = 10,
    113.             MapMaker = 11,
    114.             Moonwalker = 12,
    115.             NearSighted = 13,
    116.             Noir = 14,
    117.             Pathfinder = 15,
    118.             Prepared = 16,
    119.             QuickReload = 17,
    120.             Slugger = 18,
    121.             SmallFrame = 19,
    122.             Tank = 20,
    123.             TragicStory = 21
    124.         }
    125.  
    126.         public enum Abilities
    127.         {
    128.             None = 0,
    129.             Dodge = 1,
    130.             Hide = 2,
    131.             Lockpick = 3,
    132.             PlantDNA = 4,
    133.             PushPull = 5,
    134.             TaintedBlood = 6,
    135.             SpringBoots = 7,
    136.             Vampirism = 8,
    137.             VentCrawl = 9,
    138.             K9Unit = 10,
    139.             Partnered = 11,
    140.             EscortMission = 12
    141.         }
    142.  
    143.         public enum Personalities
    144.         {
    145.             Silent = 0
    146.         }
    147.  
    148.  
    149.  
    150.         [System.Serializable]
    151.         private class Token
    152.         {
    153.             public BodyMeshes BodyMesh;
    154.             public string Name;
    155.             public string Surname;
    156.             public int Face;
    157.             public int HeadAcc;
    158.             [Range(1, 99)]
    159.             public int Rank;
    160.             public Classes Class;
    161.             public Traits Trait1;
    162.             public Traits Trait2;
    163.             public Abilities Ability;
    164.             public Personalities Personality;
    165.             [Range(0, 9999)]
    166.             public int Cash;
    167.         }
    168.  
    169.         #endregion
    170.  
    171.     }
    172.  
    173. }
    A very simple example where the state is just a single 'bool':
    Code (csharp):
    1. using UnityEngine;
    2.  
    3. using com.spacepuppy;
    4. using com.spacepuppy.Dynamic;
    5. using com.spacepuppy.Utils;
    6.  
    7. namespace com.mansion.Entities.Inventory
    8. {
    9.  
    10.     [CreateAssetMenu(fileName = "InventoryItem", menuName = "Inventory/FuseInventoryItem")]
    11.     public class FuseInventoryItem : InventoryItem, ITokenizable
    12.     {
    13.  
    14.         #region Fields
    15.      
    16.         #endregion
    17.  
    18.         #region Properties
    19.  
    20.         public bool Blown
    21.         {
    22.             get;
    23.             set;
    24.         }
    25.  
    26.         #endregion
    27.  
    28.         #region Methods
    29.  
    30.         object ITokenizable.CreateStateToken()
    31.         {
    32.             return this.Blown;
    33.         }
    34.  
    35.         void ITokenizable.RestoreFromStateToken(object token)
    36.         {
    37.             this.Blown = ConvertUtil.ToBool(token);
    38.         }
    39.  
    40.         #endregion
    41.  
    42.     }
    43.  
    44. }
    A complex example where the state has a lot going on since it relies on a list of known ScriptableObjects which can't be easily serialized to disk.

    Code (csharp):
    1. using UnityEngine;
    2. using System.Collections.Generic;
    3. using System.Linq;
    4.  
    5. using com.spacepuppy;
    6. using com.spacepuppy.Dynamic;
    7. using com.spacepuppy.Project;
    8. using com.spacepuppy.Scenario;
    9. using com.spacepuppy.Utils;
    10.  
    11. namespace com.mansion.Entities.Inventory
    12. {
    13.  
    14.     public class InventoryPouch : SPComponent, ITokenizable
    15.     {
    16.  
    17.         #region Fields
    18.      
    19.         [SerializeField]
    20.         private DiscreteFloat _maxTrackedItems = DiscreteFloat.PositiveInfinity;
    21.         [SerializeField]
    22.         [ReorderableArray]
    23.         [DisableOnPlay]
    24.         private List<InventoryItem> _items = new List<InventoryItem>();
    25.         [System.NonSerialized]
    26.         private ItemCollection _itemColl;
    27.  
    28.         [SerializeField]
    29.         private Trigger _onInventoryChanged;
    30.  
    31.         [System.NonSerialized]
    32.         private IEntity _entity;
    33.      
    34.         #endregion
    35.  
    36.         #region CONSTRUCTOR
    37.  
    38.         protected override void Awake()
    39.         {
    40.             base.Awake();
    41.  
    42.             _entity = IEntity.Pool.GetFromSource<IEntity>(this);
    43.         }
    44.  
    45.         protected override void Start()
    46.         {
    47.             base.Start();
    48.  
    49.             if (!this.IsInitialized) this.InitItems();
    50.         }
    51.  
    52.         internal virtual void InitItems()
    53.         {
    54.             if (this.IsInitialized) return;
    55.             this.IsInitialized = true;
    56.  
    57.             for (int i = 0; i < _items.Count; i++)
    58.             {
    59.                 var item = _items[i];
    60.                 if (item == null)
    61.                 {
    62.                     _items.RemoveAt(i);
    63.                     i--;
    64.                 }
    65.                 else
    66.                 {
    67.                     this.OnItemAdded(item);
    68.                 }
    69.             }
    70.         }
    71.  
    72.         #endregion
    73.  
    74.         #region Properties
    75.  
    76.         public IEntity Entity
    77.         {
    78.             get { return _entity; }
    79.         }
    80.  
    81.         public bool IsInitialized
    82.         {
    83.             get;
    84.             private set;
    85.         }
    86.      
    87.         public DiscreteFloat MaxTrackedItems
    88.         {
    89.             get { return _maxTrackedItems; }
    90.             set { _maxTrackedItems = value; }
    91.         }
    92.  
    93.         public ItemCollection Items
    94.         {
    95.             get
    96.             {
    97.                 if (_itemColl == null) _itemColl = new ItemCollection(this);
    98.                 return _itemColl;
    99.             }
    100.         }
    101.  
    102.         public Trigger OnInventoryChanged
    103.         {
    104.             get { return _onInventoryChanged; }
    105.         }
    106.      
    107.         #endregion
    108.  
    109.         #region Methods
    110.      
    111.         protected virtual void OnItemAdded(InventoryItem item)
    112.         {
    113.             if (!this.IsInitialized) return; //if we haven't been initialized yet, this work should wait until then
    114.  
    115.             item.OnItemAddedToInventory(this);
    116.  
    117.             if (_onInventoryChanged.Count > 0) _onInventoryChanged.ActivateTrigger(this, this);
    118.         }
    119.  
    120.         protected virtual void OnItemRemoved(InventoryItem item)
    121.         {
    122.             item.OnItemRemovedFromInventory(this);
    123.  
    124.             if (_onInventoryChanged.Count > 0) _onInventoryChanged.ActivateTrigger(this, this);
    125.         }
    126.  
    127.         protected virtual void OnClearingItems()
    128.         {
    129.             foreach(var item in _items)
    130.             {
    131.                 if (!object.ReferenceEquals(item, null)) item.OnItemRemovedFromInventory(this);
    132.             }
    133.  
    134.             if (_onInventoryChanged.Count > 0) _onInventoryChanged.ActivateTrigger(this, this);
    135.         }
    136.  
    137.         #endregion
    138.  
    139.         #region ITokenizable Interface
    140.  
    141.         public object CreateStateToken()
    142.         {
    143.             object[] arr = new object[_items.Count + 1];
    144.             arr[arr.Length - 1] = (float)_maxTrackedItems;//we stick the max at the end
    145.             for (int i = 0; i < _items.Count; i++)
    146.             {
    147.                 var item = _items[i];
    148.                 var tk = (item is ITokenizable) ? (item as ITokenizable).CreateStateToken() : null;
    149.                 if (tk != null)
    150.                     arr[i] = new Token(item.name, tk);
    151.                 else
    152.                     arr[i] = item.name;
    153.             }
    154.             return arr;
    155.         }
    156.  
    157.         public void RestoreFromStateToken(object token)
    158.         {
    159.             this.Items.Clear();
    160.  
    161.             var arr = token as object[];
    162.             if (arr == null || arr.Length == 0) return;
    163.  
    164.             var items = Game.EpisodeSettings.InventorySet;
    165.             if (object.ReferenceEquals(items, null)) return;
    166.  
    167.             int len = arr.Length;
    168.             if (ConvertUtil.IsNumeric(arr[len - 1]))
    169.             {
    170.                 //we store max size as the last entry
    171.                 _maxTrackedItems = ConvertUtil.ToSingle(arr[len - 1]);
    172.                 len--; //redact from len so we don't parse this as an InventoryItem
    173.             }
    174.  
    175.             for (int i = 0; i < len; i++)
    176.             {
    177.                 InventoryItem item = null;
    178.                 object tk = null;
    179.                 if (arr[i] is string)
    180.                 {
    181.                     item = items.GetAsset(arr[i] as string) as InventoryItem;
    182.                 }
    183.                 else if (arr[i] is Token)
    184.                 {
    185.                     var pair = (Token)arr[i];
    186.                     tk = pair.token;
    187.                     item = items.GetAsset(pair.name) as InventoryItem;
    188.                 }
    189.  
    190.                 //we don't store the token if the token is null to conserve space, so check if tokenizable even if no token
    191.                 if (item is ITokenizable)
    192.                     (item as ITokenizable).RestoreFromStateToken(tk);
    193.  
    194.                 if (!object.ReferenceEquals(item, null))
    195.                     this.Items.Add(item);
    196.             }
    197.         }
    198.      
    199.         #endregion
    200.  
    201.         #region Special Types
    202.  
    203.         public class ItemCollection : IList<InventoryItem>
    204.         {
    205.  
    206.             private InventoryPouch _owner;
    207.  
    208.             #region CONSTRUCTOR
    209.  
    210.             public ItemCollection(InventoryPouch pouch)
    211.             {
    212.                 if (pouch == null) throw new System.ArgumentNullException("pouch");
    213.                 _owner = pouch;
    214.             }
    215.  
    216.             #endregion
    217.  
    218.             #region Properties
    219.  
    220.             public InventoryItem this[int index]
    221.             {
    222.                 get { return _owner._items[index]; }
    223.             }
    224.  
    225.             public int TrackedCount
    226.             {
    227.                 get
    228.                 {
    229.                     var e = _owner._items.GetEnumerator();
    230.                     int cnt = 0;
    231.                     while(e.MoveNext())
    232.                     {
    233.                         if(e.Current.Usage != InventoryUsage.Untracked)
    234.                         {
    235.                             cnt++;
    236.                         }
    237.                     }
    238.                     return cnt;
    239.                 }
    240.             }
    241.  
    242.             public bool IsFull
    243.             {
    244.                 get
    245.                 {
    246.                     return (float)this.TrackedCount >= (float)_owner.MaxTrackedItems;
    247.                 }
    248.             }
    249.  
    250.             #endregion
    251.  
    252.             #region Methods
    253.  
    254.             public bool CanAddItem(InventoryItem inv)
    255.             {
    256.                 if (inv == null) return false;
    257.                 if (inv.Unique && _owner._items.Contains(inv)) return false;
    258.                 if (inv.Usage != InventoryUsage.Untracked && this.IsFull) return false;
    259.  
    260.                 return true;
    261.             }
    262.  
    263.             public bool Add(InventoryItem inv)
    264.             {
    265.                 if (inv == null) throw new System.ArgumentNullException("inv");
    266.              
    267.                 if(this.CanAddItem(inv))
    268.                 {
    269.                     _owner._items.Add(inv);
    270.                     _owner.OnItemAdded(inv);
    271.                     return true;
    272.                 }
    273.                 else
    274.                 {
    275.                     return false;
    276.                 }
    277.             }
    278.  
    279.             public InventoryItem Find(string name)
    280.             {
    281.                 foreach (var item in _owner._items)
    282.                 {
    283.                     if (item.CompareName(name)) return item;
    284.                 }
    285.                 return null;
    286.             }
    287.  
    288.             #endregion
    289.  
    290.             #region IList Interface
    291.  
    292.             public int Count { get { return (_owner._items != null) ? _owner._items.Count : 0; } }
    293.  
    294.             bool ICollection<InventoryItem>.IsReadOnly { get { return false; } }
    295.          
    296.             void ICollection<InventoryItem>.Add(InventoryItem item)
    297.             {
    298.                 this.Add(item);
    299.             }
    300.  
    301.             public bool Remove(InventoryItem inv)
    302.             {
    303.                 if (inv == null) return false;
    304.  
    305.                 if (_owner._items.Remove(inv))
    306.                 {
    307.                     _owner.OnItemRemoved(inv);
    308.                     return true;
    309.                 }
    310.                 else
    311.                 {
    312.                     return false;
    313.                 }
    314.             }
    315.  
    316.             public void Clear()
    317.             {
    318.                 _owner.OnClearingItems();
    319.                 if (_owner._items != null) _owner._items.Clear();
    320.             }
    321.  
    322.             public bool Contains(InventoryItem inv)
    323.             {
    324.                 return _owner._items.Contains(inv);
    325.             }
    326.  
    327.             public int CountItem(InventoryItem inv)
    328.             {
    329.                 if (inv == null) return 0;
    330.  
    331.                 int cnt = 0;
    332.                 var e = _owner._items.GetEnumerator();
    333.                 while (e.MoveNext())
    334.                 {
    335.                     if (e.Current == inv) cnt++;
    336.                 }
    337.                 return cnt;
    338.             }
    339.  
    340.             public void CopyTo(InventoryItem[] array, int arrayIndex)
    341.             {
    342.                 for (int i = 0; i < _owner._items.Count; i++)
    343.                 {
    344.                     array[arrayIndex + i] = _owner._items[i];
    345.                 }
    346.             }
    347.  
    348.             public IEnumerator<InventoryItem> GetEnumerator()
    349.             {
    350.                 for (int i = 0; i < _owner._items.Count; i++)
    351.                 {
    352.                     yield return _owner._items[i];
    353.                 }
    354.             }
    355.  
    356.             System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
    357.             {
    358.                 return this.GetEnumerator();
    359.             }
    360.  
    361.             public int IndexOf(InventoryItem item)
    362.             {
    363.                 return _owner._items.IndexOf(item);
    364.             }
    365.  
    366.             public void RemoveAt(int index)
    367.             {
    368.                 if (index < 0 || index >= _owner._items.Count) return;
    369.  
    370.                 var weapon = _owner._items[index];
    371.                 _owner._items.RemoveAt(index);
    372.                 _owner.OnItemRemoved(weapon);
    373.             }
    374.  
    375.             void IList<InventoryItem>.Insert(int index, InventoryItem inv)
    376.             {
    377.                 if (inv == null) throw new System.ArgumentNullException("inv");
    378.  
    379.                 if (this.CanAddItem(inv))
    380.                 {
    381.                     _owner._items.Insert(index, inv);
    382.                     _owner.OnItemAdded(inv);
    383.                 }
    384.             }
    385.  
    386.             InventoryItem IList<InventoryItem>.this[int index]
    387.             {
    388.                 get { return _owner._items[index]; }
    389.                 set
    390.                 {
    391.                     throw new System.NotSupportedException();
    392.                 }
    393.             }
    394.  
    395.             #endregion
    396.  
    397.         }
    398.      
    399.         [System.Serializable]
    400.         private struct Token
    401.         {
    402.             public string name;
    403.             public object token;
    404.  
    405.             public Token(string nm, object tk)
    406.             {
    407.                 this.name = nm;
    408.                 this.token = tk;
    409.             }
    410.         }
    411.  
    412.         #endregion
    413.  
    414.     }
    415.  
    416. }
     
    Last edited: Apr 23, 2021
    leegod and Bunny83 like this.