1
\$\begingroup\$

I am looking for advice on how to proceed, currently in my project all items extend from a base ScriptableObject called BaseItem, this class has a property that holds the ItemPrefab for when the item is spawned in-scene.

[CreateAssetMenu(fileName = "BaseItem", menuName = "Item/New BaseItem")]
public class BaseItem : ScriptableObject
{
    public string name;
    public string id;
    [TextArea]
    public string description;
    public Sprite icon;
    public GameObject ItemPrefab;

    public virtual void Use()
    {

    }

    public virtual string GetDescription()
    {
        return description;
    }

    public string GetName()
    {
        return name;
    }
}

I also have "EquippableItems" that as you might have guessed are equippable by the player, these weapons/armour etc have "Rune slots" on them, where a player can slot in runes to alter the behaviour/stats of the equipment via the inventory.

The problem I have is, I am altering the Prefab of the weapon, when I only want this to effect the current item I'm modifying.

I know why this is occurring because I'm editing the prefab and not an instance of it, I know sort-of what I need to do to fix it, but I can't find many examples to learn from.

I belive I need some kind of Manager that handles creating specific instances of a prefab and assigning them an ID, but I do not understand how this would work for inventory items which is where you alter the runes.

Because;

A: all items are essentially "stateful" scriptable objects while in my inventory and

B: instantiating all the items in my inventory hiding them and referencing that instead seems like a silly way to go about it.

Example Scenario:

I have two metal katana's in my inventory, I right click one of the metal katana's and click "Manage Runes" and add a +5 Fire Damage rune, I "drop" both katana's so they are now spawned into the scene on the floor, both weapons have the +5 Fire Damage rune slotted.

How do I handle this scenario so only Katana #1 is slotted with the rune? I know it needs its own instance of that Prefab, but when do I create this while it's in-game but not in scene?

thanks for reading! Apologies I feel this question is rather poorly worded but If I had the correct vocabulary for wording this then I could've done a better job of googling/github..ing.

\$\endgroup\$

2 Answers 2

1
+150
\$\begingroup\$

I'd make the prefab member protected and serialized. This prevents your other code from accidentally modifying the source prefab.

To ensure you can still spawn an instance of it when you want, you expose a public method to do this. You make the method virtual so its behaviour can change in your derived types. The base version can simply call Instantiate the way you probably would have anyway:

public class BaseItem : ScriptableObject
{
    // ...
    [SerializeField]
    protected GameObject ItemPrefab;
    
    public virtual GameObject InstantiatePrefab(
                      Vector3 position = default, 
                      Quaternion orientation = default) {

        return Instantiate(ItemPrefab, position, orientation);
    }

So instead of calling Instantiate(item.ItemPrefab), your code would ask the item to spawn itself: item.InstantiatePrefab().

Now your EquippableItem ScriptableObject can store its customizations:

public class EquippableItem : BaseItem
{
    public Rune[] runeSlots;

...and override the instantiation method to not just spawn the prefab, but also customize the instance "just in time" before returning it to its caller.

    public override GameObject InstantiatePrefab(
                      Vector3 position = default, 
                      Quaternion orientation = default) {

        var instance = base.InstantiatePrefab(position, orientation);

        var equippable = instance.GetComponent<Equippable>();

        equippable.ApplyRunes(runeSlots);

        return instance;
    }

Because we're modifying the instance we've spawned, not the source prefab, this won't have side effects on other items sharing the same prefab. And we pay nothing for equippables we haven't spawned, since we do this work at the last possible moment when spawning them into the world.

\$\endgroup\$
1
  • \$\begingroup\$ Had to change things around a bit, but for the better hopefully, it also indirectly answered another question I had too thank you! (I cant award that rep for 20 hours apparently, but I'll do it tomorrow!) \$\endgroup\$ May 18, 2021 at 14:50
1
\$\begingroup\$

I would recommend you to avoid stateful ScriptableObject's in this case. Instead, consider your ScriptableObjects as immutable item types, not as concrete items with mutable state.

Create a separate class (or multiple) to represent item instances which then reference the original ScriptableObject to access various pieces of information they are supposed to have in common with all other items of the same type. But any information which is specific to that item instance is held within the instance. The rune configuration would be an example of such an information which would be part of the concrete item.

This is called the "Flyweight Pattern".


However, if you really want to avoid creating separate classes for item types and item instances (with the latter delegating 90% of their functionality to the type anyway), then there is another option: ScriptableObjects can be cloned at runtime by using var clone = Instantiate(scriptableObject);. This gives you an independent copy of the scriptable object.

So when you give each item in the players inventory a reference to a separate clone instead of a reference to the original ScriptableObject asset, then each one has its private copy to store state in, and making changes to that copy will not affect the others.

\$\endgroup\$
2
  • \$\begingroup\$ I had read something similar to this on a blog post somewhere but I couldn't properly envision how I would go about doing it / where I'd start because I was too caught up on the spawning of the weapons, but I actually think I have a better understanding now due to your explanation, the ItemInstance class could even reference the ScriptableObject for all the static stuff name, icon, etc, then like you say ItemInstance could have a list of runes to apply for that instance alone. Thank you it's the first time that pattern has 'clicked' for me. \$\endgroup\$ May 18, 2021 at 14:56
  • \$\begingroup\$ That I believe is how Minecraft still handles it... \$\endgroup\$
    – Weckar E.
    May 18, 2021 at 18:16

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .