How to assign a script to a ScriptableObject in the inspector?

Hello yet again, I am still working on my card project and have run into yet another wall. In order to get my cards to actually perform actions, I need to be able to assign polymorphs of a script called BaseEffect to my scriptable objects. Yet apparently I am unable to do so, I cannot assign a MonoBehavoir of any kind to my scriptableobjects. So yet again, I request your help. Are scriptable objects unable to be assigned scripts in the inspector? Is there a work around here?

Thank you!

1 Like

@CannedSmeef

Show some code?

You can’t. ScriptableObject is not GameObject and MonoBehaviour script might be assigned to game objects only.

Here you go! It’s pretty much the same as last time.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

// The absolute bare bones of every card.
// Does nothing other than serve as the core framework.
// I should probably add "ghost" variables and functions.

[CreateAssetMenu(fileName = "New Card", menuName = "Cards/New Card")]
public class Card : ScriptableObject
{
    public string title;
    public string description;

    public Sprite artwork;

    public int cost;
    private int count; // -1 indicates the card has not been discovered, 0 is not in the deck, 1, 2, 3... are how many to add to the player's deck
    public int defaultCount; // when the card is added to the deck, how many should be added at the start (before possible upgrades)

    // these variables allow for cards to have effects inserted via the inspector
    public BaseEffect effectA; // this is what I am trying to assign to, but the inspector won't let me

    public void Awake()
    {
        count = -1; // make the card unavailable by default
    }

    public void Discover()
    {
        count = 0; // card can now be viewed
    }

    public void AddToDeck()
    {
        count = defaultCount; // the card(s) will now be added to the deck every battle
    }

    public int GetCount()
    {
        return count;
    }

    public void SetCount(int num)
    {
        count = num;
        if (count < 0)
            count = 0;
    }

    public string GetTitle()
    {
        return title;
    }

    public void ReportStatus()
    {
        Debug.Log(title + "'s count is currently " + count.ToString());
    }
}



using System.Collections;
using System.Collections.Generic;
using UnityEngine;
//using System;
//using System.Runtime.Serialization;

//[Serializable]
public abstract class BaseEffect : MonoBehaviour
{
    public Core target;  // the stats of the object in question
    public int power;  // the scaling strength of the card, higher is better (usually)
    public abstract void Play();
}

Did you find a way around this ?

There’s no workaround. The original post is complete gibberish regards this:

That’s meaningless which is why there is no followup response.

If you have a MonoBehaviour instance (in scene or in prefab) and you want to reference a ScriptableObject instance, it is no different than anything else: make a public field and drag it in.

1 Like

Well you can’t assign scene objects to fields in scriptable objects (I know you know this of course).

However nowadays you have SerializeReference, which does let you serialise polymorphic references in Unity native. However Unity doesn’t draw these fields, so you either need to implements your own drawers or use addons.

1 Like

The work around for not being able to drag scene objects into fields in assets is to use a unique identifier to locate the object at runtime. The simplest built-in solution for this is GameObject.FindWithTag.

With some clever editor code it’s possible to use such a system behind the scenes to enable dragging and dropping references across scenes and assets in the Inspector:

I had a different solution to this conundrum, when you really want to connect an SO with something in a scene, something I called a ‘game object link’. As, while you can’t serialise references to scene objects in an SO, you can of course hold non serialised references.

It consists of two small parts. An SO, the game object link, and the game object link container:

//game object link SO
    using UnityEngine;
    using Sirenix.OdinInspector;

    /// <summary>
    /// Scriptable Object that provides a link for other scriptable objects to game objects at the scene level.
    /// </summary>
    [CreateAssetMenu(menuName = "Game Logic/Game Object Link")]
    public class GameObjectLink : ScriptableObject
    {
        [ShowInInspector, ReadOnly]
        private GameObject linkedGameObject;

        public GameObject LinkedGameObject { get { return linkedGameObject; } }

        public bool TryGetLinkedObject(out GameObject linkedObject)
        {
            linkedObject = linkedGameObject;
            return linkedObject != null;
        }

        public void SetLinkedGameObject(GameObject linkedObject)
        {
            if (linkedGameObject == null)
            {
                linkedGameObject = linkedObject;
            }
        }
    }

//game object link container
    using UnityEngine;
    using Sirenix.OdinInspector;

    /// <summary>
    /// Monobehaviour component to be put on game objects that need to be linked.
    /// </summary>
    public class GameObjectLinkContainer : MonoBehaviour
    {
        [BoxGroup("Game Object Link:")]
        [SerializeField, AssetSelector, HideLabel]
        private GameObjectLink objectLink;

        private void Awake()
        {
            if (objectLink)
            {
                objectLink.SetLinkedGameObject(this.gameObject);
            }
        }
    }

Kept the Odin attributes because I’m lazy. But it’s pretty simple and works a treat. Mostly used it in a node-based dialogue system (which was all SO’s) so I could hook them up with scene level stuff.

Wrote this ages ago, so if I were to write it now I’d probably do it differently. Guess I’ll do that now.

2 Likes

Running into the same wall myself, I’ve found that you can indeed attach a script to a ScriptableObject using the MonoScript type. The drawback is that you cannot strongly type those scripts in your ScriptableObject.

In this example, I attached the following AttackBehaviour to a ScriptableObject inside unity editor, then called it via the bottom Cast method.

// Exemple ScriptableObject and custom script stored in ScriptableObject.behaviour
[CreateAssetMenu(fileName = "New Skill", menuName = "Skill")]
public class ScriptableSkill : ScriptableObject
{
    public MonoScript behaviour;
}

public class SkillBehaviour {
    public void Cast(CharacterController source, CharacterController target){
           // do your things in inherited classes
    }
}

public class AttackBehaviour: SkillBehaviour {
    public void Cast(CharacterController source, CharacterController target){
           target.takeDamage(source.getPhysicalDamage());
    }
}

Then you can call your behaviour like so :

// Exemple MonoScript call
/*...*/
public void Cast(ScriptableSkill skill){
    SkillBehaviour sb = Activator.CreateInstance(behaviour.GetClass()) as SkillBehaviour;
    sb.Cast(this, this); // This call will indeed call the stored AttackBehaviour
}
/*...*/

Monoscript is only available in the editor, so this can’t be used at runtime.

2 Likes

Nice, I like your solution. Curious what you wrote differently.

As in what I would differently today?

Honestly haven’t use that aforementioned system for a while, and haven’t had to engineer a better solution yet. Though the need will come up soon for my current project.

[SerializeReference] solves so many problems today which is part of the reason.

1 Like

Indeed it does! I just changed from using scriptable objects for my use case to use SerializeReference instead, much, much better! I was having to create one scriptable object instance per class, plus all the gameobject links, it was cluttering my project.

SerializeReference is so much better for this. Shame that it isn’t exactly functional out of the box. I mean it is technically speaking, if you’re willing to do some editor coding for custom drawers and stuff, to be able to actually use it in the inspector on its most powerful way (creates instances of your references on a list, say). So I consider this a half baked solution until we get basic editor support. For now, I consider that it basically requires Odin Inspector to be useful.

Though…I suspect Odin Inspector has had its own way to serialize references for ages, way before Unity implemented SerializeReference, so I might as well learn how to do it using Odin only since I have to use it anyways.

Yeah the lack of inspector support is a real bummer for what is a really powerful tool. I wrote in a post here probably about the simplest way to incorporate it with a custom inspector: building a large ability system with Scriptable Object (SO) - Unity Engine - Unity Discussions

Odin’s out of the box support is a godsend. I use it so much I made my own extension for Odin to make it even better: GitHub - spiney199/Subclass-Selector

(Need to push an update for it)

1 Like

I’m getting the error InvalidOperationException: managedReferenceValue is only available on fields with the [SerializeReference] attribute when trying to use your custom editor with a class that looks like this

public abstract class MyAbstractClass: MonoBehaviour {
[SerializeReference] public List baseReferences;

}

Yes, I checked that I got the name right.

EDIT

This package solved it for me. Not even 30KB, MIT license. Recommend it.

1 Like

The way it works is different for collections than for fields. My example would only work on a non-collection field.

Collections are a serialized property with child properties. You can’t just assign a managed value to the collection itself. You need to get the appropriate child property and assign to that.