Unity field containing ScriptableObject not serializing - c#

I'm having a problem getting a field to serialize properly.
It links to a custom type which extends ScriptableObject. A custom editor is creating the instance of this object, and it's not being saved as an asset file. It's my understanding at the moment that you can do this - Unity should be saving this data into the scene instead of the project automatically.
First up, the MonoBehaviour class. This I've set up to hold an instance of Bar and a string just to double check the serialization is working at all. I use fields with exposing properties, so I've done it here as well as I know reflection is involved.
public class Foo : MonoBehaviour
{
[SerializeField]
private Bar bar;
[SerializeField]
private string check;
public Bar Bar { get { return this.bar; } set { this.bar = value; } }
}
Next, the ScriptableObject based Bar class. This is the class I want to instance and link, but not save as an asset file.
[Serializable]
public class Bar : ScriptableObject
{
[SerializeField]
private string data;
}
Finally, a CustomEditor which allows me to create and link an instance of Bar:
[CustomEditor(typeof(Foo))]
public class StreamableResourceTestEditor : Editor
{
public override void OnInspectorGUI()
{
base.OnInspectorGUI();
if (GUILayout.Button("Link unsaved Bar asset"))
{
Bar bar = ScriptableObject.CreateInstance<Bar>();
bar.name = "Unsaved asset";
Foo foo = (Foo)this.target;
foo.Bar = bar;
EditorUtility.SetDirty(foo);
}
}
}
Before running, having entered data and pressed the button:
During running:
After running:
The string serializes properly.
However, the ScriptableObject fails to be serialized into the runtime data. Not only that, after running the project the instance is still linked, but has lost it's type!
Does anyone know what the problem is with this?
Update:
Just tested this without extending Bar from ScriptableObject, and creating it as a normal C# instance instead of with ScriptableObject.CreateInstance<Bar> and it all works as expected.
Whatever the problem is, it relates to extending from ScriptableObject.

Related

Serialize a custom class in a custom inspector in Unity

I need to serialize a class in a custom inspector (using visually the Editor) like as doing in a Monobehaviour script like this:
[System.Serializable]
public class CustomClass
{
int myInt
}
public class OtherClass : MonoBehaviour
{
[SerializeField] CustomClass customClass;
}
which gives this result:
result wanted and given using the code above, where DamageEffect = CustomClass and Damage = myInt
In my custom editor, I'd like something like this:
[CustomEditor(typeof(CardObject))]
class AnotherClassEditor : Editor
{
public override void OnInspectorGUI()
{
[SerializeField] CustomClass customclass;
}
}
but, as expected, it points out an error.
I also tried with EditorGUILayout.ObjectField() but I haven't been able to, I'm not so experienced so please try to keep the answers simple.
Actually, I need this serialization to happen only when an enum is equal to a certain value, the overall script is something like this:
using System.Collections.Generic;
using UnityEngine;
#if UNITY_EDITOR
using UnityEditor;
#endif
[CreateAssetMenu(fileName = "Card", menuName = "CardObject")]
public class CardObject : ScriptableObject
{
public List<CardEffectType> effectsTypes;
//other...
[HideInInspector] public List<CardEffect> effects;
//other...
}
#if UNITY_EDITOR
[CustomEditor(typeof(CardObject))]
class CardObjectEditor : Editor
{
public override void OnInspectorGUI()
{
base.OnInspectorGUI();
CardObject cardObject = (CardObject)target;
foreach(CardEffectType effectType in cardObject.effectsTypes)
{
switch (effectType)
{
case CardEffectType.DamageEffect:
{
//!!!
}
}
}
}
}
#endif
public enum CardEffectType
{
DamageEffect, //other...
}
I found some workarounds but the result is not as when a class is serialized in a Monobehaviour.
When you just want to show something like how Unity would, you can use EditorGUILayout.PropertyField(), but it asks for a SerializedProperty, what you need to get from the SerializedObject, not from the actual target. Something like this:
[CustomEditor(typeof(MyType))]
public class MyTypeEditor : Editor
{
private SerializedProperty _variableIWantToShow;
private void OnEnable()
{
_variableIWantToShow = serializedObject.FindProperty("<name-of-the-variable>");
}
public override void OnInspectorGUI()
{
// ...
if (ShowVariable) EditorGUILayout.PropertyField(_variableIWantToShow);
// ...
}
}
You can manage collections (array, list, etc.) as SerializedProperty, but it adds some complexity.
https://docs.unity3d.com/ScriptReference/SerializedProperty.html
https://answers.unity.com/questions/682932/using-generic-list-with-serializedproperty-inspect.html
Just make your int myInt public, or add [SerializeField] attribute to it - inspector is only designed to work with serialisable fields (public fields are serialised by default in unity editor), currently your myInt is private hence only visible from within
As mentioned in an answer by h4i, proper way to display objects in the editor is using SerializedProperty, casting 'target' seems like a good idea at start but is only useful if you want to call methods on the object, it will fail in several other cases.
What you may want to consider is declaring a PropertyDrawer while Editors service a single monobehaviour type, a PropertyDrawer handles displaying every instance of a serializable class, and will be used every time a default editor uses a PropertyField internally. This will probably match your use case better

Is it the right way to reference other script if it doesn't have MonoBehaviour?

I'm making a saving system for my mobile game. I have a script for economy that tracks the amount of in-game currency and i'd like to reference a non MonoBehaviour script that will hold the data to save
public class Money : MonoBehaviour
{
public int Gold;
public int Platinum;
public int Tokens;
public DataHolder data;
private void Update()
{
data.Platinum = Platinum;
data.Tokens = Tokens;
data.Gold = Gold;
}
}
public class DataHolder
{
public int[] dragonLevel;
public bool[] dragonMasterLevel;
public int Gold;
public int Platinum;
public int Tokens;
}
Will unity automatically get the reference and will it properly transfer this data?
You are allowed to use your own classes / structs in Unity. Unlike Unity components like ScriptableObject and MonoBehaviour, you need to create them and make sure they get deleted.
ScriptableObject
public class DataHolder : ScriptableObject {
...
}
ScriptableObjects are Unity's solution for exactly your use case. You can then create assets that are instances of DataHolder.
In this solution the data gets serialized with the GameObject so you can have default values in Unity Editor Inspector.
Keep using DataHolder as is
private void Update () {
if (data == null) data = new DataHolder();
...
}
In both these solutions the runtime values will not persist between sessions. To save values and restore them there are different options. Here are a few in order of simplicity:
PlayerPrefs:
Add WriteToPrefs() and LoadFromPrefs() methods to DataHolder.
FileSystem
online databases like Google Firebase
Adding to the other answer, you could just create an object of your DataHolder class in one of your monobehaviours, and call DontDestroyOnLoad on the gameObject. you will also need to use the code below in Awake on your DontDestroyOnLoad() object to make sure only one instance of the MonoBehaviour is available, so you dont create duplicates when you navigate between scenes
public DataHolder dataHolder;
public static MyComponent myComponent;
private void Awake()
{
DontDestroyOnLoad(gameObject);
if (myComponent == null) myComponent = this;
else Destroy(gameObject);
dataHolder = new DataHolder();
}
now you can write to this dataHolder object and keep the GameObject alive in case you want to add any more data to it. It is good practice to have a persistant GameObject in the scene to save in-game data, or just about anything that needs to be preserved between scenes.
Alternatively, you can also make your DataHolder class static and it's members static. although i would advise against this, especially if there are multiple players/characters in your game that use the same component. But this will actually help you reference it from any script, without creating objects, or maintaining a persistent GameObject in the scene.

Better way to have static class / method access a gameObject variable from several scripts away?

TLDR: How can I have a script that inherits from a public abstract class have access to an often changing Enemy gameObject variable (so it can't be static) without passing it through several other scripts first?
In my game, I have a battle system where a different "Battle Event" gets loaded for each battle. Each "Battle Event" gets its own script, and each of those events inherits from the same BattleEvent parent (which is public abstract).
The code structure basically goes:
BattleSystem (main brain of battles which holds the Enemy
gameObject) ->
BattleEventsManager (handles both which BattleEvent to load, and which methods to run on that BattleEvent) ->
a random BattleEvent (BattleEventOne or BattleEventTwo etc)
public class BattleSystem : MonoBehaviour
{
BattleEventsManager battleEventsManager;
public Enemy currentEnemy;
// the Enemy data is passed when the battle starts
public void Start(Enemy enemyToLoad)
{
battleEventsManager = GetComponent<BattleEventsManager>();
currentEnemy = enemyToLoad;
}
public void BeginPlayerTurn()
{
battleEventsManager.SetupEvent(currentEnemy);
}
}
public class BattleEventsManager : MonoBehaviour
{
BattleEvent currentBattleEvent;
private void Awake()
{
// define this battleEvent
currentBattleEvent = GetComponent<BattleEventOne>();
}
public void SetupEvent(Enemy currentEnemy)
{
// start the battleEvent with its Setup function
currentBattleEvent.Setup(currentEnemy);
}
}
// inherits from `BattleEvent` parent class, shown below
public class BattleEventOne : BattleEvent
{
// override the method from the parent
public override void Setup(Enemy currentEnemy) {
// we can now use the data we need in `currentEnemy`
// all I wanted was to get access to `BattleSystem.currentEnemy`
// but i had to pass it down all the way here. Is there a better way?
}
}
// parent of all `BattleEvents`
public abstract class BattleEvent : MonoBehaviour
{
public abstract void Setup(Enemy currentEnemy);
} // end BattleEvent class
As you can see, the the currentEnemy variable needs to be passed down through 2 classes in order to get to where it needs to be: BattleEventOne.Setup().
Furthermore, I needed to add the Enemy currentEnemy param to the parent BattleEvent, which is problematic because not all BattleEvents will need this information.
I originally wanted to just call BattleSystem.currentEnemy from BattleEventOne (using a property or something), but because the BattleSystem is abstract/static, it can't access it. And because currentEnemy contains new data each battle, I can't make that a static variable.
So, how can I have BattleEventOne here access BattleSystem.currentEnemy without having to pass it down as I've done above?
(I still struggle a lot with passing information between scripts, so any help here is really appreciated!)

Create new default object for custom PropertyDrawer

When you create new class and mark it as [System.Serializable] your inspector will create and show its default object for property of new class' type in your MonoBehaviour component.
When creating custom PropertyDrawer though you need to create this default object on your own and put its reference into SerializedProperty.objectReferenceValue (as far as I understand).
But this field is of type UnityEngine.Object and my new class cant be assigned there. How to overcome it? Inheriting your class from UnityEngine.Object doesnt help as SerializedProperty.objectReferenceValue is still null, even after assigning in there the newly created object (which is actually of the same type – UnityEngine.Object).
I hope I understood your question correctly, taken from the Unity documentation:
using UnityEngine;
public enum IngredientUnit { Spoon, Cup, Bowl, Piece }
// Custom serializable class
[Serializable]
public class Ingredient
{
public string name;
public int amount = 1;
public IngredientUnit unit;
}
public class Recipe : MonoBehaviour
{
public Ingredient potionResult;
public Ingredient[] potionIngredients;
}
[CustomPropertyDrawer(typeof(Ingredient))]
public class IngredientDrawerUIE : PropertyDrawer
{
public override VisualElement CreatePropertyGUI(SerializedProperty property)
{
// Create property container element.
var container = new VisualElement();
// Create property fields.
var amountField = new PropertyField(property.FindPropertyRelative("amount"));
var unitField = new PropertyField(property.FindPropertyRelative("unit"));
var nameField = new PropertyField(property.FindPropertyRelative("name"), "Fancy Name");
// Add fields to the container.
container.Add(amountField);
container.Add(unitField);
container.Add(nameField);
return container;
}
}
So when you view a GameObject with the Recipe component on it, Unity's inspector will show something like this:
So you do not need to inherit from anything, simply mark the class you want to create a property drawer as Serializable, and create a property drawer class for it (Make sure to place it in the Editor folder, or create a assembly definition file which targets the editor only if you are working with assembly definition files).

Unity PropertyDrawer access parent object

I have a class similar to this one:
class Foo : MonoBehaviour {
public Car a;
[MyProperty]
public Trunk b;
}
I've implemented MyPropertyAttribute and a MyPropertyDrawer which inherits PropertyDrawer to create custom inspector for MyProperty decorated attributes.
Now, my issues is that I want to access somehow the actual object instance of the class Foo who owns that b instance.
In other words, I have:
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
{
this._property = property;
Car c = property.serializedObject.targetObject as Car;
Foo ownerOfCar = ...; // <<< Idk how to get this instance
}
I want to use the Foo reference to change other properties in the class, for example the b variable, which's a Trunk.
I've tried many properties and methods (like objectReferenceValue, tweaking the serializedObject values etc.) but none really worked :(.
Is it possible?

Categories

Resources