I’m making a game which uses ScriptableObjects to store some upgrade data, like price, level, name, etc. So basically I have an “Upgrade” ScriptableObject with “CreateAssetMenu”, so I can right-click to create a new Upgrade.
The thing is that I don’t want the values of the assets to change when testing the game, so I created instances of each Upgrade with something like:
public Upgrade[] upgrades;
// I populate the array by dragging the assets on the inspector.
private void InstantiateSO(){
for (int i = 0; i < upgrades.Length; i++)
upgrades[i] = Instantiate(upgrades[i]);
}
The problem is that I have other places that need to store an upgrade, but they don’t seem to store the same instance of the ScriptableObject. If I change the level of upgrade “Double Jump” to 2, for example, I need it to be changed for all instances of this upgrade, but it seems to affect only one. Example:
private void SetUpgradeLevel(int index, int level){
upgrades[index].level = level;
}
In this example, only the instance that is inside the array “upgrades” will have the level changed, and any other instance of the same upgrade will remain untouched.
Is there a way to do this? Like creating a global instance of each upgrade or something?
Hiya. This is my first post and to be honest I’ve never used ScriptableObjects in depth before, so this might be COMPLETELY wrong. But…
From reading the documentation (Unity - Manual: ScriptableObject) it seems to me that where you are going wrong is that you are trying to alter the contents of the scriptable object instances programatically, when that is in fact impossible (or at least, not its intended design); scriptable objects are meant to store static data. Thus, they can not be altered in code like it seems you are doing (well they can, according to the documentation, so long as you’re in the editor, but I don’t see why you would want to). For example, you are doing the following:
private void SetUpgradeLevel(int index, int level){
upgrades[index].level = level;
}
The better way to do it would simply be to make a scriptable object for each upgrade, assign them into your array (as you do currently), but instead of altering them programatically, simply alter the values of each upgrade in the editor. I think that’s why your changes aren’t appearing globally … you’re editing the instances in script instead of the actual asset itself.
Not sure if I have understood correctly But hope that maybe possibly helps.
Yes, I’m making changes to the instances because if I make changes to the actual asset I have to change all the values back to default every time I test a feature, as the values would save.
I want a way to make changes to the instance of a scriptable object and have it affect all instances of that scriptable object. Is that possible?
But the point of ScriptableObjects is that you only have to change one value and that that one value can then be used accross a number of different scripts:
Unless I am misunderstanding you which I think might possibly be the case …
I don’t think so … SOs work as static data containers. But on the upside that means you can just edit the SO asset itself and its change will be reflected everywhere, which is what I think you want.
Everything said already is correct. However, I have used scriptable objects as templates before to good effect. But this involves instantiating them at runtime and editing the instance values. I would then serialize the changed values to file and when loading I would again use the scriptable object as a base and deserialize my saved values into each instance.
If you don’t want separate instances and want them all to reference the same object/data, simply don’t instantiate them. Holding the reference is enough.
If you only want a specific variable to be shared I would suggest holding that particular variable in a separate scriptable object and referencing that in the class you are creating instances of.
So in your case maybe something like:
public class Upgrades : ScriptableObject
{
public int level;
public GlobalVariableExample sharedVariable;
}
public class GlobalVariableExample : ScriptableObject
{
public float sharedVariable;
}
This way any upgrade you create can be instanced and have its level changed on a per instance basis, whilst the shared variable value will be shared across all instances.
I got it working the way I wanted to. Let me explain how I did it:
I have this class:
public class Upgrade : ScriptableObject
{
public string title = "New Upgrade";
public int level = 0;
public PreRequisiteUpgrade[] preRequisites;
public double price = 0;
public bool owned = false;
}
[System.Serializable]
public struct PreRequisiteUpgrade
{
public Upgrade upgrade;
public int minimumLevel;
}
And then I have in my main script:
public class GameManager : MonoBehaviour
{
public Upgrade[] upgrades;
private void Awake()
{
SetUpgradeIDs();
InstantiateScriptableObjects();
}
public Upgrade FindUpgrade(int id)
{
for (int i = 0; i < upgrades.Length; i++)
{
Upgrade currentUpgrade = upgrades[i];
if (currentUpgrade.id == id)
return currentUpgrade;
}
return null;
}
private void InstantiateScriptableObjects()
{
for (int i = 0; i < upgrades.Length; i++)
upgrades[i] = Instantiate(upgrades[i]);
for (int i = 0; i < upgrades.Length; i++)
for (int j = 0; j < upgrades[i].preRequisites.Length; j++)
upgrades[i].preRequisites[j].upgrade = FindUpgrade(upgrades[i].preRequisites[j].upgrade.id);
}
private void SetUpgradeIDs()
{
for (int i = 0; i < upgrades.Length; i++)
upgrades[i].id = i;
}
}
This way, I replace all Upgrades in my upgrades array with instances of each one, and when the game is started, every reference to those upgrades are replaced with the same instance created (contained in the array). It’s working like I wanted: for example, if I change the level of upgrade A in the upgrades array and this upgrade is a pre-requisite of upgrade B, the level gets changed both on the array and the reference to upgrade A contained in upgrade B.