Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. Dismiss Notice

Create copy of Scriptableobject (during runtime)

Discussion in 'Scripting' started by mlepp, Sep 18, 2015.

  1. mlepp

    mlepp

    Joined:
    May 22, 2013
    Posts:
    26
    I'm been starting to work with ScriptableObjects recently and are loving the functionality so far.

    I'm working on an editor window for creating and managing spells in my game.
    The spells that are created mostly store data but also contains some methods for controlling the spells. It creates spells as .asset files.
    Like if the spell contains a Projectile component the Projectile component is responsible for propelling a gameobject forward.
    However I have a spell type which is a ground target spell which ticks X number of times. The problem with this is that it contains a tick counter which MUST be independent from each other instance of the same spell.

    In my current version the counter works the first time the spell is used but the next time the tick count has already been increased (because they all use the same skill object).

    My question now is:
    Is it possible to copy a ScriptableObject so that all the private fields are independent from each other.

    Code (csharp):
    1.  
    2. public class TestObject : ScriptableObject {
    3.     public int counter;
    4.  
    5.     public void Init() {
    6.         counter = 0;
    7.     }
    8. }
    9.  
    10. public class TestClass : MonoBehaviour {
    11.      public TestObject to1;
    12.      public TestObject to2;
    13.  
    14.     void Awake() {
    15.         to1 = CreateInstance<TestObject>();
    16.         to1.Init();
    17.         to2 = COPY(to1);
    18.  
    19.         to1.counter = 10;
    20.  
    21.         Debug.Log(to1.counter);   //Expected value: 10
    22.         Debug.Log(to2.counter);   //Expected value: 0
    23.     }
    24. }
    25.  
    I hope I got my point across.
     
  2. mlepp

    mlepp

    Joined:
    May 22, 2013
    Posts:
    26
    I actually found the solution to this.
    If you use
    Code (csharp):
    1. var clone = Instantiate(SCRIPTABLEOBJECT);
    you get a clone.
    But in my case I had nested ScriptableObjects so I had to write a Clone function which did this for all "sub scriptableobjects" so that they are cloned along with the parent.
     
  3. Leone

    Leone

    Joined:
    Dec 15, 2013
    Posts:
    10
    Hey dude, just wanted to say thank you =)
     
  4. Dave04711

    Dave04711

    Joined:
    Mar 19, 2020
    Posts:
    1
    thx so much man
     
  5. oldManPig

    oldManPig

    Joined:
    Oct 25, 2019
    Posts:
    13
    Just jumping in to say thanks @mlepp! I had a heart attack when I realised the scriptable object in charge of my pluggable AI state machine were shared between all the enemies! Now the SO's are cloned on init. (Really I should have designed things better to not have any internal logic, but this is how you learn I guess)
     
    unity_1gO9oSOm458fmA likes this.
  6. angelonit

    angelonit

    Joined:
    Mar 5, 2013
    Posts:
    40
    Thanks for this! I ended up wondering how to prevent the cloned scriptable object to be named "(Clone)Name" so I did this in case any of you find it necessary, it's simple:
    Code (CSharp):
    1.                 string s = SCRIPTABLEOBJECT.name;
    2.                 NEWSCRIPTABLEOBJECT = Instantiate(SCRIPTABLEOBJECT);
    3.                 NEWSCRIPTABLEOBJECT.name = s;
     
  7. IainCarr

    IainCarr

    Joined:
    Mar 9, 2018
    Posts:
    5
    Here's a handy extension script you can use to clone your scriptable objects.
    Code (CSharp):
    1. public static class ScriptableObjectExtension
    2. {
    3.     /// <summary>
    4.     /// Creates and returns a clone of any given scriptable object.
    5.     /// </summary>
    6.     public static T Clone<T>(this T scriptableObject) where T : ScriptableObject
    7.     {
    8.         if (scriptableObject == null)
    9.         {
    10.             Debug.LogError($"ScriptableObject was null. Returning default {typeof(T)} object.");
    11.             return (T)ScriptableObject.CreateInstance(typeof(T));
    12.         }
    13.  
    14.         T instance = Object.Instantiate(scriptableObject);
    15.         instance.name = scriptableObject.name; // remove (Clone) from name
    16.         return instance;
    17.     }
    18. }
    You can use it like this:
    Code (CSharp):
    1. scriptObj = scriptObj.Clone();
    Or:
    Code (CSharp):
    1. var cloneObj = scriptObj.Clone();
     
    Last edited: Mar 25, 2021
  8. cael00

    cael00

    Joined:
    Jan 2, 2018
    Posts:
    1
    Thanks dude!
     
  9. ratmiroko

    ratmiroko

    Joined:
    Nov 28, 2019
    Posts:
    1
    Thank you!
     
  10. lloydsummers

    lloydsummers

    Joined:
    May 17, 2013
    Posts:
    344
    One more person logging in to say thank you :)
     
  11. Malforacic105

    Malforacic105

    Joined:
    Aug 17, 2022
    Posts:
    3
    The problem with this method is that when one clone changes, all the clones change. I am currently trying to find a way around this

    Edit: turns out this was actually a problem on my part, Instantiate works just fine
     
    Last edited: Aug 18, 2022
  12. spiney199

    spiney199

    Joined:
    Feb 11, 2021
    Posts:
    5,856
    If changing the value of one supposed instance is changing the values of others... then you don't actually have a separate instance.

    Perhaps show an example of your code.

    That said I personally find instantiating scriptable objects pointless when you could do the same with plain classes. That way you don't have to deal with the life time of a managed object either.
     
  13. Malforacic105

    Malforacic105

    Joined:
    Aug 17, 2022
    Posts:
    3
    here is an example of my code, which should re-create the problem.
    Code (CSharp):
    1. namespace Example
    2. {
    3.     public class Item : ScriptableObject
    4.     {
    5.         [SerializeField] private string iD;
    6.         [SerializeField] new private string name;
    7.  
    8.         public string ID => iD;
    9.  
    10.         public Item Clone()
    11.         {
    12.             return Instantiate(this);
    13.         }
    14.     }
    15.  
    16.     public class DataBase
    17.     {
    18.         private static Item[] items;
    19.  
    20.         public static Item GetItem(string iD)
    21.         {
    22.             foreach (Item item in items)
    23.             {
    24.                 if (item.ID == iD)
    25.                 {
    26.                     return item.Clone();
    27.                 }
    28.             }
    29.             return null;
    30.         }
    31.     }
    32. }
    Basically, the DataBase class will hold an array of all the items in my game, and I'll use DataBase.GetItem() to retrieve an Item by iD and return a clone. All Items retrieved this way with the same Id do not affect the ScriptableObject that they were cloned from, but but when I change one clone, it changes all the other clones as if they are all references to the same scriptableObject.
     
  14. spiney199

    spiney199

    Joined:
    Feb 11, 2021
    Posts:
    5,856
    Well if you have multiple references to the same clone, then you will experience the behaviour you're seeing. You need to make sure they are distinct, separate instances from one another.
     
    Bunny83 likes this.
  15. Bunny83

    Bunny83

    Joined:
    Oct 18, 2010
    Posts:
    3,525
    Well, that's not really an example as it's not clear how you actually use this code. For example when you call GetItem 3 times with the same id and store each item in a seperate variable, you would have 3 independent objects. Keep in mind that if the scriptable references other scriptable objects or prefabs / components on prefabs, those would not be duplicated. Such references would still be references and a clone of the scriptable object would still reference the same instance. However custom serializable classes or fields inside the scriptable object would be duplicated and would be seperate.

    So please provide an "actual" usecase. That means show us which of the fields you manipulate do change in the other instances.
     
  16. Malforacic105

    Malforacic105

    Joined:
    Aug 17, 2022
    Posts:
    3
    Soooo... my bad.

    This isn't exactly what was happening but basically, I was fetching the the Item from the DataBase and then adding it to a list of Items for a certain amount. I was adding the item to the List directly each time, instead of instantiating it again for each time. So each item in the list of was actually a reference to the same instance instead of a new instance.

    Sorry for wasting your time
     
  17. alfredbaudisch

    alfredbaudisch

    Joined:
    Dec 13, 2010
    Posts:
    45
    Actually there's an usage for that:
    • Let's say you have a ScriptableObject asset from the class FoodRecipe called "Recipe A", the recipe has a bunch of ingredients
    • Dynamically you want to create a temporary recipe which uses the same data from "Recipe A" but requires other ingredients or removes ingredients from "Recipe A".
    • This is where cloning ScriptableObjects gets its value.
    Because you still can define how RecipeA works by creating a ScriptableObject asset in the Editor, which is shared, and then on the fly re-use that data to evolve it.

    With a plain class, how are you going to define the ingredients of Recipe A in the Editor to start with?
     
  18. spiney199

    spiney199

    Joined:
    Feb 11, 2021
    Posts:
    5,856
    Well... pretty easily... just stick the plain class inside the SO and have the means of copying out as necessary.

    I never suggested that one should author your data in plain classes, but instead use encapsulation.

    And if I want to mutate SO's, I use a wrapper class for the SO instead and filter any requests for its information through the wrapper. This is a lot more flexible in the long run as well, and means you're never at risk of mutating data which should otherwise be 100% immutable.

    And so long as you're dealing with plain classes, you're letting the GC do the work for you, rather than having to play rubbish man yourself.
     
    alfredbaudisch likes this.
  19. canyon_gyh

    canyon_gyh

    Joined:
    Aug 15, 2018
    Posts:
    47
    Thanks
     
  20. brian1gramm1

    brian1gramm1

    Joined:
    Sep 15, 2017
    Posts:
    55
    My scriptableObject had a dictionary holding all the unique info for each gameobject that used it.

    Dictionary<GameObject, struct ifno> Users;

    But I learned how to Instantiate scriptable now. Thanks.