How can I create foldout EditorGUI element with toggle in Unity Inspector - unity3d

Need your help.
Easily create a foldout element with toggle list. Like that
But I need to create foldout element with toggle in a header. Like that
I think it's possible because scripts header already have this
I tried to find the answer here but didn't find anything like it.
Thank you for help

You can override how your Serializable class is rendered using EditorGUI by creating a custom PropertyAttribute and PropertyDrawer.
Example
I cooked up an attribute that takes a boolean field (specified by you) and instead of rendering it as usual, it renders it as a checkbox at the top. You can have any number of boolean fields inside your class, they should render just fine.
This implementation renders only boolean fields. If you wish to render other kind of stuff besides that, feel free to extend this solution.
Implementation
using UnityEngine;
public class ToggleListAttribute : PropertyAttribute
{
public string StatusPropertyName { get; private set; }
public ToggleListAttribute(string statusPropertyName)
{
StatusPropertyName = statusPropertyName;
}
}
using System;
using UnityEditor;
using UnityEngine;
[CustomPropertyDrawer(typeof(ToggleListAttribute))]
public class ToggleListDrawer : PropertyDrawer
{
private bool show;
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
{
var statusProperty = GetStatusPropertyFrom(property);
var foldoutRect = GetLinePositionFrom(position, 1);
show = EditorGUI.Foldout(
foldoutRect,
show,
string.Empty,
false);
statusProperty.boolValue = EditorGUI.ToggleLeft(
foldoutRect,
property.displayName,
statusProperty.boolValue);
if (show)
RenderSubproperties(property, position);
}
public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
{
if (show)
return EditorGUIUtility.singleLineHeight * (GetBooleanPropertyCount(property) + 1);
else
return EditorGUIUtility.singleLineHeight;
}
private SerializedProperty GetStatusPropertyFrom(SerializedProperty property)
{
var listAttribute = attribute as ToggleListAttribute;
var statusProperty = property.FindPropertyRelative(
listAttribute.StatusPropertyName);
if (statusProperty == null)
throw new Exception($"No property named \"{listAttribute.StatusPropertyName}\" found!");
return statusProperty;
}
private void RenderSubproperties(SerializedProperty property, Rect position)
{
var innerPosition = new Rect(
position.x + EditorGUIUtility.standardVerticalSpacing * 4,
position.y,
position.width,
position.height);
var statusProperty = GetStatusPropertyFrom(property);
int line = 2;
foreach (var instance in property)
{
var subproperty = instance as SerializedProperty;
if (subproperty.propertyType != SerializedPropertyType.Boolean ||
subproperty.name == statusProperty.name)
{
continue;
}
subproperty.boolValue = EditorGUI.ToggleLeft(
GetLinePositionFrom(innerPosition, line),
subproperty.displayName,
subproperty.boolValue);
line++;
}
}
private int GetBooleanPropertyCount(SerializedProperty property)
{
int count = 0;
foreach (var instance in property)
{
var subproperty = instance as SerializedProperty;
if (subproperty.propertyType != SerializedPropertyType.Boolean)
continue;
count++;
}
return count - 1;
}
private Rect GetLinePositionFrom(Rect rect, int line)
{
float heightModifier = EditorGUIUtility.singleLineHeight * (line - 1);
return new Rect(
rect.x,
rect.y + heightModifier,
rect.width,
EditorGUIUtility.singleLineHeight);
}
}
Usage
using System;
using UnityEngine;
public class Example : MonoBehaviour
{
[ToggleList("enabled")]
public RenderList list1;
[ToggleList("enabled")]
public RenderList2 list2;
}
[Serializable]
public class RenderList
{
public bool enabled;
public bool floor;
public bool car;
public bool train;
}
[Serializable]
public class RenderList2
{
public bool enabled;
public bool one;
public bool two;
public bool three;
public bool four;
public bool five;
public bool six;
public bool seven;
}

Use EditorGUILayout.InspectorTitlebar(foldout: foldout, editor: targetEditor);

Related

Unity2D: my instantiate random object is still error even if I attached a scripts to it

I'm currently making a simple game for my project, which is spawning food and damaging it until its vanishes. I have to write a script that instantiates a random object from an array, attaches the script to it, and damages the instantiated food in another script. The current problem now is Unity telling me that I have a Null Reference Exception on both scripts, I tried to fix it by attaching the script to the instantiated object, but still, the problem remains.
Here the code on the script that attaches to the instantiated object, and also spawning the object too:
`
public class Food : MonoBehaviour
{
public GameObject[] food;
public Vector3Int spawnPosition;
public int health = 200;
public int currentHealth;
private GameObject clone;
public void Start()
{
currentHealth = health;
SpawnFood();
}
//Spawning food
public void SpawnFood()
{
int random = Random.Range(0, food.Length); //Null Reference Exception happen in this line.
clone = Instantiate(food[random], this.spawnPosition, Quaternion.identity) as GameObject;
clone.AddComponent<Food>();
}
public void TakeDamage(int damage)
{
currentHealth -= damage;
//play hurt effect
if(currentHealth < 0)
{
Vanish();
}
}
void Vanish()
{
Debug.Log("Vanished");
}
}
`
Here is the other script:
`
public class Board : MonoBehaviour
{
public Tilemap tilemap { get; private set; }
public Piece activePiece { get; private set; }
public TetrominoData[] tetrominoes;
public Vector3Int spawnPosition;
public Vector2Int boardSize = new Vector2Int(10, 20);
public int damage;
public Food clone;
public TextMeshProUGUI hud_score;
public static int currentScore = 0;
public int scoreOneLine = 40;
public int scoreTwoLine = 100;
public int scoreThreeLine = 300;
public int scoreFourLine = 1200;
private int numberOfRowsThisTurn = 0;
public RectInt Bounds
{
get
{
Vector2Int position = new Vector2Int(-this.boardSize.x / 2, -this.boardSize.y / 2);
return new RectInt(position, this.boardSize);
}
}
private void Awake()
{
this.tilemap = GetComponentInChildren<Tilemap>();
this.activePiece = GetComponentInChildren<Piece>();
//call Tetromino.Initialize() to spawn pieces
for (int i = 0; i < this.tetrominoes.Length; i++)
{
this.tetrominoes[i].Initialize();
}
}
private void Start()
{
SpawnPiece();
}
private void Update()
{
UpdateScore();
UpdateUI();
}
public void UpdateUI()
{
hud_score.text = currentScore.ToString();
}
public void UpdateScore()
{
if(numberOfRowsThisTurn > 0)
{
if(numberOfRowsThisTurn == 1)
{
ClearedOneLine();
}
else if (numberOfRowsThisTurn == 2)
{
ClearedTwoLine();
}
else if (numberOfRowsThisTurn == 3)
{
ClearedThreeLine();
}
else if (numberOfRowsThisTurn == 4)
{
ClearedFourLine();
}
numberOfRowsThisTurn = 0;
}
}
public void ClearedOneLine()
{
currentScore += scoreOneLine;
clone.GetComponent<Food>().TakeDamage(10); //Null Reference Exception happen in this line.
}
public void ClearedTwoLine()
{
currentScore += scoreTwoLine;
clone.GetComponent<Food>().TakeDamage(20); //Null Reference Exception happen in this line.
}
public void ClearedThreeLine()
{
currentScore += scoreThreeLine;
clone.GetComponent<Food>().TakeDamage(40); //Null Reference Exception happen in this line.
}
public void ClearedFourLine()
{
currentScore += scoreFourLine;
clone.GetComponent<Food>().TakeDamage(80); //Null Reference Exception happen in this line.
}
`
Please help and thank you for the help.
Here is the Inspector image with 64 elements in an array:
I have tried to attach the script to the instantiated object when that object is being spawned.
I don't see food set anywhere. You need to populate the array inside Food from the Inspector or at least, populate programmatically in Food.Start() before using it.
Can you also show us a printscr of the Inspector view where Food is attached as component? The main focus here is the food array.
the food array is empty it should be filled by you,
instantiate doesn't fill it, it takes an object from the array and spawn it.
fill it with prefabs of foods that already has the food component no need to add it in code

Serialisation not working when inheriting from monobehaviour

Im trying to do a property drawer for a class that i need to be updated as well as editable from the editor.
If i make the class a monobehaviour the serialisation stops working, but if i remove the monobehaviour inheritance it wont update with the game loop.
Is there any way to have both? I would need the object to be able to instantiate with default (empty) values if a monobehaviour script has non instantiated reference.
[Serializable]
public class MySmallTestProp : MonoBehaviour, ISerializationCallbackReceiver
{
[SerializeField]
private string name;
[SerializeField]
private string _name;
[SerializeField]
private float _someFloat;
public float someFloat;
public MySmallTestProp()
{ }
public void OnBeforeSerialize()
{
_name = name;
}
public void OnAfterDeserialize()
{
name = _name;
}
}
[CustomPropertyDrawer(typeof(MySmallTestProp))]
public class MySmallTestPropPropertyDrawer : PropertyDrawer
{
float rowHeight;
int rowSpacing = 5;
int index;
Rect currentPosition;
public override float GetPropertyHeight(SerializedProperty prop, GUIContent label)
{
rowHeight = base.GetPropertyHeight(prop, label);
var rows = 2;
if (Application.isPlaying)
{
rows++;
}
else
{
rows++;
}
return rowHeight * rows;
}
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
{
index = 1;
currentPosition = position;
EditorGUI.BeginProperty(position, label, property);
var nameProp = property.FindPropertyRelative("_name");
EditorGUI.PropertyField(NextPosition(), nameProp, new GUIContent("Name"));
EditorGUI.EndProperty();
}
Rect NextPosition()
{
currentPosition.height = rowHeight;
currentPosition.y = rowSpacing + (rowHeight + rowSpacing) * index++;
return currentPosition;
}
}
So if I understand you correctly what you want to achieve is having a class which is
Serializable
has some default field values
Receives an Update call every frame
Actually I don't think you need any custom property drawer for this.
First two points are as simple as having e.g.
[Serializable]
public class Example
{
// By default this has the value "Default String"
public string someString = "Default String";
// This can be edited only via the Inspector
// by default it is 42
[SerializeField] private float someFloat = 42.0f;
// This is a read-only public access
public float SomeFloat => someFloat;
}
Now to the last and tricky part - the update calls.
The easiest way is to have a dedicated MonoBehaviour like e.g.
public class UpdateDispatcher : MonoBehaviour
{
// Storing the instance for Singleton Pattern
private static UpdateDispatcher _instance;
// Register to this event to receive one call each Update
public static event Action OnUpdate;
// This method is automatically called by Unity when the application is started
// or you enter play mode in the editor
[RuntimeInitializeOnLoadMethod]
private static void Init()
{
// _instsnce is already assigned and alive?
if(_instance) return;
// Otherwise search for one in the scene
_instance = FindObjectOfType<UpdateDispatcher>();
// Found one?
if(_instance) return;
// Otherwise create it now
_instance = new GameObject(nameof(UpdateDispatcher)).AddComponent<UpdateDispatcher>();
}
private void Awake ()
{
// Does another instance already exist?
if(_instance && _instance != this)
{
// Destroy this one
Destroy (gameObject);
return;
}
// Otherwise assign this as the instance and make sure it isn't destroyed when the scene chsnges
_instance = this;
DontDestroyOnLoad (gameObject);
}
private void Update ()
{
// Call the event every frame if something is registered
OnUpdate?.Invoke ();
}
}
And then you can use ISerislizationCallbackReceiver but not for actually doing the serialization (it is already done automatically for the fields) but rather for registration to the update callback like e.g.
[Serializable]
public class Example, ISerializationCallbackReceiver
{
// By default this has the value "Default String"
public string someString = "Default String";
// This can be edited only vis the Inspector
// by default it is 42
[SerializeField] private float someFloat = 42.0f;
// This is a read-only public access
public float SomeFloat => someFloat;
// Nothing to do here, only needed for the interface
public void OnBeforeSerialize() { }
public void OnAfterDeserialize()
{
// Register to the Update event
// It is save to unregister before registering even if we haven't been registered before
// this makes sure we are registered only exactly once
UpdateDispatcher.OnUpdate -= Update;
UpdateDispatcher.OnUpdate += Update;
}
private void Update ()
{
someFloat += Time.deltaTime;
}
}
This answer should be more like a comment, but due to the extension I've decided to post it here.
The objective of a PropertyDrawer is to display properties differently on the editor.
To achieve that you need 2 things:
1.One class that inherits from PropertyAttribute, this will be the reference used in your future scripts.
2.Another class that inherits from PropertyDrawer, here you can type HOW to display the attribute.
One implementation example of a property drawer that shows an attribute without leting the user to edit it from editor:
using UnityEditor;
using UnityEngine;
public class DisplayWithoutEdit : PropertyAttribute
{
}
[CustomPropertyDrawer(typeof(DisplayWithoutEdit))]
public class DisplayWithoutEditDrawer : PropertyDrawer
{
public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
{
return EditorGUI.GetPropertyHeight(property, label, true);
}
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
{
GUI.enabled = false;
EditorGUI.PropertyField(position, property, label, true);
GUI.enabled = true;
}
}
Then you can use it on another script doing something like:
[DisplayWithoutEdit] public float randomNumber = 0f;

I cannot define the class to the Scriptableobject

I created ScriptableObject but unfortunately I cannot assign the class which i created into it. What should I do?
Codes
CardScript
Screenshot
I've tried to do something like this but now i cannot add script to the card gameobject
public class CardDisplay : MonoBehaviour
{
public Card card;
public TextMeshProUGUI nameText;
public TextMeshProUGUI descText;
public TextMeshProUGUI APText;
public Image cardbackground;
public Image artworkImage;
public Image maskImage;
public void SetProperties()
{
///To take a data from localizer. Shalalalaaaa.
var NameText = LocalizationSettings.StringDatabase.GetLocalizedStringAsync("CARD_NAME", card.Key);
NameText.Completed += (op) => nameText.text = op.Result;
var DescText = LocalizationSettings.StringDatabase.GetLocalizedStringAsync("CARD_DESC", card.Key);
DescText.Completed += (op) => descText.text = op.Result;
APText.text = card.ActionPoint.ToString();
if (card.Artwork != null)
artworkImage.sprite = card.Artwork;
cardbackground.sprite = Resources.Load<Sprite>("Items/Card/CardBackgrounds/Card_"+card.rarity);
maskImage.sprite = Resources.Load<Sprite>("Items/Card/CardMasks/"+ card.rarity +"_Mask");
foreach (CardScript a in Resources.LoadAll<CardScript>("Items/Card/CardScripts"))
{
Debug.Log(a.name);
}
if(Resources.Load<CardScript>("Items/Card/CardScripts/"+card.Key) != null)
gameObject.AddComponent(Resources.Load("Items/Card/CardScripts/"+card.Key).GetType());
}
}

How can I build a unity UI component that is reusable across scenes

I want to build a UI modal in Unity that can be used across multiple scenes, how do I achieve this and later on probably build it as a stand-alone library people can use in their unity projects. Are there tutorials on this?
Explore this repo. It's usable, but experimental for now. UIElements are becoming a new standard for Unity, so there will be a preview package in a few weeks/month.
If you want to use current UI, you can create separate scenes for each reusable window and open them additive, passing parameters and callbacks to static methods, see my example
Line
public class ResultLineEntity {
public int Place;
public readonly string Title;
public readonly TimeSpan Time;
public ResultLineEntity(string title, TimeSpan time, int? place = null) {
if (place != null) Place = place.Value;
Title = title;
Time = time;
}
}
Window
public class ResultsUi : MonoBehaviour {
public static ResultLineEntity[] Lines
{
get => _lines;
set
{
_lines = value;
OnLinesChange?.Invoke();
}
}
private static ResultLineEntity[] _lines;
public static UnityEvent OnLinesChange { get; private set; } = new UnityEvent();
[SerializeField] private Transform root;
[SerializeField] private ResultLineUi prefab;
private void Awake() {
SetLines();
OnLinesChange?.AddListener(SetLines);
}
public void SetLines() {
if (Lines==null || Lines.Length < 1) return;
foreach (var child in root.GetComponentsInChildren<ResultLineUi>()) {
Destroy(child.gameObject);
}
var resultsOrdered = Lines.OrderBy(x => x.Time).ToArray();
for (var index = 0; index < resultsOrdered.Length; index++) {
var line = resultsOrdered[index];
line.Place = index + 1;
var currentLine = Instantiate(prefab, root);
currentLine.SetLine(line);
}
}
}
Example
ResultsUi.Lines = lines;
SceneManager.LoadScene(ResultsSceneName, LoadSceneMode.Additive);

Unity Doesnt Serialize int? field

I have a class i want to change the properties of in the editor. So i made my class System.Serializable and made the variables public that i want to be able to change.
Like so:
[System.Serializable]
public class UIOptionsRing
{
public float Radius, DistanceBetweenPoints, StartOffset, GapInDegrees;
public int? GapAfterElementNumer = 3; //this var doesnt show up
public Vector3 CircleCenter;
public GameObject CircleElementsContainer;
}
But the problem i am having is that the GapAfterElementNumer is not show up in the editor at all the other fields are. How i can i make it so that int? also shows up?
Nullable types are not serialized in Unity Editor because it's serializer doesn't support null.
There's a small workaround if you're not going to serialize this class to json using JsonUtility.
The key idea is that you have to create your own nullable int. Something like
public class IntNullable
{
public int Value;
public bool HasValue;
}
Just like it's done inside .NET. Then you can create a Custom Editor for IntNullable or your UIOptionsRing. In this editor you can make a filed for int value and a button "Set Null", which will change the value of HasValue variable. And further you need to work with this custom IntNullable in your code.
Unity not only can't show nullable fields in the inspector, it cannot serialize them. In order to support this we need to make a custom version of System.Nullable (as #vmchar explains) that is serializable and then give it a property drawer. Making a seamless replacement for System.Nullable is not necessarily obvious, so I've included this example. It should be a drop in replacement for nullable ( int? can be replaced with SN<int> and all else should work due to the implicit casts) along with a basic custom property drawer.
using UnityEngine;
#if UNITY_EDITOR
using UnityEditor;
#endif
/// <summary>
/// Serializable Nullable (SN) Does the same as C# System.Nullable, except it's an ordinary
/// serializable struct, allowing unity to serialize it and show it in the inspector.
/// </summary>
[System.Serializable]
public struct SN<T> where T : struct {
public T Value { get {
if (!HasValue)
throw new System.InvalidOperationException("Serializable nullable object must have a value.");
return v;
} }
public bool HasValue { get { return hasValue; } }
[SerializeField]
private T v;
[SerializeField]
private bool hasValue;
public SN(bool hasValue, T v) {
this.v = v;
this.hasValue = hasValue;
}
private SN(T v) {
this.v = v;
this.hasValue = true;
}
public static implicit operator SN<T>(T value) {
return new SN<T>(value);
}
public static implicit operator SN<T>(System.Nullable<T> value) {
return value.HasValue ? new SN<T>(value.Value) : new SN<T>();
}
public static implicit operator System.Nullable<T>(SN<T> value) {
return value.HasValue ? (T?)value.Value : null;
}
}
#if UNITY_EDITOR
[CustomPropertyDrawer(typeof(SN<>))]
internal class SNDrawer : PropertyDrawer {
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) {
EditorGUI.BeginProperty(position, label, property);
// Draw label
position = EditorGUI.PrefixLabel(position, GUIUtility.GetControlID(FocusType.Passive), label);
// Don't make child fields be indented
var indent = EditorGUI.indentLevel;
EditorGUI.indentLevel = 0;
// Calculate rects
var setRect = new Rect(position.x, position.y, 15, position.height);
var consumed = setRect.width + 5;
var valueRect = new Rect(position.x + consumed, position.y, position.width - consumed, position.height);
// Draw fields - pass GUIContent.none to each so they are drawn without labels
var hasValueProp = property.FindPropertyRelative("hasValue");
EditorGUI.PropertyField(setRect, hasValueProp, GUIContent.none);
bool guiEnabled = GUI.enabled;
GUI.enabled = guiEnabled && hasValueProp.boolValue;
EditorGUI.PropertyField(valueRect, property.FindPropertyRelative("v"), GUIContent.none);
GUI.enabled = guiEnabled;
// Set indent back to what it was
EditorGUI.indentLevel = indent;
EditorGUI.EndProperty();
}
}
#endif
It's performance may not be on par with System.Nullable, but it should be fine for most purposes. It's been serving me well so far in Unity 2021.1 with C# 4 enabled.
An improvement on vmchar's answer, which allows null assignment:
[Serializable]
public struct NullableInt
{
public int Value;
public bool HasValue;
public NullableInt(int value)
{
Value = value;
HasValue = true;
}
public static implicit operator NullableInt(int value) => new NullableInt(value);
public static implicit operator NullableInt(NullableNull value) => new NullableInt();
public static implicit operator int(NullableInt value) => value.Value;
public static implicit operator int? (NullableInt value) => value.HasValue ? value.Value : new int?();
}
public sealed class NullableNull
{
private NullableNull()
{ }
}