How do you bind data from scriptable object?

AnimationProvider is a code that works independently of ai, so being aware of the ai scriptable object will disrupt the other places it works. Therefore, we need to send this data to the animation provider somehow.

In this case, two solutions come to my mind:
1.Giving animation data to everyone required manually with getcompenent as shown in the picture
2. Injecting using the dependency injection framework
What would you do in this situation? Is there another way you suggest?

Just wrap all the data up into an object and pass that through to the scriptable object as a parameter.

I’m sorry, I didn’t quite understand, is it like in the picture?
Could you please provide an example code, thank you.

Honestly the more I read your question the less I understand it. Is this about providing the information to the scriptable object, or about… well I’m honestly not sure what else it could be about. Animation Provider sounds like a lower level class, and should just be designed like an API so that anything else can use it indiscriminately.

If anything, the Animations should depend on the AI. When you get the animation component in your AI script you’re setting up the dependency in the opposite way.

Animation should be the component looking for the AI component and getting any data it needs out of the AI controller. The AI controller should be able to do its job even with no animations present.

Sorry, my mistake, I didn’t explain it properly.
Let me try to explain it this way

We have systems that work completely independently of each other.
Attack System
Navmesh Movement System
Animation System
status system
etc…

Each of these requires its own data to work, for example
AttackData for Attack system
Movement data for Movement
animation data for animation
…

Normally, when we assign the necessary data via the inpsector, the system works and there is no problem.

Now let’s say we will use all these systems in Ai and we opened the ai scriptable object for this.

9698201--1384307--upload_2024-3-13_10-58-41.png

What would be the proper way for these independet systems to retrieve the data from the AIs scriptable object?

-Making getcompenent from the monobehivour named AiController and assigning it manually? (like the picture in the post)
-Using gameobject context in dependency injection framework?
Or is it something else?

Just pass the game object into the SO and let it GetComponent them itself.

Though it’d be better if the SO was stateless, and you just wrap these components up into an object and pass them through, so you can have this SO used in multiple places without them interfering with one another.

If I’m not mistaken, I’m asking the exact opposite of what you said. I’m having trouble explaining myself, sorry.

Let’s forget about the Scriptable object, it was a bit unnecessary, sorry.
We have one dataprovider that keeps all the data.
And there are scripts that request some of the data held in the same object.

Solution 1:

9698243--1384313--upload_2024-3-13_11-20-43.png

Solution 2:
9698243--1384316--upload_2024-3-13_11-24-17.png

9698243--1384319--upload_2024-3-13_11-24-40.png

We cannot get the data as in the picture below because the dataprovider may be different each time.

Sometimes we will provide it by just dragging it from the inspector.
Sometimes we will provide it through another script, as in the examples.

Please stop bolding your posts. It doesn’t make them more understandable. Just more annoying.

Why would the provider be different each time? That’d be a pretty useless provider.

Why can’t each individual component just GetComponent<T> for whatever it cares about? Why does it need to be any more complicated than that?

Our DataProvider will sometimes be human, sometimes animal, sometimes car.
Let’s say they all have a common Data1 (for stat system start values).
How can I get the necessary data with getcompenent without opening an interface called IProvideData1 or using a baseclass?

I thought Bold was helpful, sorry.:smile:

Your data provider sounds useless and cumbersome then. I tried the pattern out once and quickly scrapped it.

It’s generally more flexible to let each module implement their own manner of getting whatever references they care about.

And “without interfaces or a base class” is pretty much saying “without using the two most useful parts of C#”.

Seeing as you called your profile Dependency Injection, I feel like this thread is just a bit set up to you saying the answer is Dependency Injection.

Yes, it really looked like I was advertising dependency injection, you are right. But I definitely don’t have such a purpose. I don’t have enough knowledge to recommend dependency injection to anyone. I just did it because it came to my mind when I opened the account.

The dataprovider will generally be a monobehavior that holds the scriptable object.
The reason I say without interface and base class is that this time we will need to open a separate interface for each data, if I’m not mistaken.

In fact, if it is not a problem to open an interface for each data, I can assign interfaces directly to the scriptable object and solve the problem. (so I can dragdrop directly by serializing the interface)

The purpose of using this dataprovider was to ensure that independent systems are only aware of their own data.
NavmeshMovement does not need to go and know all the data in AiSO, for example, it is enough if it only knows the movement data.

Note: I’m not trying to prove anything, I’m just trying to learn how to handle this problem properly. I learned a lot of information thanks to the answers you gave in my old forum account, please do not misunderstand.

Well I don’t think the provider should do that. A purposeful component should hold the scriptable object. The provider ought to be non-specific and generalised.

When I toyed around with this idea I opted to compose each point of data:

public sealed class PlayerDataContainer : MonoBehaviour, IPlayerDataProvider
{
    #region Inspector Fields

    [SerializeReference]
    private List<PlayerDataComponentBase> _dataComponents = new();

    #endregion

    #region Component Methods

    public T GetDataComponent<T>() where T : PlayerDataComponentBase
    {
        int componentCount = _dataComponents.Count;
        for (int i = 0; i < componentCount; i++)
        {
            var component = _dataComponents[i];
            if (component is T cast)
            {
                return cast;
            }
        }

        return null;
    }

    public bool TryGetDataComponent<T>(out T component) where T : PlayerDataComponentBase
    {
        component = this.GetDataComponent<T>();
        return component != null;
    }

    #endregion
}

And everything that needed some kind of data could interface through this.

Problem was it made getting any reference annoyingly verbose. And then I realised it was overkill for the handful of objects that it would ever reference. So instead, anything that cares for these components just uses GetComponentInParent<T> as they were always going to be children objects of the relevant components.

Again, it’s more flexible to let each module (component) implement their own way of getting the required interfaces.

I understand, then getting the necessary data using the interface seems to be the best solution. Thank you very much for your time.

I wouldn’t use DI if a simple “collect all” component or scriptable object suffices.

I have a script at the root of bigger prefabs that simply provides access to commonly needed references within the prefab. I may call this PrefabReferences and it has several public fields for Components that the script either gathers automatically during Awake, or have been assigned in the Inspector by the user.

The PrefabReferences component has no code besides getting and/or verifying that none of the references are null in Awake. Otherwise it just provides access to references from any script within the prefab which can get the PrefabReferences via GetComponent or GetComponentInParent.

The same can be applied to ScriptableObjects where you could create a single ScriptableObjectReferences asset which either gathers the other ones via the Search API or the AssetDatabase (caution: both can drag down editor performance as you add more assets), or simply by assigning them manually (recommended).

For something straightforward like managing dependencies in a subtree or the project’s assets I strongly prefer manual reference assignments over “magic” that often does way more than expected, with possible side-effects like injecting the wrong instance.

So, instead of creating an interface and connecting to it, you create a ReferenceHolder (PlayerReferanceHolder, EnemyReferanceHolder…) class and connect all classes to it, did I understand correctly?

If you can show me an example so I can understand, I would be very happy, thank you.

More or less.

If the reference is assignable in the Inspector, I’ll do that. Even if the script itself gets the component out of mere convenience and to avoid human error, such as gathering all checkpoints in a “level” scene. This I’d try to do during scene save in edit mode actually, not at runtime - but regardless, I’d have a single component at the root of the level that contains all checkpoints, and is easy to find by just trying to get that component in a specific scene (I load scenes additively).

For scene-wide dynamic references I’d be more likely to have singleton such as “Enemies” which every enemy registers to in OnEnable and removes itself in OnDisable - unless Enemies script also does the spawning.

Then any other system, such as GUI or exploding projectiles can enumerate these or possibly even filter them (by type, by location, etc) by going through Enemies.Singleton.GetInRect(rect) or something.

Pure dependency injection would also be an option - albeit a bit awkward given Unity’s lack of built-in support for dragging-and-dropping references into interface type fields.

public interface IDataProvider<TData>
{
    TData Data { get; }
}

public sealed class AiController : MonoBehaviour, IDataProvider<AnimationData>
{
    [SerializeField] private AiSO aiData;

    AnimationData IDataProvider<AnimationData>.Data => aiData.AnimationData;
}

public sealed class AnimationProvider : MonoBehaviour
{
    [SerializeField] AnimationData targetAnimations;
    [SerializeField] Object targetAnimationsProvider;

    private void Reset() => targetAnimationsProvider = Component<IDataProvider<AnimationData>>() as Object;
    private void OnValidate() => UpdateAnimationDataFromProvider();
    private void Awake() => UpdateAnimationDataFromProvider();

    private void UpdateAnimationDataFromProvider()
    {
        if(targetAnimationsProvider == null)
        {
            return;
        }

        if(targetAnimationsProvider is IDataProvider<AnimationData> animationDataProvider)
        {
            targetAnimations = animationDataProvider.Data;
        }
        else
        {
            Debug.LogWarning($"{targetAnimationsProvider.GetType().Name} does not implement IDataProvider<AnimationData>.", targetAnimationsProvider);
            targetAnimationsProvider = null;
        }
    }
}

Or if the additional flexibility that using dependency injection for the data provider offers is not needed, then could implement it like this:

public sealed class AnimationProvider : MonoBehaviour
{
    [SerializeField] AnimationData targetAnimations;

    private void OnValidate() => UpdateAnimationDataFromProvider();
    private void Awake() => UpdateAnimationDataFromProvider();

    private void UpdateAnimationDataFromProvider()
    {
        if(TryGetComponent(out IDataProvider<AnimationData> animationDataProvider))
        {
            targetAnimations = animationDataProvider.Data;
        }
    }
}

This could also be flipped around:

foreach(var animationDataUser in GetComponents<IDataUser<AnimationData>>())
{
    animationDataUser.SetData(aiData.AnimationData);
}

But then script execution order could become a source of bugs.

Some time ago I made this SerializableInterface wrapper which allows you to drag and drop gameobjects / components / scriptable objects as long as that component / SO implements the given interface. When you drag a gameobject and there are multiple components which implement that interface, you get a selection dropdown. Though you can always directly drag a component directly (by grabbing the component header from the inspector. Usually requires two inspector windows or a popped out property to actually assign it).

ScriptableObjects can be used for really powerful constructs. If you haven’t seen it yet, I recommend watching the ScriptableObject Unite talk from 2017 by Ryan Hipple. They went so far to break down the data to single variables being stored as ScriptableObjects which makes it possible to setup all references in the inspector. As he mentions in the talk, the inspector in Unity kinda acts like a dependency injector.

Of course using ScriptableObject assets to store and handle runtime variables, not just static configuration data means you have to handle the initialization / resetting yourself or make sure that the variable itself isn’t actually serialized. So the concept of a “FloatVariable” scriptable object means could you have generic getter / setter and the only serialized data may just be the default value. You can get really fancy with that approach. Though building the basis for such a system would take some time (or you find a third party system that’s ready-to-use).