How to manage different types of item?

I remember you pointed this in another thread and I don’t understand that. To save a data, you parse it as json. And references to scriptable object takes the ID of the scriptable object which is static, isn’t it? If not, in that case, I understand the need of an static ID database but don’t understand why Unity implemented the scriptable objects as a way to store persistante data.

I didn’t know that and I could not imagine that. When I look for a youtube video to make an inventory system, the only thing I see is a basic inventory with enums as item type using scriptable object directly without an intermediate class. I prefer videos than article so many they are good ones I missed but on youtube, aside git-amend who makes more advance tutorials.

I totally agree, but if you lose your patience after 3 questions, like I felt you did with your first answer, it is hard for me to ask you more and understand. I understood the general concept and it seems to be very suitable for the game I am making, but I am struggling with all it requires to do, while with my simple view it seems more complicated than it should be. But as you said, I will learn.

They’re not. Their main purpose is to store immutable (non changing) data. Changes to scriptable object data does persist between editor playmode sessions, but they do not persist between a build project’s sessions. In a build, they will reset once they leave memory. A built project’s assets are immutable. You are only modifying the in-memory instance.

The persistence of changes in the editor during playmode is just an artifact of them being assets. This behaviour is true of all assets. And their use as a way to communicate data across scenes is more or less a… quirk? Something we can do because we can customise their fuctionality.

And because we cannot serialise references to any kind of Unity object out to disk, we always need to implement some kind of system to either turn object references into some kind of identifier, or to just keep our data completely free of Unity object references in the first place.

My current approach uses loose or indirect references via a locator implementation (composed through an interface) said objects provide in place of all Unity object references, and some non-Unity object references as well.

And if I came across a bit harshly earlier, I apologise.

I have forgotten about that.

I found this article on the service locator and it seems that many videos talk about that. I did not know about this so I have to dive into that.

But, how do you know that? I mean, except knowing all the different patterns, how are we supposed to know which one to use for this or that system?

No problem

Hard to say, honestly. It was, to me, a logical progression in designing the right system that fit my needs and desire to keep improving. The first time I did a save system for an inventory, I brute-forced it by converting all the non-saveable runtime data into data that was good to serialise and back again. It worked, but was cumbersome.

Then I toyed with serialisers that would let me process the data before it was written to disk, such as the Odin Serialisers external reference resolvers, to swap out a reference with an ID and back again. It worked, but was limiting.

And this indirect-reference system came after that, requiring no serialisation processing or conversion, and also works on non-Unity objects as well.

In a really rough sense it looks like this:

public interface IObjectLocator<out T> where T : class, ILocatableObject<T>
{
    T LocateObject();
}

public interface ILocatableObject<out T> where T : class, ILocatableObject<T>
{
    IObjectLocator<T> GetObjectLocator();
}

[System.Serializable]
public sealed class ObjectLocatorReference<T> where T : class, ILocatableObject<T>
{
    [SerializeReference]
    private IObjectLocator<T> _locator = null;
   
    [System.NonSerialized]
    private T _cachedAsset;
   
    public ObjectLocatorReference() { }
   
    public ObjectLocatorReference(T obj)
    {
        _locator = obj.GetObjectLocator();
        _cachedAsset = obj;
    }
   
    public T GetObject()
    {
        CacheObject();
        return _cachedAsset;
    }
   
    public void CacheObject()
    {
        if (HasCachedObject() == true)
        {
            return;
        }
       
        _cachedAsset = _locator.LocateObject();
    }
   
    public bool HasCachedObject()
    {
        if (_cachedAsset is UnityEngine.Object unityObject)
        {
            return unityObject != null;
        }
        else
        {
            return _cachedAsset != null;
        }
    }
}

[System.Serializable]
public class ObjectLocatorUUIDAsset<T> : IObjectLocator<T> where T : UnityEngine.Object, IHasUUID, ILocatableObject<T>
{
    [SerializeField]
    private UUID _assetUUID;
   
    public ObjectLocatorUUIDAsset(T asset)
    {
        _assetUUID = asset.UUID;
    }
   
    public T LocateObject()
    {
        if (_assetUUID == UUID.Zero)
        {
            // throw warning/error
            return null;
        }
       
        return AssetLookup.LookupAsset<T>(_assetUUID);
    }
}

[CreateAssetMenu]
public class Item : ScriptableObject, IHasUUID, ILocatableObject<Item>
{
    [SerializeField]
    private UUID _uuid;
   
    public UUID UUID => _uuid;
   
    public GetObjectLocator() => new ObjectLocatorUUIDAsset(this);
}

Yes, lots of generics. And explaining how AssetLookup works would require multiple tutorials.

But because the locator is done via an interface, I can have any number of locator implementation. Right now there’s about six… or so. No doubt going to be more. And I’ve seen a few other folks who have come up with a system like this as well.

I suppose a lot of my initial inspiration/knowledge came from the Gang of Four’s Programming Patterns. They used to have a site but I can’t find it anymore…

Is this something you wrote?

That is very rich in information. I will try to understand how it works or at least the reasoning.

In a first attempt, I did something similar I think by creating a save class for all the savable data but it was a mess and abandoned this idea.

Yes it’s a slimmed down summary of a system I developed in a current project. I need to hot look-up different kinds of objects, and in some cases it could be a Unity object OR a pure data object. Allowing objects to provide their own implementation on how they should be retrieved ended up being the most flexible option.

1 Like

I just would like to go back to the WeaponType class. It is a scriptable object which stores the data of the type of weapon such as the name of the type or an icon. But in any case it is an ItemData right? You really make the difference between an item which can be whatever we want like a sword and attach to this sword a WeaponData (properties + type) to this item, right?

And in the PickUp class, there are 2 fields Item and amount and using a custom inspector you can change the Item as a Weapon class or Wand class or Shield class etc.

I mean it was just an example in how you would architect a system to design different types of weapons without having to make a different class for bow, sword, crossbow, etc. It really is just an example in being data-driven, as opposed to having to hard-code everything. Again it’s intent was to be shared/reusable data that would be used across multiple different items, and not be part of the per-instance data.

I’m mostly just trying get across ways to design more flexible systems, rather than something to copy by wrote.

(The same thing can be done with [SerializeReference] + plain C# classes, which is often what I opt for to ensure the data is innately serialisable as well)

1 Like

I searched for that but to be honest, I did not really understand the purpose of it. I will look more into this to see if I find something interesting.

EDIT: I just watch this video which links a unity package to use the SerializeReference to show the correct class.
Now I understand the purpose of having a WeaponType and a WeaponProperties since you can directly use the desired item.