Jelly Meshdeformation Part III – Editor Scripting

We’ve reached the final part of this tutorial series where we are going to write a fancy custom editor that will do all the work for us.

What we are trying to achieve here is a custom editor window that will list us every object that would be suitable to be jellified. It also allows us to decide if we want to use the volume or if we want custom manual settings to be set for all of our jellied objects. We will have a nice preview window and some buttons.

Here is what you will receive by following this tutorial:

JellyfierEditor:

When writing custom editors it is commonly known to be good practice to create a folder named Editor which will hold all of your Editor scripts.

Please do that now and create a new C# Script JellyfierEditor.cs – in the following snippet you’ll see how every editor window script needs to start out.

You need to implement the UnityEditor namespace and you need to inherit from EditorWindow. The MenuItem will create a folder structure within the Editor for you. The ShowWindow() function is what is going to open the actual window and everything that we are adding to OnGUI() is going to be the content of our window.

using UnityEngine;
using UnityEditor;

public class JellyfierEditor : EditorWindow 
{
  [MenuItem("itsKristin/Jellyfier")]
  public static void ShowWindow()
  {
    JellyfierEditor window = GetWindow<JellyfierEditor>("Jellyfier");
    window.position = new Rect(20f,80f,550f,500f);
  }

  void OnGUI() 
  {
    
  }
}

Here is a gif to visualize the expected outcome of the above-mentioned code.

Now, let’s get into the fun stuff and actually draw some content into our window. If you have been using Unity before they implemented the new UI System (I think in 5.x) you are in luck because you might remember some of the syntax from back then, which coincidentally is what we need to use here.

As you’ll see I am going to be needing some cached variables that will help me to draw my window. The is an int for editor mode and an array of strings named editorModes – I am using them to create tabs within my editor window. I want to have one tab that will display all the prefabs that are currently jellied and one that can display all the prefabs that would be suitable to be jellied.

I am also caching the same subset of things for the setup type which is going to help me create some kind of toggle later one.

This first part of the code includes the drawing and the placement of my tabs as well as adding some sort of description and a header.

using UnityEngine;
using UnityEditor;
using System.Collections.Generic;

public class JellyfierEditor : EditorWindow 
{
  int editorMode;
  int setupType;

  string[] editorModes = new string[] {"Jellied Objects", "Unjellied Objects"};
  string[] setupTypes = new string[] {"Manually","Automatic"};

  List<string> usedPrefabs = new List<string>();
  List<string> possiblePrefabs = new List<string>();

  GameObject previewGameObject;
  Editor previewEditor;

  Vector2 usedPrefabsScroll;
  Vector2 possiblePrefabsScroll;	

  [MenuItem("itsKristin/Jellyfier")]
  public static void ShowWindow()
  {
    JellyfierEditor window = GetWindow<JellyfierEditor>("Jellyfier");
    window.position = new Rect(20f,80f,550f,500f);
  }

  void OnGUI() 
  {
    GUILayout.Label("itsKristin's Jellyfier", EditorStyles.boldLabel);
    GUILayout.Space(10);

    GUILayout.Label("Browse through the prefabs in your Project!", 
    EditorStyles.label);
    GUILayout.Label("Decide weather or not you want them to be jellyfied.", 
    EditorStyles.label);
    GUILayout.Space(15);

    int previousPadding = GUI.skin.window.padding.bottom;
    GUI.skin.window.padding.bottom = -20;

    Rect windowRect = GUILayoutUtility.GetRect(1f,17f);
    windowRect.x += 4f;
    windowRect.width -= 7f;

    editorMode = GUI.SelectionGrid(windowRect,editorMode,editorModes,2,"window");
    GUI.skin.window.padding.bottom = previousPadding;
  }
}

JelliedObjects:

We are going to start working on our Jellied Object tab but first of all, we need to populate our two lists of strings that keep track of our used and possible prefabs. In order to do so, we are going to add the following function to our script. In here we are caching all of the AssetPaths of our project using AssetDataBase.GetAllAssetPaths(). We then filter them for “.prefab” which is the file ending of Prefabs within Unity. Following this is a for-loop that will iterate over ever entry trying to cast it to a GameObject and then checking its components.

void GetPrefabs()
  {
    string[] assetPaths = AssetDatabase.GetAllAssetPaths();
    List<string> prefabPaths = new List<string>();

    for(int i = 0; i < assetPaths.Length; i++)
    {
      if(assetPaths[i].Contains(".prefab"))
      {
        prefabPaths.Add(assetPaths[i]);
      }
    }

    if(prefabPaths.Count != 0)
    {
      for(int j = 0; j < prefabPaths.Count; j++)
      {
        Object obj = AssetDatabase.LoadMainAssetAtPath(prefabPaths[j]);
        GameObject go;

        try
        {
          go = (GameObject) obj;
          if(go.GetComponent<JellyBody>() || 
          go.GetComponentInChildren<JellyBody>())
          {
            if(!usedPrefabs.Contains(prefabPaths[j]))
            {
              usedPrefabs.Add(prefabPaths[j]);
            }
          }
          else if (go.GetComponent<MeshFilter>() || 
          go.GetComponentInChildren<MeshFilter>())
          {
            if(!possiblePrefabs.Contains(prefabPaths[j]))
            {
              possiblePrefabs.Add(prefabPaths[j]);
            }
          }
        } 
        catch
        {
          Debug.LogError("Prefab " + prefabPaths[j] + 
          " doesn't cast to GameObject!"); 
        }
      }
    }
  }

Let’s get some actual content in our tab. Now, we should have a system in place that will warn us f.e. if we don’t have any prefabs in our project or if we just haven’t jellied anything yet, we also want a preview window at the very top that will display the item we have selected, as well as some fields for changing settings on the selected prefabs, as we want to allow ourselves to modify all of them individually. Right underneath the preview window and the settings I want to display a list of all of our jellied objects, so we can browse through them, select them to see their preview and eventually change their settings or even unjellyfy them.

And as this is way to hard to explain, just look at the gif.

The following code snippet contains a switch statement, which is being used in order to switch between the tabs. The first case (case 0) is going to be our Jellyfied Objects tab.
This code belongs in our OnGUI() function.

switch(editorMode)
    {
      case 0:
      if(usedPrefabs.Count == 0 && possiblePrefabs.Count == 0)
      {
        GUILayout.Label("Your project doesn't seem to have any suitable Prefabs yet!", 
        EditorStyles.boldLabel);
      }
      else if(usedPrefabs.Count == 0 && possiblePrefabs.Count != 0)
      {
        GUILayout.Label("There are no jellied prefabs yet.",
        EditorStyles.boldLabel);
      }
      else 
      {
        GUILayout.Label("Preview:", EditorStyles.boldLabel);

        if(previewGameObject != null)
        {
          if(previewGameObject.GetComponent<JellyBody>())
          {
            previewJelly = previewGameObject.GetComponent<JellyBody>();
          } else if (previewGameObject.GetComponentInChildren<JellyBody>())
          {
            previewJelly = previewGameObject.GetComponentInChildren<JellyBody>();
          }

          if(previewEditor == null)
          {
            previewEditor = Editor.CreateEditor(previewGameObject);
          }
          previewEditor.OnInteractivePreviewGUI(GUILayoutUtility.GetRect(256f,256f),GUIStyle.none);
        }

        GUILayout.Label("Jelly Settings:",EditorStyles.boldLabel);
        using (new EditorGUI.DisabledScope(!previewGameObject))
        {
          GUILayout.Label("Jelly Consistency:", EditorStyles.boldLabel);
          GUILayout.Label("REMEBER: If you toggle automatic setup all personalized settings will be ignored!",
          EditorStyles.label);
          setupType = (JellyManagement.UseManualSettings) ? 0 : 1;
          setupType = GUILayout.SelectionGrid(setupType,setupTypes,2,"Toggle");
          JellyManagement.UseManualSettings = (setupType == 0) ? false : true; 

          using (new EditorGUI.DisabledScope(setupType == 0))
          {
            if(previewJelly)
            {
              GUILayout.Label("Physics Settings:", EditorStyles.boldLabel);
              previewJelly.ReactToGravity = EditorGUILayout.Toggle("React to Gravity", 
              previewJelly.ReactToGravity);
              previewJelly.AllowRotation = EditorGUILayout.Toggle("Allow Rotation", 
              previewJelly.AllowRotation);
              GUILayout.Space(10);
              previewJelly.Stiffness = EditorGUILayout.FloatField("Stiffness", 
              previewJelly.Stiffness);
              previewJelly.Attenuation = EditorGUILayout.FloatField("Attenuation", 
              previewJelly.Attenuation);
            }
          }
          
        }
  
        GUILayout.Space(10);
        GUILayout.Label("Jellyfied Prefabs:",EditorStyles.boldLabel);
        usedPrefabsScroll = GUILayout.BeginScrollView(usedPrefabsScroll);
        for(int i = 0; i < usedPrefabs.Count; i++)
        {
          GUILayout.BeginVertical();
          GUILayout.Label(PrefabName(usedPrefabs[i]), EditorStyles.miniBoldLabel);
          GUILayout.Label(usedPrefabs[i], GUILayout.Width(position.width/2));

          GUILayout.BeginHorizontal();

          if(GUILayout.Button("Select",GUILayout.Width(position.width/2-10)))
          {
            Selection.activeObject = AssetDatabase.LoadMainAssetAtPath(usedPrefabs[i]);
            previewGameObject = (GameObject) AssetDatabase.LoadMainAssetAtPath(usedPrefabs[i]);
            previewEditor = Editor.CreateEditor(previewGameObject);
            previewEditor.OnInteractivePreviewGUI(GUILayoutUtility.GetRect(256,256),
            GUIStyle.none);
          }
          if(GUILayout.Button("Unjellify", GUILayout.Width(position.width/2-10)))
          {
            Selection.activeObject = AssetDatabase.LoadMainAssetAtPath(usedPrefabs[i]);
            previewGameObject = (GameObject) AssetDatabase.LoadMainAssetAtPath(usedPrefabs[i]);
            Unjellify(previewGameObject);
            GetPrefabs();
            Repaint();
            previewGameObject = null;							
          }
          
          GUILayout.EndHorizontal();
          GUILayout.EndVertical();
        }
        GUILayout.EndScrollView();
      }
      
      break;

I am using two functions in the last snippet you inspected that you haven’t seen yet. Repaint() which is part of Unity’s EditorWindow namespace – we are using it to redraw our window after unjellifying one of the objects of the list. This gives us room to actually remove the object from the list and it allows our list to always be up to date.

And there is the Unjellify() function, in here we are basically removing the JellyBody component from a gameObject.

void Unjellify(GameObject _object)
  {
    Component component = null;
    if(_object.GetComponent<JellyBody>()){
      component = _object.GetComponent<JellyBody>();

    } else if (_object.GetComponentInChildren<JellyBody>()){
      component = _object.GetComponentInChildren<JellyBody>();
    }

    if(component != null)
    {
      DestroyImmediate(component,true);
    }
  }

 

Unjellied Objects:

Our second tab is going to be much easier to do. Here we will only generate a scrollrect of objects that have a meshfilter component attached to them but no JellyBody.

We will add a button to each entry of our list that will allow us to turn the object into a jelly by adding the component onto the game object.

As you can see here – our second tab is going to be pretty identical except for the fact that we won’t display any settings and that our second button is going to turn the object to jelly.

 

It almost seems a bit redundant to show you this snippet as we are literally repeating what we just did, except for the fact that we are iterating over another list, but for the sake of completion, the next snipped will explain the second tab of our editor window.

case 1:
      if(usedPrefabs.Count == 0 && possiblePrefabs.Count == 0)
      {
        GUILayout.Label("Your project doesn't seem to have any suitable Prefabs yet!", 
        EditorStyles.boldLabel);
      }
      else if(usedPrefabs.Count != 0 && possiblePrefabs.Count == 0)
      {
        GUILayout.Label("There are no suitable prefabs that aren't jellyfied.",
        EditorStyles.boldLabel);
      }
      else 
      {
        GUILayout.Label("Preview:", EditorStyles.boldLabel);

        if(previewGameObject != null)
        {
          if(previewEditor == null)
          {
            previewEditor = Editor.CreateEditor(previewGameObject);
          }
          previewEditor.OnInteractivePreviewGUI(GUILayoutUtility.GetRect(256f,256f),
          GUIStyle.none);
        }


        GUILayout.Space(10);
        GUILayout.Label("Suitable Prefabs:",EditorStyles.boldLabel);
        usedPrefabsScroll = GUILayout.BeginScrollView(possiblePrefabsScroll);
        for(int i = 0; i < possiblePrefabs.Count; i++)
        {
          GUILayout.BeginVertical();
          GUILayout.Label(PrefabName(possiblePrefabs[i]), EditorStyles.miniBoldLabel);
          GUILayout.Label(possiblePrefabs[i], GUILayout.Width(position.width/2));

          GUILayout.BeginHorizontal();

          if(GUILayout.Button("Select",GUILayout.Width(position.width/2-10)))
          {
            Selection.activeObject = AssetDatabase.LoadMainAssetAtPath(possiblePrefabs[i]);
            previewGameObject = (GameObject) AssetDatabase.LoadMainAssetAtPath(possiblePrefabs[i]);
            previewEditor = Editor.CreateEditor(previewGameObject);
            previewEditor.OnInteractivePreviewGUI(GUILayoutUtility.GetRect(256,256),
            GUIStyle.none);
          }
          if(GUILayout.Button("Jellify", GUILayout.Width(position.width/2-10)))
          {
            Selection.activeObject = AssetDatabase.LoadMainAssetAtPath(possiblePrefabs[i]);
            previewGameObject = (GameObject) AssetDatabase.LoadMainAssetAtPath(possiblePrefabs[i]);
            Jellify(previewGameObject);
            GetPrefabs();
            Repaint();
            previewGameObject = null;							
          }
          
          GUILayout.EndHorizontal();
          GUILayout.EndVertical();
        }
        GUILayout.EndScrollView();
      }
      break;

And this is the function I am using to Add the JellyBody component to my gameobject that is being called by pressing the Jellify Button.

void Jellify(GameObject _object)
  {
    if(_object.GetComponent<MeshFilter>()){
      _object.AddComponent<JellyBody>();
    } else if (_object.GetComponentInChildren<MeshFilter>())
    {
      MeshFilter component = _object.GetComponentInChildren<MeshFilter>();
      component.gameObject.AddComponent<JellyBody>();
    }
  }

 

Get the Repository on GitHub!

 

 

 

 

 

 

Liked it? Take a second to support Kristin on Patreon!

3 comments

  1. Thanks for your write-up. I also believe that laptop computers are getting to be more and more popular currently, and now tend to be the only type of computer found in a household. This is due to the fact that at the same time actually becoming more and more economical, their working power keeps growing to the point where there’re as powerful as desktop computers coming from just a few years back.

  2. Its like you read my mind! You appear to know a lot about this, like you wrote the book in it or something. I think that you can do with a few pics to drive the message home a bit, but instead of that, this is wonderful blog. A great read. I will definitely be back.

Leave a Reply

Your email address will not be published. Required fields are marked *