Am I using ScriptableObject properly for my inventory/items?

I’m relatively new to gamedev, Unity, and ScriptableObjects, and I’m trying to figure out if my current inventory implementation, using ScriptableObjects, is the “correct” approach, or if I’m missing something and should be doing it a better way.

TLDR version: I have two concerns, one is that I want some items to have randomized attributes and I’m not sure that creating a bunch of SO assets at runtime, during gameplay, is ok. Second concern is more nuanced, but I have some SO assets with a List, and objects are added and removed from this list during gameplay - will I have issues saving/serializing the state of this List?

My game really only has two types of equipment or items that I’m concerned about here: weapons and “mods” that can be equipped to slots in the weapons. They are each currently implemented as ScriptableObjects.

For example, my Weapon SO class (some irrelevant code removed to keep it shorter):

[CreateAssetMenu]
public class Weapon : ScriptableObject
{
    public Sprite weaponSprite;
    public GameObject projectile;
       
    public string weaponName;
    [Space]
    public float damage;
    public float range;
    public int energyCost;
    public float accuracy;
    public float projectileForce;
    public float firingCooldown;

    public float numberOfProjectiles;
    [Space]
    public List<WeaponMod> equippedMods;   

    public void Equip(WeaponInventoryManager wim)
    {
        // code to equip weapon
        // code to ensure any mods of this weapon are also re-equipped from the List of equippedMods
    }
}

And here is the code for my weapon mod SO class (some irrelevant code removed to keep it shorter):

[CreateAssetMenu]
public class WeaponMod : ScriptableObject
{
    public Sprite modSprite;

    public string modName;

    public float damageModifier;
    public float rangeModifier;
    public float energyCostModifier;
    public float accuracyModifier;
    public float forceModifier;
    public float cooldownModifier;
    public int numProjectilesModifier;

    public void Equip(ModInventoryManager mim)
    {
        // tell the equipped Weapon that this mod is equipped
        mim.playerStats.equippedWeapon.equippedMods.Add(this);

        // update the player's modifier stats based on this mod's values
    }

    // this version of Equip is for when a weapon with mods is re-equipped
    public void EquipWithWeapon(WeaponInventoryManager wim)
    {
        // update the player's modifier stats based on this mod's values
    }

    public void UnEquip(ModInventoryManager mim)
    {
        // tell the equipped Weapon that this mod is unequipped
        mim.playerStats.equippedWeapon.equippedMods.Remove(this);

        // update the player's modifier stats based on this mod's values
    }

    // this version of UnEquip is called when the weapon is unequipped
    public void UnEquipWithWeapon(WeaponInventoryManager wim)
    {
        // update the player's modifier stats based on this mod's values
    }
}

There will only ever be 5 or 6 weapons in the game. The only thing about the weapons that changes is the List of equippedMods - the player can add or remove mods to each weapon during gameplay.

There will be a few unique weapon mods, but many of them will be randomly generated during gameplay to have different attribute values.

My code is working with the two weapons and six mods i’ve created manually, but I don’t want to run into issues going forward with this approach, so regarding my two concerns:

  1. the weapon mods will be kinda like randomized loot. Is there any issue with the SO implementation in this situation? Specifically, I would anticipate needing to create and destroy a bunch of SO assets as random mods are generated and as the player deletes them while they play. It seems a little odd to be creating and destroying assets during gameplay - is this an issue and/or is there a better approach?

  2. when testing my game, I add and remove mods from the weapons I have, and each weapon needs its list of mods so that it keeps track of its own mods when that weapon is swapped out for another one. This way, when the weapon is re-equipped, it knows which mods it has on it. Am I going to have any issues with persistence or serialization (I haven’t implemented a save game feature yet) with respect to that List? Between different runs of my game while testing, this list persists in the SO asset for the weapon (expected), but I’m not sure if this will also be the case when it comes to serialization and saving the game (obviously, when the player loads back into their game their weapons should have the mods on them still).

If either of those concerns doesn’t make any sense, hopefully I can clarify (or that means it isn’t something I should be concerned about!). I appreciate any help I can get!

I don’t know about others, maybe there are some uses for SO I haven’t been able to explore so far, but I’m using SO’s strictly as programmable serialization objects, and I find their implementation incredibly close to what is essentially a product of a very simple train of thoughts:

A: I need some hard data to provide a variety in my game, or to describe something domain-specific, or to give something tweakable to non-programmers.
B: Well you could just make an XML or JSON file, then parse that file in during the runtime.
A: But I’d also need to make an integration system for the editors. Wait, why don’t we just use Unity’s serialization system, it’s all over the place anyway?
B: Well it would have to be MonoBehaviours, every little bit of data you want to deserialize would be MB.
A: But that’s awesome, I could have also add some scripts, make my data initialize or bind dynamically.
B: It’s also an overkill, and it would also needlessly react to messages. You’d also have to populate your GameObjects with such objects for them to stay persistent in your runtime.
A: All valid issues. So maybe a lighter system?
B: Exactly, and let’s make it easier to get started with, and also integrated with the editor.

And that’s how we got ScriptableObjects.

Regarding (1), no there isn’t anything considering randomization. It’s your job. You’re supposed to discern between the “authoring time” and the “run time” of your application/game. What you want to do is aimed at “run time”. What ScriptableObjects are for is mainly the “authoring time” that can survive the barrier and swim over into the “run time” as fully-fledged objects that are also C# scriptable and can react to selected system events. But once there, it’s your job to do something creative with that data. There are no ‘assets’ once you get to this points, in your final build. You only have assets in the editor, or at least in the way they are presented to you.

In other words, you’re supposed to instantiate your own objects and collections based on data provided by ScriptableObjects, and manage these objects/collections of your own. ScriptableObjects are just a feature that is a replacement for having to implement your own data management system, as described by the ‘train of thoughts’ above.

Actually my first game ever was made in Unity 4.3.x and I think I didn’t know about ScriptableObjects or they didn’t exist yet, and my game was driven by a custom-built XML that lived in Resources. It was an iPad game and some furry creatures would appear off screen in random orders and sequences, a player would tap them and use power-ups to build up combos. Their spawning rate, randomization, their patterns of behavior, loot chances, EVERYTHING macroscopic about them lived in that XML, which was designed like a gameplay director of sorts, and made to progress with time, from one temporal scene to another.

With ScriptableObjects I’d make this differently today, and would skip making my own data parser, and also made editors or in-editor visualizations to make the level-editing much more friendly. But I wouldn’t spawn more ScriptableObjects in the scene, I’d still spawn monsters and other game objects normally, BASED on this data.

Thank you for the detailed post. So it sounds like my first concern was valid - I’m not really making proper use of SOs in my current implementation. If I want mods randomized at runtime I should probably not use SOs.

That might help answer my second concern regarding weapons, too. I think the SO implementation is fine for that, but instead of having a dynamic list in the SO I might get a reference to another object that holds the list instead.

Thanks again for the help!

I think weapons and modes should be classes, not SO. The weaponModSO should hold the min and max values, e.g. minFireRate and maxFireRate. Then have a class that reads out the data from the modSO and creates a mod class at runtime. Same for weapons.

Saving things would be really easy too. By making the runtime weapons and mods plain classes that inherit neither from ScriptableObject nor Monobehvaiour, you can keep track of them in a list. E.g. each weapon holds a list with current mods, another (could be static) class has a list of all active weapons. Just serialize that last class with json and all your data will be inside.

I appreciate the suggestions! I think I’ll follow the approach you indicated.