Image showing the problem:
https://i.stack.imgur.com/cmMYy.png
How can i fix this?
This is the struct code:
[Serializable] private struct SpawnableObject
{
public SpawnableObject(GameObject entity, float lowEndChance, float highEndChance)
{
chanceRange = new float[2];
if(lowEndChance > highEndChance)
{
float placeholder;
placeholder = lowEndChance;
lowEndChance = highEndChance;
highEndChance = placeholder;
Debug.LogWarning("lowEndChance is greater than highEndChance, swapping values.");
}
else if(lowEndChance == highEndChance)
{
highEndChance += 0.01f;
Debug.LogWarning("lowEndChance equals highEndChance");
}
this.entity = entity;
isDefault = false;
chanceRange[0] = lowEndChance;
chanceRange[1] = highEndChance;
}
public SpawnableObject(GameObject entity, float lowEndChance, float highEndChance, bool isDefault)
{
chanceRange = new float[2];
if(lowEndChance > highEndChance)
{
float placeholder;
placeholder = lowEndChance;
lowEndChance = highEndChance;
highEndChance = placeholder;
Debug.LogWarning("lowEndChance is greater than highEndChance, swapping values.");
}
else if(lowEndChance == highEndChance) Debug.LogWarning("lowEndChance equals highEndChance");
this.entity = entity;
this.isDefault = isDefault;
chanceRange[0] = lowEndChance;
chanceRange[1] = highEndChance;
}
[SerializeField] public GameObject entity;
[SerializeField] public float[] chanceRange;
[SerializeField] public bool isDefault;
}
Other details:
The problem persists after restarting unity.
The problem shows up when an array of the struct is shown in the
editor (It's ok for an individual one).
My editor version is 2021.3.3f1 LTS
Extra note for anyone experiencing this problem:
You can click on the 3 dots next to the script/component(when assigned to a GameObject) and click on "Properties...". This will open a menu where the UI is usable.
Not a good fix but at least allows you to use Unity.
Extra note again:
This only seems to happen with the element 0 of the array, so you can skip that buggy one and work starting from element 1, this time on the standard inspector.
Related
I have a Container (1) that I want to grow with its content.
Inside this Container (1), I have a Scrollrect (2) that I also want to grow with its content but with max height.
The goal is that if there is only one line of text in (2), (1) will be much smaller. When a lot of text is added, (2) will grow until its max height is reached and the scrollbar will take over.
I have been at it for hours and I cannot seem to find a way to make it work.
You can do this via Text.preferredHeight e.g. like this component on your scroll content
public class ContentScaler : MonoBehaviour
{
[SerializeField] private RectTransform _scrollRectTransform;
[SerializeField] private RectTransform _contentRectTransform;
[SerializeField] private Text _text;
[SerializeField] private float minScrollHeight;
[SerializeField] private float maxScrollHeight;
private void Awake()
{
if (!_contentRectTransform) _contentRectTransform = GetComponent<RectTransform>();
}
private void Update()
{
// Get the preferred Height the text component would require in order to
// display all available text
var desiredHeight = _text.preferredHeight;
// actually make your text have that height
var textSizeDelta = _text.rectTransform.sizeDelta;
textSizeDelta.y = desiredHeight;
_text.rectTransform.sizeDelta = textSizeDelta;
// Then make the content have the same height
var contentSizeDelta = _contentRectTransform.sizeDelta;
contentSizeDelta.y = desiredHeight;
_contentRectTransform.sizeDelta = contentSizeDelta;
var scrollSizeDelta = _scrollRectTransform.sizeDelta;
scrollSizeDelta.y = Mathf.Clamp(desiredHeight, minScrollHeight, maxScrollHeight);
_scrollRectTransform.sizeDelta = scrollSizeDelta;
}
}
Building upon Ryan Pergent's answer, I made the code do what he wanted (Mostly) and added the ability to set the preferred height and width
using UnityEngine;
using UnityEngine.UI;
#if UNITY_EDITOR
using UnityEditor;
#endif
public class AdaptablePreferred : LayoutElement, ILayoutElement {
#if UNITY_EDITOR
[CustomEditor(typeof(AdaptablePreferred))]
public class Drawer : Editor {
public override void OnInspectorGUI() {
EditorGUI.BeginDisabledGroup(true);
EditorGUILayout.PropertyField(serializedObject.FindProperty("m_Script"));
EditorGUI.EndDisabledGroup();
EditorGUI.BeginChangeCheck();
EditorGUILayout.BeginHorizontal();
EditorGUILayout.PrefixLabel("Preferred Height");
var prop = serializedObject.FindProperty("_usePreferredHeight");
EditorGUILayout.PropertyField(prop, new GUIContent(""), GUILayout.Width(EditorGUIUtility.singleLineHeight));
if(prop.boolValue) {
var tmp = EditorGUIUtility.labelWidth;
EditorGUIUtility.labelWidth = 35;
EditorGUILayout.PropertyField(serializedObject.FindProperty("_preferredHeightMax"), new GUIContent("Max"));
EditorGUIUtility.labelWidth = tmp;
}
EditorGUILayout.EndHorizontal();
EditorGUILayout.BeginHorizontal();
EditorGUILayout.PrefixLabel("Preferred Width");
prop = serializedObject.FindProperty("_usePreferredWidth");
EditorGUILayout.PropertyField(prop, new GUIContent(""), GUILayout.Width(EditorGUIUtility.singleLineHeight + 2));
if(prop.boolValue) {
var tmp = EditorGUIUtility.labelWidth;
EditorGUIUtility.labelWidth = 35;
EditorGUILayout.PropertyField(serializedObject.FindProperty("_preferredWidthMax"), new GUIContent("Max"));
EditorGUIUtility.labelWidth = tmp;
}
EditorGUILayout.EndHorizontal();
EditorGUILayout.PropertyField(serializedObject.FindProperty("_contentToGrowWith"));
if(EditorGUI.EndChangeCheck()) {
serializedObject.ApplyModifiedProperties();
}
}
}
#endif
[SerializeField] private RectTransform _contentToGrowWith;
[SerializeField] private bool _usePreferredHeight;
[SerializeField] private float _preferredHeightMax;
[SerializeField] private bool _usePreferredWidth;
[SerializeField] private float _preferredWidthMax;
private float _preferredHeight;
private float _preferredWidth;
public override float preferredHeight => _preferredHeight;
public override float preferredWidth => _preferredWidth;
public override void CalculateLayoutInputVertical() {
if(_contentToGrowWith == null) {
return;
}
if(_usePreferredHeight) {
var height = LayoutUtility.GetPreferredHeight(_contentToGrowWith);
_preferredHeight = _preferredHeightMax > height ? height : _preferredHeightMax;
} else {
_preferredHeight = -1;
}
}
public override void CalculateLayoutInputHorizontal() {
if(_contentToGrowWith == null) {
return;
}
if(_usePreferredWidth) {
var width = LayoutUtility.GetPreferredWidth(_contentToGrowWith);
_preferredWidth = _preferredWidthMax > width ? width : _preferredWidthMax;
} else {
_preferredWidth = -1;
}
}
}
I achieved it by adding the following script to the Viewport:
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;
[RequireComponent(typeof(LayoutElement))]
public class AdaptablePreferredHeight : UIBehaviour, ILayoutElement
{
public float MaxHeight = 100;
public RectTransform ContentToGrowWith;
public int LayoutPriority = 10;
LayoutElement m_LayoutElement;
float m_Preferredheight;
public float minWidth => m_LayoutElement.minWidth;
public float preferredWidth => m_LayoutElement.preferredWidth;
public float flexibleWidth => m_LayoutElement.flexibleWidth;
public float minHeight => m_LayoutElement.minHeight;
public float preferredHeight => m_Preferredheight;
public float flexibleHeight => m_LayoutElement.flexibleHeight;
public int layoutPriority => LayoutPriority;
public void CalculateLayoutInputHorizontal()
{
if (m_LayoutElement == null)
{
m_LayoutElement = GetComponent<LayoutElement>();
}
}
public void CalculateLayoutInputVertical()
{
if(m_LayoutElement == null)
{
m_LayoutElement = GetComponent<LayoutElement>();
}
float contentHeight = ContentToGrowWith.sizeDelta.y;
if (contentHeight < MaxHeight)
{
m_Preferredheight = contentHeight;
}
else
{
m_Preferredheight = MaxHeight;
}
}
}
I would have just inherited LayoutElement instead of making it a RequireComponent, but then the inspector won't show my new MaxHeight and ContentToGrowWith variables.
The ContentToGrowWith variable points to the content object inside the ViewPort.
The final setup is:
Scroll View with an horizontal layout group Control Child Size: "Height". (no child force expand, so it can shrink with its children)
Viewport with LayoutElement and my script AdaptablePreferredHeight.
Content with ContentSizeFitterset on Vertical Fit: "Preferred Size"
Fairly new to Unity and have tried several solutions without success. I have read so many forum posts but none are working for me.
I have been attempting to snap game objects to other game objects at runtime (such as to equip ship parts to a ship). The script works as intended but the child objects continue to be scaled with the parent object.
I have tried attaching scaling scripts that dynamically scale the child object based on the parent object scale. I've tried stepping through the logical way one would go about doing this. I have tried hierarchal fixes using empty game objects and nothing seems to be maintaining a constant global scale for my child objects. Maybe someone can see something I am missing.
Please ignore other issues that may be in the code at this point unless they directly are affecting the scaling issue. But suggestions are welcome. Interested in learning all I can.
-Hierarchy-
Ship (scale (2,2,2)) parent of the parent
-Empty Object (scale (0.5,0.5,0.5)) this is the parent set to the same scale as the snappable object
public class Interactable : MonoBehaviour
{
public Transform pickUpDestination;
public Transform snapDestination;
public Vector3 snapPointPos;
private bool isSnapped;
public bool isClicked;
public float unsnapMouseDist = 0.5f;
public float distCheck;
private void FixedUpdate()
{
distCheck = Vector3.Distance(pickUpDestination.position, snapPointPos);
if (distCheck >= unsnapMouseDist && isClicked)
{
isSnapped = false;
transform.SetParent(pickUpDestination);
transform.position = transform.parent.position;
Debug.Log("UNSNAPPED");
}
else if (distCheck <= unsnapMouseDist && isClicked)
{
isSnapped = true;
transform.SetParent(snapDestination, true);
transform.position = transform.parent.position;
Debug.Log("SNAPPED!");
}
if (transform.parent != null)
{
Debug.Log("Parent =" + transform.parent.name);
}
Debug.Log("Global Scale = " + transform.lossyScale);
Debug.Log("Local Scale = " + transform.localScale);
}
void OnMouseDown()
{
isClicked = true;
Debug.Log("Clicked = " + isClicked);
if (isSnapped == false)
{
GetComponent<Rigidbody>().useGravity = false;
GetComponent<Rigidbody>().isKinematic = true;
transform.SetParent(pickUpDestination);
}
}
void OnTriggerEnter(Collider snap)
{
if (snap.gameObject.CompareTag("Snap Point"))
{
snapPointPos = snap.transform.position;
snapDestination = snap.transform;
transform.rotation = snap.transform.rotation;
}
}
void OnMouseUp()
{
isClicked = false;
Debug.Log("Clicked = " + isClicked);
if (isSnapped == false)
{
this.transform.parent = null;
GetComponent<Rigidbody>().isKinematic = false;
GetComponent<Rigidbody>().useGravity = true;
}
}
}
And the Scaling Script:
public class Scaler : MonoBehaviour
{
public Transform parent;
private void FixedUpdate()
{
parent = transform.parent;
transform.localScale = new Vector3(transform.localScale.x / parent.localScale.x, transform.localScale.y / parent.localScale.y, transform.localScale.z / parent.localScale.z);
}
}
lossyScale is the combined effects of all parent scales on that object (some caveats but not likely to apply here).
localScale is the scale effect of only that object (the numbers you see in the transform inspector)
var trueScale == new Vector3(
desiredScale.x / parentTransform.lossyScale.x,
desiredScale.y / parentTransform.lossyScale.y,
desiredScale.z / parentTransform.lossyScale.z);
transform.localScale = trueScale;
I instantiate a number of prefabs and need to set the paths. The path nodes are between 1 and 3 sections long.
public GameObject mb;
void Start()
{
GameObject planeIcon;
foreach (PlaneData p in PlaneManager.planesData)
{
if(p.route != 0)
{
planeIcon = Instantiate(iconPrefab);
planeIcon.GetComponent<Image>().sprite = Resources.Load<Sprite>(p.planeFilename + "_icon");
planeIcon.name = p.planeName;
planeIcon.GetComponent<AnimatePlane>().moveSpeed = p.planeSpeed;
planeIcon.GetComponent<AnimatePlane>().range = p.planeRange;
planeIcon.GetComponent<AnimatePlane>().pathNode[0] = mb;
}
this throws an IndexOutOfRangeException: Index was outside the bounds of the array on the last line
The AnimatePlane code is attached to the prefab that is instantiated above
public class AnimatePlane : MonoBehaviour
{
public GameObject[] pathNode = new GameObject[3];
public float moveSpeed;
public int range;
// Use this for initialization
void Start()
{
Debug.Log("pathnode length: " + pathNode.Length);
CheckNode();
}
Debug output is show pathnode length = 0
I'm assuming I need to initialise the array somehow in the AnimatePlane code, but can't think what I'm missing.
thanks
Hi try this.
public GameObject mb;
void Start()
{
GameObject planeIcon;
foreach (PlaneData p in PlaneManager.planesData)
{
if(p.route != 0)
{
planeIcon = Instantiate(iconPrefab);
planeIcon.GetComponent<Image>().sprite = Resources.Load<Sprite>(p.planeFilename + "_icon");
planeIcon.name = p.planeName;
AnimatePlane plane = planetIcon.GetComponent<AnimatePlane>();
plane.moveSpeed = p.planeSpeed;
plane.range = p.planeRange;
plane.pathNode = new GameObject[3];
plane.pathNode[0] = mb;
}
With this "public GameObject[] pathNode = new GameObject[3];" the object pathNode is being overwrited with null value. You don't need to instantiate public GameObjects. You can set the size on Unity's inspector and add the objects there.
ScrollView performance is a real drag (get it?), especially on mobile platforms. I frequently found myself getting less that 15 fps which left the user experience feeling jolting and underwhelming. After a lot of research and testing I have compiled a checklist to drastically improve performance. I now get at least 30 fps, with the majority of the CPU time allocated to WaitForTargetFPS.
I hope this helps anyone who is also having trouble in this area. Optimization solutions are hard to come by. Feel free to use and modify any of my code here.
ONE:
.GetComponent<>() calls are inefficient, especially outside the editor. Avoid using these in any kind of Update() method.
TWO:
OnValueChanged() is called every frame that the ScrollView is being dragged. It is therefore in a sense equivalent to Update() so you should avoid using .GetComponent<>() calls in this method.
THREE:
Whenever any element on a Canvas is changed the entire Canvas must rebuild its batches. This operation can be very expensive. It is therefore recommended to split your UI elements across at least two Canvases, one for elements that are changed rarely or never and one elements that change often.
Whenever a ScrollView scrolls the entire Canvas it is on is dirtied. It is therefore recommended that you have each ScrollView on a separate Canvas.
Unity Canvas Rebuilds Explanation: https://unity3d.com/learn/tutorials/topics/best-practices/fill-rate-canvases-and-input?playlist=30089
FOUR:
EventSystem.Update() handles the input detection in a scene, using raycasts to filter through the hierarchy in order to find a component to accept this input. Therefore these calculations are only done when interacting with the scene, like when a ScrollView is being scrolled. Removing unnecessary RaycastTarget properties from graphics and text will improve this processing time. It mightn't make a big difference, but if you're not careful enough objects can make the input handling time really add up.
FIVE:
With any kind of mask component, even a RectMask2D, all objects in a ScrollView are batched and rendered. If you have a lot of elements in your ScrollView, it is recommended that you utilize some kind of pooling solution. There are many of these available on the app store.
Unity Pooling Explanation: https://unity3d.com/learn/tutorials/topics/best-practices/optimizing-ui-controls
If, however, your project is incompatible with this, needing persistent elements, I would recommend that you hide your off screen objects to reduce performance overhead. Transform.SetParent() and GameObject.SetActive() are both resource intensive methods, instead attach a CanvasGroup component to each element and adjust the alpha value to achieve the same effect.
Here is a static script to detect if an object is visible or not and to set the alpha accordingly:
using UnityEngine;
using UnityEngine.UI;
public class ScrollHider : MonoBehaviour {
static public float contentTop;
static public float contentBottom;
static public bool HideObject(GameObject givenObject, CanvasGroup canvasGroup, float givenPosition, float givenHeight) {
if ((Mathf.Abs(givenPosition) + givenHeight > contentTop && Mathf.Abs(givenPosition) + givenHeight < contentBottom) || (Mathf.Abs(givenPosition) > contentTop && Mathf.Abs(givenPosition) < contentBottom)) {
if (canvasGroup.alpha != 1) {
canvasGroup.alpha = 1;
}
return true;
} else {
if (canvasGroup.alpha != 0) {
canvasGroup.alpha = 0;
}
return false;
}
}
static public void Setup(Scroll givenScroll) {
contentTop = (1 - givenScroll.verticalNormalizedPosition) * (givenScroll.content.rect.height - givenScroll.viewport.rect.height);
contentBottom = contentTop + givenScroll.viewport.rect.height;
}
}
SIX:
Unity's built in ScrollRect component allows for broad, modular functionality. However, in terms of performance it can be noticeably slower than if you were to write your own. Here is a Scroll script that achieves the same ends, but only supports the vertical, clamped and inertia properties of Unity's ScrollRect.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.EventSystems;
public class Scroll : MonoBehaviour, IPointerDownHandler, IPointerUpHandler, IDragHandler, IScrollHandler {
private Camera mainCamera;
private RectTransform canvasRect;
public RectTransform viewport;
public RectTransform content;
private Rect viewportOld;
private Rect contentOld;
private List<Vector2> dragCoordinates = new List<Vector2>();
private List<float> offsets = new List<float>();
private int offsetsAveraged = 4;
private float offset;
private float velocity = 0;
private bool changesMade = false;
public float decelration = 0.135f;
public float scrollSensitivity;
public OnValueChanged onValueChanged;
[System.Serializable]
public class OnValueChanged : UnityEvent { }
[HideInInspector]
public float verticalNormalizedPosition
{
get
{
float sizeDelta = CaculateDeltaSize();
if (sizeDelta == 0) {
return 0;
} else {
return 1 - content.transform.localPosition.y / sizeDelta;
}
}
set
{
float o_verticalNormalizedPosition = verticalNormalizedPosition;
float m_verticalNormalizedPosition = Mathf.Max(0, Mathf.Min(1, value));
float maxY = CaculateDeltaSize();
content.transform.localPosition = new Vector3(content.transform.localPosition.x, Mathf.Max(0, (1 - m_verticalNormalizedPosition) * maxY), content.transform.localPosition.z);
float n_verticalNormalizedPosition = verticalNormalizedPosition;
if (o_verticalNormalizedPosition != n_verticalNormalizedPosition) {
onValueChanged.Invoke();
}
}
}
private float CaculateDeltaSize() {
return Mathf.Max(0, content.rect.height - viewport.rect.height); ;
}
private void Awake() {
mainCamera = GameObject.Find("Main Camera").GetComponent<Camera>();
canvasRect = transform.root.GetComponent<RectTransform>();
}
private Vector2 ConvertEventDataDrag(PointerEventData eventData) {
return new Vector2(eventData.position.x / mainCamera.pixelWidth * canvasRect.rect.width, eventData.position.y / mainCamera.pixelHeight * canvasRect.rect.height);
}
private Vector2 ConvertEventDataScroll(PointerEventData eventData) {
return new Vector2(eventData.scrollDelta.x / mainCamera.pixelWidth * canvasRect.rect.width, eventData.scrollDelta.y / mainCamera.pixelHeight * canvasRect.rect.height) * scrollSensitivity;
}
public void OnPointerDown(PointerEventData eventData) {
velocity = 0;
dragCoordinates.Clear();
offsets.Clear();
dragCoordinates.Add(ConvertEventDataDrag(eventData));
}
public void OnScroll(PointerEventData eventData) {
UpdateOffsetsScroll(ConvertEventDataScroll(eventData));
OffsetContent(offsets[offsets.Count - 1]);
}
public void OnDrag(PointerEventData eventData) {
dragCoordinates.Add(ConvertEventDataDrag(eventData));
UpdateOffsetsDrag();
OffsetContent(offsets[offsets.Count - 1]);
}
public void OnPointerUp(PointerEventData eventData) {
dragCoordinates.Add(ConvertEventDataDrag(eventData));
UpdateOffsetsDrag();
OffsetContent(offsets[offsets.Count - 1]);
float totalOffsets = 0;
foreach (float offset in offsets) {
totalOffsets += offset;
}
velocity = totalOffsets / offsetsAveraged;
dragCoordinates.Clear();
offsets.Clear();
}
private void OffsetContent(float givenOffset) {
float newY = Mathf.Max(0, Mathf.Min(CaculateDeltaSize(), content.transform.localPosition.y + givenOffset));
if (content.transform.localPosition.y != newY) {
content.transform.localPosition = new Vector3(content.transform.localPosition.x, newY, content.transform.localPosition.z);
}
onValueChanged.Invoke();
}
private void UpdateOffsetsDrag() {
offsets.Add(dragCoordinates[dragCoordinates.Count - 1].y - dragCoordinates[dragCoordinates.Count - 2].y);
if (offsets.Count > offsetsAveraged) {
offsets.RemoveAt(0);
}
}
private void UpdateOffsetsScroll(Vector2 givenScrollDelta) {
offsets.Add(givenScrollDelta.y);
if (offsets.Count > offsetsAveraged) {
offsets.RemoveAt(0);
}
}
private void LateUpdate() {
if (viewport.rect != viewportOld) {
changesMade = true;
viewportOld = new Rect(viewport.rect);
}
if (content.rect != contentOld) {
changesMade = true;
contentOld = new Rect(content.rect);
}
if (velocity != 0) {
changesMade = true;
velocity = (velocity / Mathf.Abs(velocity)) * Mathf.FloorToInt(Mathf.Abs(velocity) * (1 - decelration));
offset = velocity;
}
if (changesMade) {
OffsetContent(offset);
changesMade = false;
offset = 0;
}
}
}
A nice article explains that the default targetFrameRate might be responsible for the unsmooth scrolling behavior of the scrollView. This can be resolved via:
Application.targetFrameRate = 60; // or whatever you wish. 60 turned out enough for us
Of course this setting is only effective if you have resolved the performance issues (as it is nicely explained by Phedg1).
I'm writing a very simple class for storing data (at least, for now) for use in a Unity project to do with a learning AI. I want to be able to easily customize multiple agents in the inspector and the number of checkboxes stacked vertically makes this part of my project dominate the inspector. If I could simply have one section make use of the ample empty space on the right side of the inspector, that would be considerably less ugly.
I've been reading a lot about custom property drawers and inspector windows, but it seems like a lot of work, involving rewriting how the entire class is displayed, for one change.
For reference, here is the class itself.
[System.Serializable]
public class NNInfo
{
public string networkName = "Agent";
[Header("Movement Properties")]
public float movementSpeed = 10f;
public float rotationSpeed = 1f;
[Header("Learning Properties")]
public float learningRate = 0.1f;
public float momentum = 0f;
public Rewards rewardFunc;
public int[] hiddenLayers;
[Header("Other Properties")]
public int maxHealth = 1000;
[Header("Optional Inputs")]
public bool m_PointToNearestBall = false; // input which is 1 while the agent is facing a ball and -1 when facing away
public bool m_DistanceToNearestBall = false; // input which is 1 while the agent is at the ball and -1 when at max possible distance away
public bool m_PointToEndzone = false; // similar to m_PointToNearestBall but for the endzone
public bool m_Position = false; // two inputs which inform the player of its normalized x and y coordinates on the field
public bool m_WhetherHoldingBall = false; // tells the player whether its holding a ball
public bool m_CanSeeHealth = false; // Whether the player can know its own health
[Header("Optional Outputs")]
public bool m_ForwardLeft = false; // turn left and move forward simultaneously
public bool m_ForwardRight = false; // turn right and move forward simultaneously
public bool m_Reverse = false; // move backwards
public bool m_Flip = false; // instantly switch to opposite direction
public bool m_TurnToBall = false; // instantly turn to face nearest ball
public bool m_TurnToLeft = false; // instantly turn to face left side of field
public bool m_Attack = false; // attack a player (or idle if no contact)
}
Custom Property Drawer is keyword which should be interesting for you.
Writing your own code for managing those - lets you describe how you want to show your properties inside Inspector View in Unity editor.
To start, go to official documentation site, which contains code you can base on.
Code snippets (Javacrpt, c# version can be found under the link):
Object's code:
enum IngredientUnit { Spoon, Cup, Bowl, Piece }
// Custom serializable class
class Ingredient extends System.Object {
var name : String;
var amount : int = 1;
var unit : IngredientUnit;
}
var potionResult : Ingredient;
var potionIngredients : Ingredient[];
function Update () {
// Update logic here...
}
Editor code:
#CustomPropertyDrawer(Ingredient)
class IngredientDrawer extends PropertyDrawer {
// Draw the property inside the given rect
function OnGUI (position : Rect, property : SerializedProperty, label : GUIContent) {
// Using BeginProperty / EndProperty on the parent property means that
// prefab override logic works on the entire property.
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 amountRect = new Rect (position.x, position.y, 30, position.height);
var unitRect = new Rect (position.x+35, position.y, 50, position.height);
var nameRect = new Rect (position.x+90, position.y, position.width-90, position.height);
// Draw fields - passs GUIContent.none to each so they are drawn without labels
EditorGUI.PropertyField (amountRect, property.FindPropertyRelative ("amount"), GUIContent.none);
EditorGUI.PropertyField (unitRect, property.FindPropertyRelative ("unit"), GUIContent.none);
EditorGUI.PropertyField (nameRect, property.FindPropertyRelative ("name"), GUIContent.none);
// Set indent back to what it was
EditorGUI.indentLevel = indent;
EditorGUI.EndProperty ();
}
}