Question about Scriptable Objects

Hi,

I’m using Scripable Objects in my game for the abilities and their stats.
Now i’m working on a talent screen in the Main Menu, to for example increase damage etc.
To carry this into the game scene, would Scriptable objects be a decent way or?
If i for example; put a point into FireBall, which increases the base damage by 10,
the button for FireBall calls a method, changing the FireBall damage directly on the Scriptable object.
Or would it be better to use the/a saving system for this, and load the stats back up in the game scene?

For what you’re doing, there’s not that much difference.

Basically, you’re storing simple information and retrieving/using/manipulating when needed.

1 Like

This is a tricky bit, because ScriptableObjects behave differently in the PIE (Play In Editor) than they do in a built game.

In the PIE, any changes you make to a ScriptableObject’s fields are persistent. This means that you could have something like this:

[SerializeField] int score;

public void AddToScore(int value)
{
     score+=value;
}

and the changes would be serialized for the next time you run the game… so if you added 100 to the score while playing the game, when you play it again, the score will remain the same.

This behaviour is deceptive, because in a built game, the ScriptableObject’s fields are frozen in the asset bundle. Each time you run the game the ScriptableObject is reloaded from the asset bundle, so things would revert to the values that were serialized when the game is built.

This means that we should be treating ScriptableObjects as immutable objects during the game.

(1/2)

1 Like

I think the way that I would approach this is to save the number of points allocated to a particular ability within the talent tree itself. Then the Ability can look up the number of points and adjust the damage on the fly… So in Fireball, I might have something like this:

[SerializeField] float[] damages;

public float GetDamage(GameObject user)
{
     if(user.TryGetComponent(out TalentTree tree)
     {
          return damages[tree.GetRank(this)];
     }
     return damages[0];
}

You could then save the ranks in the saving system within the talent tree.

1 Like

Hi Brian,

Thanks for the fast reply! I’m glad i asked before i started implementing this.
Your approach actually seems alot nicer.

Not that i would do this, since it seems kinda inefficient to have to run this each time the game starts,
but just to check if i understand things right for future reference.
The Scriptable Objects in a built game reset on game close, and not on scene change?
So if i theoretically were to let the talent tree process points spent from a save file into the Scriptable Objects, for as long as the game runs those stats remain changed?

That is correct.

I would still consider this to be a questionable approach. If you leave the state of the data in a talent tree, then you could have the enemy cast fireball too, at it’s own skill level…

Imagine you are going up against a sorcerer who also casts Fireball… You have ranked Fireball to level 4, so the Sorcerer will cast a level 4 fireball on you…

1 Like

Oh yes i agree 100% your solution is better,
i was just checking to be sure as to how SO function in a built game,
so i don’t use them in a wrong way.
It is kinda confusing it works different in a built game than in the Unity Editor.

Anyways, thanks for the replies once more, my questions are answered!
Will implement it as you said.

I would go so far as to say infuriating. I did not know this behaviour when I first learned about ScriptableObjects, and planned a whole saving system around them (this was long before the RPG course)… weeks of work tanked in a single Build.

Bonus tip: You may wish to consider bonus artifacts… you might have a staff that grants +2 to Fireball… The trouble with this is you’re having to check lots of areas for bonuses, and that’s not terribly convenient and could easily create cross dependencies… So you might want to consider an interface like the IModiiferProvider

public interface IAbilityLevelProvider
{
    IEnumerable<int> GetAbilityModifer(InventoryItem ability);
}

Then your Talent Tree would be an IAbilityLevelProvider, as well as your StatsEquipableItem, and your StatsEquipment…
In StatsEquipableItem you just need to add something like:

[System.Serializable]
class Boost
{
     public InventoryItem item;
     public int modifier;
}

[SerializeField] Boost[] boosts;
Dictionary<InventoryItem, int> boostDict;

void PopulateBoostDict()
{
    if(boostDict!=null) return;
    boostDict = new Dictionary<InventoryItem, int>();
    foreach(Boost boost in boosts)
    {
         boostDict[boost.item] = modifier;
    }
}

public IEnumerable<int> GetAbilityModifier(InventoryItem ability)
{
    PopulateBoostDict();
    if(boostDict.ContainsKey(ability) yield return boostDict[ability];
}

Your StatsEquipment just needs to find any IAbilityLevelProvider equipped and yield return their values, and of course your talent tree would yield return the current value as well.

1 Like

Oh damn, yeah… In your case having cost so much time, i would definetly label it under infuriating indeed.
To me too it seemed like a decent way to store data/transfer some variables into the next scene,
glad i found out through you today that it’s not…

Hmm, the artifacts might be a good solution for the whole TalentTree system i had in mind,
i will get to testing this! thanks :slight_smile:

This topic was automatically closed 24 hours after the last reply. New replies are no longer allowed.

Privacy & Terms