Serialize a custom class in a custom inspector in Unity - c#

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

Related

How do I add a card effect to my card ScriptableObject in unity?

I have this ScriptableObject and I would like to add an effect to each card at creation; I have a script called "EffectManager" that has some methods as effects.
This is my card script
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
[CreateAssetMenu(fileName = "New Card", menuName = "Card")]
public class Card : ScriptableObject
{
public string m_cardName;
public Sprite m_sprite;
public int m_manaCost;
public int m_health;
public int m_attack;
}
and this is my EffectManager Script
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class EffectManager : MonoBehaviour
{
Deck dk;
Hand hnd;
Player p1;
private void Awake()
{
dk = GameObject.FindObjectOfType<Deck>();
hnd = GameObject.FindObjectOfType<Hand>();
jg1 = GameObject.FindObjectOfType<Player>();
}
public void draw(int num)
{
jg1.DrawCards(num);
}
public void discard()
{
hnd.hand.RemoveAt(0);
}
private void mill()
{
dk.deck.RemoveAt(0);
}
}
How can I make so each card I create has an effect linked to it that activates when conditions are met? (in this case it would be when the card is summoned)
You could use a pattern I like to call "Scriptable Behavior" which basically simply consists of another ScriptableObject type and implement different effects like e.g.
public abstract class CardEffect : ScriptableObject
{
// You can give that the signature (parameters and return type) you want/need of course
// Every type inherited from this HAS TO implement this method
public abstract void ExecuteEffect (EffectManager effectManager);
}
Now you can implement certain configurable effects like e.g.
public class DrawEffect : CardEffect
{
// Adjust how many cards are drawn
public int amount;
public override void ExecuteEffect (EffectManager effectManager)
{
effectManager.draw(amount);
}
}
Or e.g.
public class DrawAndDiscardEffect : DrawEffect
{
public override void ExecuteEffect (EffectManager effectManager)
{
// First do whatever the parent class implements
base.ExecuteEffect(effectManager);
// Additionally discard the same amount of cards
for(var i = 0; i < amount; i++)
{
effectManager.discard();
}
}
}
Finally you would have an additional field in the card definitions. Personally I would even use an array so you can totally flexible combine different kinds of effects without having to inherit one from another like
public class Card : ScriptableObject
{
public CardEffect[] effects;
...
public void Summon(EffectManager effectManager)
{
foreach(var effect in effects)
{
effect.ExecuteEffect(effectManager);
}
}
}
Now all that's left is that something in your setup actually calls that
// Wherever you get these from
EffectManager effectManager;
Card someCard;
someCard.Summon(effectManager);
This then can easily be extended to the comolexibility you need. You could e.g. instead of having a Summon method and only one collection of effects have yet another ScriptableObject type called EffectCondition and implement different types of conditions which will then run their attached effect collection.
Then the central controller could go through all cards and rather call e.g. CheckCondition and then every card goes through all attached conditional setups, checks if the according condition is fulfilled and then executes all effects bound to that condition ;)

How can i re-use a code in monobehaviour class?

First i'd like to give a short version of my question:
How can i access another code pieces attached to another game object, or how can i initiazlie a class without have an game object attched.
When i making a small game in Unity, i made an unit designer where you give some value such as how many weapon does it carry, and the status of that unit (attack, range, speed, etc.) will be calculated by ComputeValues() and saved when you click confirm. But all those values were adjusted by clicking a button instead of direct input. (I.e. Click a button and add/reduce 1 weapon)
However, when i try to add some template unit at start up it won't work. So i made a CreateDesignWithValue() function. Which takes input for all the related data, and use the ComputeValues() above to compute the value for that object.
The problem is i'm trying to do it in player class. But i can't create new ShipDesigner, and neither can i set it to static. How can i get access to it?
Without knowing you exact usecase and what the methods do you are talking about we can only give a very general answer:
Not all classes have to be of type MonoBehaviour it really depends on your needs.
Extension Methods
If you have a certain calculation for a certain type you can use Extension Methods like
public static class Vector3Extensions
{
public static Vector3 DevideBy(this Vector3 a, Vector3 b)
{
return new Vector(a.x / b.x, a.y / b.y, a.z / b.z);
}
}
which you can use like e.g.
var newVector = transform.position.DevideBy(new Vector(1, 2, 3));
in all other classes.
public static class
In general you can use a public static class to implement methods and store values that shall be executable from everywhere e.g.
public static class Settings
{
private static int _currentInt = 7;
public static void SaySomething(string something)
{
Debug.Log(something);
}
public static void DoubleCurrentInt()
{
_currentInt *= 2;
}
public static int GetSquareOfCurrentInt()
{
return _currentInt * _currentInt;
}
}
which you can call now from everywhere like
Settings.DoubleCurrentInt();
Settings.SaySomething(Settings.GetSquareOfCurrentInt.Tostring);
Instances
Ofcourse sometimes you do not want that something is accessible from everywhere so you can also simply have a normal instanced class for your calculation like
public class Settings
{
private int _currentInt = 7;
public Settings(int initialInt = 0)
{
_currentInt = initialInt;
}
public void SaySomething(string something)
{
Debug.Log(something);
}
public void DoubleCurrentInt()
{
CurrentInt *= 2;
}
public int GetSquareOfCurrentInt()
{
return CurrentInt * CurrentInt;
}
}
So you can use
private Settings settings;
private void Start()
{
new Settings(3);
}
in one MonoBehaviour and
private Settings settings;
private void Start()
{
new Settings(26);
}
in another MonoBehaviour, both have different instances but can use all the implemention in it for calculating and doing stuff individually.
public static void
you can also only "share" one method among all instances of a certain type (static) and also allow other types to access it (public)
public class A : MonoBehaviour
{
// A prefab only this specific component has access to
[SerializeField] private GameObject prefab;
// example for a kind of singleton pattern
private static GameObject prefabSingleton;
private void Start()
{
prefabSingleton = prefab;
}
public static void Spawn(int someIntToAssign, string someTextToAssign)
{
var obj = Instantiate(prefabSingleton)
;
componentReference = obj.GetComponent();
componentReference.someIntField = someIntToAssign;
componentReference.Getcomponent<Text>().text = someTextToAssign;
}
}
this you can call from other types as well like
A.Setup(someExampleReference, "Yeay!");
(in this example you could consider to rather implement it in SomeExampleType, though ^^)
ScriptableObjects
What you described also sounded like ScriptableObjects (Tutorial) might be interesting for you.
ScriptableObjects are kind of assets similar to prefabs but can store values and also methods. You than can reference them in fields of MonoBehaviour components to change their behaviour according to the values or in order to share it as kind of container between multiple instances and different types.
Instance with public method
Last but not least the most "usual" of doing it would be to have a
public class A : MonoBehaviour
{
[SerializeField] private Transform someObject;
public Vector3 GetObjectPosition()
{
return someObject.position;
}
}
and access it via one of the many GetComponent or/and FindObjectOfType variants or simply by referencing the according component like
public class B : MonoBehaviour
{
// drag in via the Inspector
public A AReference;
private void Start()
{
// or get it on runtime e.g.
AReference = GameObject.Find("ObjectWithA").GetComponent<A>();
// or if there is only one e.g.
AReference = FindObjectOfType<A>();
Debug.Log(AReference.GetObjectPosition());
}
}
Answer of short versions:
How can i access another code pieces attached to another game object:
Declare a public field for the script you want to reach e.g. public ExampleScript exampleScript; and assign the gameobject which has ExampleScript to your field in the inspector.
how can i initiazlie a class without have an game object attched: You can't create an instance of a script derived from MonoBehaviour just like new ExampleScript();. But instead you can add that script to your existing gameobject with gameObject.AddComponent<ExampleScript>(); and you can reach this script from another script which is attached the very same gameObject like: gameObject.GetComponent<ExampleScript>();

How to create a dynamic variable system with Scriptable Objects

In this talk I learned how to create variables with scriptable objects, creating classes like FloatVariable, DoubleVariable, StringVariable and others. But, in the same talk, the guy said that he uses a more dynamic variable system, that prevents creating several classes to handle all variable types.
Using the first system, I had a C# script called ImageFillSetter, that given two float variables and a Image script, it returns the division of the two variables to the fillAmount variable of the image.
But, when I get a Double Variable, and I'd like to set a progress bar with this value, I need to create another script called ImageFillSetterDouble, and put in these variables. And if I needed to create one with Integers? Every time I create a script like this, I will need to create two duplicates to handle the other number variable types?
With this dynamic variable system, this problem should be solved, but I have no idea how to start/create this system.
The code looks like this:
[CreateAssetMenu(menuName="Variable/Float")]
public class FloatVariable : ScriptableObject, ISerializationCallbackReceiver
{
public float initialValue;
[NonSerialized]
public float value;
public void OnAfterDeserialize()
{
value = initialValue;
}
public void OnBeforeSerialize() { }
}
What I want is something like this (Totally hypothetical, I know that this doesn't works)
[CreateAssetMenu(menuName="Variable")]
public class Variable : ScriptableObject, ISerializationCallbackReceiver
{
public var initialValue;
[NonSerialized]
public var value;
public void OnAfterDeserialize()
{
value = initialValue;
}
public void OnBeforeSerialize() { }
}
I know there is an accepted answer that works, but I feel that the usage of ScriptableObject variables as described in the linked video was misinterpreted.
I think you would be better off making your FloatVariable independent of the calculation.
Let's say the calculation is for player health and your fill value would be calculated by currentHealth/maxHealth.
public class PlayerHealth: MonoBehaviour
{
[SerializeField] private FloatVariable floatReference;
[SerializeField] private float maxHealth;
[SerializeField] private float currentHealth;
void Update()
{
this.floatReference.value = currentHealth/maxHealth;
}
}
public class ImageFillSetter: MonoBehaviour
{
[SerializeField] private FloatVariable floatReference;
[SerializeField] private Image imageReference;
void Update()
{
this.imageReference.fill = this.floatReference.value;
}
}
Or let's say that player health is stored as double:
public class PlayerHealth: MonoBehaviour
{
[SerializeField] private FloatVariable floatReference;
[SerializeField] private double maxHealth;
[SerializeField] private double currentHealth;
void Update()
{
this.floatReference.value = (float)(currentHealth/maxHealth);
}
}
Now let's say that you add an input field where the fill value can be entered as a percentage string (like '76'):
public class FillInput: MonoBehaviour
{
[SerializeField] private FloatVariable floatReference;
[SerializeField] private Input input;
void Update()
{
if(Input.GetKeyDown(KeyCode.Enter))
{
this.floatReference.value = float.Parse(input.text)/100f;
}
}
}
The ImageFillSetter will 'observe' the FloatVariable without being aware of how that float was calculated.
This way you only ever have to have one ImageFillSetter that can be used for any image and any data source, while having 1 or more ways of altering the fill that does not require any changes to be made to ImageFillSetter.
For example, let's say that you want to use the same approach to indicate async level load progress:
public class FillInput: MonoBehaviour
{
[SerializeField] private FloatVariable floatReference;
private AsyncOperation loadOperation;
void LoadLevelAsync(string levelName)
{
this.loadOperation = SceneManager.LoadLevelAsync(levelName, LoadSceneMode.Additive);
}
void Update()
{
this.floatReference.value = this.loadOperation?.progress ?? 0;
}
}
This will work without making any other changes as long as your ImageFillSetter references the same FloatVariable.
Think of the FloatVariable (or whichever primitive you have eg. DoubleVariable) as a value stored in a database. Anyone can read the value and anyone can save a new value. It would be strange to store all possible calculations for the value in the database instead of doing the calculation and just storing the answer.
This does not change the fact that you need Scriptable implementations for each primitive:
FloatVariable
DoubleVariable
StringVariable
BoolVariable
etc
but you will only need one of each as demonstrated in the first section of derHugo's answer.
Have a look at Generics
Have one abstract class like
public abstract class ValueAsset<T> : ScriptableObject
{
public T value;
// Add your methods
// Here some more examples also using the T value. They might also be abstract but they don't have to be
// return a T
public T GetValue()
{
return value;
}
// or pass a T
public void SetValue(T input)
{
value = input;
}
}
This class you will never instantiate but now derive multiple implementations from it e.g.
[CreateAssetMenu(fileName = "new int", menuName = "ValueAssets/int")]
public class IntValue : ValueAsset<int>
{
// Maybe constructors here or additional fields and methods
}
[CreateAssetMenu(fileName = "new float", menuName = "ValueAssets/float")]
public class FloatValue : ValueAsset<float>
{
// Maybe constructors here or additional fields
}
You can also have multiple generic values like
public abstract class OtherExample<TKey, TValue> : ScriptableObject
{
// Note that this is just an example
// Dictionary is not serializable
public Dictionary<TKey, TValue> values = new Dictionary<TKey, TValue>();
public void AddPair(TKey key, TVakue value)
{
values.Add(key, value);
}
}
And implement something like
public OneImplementation : OtherExample<string, GameObject>
{
//...
}
The same way this can be used for reference values (components, GameObject etc)
So for IntValue the method GetValue will return an int and SetValue will take an int as parameter. The same way they take and return a float in FloatValue.
Doing the same thing with an ImageFillSetter<T> you can than make your method abstract and implement different behaviours for different T values (like e.g. a different parsing etc)
Note: I don't know why exactly but in the past I noticed that
public ValueAsset<T> valueAsset;
will not be serialized in the inspector even if later implemented so you have to implement the field with the correct type in the implementation instead. You also still could override it on runtime but you can skip the whole FetchValue part if you don't need it and anyway use valueReference instead - just added it for completeness.
public abstract class ImageFillSettet<T> : MonoBehaviour
{
// Will not appear in the Inspector
public ValueAsset<T> ValueAsset;
// Override this in implementation
protected abstract void FetchValue();
// Use it for Initializing the value
private void Awake ()
{
FetchValue();
}
public abstract void SetFill();
}
Than later
public class ImageFillSetterFloat : ImageFillSetter<float>
{
// Show in the inspector
[SerializeField] private FloatValue valueReference;
// Provide the reference to the base class
protected override void Fetch value()
{
valueAsset = valueReference;
}
public override void SetFill()
{
// Use valueReference for something
}
}

Is it possible to show static fields in the Unity Editor?

I wrote little script yesterday but it isn't working. (Serialize fields isn't showing in unity and few errors eg. I can't use reference to non-static member (serialize Field)). Can You help me please.
Eg.
using UnityEngine;
public class sExample : MonoBehaviour
{
[SerializeField] public static GameObject gameObj;
public void serializeUse()
{
//Do something with gameObj
}
}
public class serializeEx : NetworkBehaviour
{
public void Update()
{
If (!isLocalPlayer)
{
sExample.serializeUse()
}
}
}
Thanks alot
That should work.
I think that you can't use static, when you want to expose something to the Editor.
using UnityEngine;
[Serializable]
public class sExample : MonoBehaviour
{
[SerializeField] public GameObject gameObj;
public void serializeUse()
{
//Do something with gameObj
}
}
public class serializeEx : NetworkBehaviour
{
public void Update()
{
If (!isLocalPlayer)
{
sExample.serializeUse()
}
}
}
Edit:
Statics seem to work for JavaScript as mentioned in this post.
To make this work you'll have to switch to the debug view in the inspector.
Like in the image shown below:
Edit2:
The explanation what the Serializeable does is taken from the unity documentation.
The Serializable attribute lets you embed a class with sub properties
in the inspector.
You can use this to display variables in the inspector similar to how
a Vector3 shows up in the inspector. The name and a triangle to expand
its properties. To do this you need create a class that derives from
System.Object and give it the Serializable attribute. In JavaScript
the Serializable attribute is implicit and not necessary.
using UnityEngine;
[System.Serializable]
class Test : System.Object
{
public int p = 5;
public Color c = Color.white;
}

Access a non-MonoBehaviour script from another script (Unity)

I'm working on a Unity project and i would like to access a non MonoBehaviour script from another script.
I can't use GetComponent cause the script isn't MonoBehabiour, is there a solution ?
Here is some code to help you understand :
public class SomeClass {
public static float coolVar = 1.0f;
private string someVar; // EDIT : I need to access this var too and AnotherClass.someVar won't work obviously
public class AnotherClass {
// i want to be able to access coolVar and change her value
// i know i can do SomeClass.coolVar but i was looking for another way close to a GetComponent approach
My SomeClass class is full of static var i need to edit, i didn't implemented those variables and i can't modify them (i know it's bad practices).
Maybe the reflection is the best way :
typeof(SomeClasse).GetField(name).SetValue(null, value);
you only need to include the script with the not MonoBehaviour class inside the Assets path of the project and you will be able to use it inside other behaviour class.
Take care of use only .NET 2.0 specifications in the external class and if u have used a namespace on it add the using in the behaviour script.
#External class
namespace DefNamespace
{
public class ModelList
{
private static List<GameObject> models;
private ModelList ()
{
}
public static List<GameObject> Models{
get{
if(models == null) models= new List<GameObject>();
return models;
}set{
models=value;
}
}
}
}
And then in a MonoBehaviour or another class:
#Behaviour
using DefNamespace;
public class DefBehaviour : MonoBehaviour
{
Start(){
GameObject go=ModelList.Models[0];
}
}

Categories

Resources