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?
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();
}
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.
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.
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.
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
}
/*...*/
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.
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.
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
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.
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.