January 17, 2019

So last week I saw someone that wanted a option in the unity GUI to “save” it during runtime. While a normal save would be a rather dangerous feature in development. (Missclicks happen sooo often trust me) I understand the advantages to see your GUI in different situations and then able to adjust it accordingly would be a neat thing.

Long story short here is a script just paste it in an “Editor” folder and you will have a button on your canvas to save a clone into “Assets/”.

Cheers,
Rudolf

PS: Sorry somehow my pretty print plugin for the code is broken gotta fix that later 😉

/*********************
* Copyright (c) 2016, Rudolf Chrispens.  All rights reserved.
* found on http://www.rudolf-chrispens.de/posts/
* Copyrights licensed under the GNU License.
* https://www.gnu.org/licenses/gpl-3.0
***********************/
using UnityEngine;
using System.Collections;
using UnityEditor;
using UnityEngine.UI;

[CustomEditor(typeof(Canvas))]
public class UISafeCopyInPlaymode : Editor 
{
    //Note this script will override the prefab if it exists already.
    string uiPath = "Assets/";
    string cloneName = "will be set automaticaly";

    public override void OnInspectorGUI()
    {
        DrawDefaultInspector ();

        if(GUILayout.Button("Save UI during Playmode!")){
            cloneTheUI();
        }
    }

    void cloneTheUI(){
        Canvas myTarget = (Canvas)target;
        cloneName = myTarget.transform.name+".prefab";
        PrefabUtility.CreatePrefab(uiPath+cloneName, myTarget.gameObject);
        Debug.Log("Saved here: " + uiPath+cloneName);
    }
}
October 21, 2016

So last week I used one of my old scripts where the properties would be hidden when a bool was set to a specific value.

And then I thought let’s make it a little but more fancy under the hood.

But before I go on with my story, here is the final script.

 

It’s a propertydrawer that can “watch” over other properties and when the values of those properties change to a specific value, it shows/hides the taged propertiefield.

In this example I use the bool UsePhysics as reference. (But you can also use int, float, double, string, enum)

  • Show on ‘true’

ShowIf_1_0_true

  • Hide on ‘false’

ShowIf_1_0_true

Simple right? Perfect for small scripts with some different settings. No need to write custom editors for this feature anymore. 🙂

  • PropertyDrawer
/*********************
* Copyright (c) 2016, Rudolf Chrispens.  All rights reserved.
* found on http://www.rudolf-chrispens.de/posts/
* Copyrights licensed under the GNU License.
* https://www.gnu.org/licenses/gpl-3.0
***********************/

using UnityEngine;
using UnityEditor;
using System;
using Object = System.Object;
using System.Collections;

[CustomPropertyDrawer(typeof(ShowIfAttribute))]
public class ShowIfDrawer : PropertyDrawer
{
    bool show = true;
    bool typeNotSupported = false;
    ShowIfAttribute hideINAttribute;
    SerializedProperty refProperty;
    
    float infoButtonWidth = 20f;
    Color storedBGColor = GUI.backgroundColor;
    Color customColor = new Color(0.639215f, 0.96078f, 1.0f);

    public override float GetPropertyHeight(SerializedProperty _Property, GUIContent _Label)
    {
        hideINAttribute = (ShowIfAttribute)attribute;

        //Search for refProperty
        if (hideINAttribute.ParentName != "")
        {
            //incapsulated
            refProperty = GetCapsulatedProperty(_Property, hideINAttribute);
        }
        else
        {
            //same depth
            refProperty = _Property.serializedObject.FindProperty(hideINAttribute.Name);
        }

        if (refProperty != null)
        {
            show = ReturnValue(refProperty, hideINAttribute.matchValue);
        }

        if (show)
        {
            return base.GetPropertyHeight(_Property, _Label);
        }
        else
            return -1f;
    }

    public override void OnGUI(Rect _Position, SerializedProperty _Property, GUIContent _Label)
    {
        if (refProperty == null)
        {
            EditorGUI.HelpBox(_Position, "Could not find: '" + hideINAttribute.Name + "' reference property missing!", MessageType.Error);
        }
        else if (typeNotSupported)
        {
            EditorGUI.HelpBox(_Position, "Type of '" + refProperty.name + "' (" + refProperty.type + ") is not supported!", MessageType.Error);
        }
        else
        {
            if (show)
            {
                GUI.backgroundColor = customColor;
                EditorGUI.indentLevel = 1;
                EditorGUI.PropertyField(new Rect(_Position.x, _Position.y, _Position.width - infoButtonWidth, _Position.height), _Property, _Label, true);
                if (GUI.Button(new Rect(_Position.x + _Position.width - infoButtonWidth, _Position.y, infoButtonWidth, _Position.height), "i"))
                {
                    if (!hideINAttribute.Revert)
                    {
                        Debug.Log("This property will be visible if ("+ refProperty.name + ")== " + hideINAttribute.matchValue);
                    }
                    else
                    {
                        Debug.Log("This property will be hidden if (" + refProperty.name + ") == " + hideINAttribute.matchValue);
                    }
                }
                GUI.backgroundColor = storedBGColor;
            }
        }
    }

    public SerializedProperty GetCapsulatedProperty(SerializedProperty _PropertyOfAttribute, ShowIfAttribute _Attribute)
    {
        SerializedProperty refPropertyHolder = _PropertyOfAttribute.serializedObject.FindProperty(_Attribute.ParentName);

        if(refPropertyHolder == null)
        {
            return null;//didnt find an array
        }

        //search for refProperty array
        for(int i=0; i< refPropertyHolder.arraySize; i++)
        {
            if (refPropertyHolder.GetArrayElementAtIndex(i).FindPropertyRelative(_Attribute.Name).propertyPath == _PropertyOfAttribute.propertyPath)
            {
                return refPropertyHolder.GetArrayElementAtIndex(i).FindPropertyRelative(_Attribute.Name);
            }
        }

        return null; //didnt find the right property in the array
    }

    public bool ReturnValue(SerializedProperty _RefProp, System.Object _CompareValue)
    {
        bool drawProperty = false;
        typeNotSupported = false;

        switch ( _RefProp.type.ToLower() )
        {
            default: typeNotSupported = true; return true;
            case "bool"     : if (_RefProp.boolValue        == (bool)_CompareValue) drawProperty = true;    else drawProperty = false; break;
            case "int"      : if (_RefProp.intValue         == (int)_CompareValue) drawProperty = true;     else drawProperty = false; break;
            case "float"    : if (_RefProp.floatValue       == (float)_CompareValue) drawProperty = true;   else drawProperty = false; break;
            case "double"   : if (_RefProp.doubleValue      == (double)_CompareValue) drawProperty = true;  else drawProperty = false; break;
            case "string"   : if (_RefProp.stringValue      == (string)_CompareValue) drawProperty = true;  else drawProperty = false; break;
            case "enum"     : if (_RefProp.enumValueIndex   == (int)_CompareValue) drawProperty = true;     else drawProperty = false; break;
        }

        if (hideINAttribute.Revert)
            return !drawProperty;
        else
            return drawProperty;
    }
}
  • Attribute
using UnityEngine;
using System;
using Object = System.Object;

[AttributeUsage(AttributeTargets.Field, Inherited = true, AllowMultiple = false)]
public class ShowIfAttribute : PropertyAttribute
{
    //the name of the attribute field
    public string Name
    {
        get; set;
    }

    public Object matchValue
    {
        get; set;
    }

    public string ParentName
    {
        get; set;
    }

    public bool Revert
    {
        get; set;
    }

    /// <summary>
    /// Short version.
    /// </summary>
    public ShowIfAttribute(string _PropertyName, Object _HideOnThisValue, bool _Revert = false)
    {
        Name = _PropertyName;
        matchValue = _HideOnThisValue;
        ParentName = "";
        Revert = _Revert;
    }

    /// <summary>
    /// Long version - Use this if the referenced property is capsulated.
    /// </summary>
    public ShowIfAttribute(string _PropertyName, Object _HideOnThisValue, string _RefParent, bool _Revert)
    {
        Name = _PropertyName;
        matchValue = _HideOnThisValue;
        ParentName = _RefParent;
        Revert = _Revert;  
    }

    //Full list on:
    //https://msdn.microsoft.com/en-us/library/aa664615(v=vs.71).aspx
    public enum SupportedTypes
    { 
        Bool = 0,
        Int,
        Float,
        Double,
        String,
        Enumeration
    }
}
  • Use like:
public bool UsePhysics = true;

        [ShowIf("UsePhysics", true)]
        public Rigidbody Rigid = null;
        [ShowIf("UsePhysics", true)]
        public Collider Col = null;
        [ShowIf("UsePhysics", true)]
        public float Force = 15f;

        [ShowIf("UsePhysics", false)]
        public CharacterController CharControl = null;

Feel free to give feedback. I will update my scripts!

 

  • And here the short story of this long road ( for me) for this little script that I already had finished (hypotheticaly):

So at the start of my post I was talking about the things I learned and I mentioned something “fancy”:

Well long story short there is nothing realy fancy in this script. I wanted to write this script so you could use all types, without specifing anything but a script for the referenced property.

But I forgott several things:

  • 1 Attributes only support some basic types ( Yeah pretty heavy mistake from me there :’D )
  • 2 System.Type.GetType(string _Str) is assembly dependend
  • 3 Using reflection is soooo slooow

Well, those where my mistakes and yeah if you (and I) look at them now, I guess they are obvious but it was late that day (approx 1am) and I overlooked that. Also I was so interested in automatic type casting and equality checks. And I learned something so let me share my steps and feel free to laugh and smile about the following. 😀

 

Unities SerializedProperties are evil beings that hide they true nature. Why? Because there is no way to cast it to any usable object, and only evil beings lurk in the shadows and hide their true self. Even unitys getter just throws an error for you with this:

refProperty.objectReferenceValue;

So casting the object itself is not possible, because we cant reach it. And therefore we can only use the types the SerializedObject delivers you with their getters. (At this point you might see how foolish this approach was because of the limitation of System.Attribute parameters! 😀 )

 

So I had this basic script drawer for boleans with some if() checks and wanted to make it fancy. Compare some objects or at least create a table automaticaly. So I searched on the msdn and found this:

public static Type GetType(
	string typeName
)

https://msdn.microsoft.com/de-de/library/w3f99sx1(v=vs.110).aspx

At this stage I searched two minutes and thought easy. I rewrote the script and added my check and found out that I would always get null. Except for Vector2 (and some others)! But why?

Thurder down the road and like 30 min later (Most of it try and error, I tend to google realy late I love to try my things on my own first) I found this post talking about the unity assemblies and that you wont be able to find the types you are searching for and thats the reason for null. Unity hase several different passes for those of you who are interested about that read here: http://answers.unity3d.com/questions/206665/typegettypestring-does-not-work-in-unity.html

Since writing a wraper or (using the one provided in the link above) was a little over the top

I though of getting all of the required data via .fieldinfo and some reflection magic. After I got everything working I noticed a realy long delay when using the editor.

At this point I realised that this is a little to much “magic” for such a simple usecase, and reverted my script back to a switch construction. ( like the one above but with a system.type in the attribute for the checks).

 

This was the moment when I realised that I coudn’t use complex objects with an Attribute. ( I mean I knew that I just forgott!! 🙂 )

But I learned some small things about unity’s assemblies, build passes and reflection, I mean it’s something right? 😀

 

So this was my short story that took long me to damn long… I think I worked on this like 2 or 3 hours.

Sorry, for the missing screenshots I didn’t take any on my “tour”. Maybe I will recreate some but not right now.

Thanks for reading.

October 6, 2016

Everyone working with a big team in Unity knows that if a script is finished the work is not.

So in our small team there where several situations where I would code something and let Properties [Serializable] or [public] so that the gamedesigners could change targets or other settings.

I guess flexibility and safety are the keyword here. So I thought: “ok there should be an easy and reusable solution”!

Here you go:

AutoAssignAttribute

This Custom Attribute and PropertyDrawer show the gamedesigners that the script will take its references automatically, but the designer can still change it manually if he needs it! Init your Properties in Start() or Awake() with an if(prop == null) statement. 😉

The code is below feel free to use it, GNU licence. 🙂

PropertyDrawer

/*********************
* Copyright (c) 2015, Rudolf Chrispens.  All rights reserved.
* found on http://www.rudolf-chrispens.de/posts/
* Copyrights licensed under the GNU License.
* https://www.gnu.org/licenses/gpl-3.0
***********************/

#region USE
using UnityEngine;
using UnityEditor;
#endregion

[CustomPropertyDrawer(typeof(AutoAssignAttribute))]
[SerializeField]
public class AutoAssignDrawer : PropertyDrawer
{
    float buttonWidth   = 70f;
    AutoAssignAttribute autoAssignAttribute = null;

    float infoButtonWidth = 20f;
    Color customColor = new Color(0.639215f, 0.96078f, 1.0f);
    Color storedBGColor = GUI.backgroundColor;

    public override void OnGUI(Rect _Position, SerializedProperty _Property, GUIContent _Label)
    {
        //button width
        EditorGUIUtility.labelWidth = EditorGUIUtility.labelWidth - buttonWidth;

        //get attribute
        autoAssignAttribute = (AutoAssignAttribute)base.attribute;

        GUI.backgroundColor = customColor;

        Rect pos1 = new Rect (_Position.x, _Position.y, buttonWidth, _Position.height);
        Rect pos2 = new Rect (_Position.x + buttonWidth, _Position.y, _Position.width - buttonWidth - infoButtonWidth, _Position.height);

        if (autoAssignAttribute.Auto && _Property.objectReferenceValue == null)
        {
            //Custom Auto Button
            //default assignable property
            if(GUI.Button(pos1, "Auto"))
            {
                autoAssignAttribute.Auto = false;
            }

            string propType = _Property.type.Remove(0, 5);
            EditorGUI.LabelField(pos2, _Property.displayName, propType.Remove(propType.Length-1));
        }
        else
        {
            //default assignable property
            if (GUI.Button(pos1, "Manual"))
            {
                autoAssignAttribute.Auto = true;
                _Property.objectReferenceValue = null;
            }
            EditorGUI.PropertyField(pos2, _Property);
        }

        //info button
        if (GUI.Button(new Rect(_Position.x + _Position.width - infoButtonWidth, _Position.y, infoButtonWidth, _Position.height), "i"))
        {
            if (autoAssignAttribute.Auto)
            {
                Debug.Log("This property will be set automatically on initialization.\nBut you can force a custom assignment.");
            }
            else
            {
                Debug.Log("This property is set manually to a referenced object.\nBut you can switch back to automatic initialization.");
            }
        }

        GUI.backgroundColor = storedBGColor;
    }
}

Attribute

#region USE
using UnityEngine;
using System;
#endregion

[AttributeUsage(AttributeTargets.Field, Inherited = true, AllowMultiple = false)]
public class AutoAssignAttribute : PropertyAttribute
{
    private bool auto = true;

    public bool Auto
    {
        get { return auto; }
        set { auto = value; }
    }
}

Using this Attribute is easy:

[AutoAssign]
 public Transform MyTransform = null;
 [AutoAssign]
 public GameObject MyGameObject = null;
 [AutoAssign]
 public MonoBehaviour MyBehaviour = null;

 

UPDATE 1.01:

Changed the color of my custom property drawers. And I will add a info button to all of my drawers. Got this idea from @Kalladystine thanks for your review!

AutoAssign_1_1

With the “i” button you will get some information about the drawer.

AutoAssign_1_1_log