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.
public class TestObject : ScriptableObject {
public int counter;
public void Init() {
counter = 0;
}
}
public class TestClass : MonoBehaviour {
public TestObject to1;
public TestObject to2;
void Awake() {
to1 = CreateInstance<TestObject>();
to1.Init();
to2 = COPY(to1);
to1.counter = 10;
Debug.Log(to1.counter); //Expected value: 10
Debug.Log(to2.counter); //Expected value: 0
}
}
I actually found the solution to this.
If you use 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.
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)
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:
string s = SCRIPTABLEOBJECT.name;
NEWSCRIPTABLEOBJECT = Instantiate(SCRIPTABLEOBJECT);
NEWSCRIPTABLEOBJECT.name = s;
Here’s a handy extension script you can use to clone your scriptable objects.
public static class ScriptableObjectExtension
{
/// <summary>
/// Creates and returns a clone of any given scriptable object.
/// </summary>
public static T Clone<T>(this T scriptableObject) where T : ScriptableObject
{
if (scriptableObject == null)
{
Debug.LogError($"ScriptableObject was null. Returning default {typeof(T)} object.");
return (T)ScriptableObject.CreateInstance(typeof(T));
}
T instance = Object.Instantiate(scriptableObject);
instance.name = scriptableObject.name; // remove (Clone) from name
return instance;
}
}
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.
here is an example of my code, which should re-create the problem.
namespace Example
{
public class Item : ScriptableObject
{
[SerializeField] private string iD;
[SerializeField] new private string name;
public string ID => iD;
public Item Clone()
{
return Instantiate(this);
}
}
public class DataBase
{
private static Item[] items;
public static Item GetItem(string iD)
{
foreach (Item item in items)
{
if (item.ID == iD)
{
return item.Clone();
}
}
return null;
}
}
}
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.
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.
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.
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.
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?
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.