Mar 19 10

FAFF cleanup: Sketch

by AngryAnt

As some of you might have read, at Unity we have a thing called FAFF – very much the same as Google and other IT businesses are doing. It generally lets you step out of the bit stream for a day and just, during work hours, go with whatever crazy idea(s) you’ve been throwing around.

Some of these ideas are pushed into the product later and others are just filed under “something to play with when bored” and “now I can stop wondering about that”. Sketch was a micro FAFF project I just remembered I had filed under “this was fun” and then forgot all about. Since then I’ve continued work on other projects and started new ones – in all likelihood I’ll not return to this project.

So I figured I’d push it to github and let you guys take it for a spin. If nothing else, it’s a nice demo of what you can do with Unity editor scripting.

The idea of Sketch is to have simple tools inside the Unity editor to modify meshes in your assets. My goal with the project was never to turn Unity into a mesh authoring environment, but rather to give people an extra tool for rapid prototyping.

So what’s in there now? The simplest I could think of: Triangle selection and movement.

    Workflow:

  1. Go to the GameObject->Create Other menu and click Sketch Cube.
  2. With the cube selected, click a triangle and with the handle that appears, drag it around to modify the mesh.
  3. GOTO 1.

Alternatively, you could drag in your own mesh from the Assets folder (make sure that it generates a mesh collider in its import settings) and attach the Sketch script to it.

    NOTICE: Sketch modifies the imported asset. This means that:

  • If you modify a some of the basic geometry meshes (such as GameObject->Create Other->Cube), the changes will apply to all cubes until editor relaunch.
  • If the geometry you’re modifying is based on an imported 3D asset, reimporting it will mean you loose your changes.

Structure-wise, Sketch consists of two components: A MonoBehaviour (Sketch/Sketch.cs) and a custom inspector (Sketch/Editor/SketchEditor.cs). The purpose of the MonoBehaviour is merely to do some bookkeeping and provide utility functions (it will even destroy itself at runtime) while the custom inspector holds the logic for doing the modifications.

Sketch

Woah. That’s a lot of talk. Here’s the repository – go nuts: http://github.com/AngryAnt/Sketch

Mar 15 10

Building a menu of delegates and enums

by AngryAnt

I like delegates. I also like enums. Furthermore, I happen to be part of a small group of people who actually enjoy the immediate mode GUI system in Unity. And I want you to like it too :)

So – menus. After having observed quite a few very funky approaches, in the IRC channel, to building main menu functionality and giving advice in there, I decided to sow together those ideas into a short example.

The main idea here is to be able to build and manage a menu system quickly and easily. This line adds a new submenu to the system – designated MyMenuState and implemented in the OnMyMenuGUI method:

m_Menus [MenuState.MyMenuState] = OnMyMenuGUI;

Notice that since we’re using delegates, the GUI method needn’t be implemented in the same script and could even live on an entirely different GameObject. Why is this so clever (besides enums and delegates being cool)? Because managing and switching between menus set up like this is insanely easy. This is the central GUI method for the menu system:

public void OnGUI ()
{
    m_Menus [m_CurrentState] ();
}

And this is GUI code inside a menu GUI method for switching to a different submenu:

m_CurrentState = GUILayout.Button ("Credits") ? MenuState.Credits : m_CurrentState;

So there. Enough talk. Have some example code:

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

public class Menu : MonoBehaviour
{
    delegate void OnGUIImplementation();
    public enum MenuState {Main,Settings,Credits};
    public struct Settings
    {
        public bool m_ThisSetting, m_ThatSetting, m_TheOtherSetting;
    };
    static Vector2 m_StandardMenuSize = new Vector2 (200.0f, 300.0f);
    static string m_LogoURL = "http://download.unity3d.com/images/top-menu/mm_unity_icon.png";
    static Texture2D m_Logo = null;

    private MenuState m_CurrentState = MenuState.Main;
    private Dictionary<MenuState, OnGUIImplementation> m_Menus;
    private Settings m_Settings;

    void Awake ()
    {
        m_Menus = new Dictionary<MenuState, OnGUIImplementation> ();
        m_Menus [MenuState.Main] = OnMainMenuGUI;
        m_Menus [MenuState.Settings] = OnSettingsGUI;
        m_Menus [MenuState.Credits] = OnCreditsGUI;
        StartCoroutine (GetLogo ());
    }

    IEnumerator GetLogo ()
    {
        WWW www = new WWW (m_LogoURL);

        yield return www;

        if (www.error == null)
        {
            m_Logo = www.texture;
        }
    }

    public void OnGUI ()
    {
        m_Menus [m_CurrentState] ();
    }

    static void BeginStandardMenu (string title)
    {
        GUILayout.BeginArea (new Rect ((Screen.width - m_StandardMenuSize.x) * 0.5f, (Screen.height - m_StandardMenuSize.y) * 0.5f, m_StandardMenuSize.x, m_StandardMenuSize.y));
            GUILayout.BeginVertical (GUI.skin.GetStyle ("Box"));
                GUILayout.Label (title, GUI.skin.GetStyle ("Box"));
    }

    static void EndStandardMenu ()
    {
        Color color;

                GUILayout.BeginHorizontal ();
                    GUILayout.FlexibleSpace ();
                        color = GUI.color;
                        GUI.color = Color.grey;
                        GUILayout.Label ("My game version 2.4X");
                        GUI.color = color;
                    GUILayout.FlexibleSpace ();
                GUILayout.EndHorizontal ();
            GUILayout.EndVertical ();
        GUILayout.EndArea ();
    }

    void OnMainMenuGUI ()
    {
        BeginStandardMenu ("Main menu");
            if (m_Logo != null)
            {
                GUILayout.BeginHorizontal ();
                    GUILayout.FlexibleSpace ();
                        GUILayout.Label (m_Logo);
                    GUILayout.FlexibleSpace ();
                GUILayout.EndHorizontal ();
            }
            if (GUILayout.Button ("New game"))
            {
                Application.LoadLevel ("Level one");
            }
            m_CurrentState = GUILayout.Button ("Settings") ? MenuState.Settings : m_CurrentState;
            m_CurrentState = GUILayout.Button ("Credits") ? MenuState.Credits : m_CurrentState;
            GUILayout.FlexibleSpace ();
        EndStandardMenu ();
    }

    void OnSettingsGUI ()
    {
        BeginStandardMenu ("Settings");
            m_Settings.m_ThisSetting = GUILayout.Toggle (m_Settings.m_ThisSetting, "This setting");
            m_Settings.m_ThatSetting = GUILayout.Toggle (m_Settings.m_ThatSetting, "That setting");
            m_Settings.m_TheOtherSetting = GUILayout.Toggle (m_Settings.m_TheOtherSetting, "The other setting", GUI.skin.GetStyle ("Button"));
            GUILayout.FlexibleSpace ();
            m_CurrentState = GUILayout.Button ("Main menu") ? MenuState.Main : m_CurrentState;
        EndStandardMenu ();
    }

    void OnCreditsGUI ()
    {
        BeginStandardMenu ("Credits");
            GUILayout.Label ("This game was created by some of the most awesomestest people on the planet. Having failed in their mission to Pluto, they started making games, which - as you can see - resulted in much joy and success!");
                GUILayout.Space (20.0f);
                GUILayout.BeginHorizontal ();
                    GUILayout.FlexibleSpace ();
                        GUILayout.Label ("The awesomestest people inc.");
                GUILayout.EndHorizontal ();

            GUILayout.FlexibleSpace ();
            m_CurrentState = GUILayout.Button ("Main menu") ? MenuState.Main : m_CurrentState;
        EndStandardMenu ();
    }
}

Example project: Menu.zip – built with Unity 2.6.1

Mar 10 10

Pick me! Pick me!

by AngryAnt

Say you’re developing an RTS or maybe a connect-the-dots sort of game. What would be the easiest way to go about handling object selection? This is my suggested solution.

In the given example, I’m taking advantage of the object-mouse event handling that MonoBehaviours on GameObject with colliders on them give for free. You might be in a scenario where raycasting or otherwise resolving selection requests makes more sense, but the basic logic still applies. This example is merely intended to get your from start to goal as fast as possible.

First off, we need a selection manager class (cleverly named SelectionManager in my example). This is where we store selection state and via static accessors, we can get and modify the current selection. Seeing as this class is both public and all accessors are static, you can put its source anywhere. In my example I’ve simply put it right after the MonoBehaviour used for testing it.

SelectionManager

using UnityEngine;

public class SelectionManager
{
    private static GameObject s_ActiveSelection;

    public static GameObject ActiveSelection
    {
        get
        {
            return s_ActiveSelection;
        }
        set
        {
            s_ActiveSelection = value;
        }
    }

    public static void Select (GameObject gameObject, bool selectionValue)
    {
        if (selectionValue)
        {
            Select (gameObject);
        }
        else
        {
            Deselect (gameObject);
        }
    }

    public static void Select (GameObject gameObject)
    {
        ActiveSelection = gameObject;
    }

    public static void Deselect (GameObject gameObject)
    {
        if (ActiveSelection == gameObject)
        {
            ActiveSelection = null;
        }
    }

    public static bool IsSelected (GameObject gameObject)
    {
        return ActiveSelection == gameObject;
    }
}

And the promised MonoBehaviour using the SelectionManager class:

using UnityEngine;

public class SelectableObject : MonoBehaviour
{
    public Rect m_SelectionWindowRect = new Rect (10.0f, 10.0f, 300.0f, 100.0f);

    public void OnMouseDown ()
    {
        SelectionManager.Select (gameObject, !SelectionManager.IsSelected (gameObject));
    }

    public void OnDisable ()
    {
        SelectionManager.Deselect (gameObject);
    }

    public void Update ()
    {
        renderer.material.color = SelectionManager.IsSelected (gameObject) ? Color.green : Color.white;
    }

    public void OnGUI ()
    {
        if (SelectionManager.IsSelected (gameObject))
        {
            m_SelectionWindowRect = GUI.Window (GetInstanceID (), m_SelectionWindowRect, SelectionWindow, gameObject.name);
        }
    }

    void SelectionWindow (int id)
    {
        GUILayout.Box ("I am the selection and my name is " + gameObject.name);
        GUI.DragWindow ();
    }
}

And that is all there is to it. By popular demand, I’m now starting to provide pre-built projects with these tips and tricks posts. Download, unzip, open and play. Requires Unity 2.6.1:
SelectableObject

Jan 14 10

Optimising coroutine yielding in C#

by AngryAnt

When needing to yield a coroutine for a single frame in C#, most people – including myself usually go about it this way:

private IEnumerator DoFadeAlphaIn ()
{
    while (m_Alpha < 1.0f)
    {
        m_Alpha += Time.deltaTime;
        yield return 0;
    }
    m_Alpha = 1.0f;
}

However, as Rodrigo recently brought to my attention, this approach ofcourse performs unneeded memory allocation, given that the zero needs boxing and unboxing before its passed to the coroutine system. Using null in stead saves you that work and allocation.

This might not seem like a lot at first glance, but if you make this a habit every time you need to yield for a frame, I guarantee you that you’ll see results in larger scenarios.

The more optimal way of yielding for a single frame in C#. Minor difference, but allocation saved – which is always awesome:

private IEnumerator DoFadeAlphaIn ()
{
    while (m_Alpha < 1.0f)
    {
        m_Alpha += Time.deltaTime;
        yield return null;
    }
    m_Alpha = 1.0f;
}

An interesting test could be to, on a larger co-routine heavy project, do a project-wide find and replace on “yield return 0;” and “yield return null;” back and forth and measuring the performance difference with the profiler (for non-pro users, maybe the performance gain is even measurable using external monitoring tools).

Jan 5 10

Downloading the hydra

by AngryAnt

So I’ve seen some people asking how you would go about downloading executable code from an external location, reading it into memory and executing it in ones running application.

Now some readers might go "eh? why the heck would someone want to do that?". Well first off, its cool™, but seeing as that is rarely an argument which convinces decision makers, here’s a few others:

  • Patching. The ability to modify the behaviour of your downloaded executable after distribution, without requiring re-download.
  • Highly dynamic online content. Say you’re building a virtual world or something. You might want to have the ability to add more complex behaviours to downloaded content – more than what a data-driven approach allows for.
  • Or how about expanding the capabilities of user generated content?

There. Those are my alibis and I’m sticking to them.

Now this example is quick and simple so as to not bloat the post too much. Regardless of your use, you’ll likely be wanting to add in for instance some cache functionality for real-world use.

Right. So this example contains three pieces of eight, uh, code:

  • The loader utility – responsible for downloading and making available the remote code.
  • The assembly – you know, the one we’re downloading.
  • An example handler. For easy re-use, I’ve designed this example to allow for easy re-use of the loader utility by externalising handling of the assemblies via messages. This example handler shows some techniques for accessing the data of loaded assemblies.

Using the example:

  1. Download and save /Assets/WWWAssemblyLoader.cs, /Assets/NewBehaviourScript.cs and /MyAssembly.cs.
  2. Download and install the mono runtime (or the .net equivalent on Windnows – not tested, but should work just fine).
  3. Build the assembly from the terminal – using the build command supplied at the end of this post.
  4. Upload the assembly to some host (save the URL for later).
  5. Open up your Unity project.
  6. Add the WWWAssemblyLoader and NewBehaviourScript scripts to a GameObject and set the URL property of the first to that of your uploaded assembly.
  7. Press play.
  8. Profit!

And now the codes!

/Assets/WWWAssemblyLoader.cs

using UnityEngine;
using System.Collections;
using System.Reflection;

public class WWWAssemblyLoader : MonoBehaviour
{
    public string m_AssemblyURL;
    private string m_ErrorString = "";
    private WWW m_WWW;
    private bool m_Complete = true;

    public void Start ()
    {
        if (m_AssemblyURL != "")
        {
            ReloadAssembly (m_AssemblyURL);
        }
    }

    public string AssemblyURL
    {
        get
        {
            return m_AssemblyURL;
        }
        set
        {
            if (m_AssemblyURL != value)
            {
                ReloadAssembly (value);
            }
        }
    }

    public float Progress
    {
        get
        {
            return m_Complete ? 1.0f : m_WWW.progress;
        }
    }

    public string Error
    {
        get
        {
            return m_ErrorString;
        }
    }

    public void ReloadAssembly (string url)
    {
        m_Complete = false;
        m_ErrorString = "";
        m_AssemblyURL = url;
        m_WWW = new WWW (m_AssemblyURL);
    }

    public void Update ()
    {
        if (!m_Complete)
        {
            if (m_WWW.error != null)
            {
                m_ErrorString = m_WWW.error;
                m_Complete = true;
                SendMessage ("OnAssemblyLoadFailed", m_AssemblyURL);
            }
            else if (m_WWW.isDone)
            {
                Assembly assembly = LoadAssembly ();
                m_Complete = true;
                if (assembly != null)
                {
                    Debug.Log ("Done");
                    SendMessage ("OnAssemblyLoaded", new WWWAssembly (m_AssemblyURL, assembly));
                }
                else
                {
                    Debug.Log ("Failed");
                    SendMessage ("OnAssemblyLoadFailed", m_AssemblyURL);
                }
            }
        }
    }

    private Assembly LoadAssembly ()
    {
        try
        {
            return Assembly.Load (m_WWW.bytes);
        }
        catch (System.Exception e)
        {
            m_ErrorString = e.ToString ();
            return null;
        }
    }
}

public class WWWAssembly
{
    private string m_URL;
    private Assembly m_Assembly;

    public string URL
    {
        get
        {
            return m_URL;
        }
    }

    public Assembly Assembly
    {
        get
        {
            return m_Assembly;
        }
    }

    public WWWAssembly (string url, Assembly assembly)
    {
        m_URL = url;
        m_Assembly = assembly;
    }
}

/Assets/NewBehaviourScript.cs

using UnityEngine;
using System.Collections;
using System.Reflection;

public class NewBehaviourScript : MonoBehaviour
{
    private string m_MessageString = "Waiting for assembly";

    void OnAssemblyLoaded (WWWAssembly loadedAssembly)
    {
        m_MessageString = "Assembly " + loadedAssembly.URL + "\n";

        System.Type type = loadedAssembly.Assembly.GetType ("MyClass");

        FieldInfo field = type.GetField ("myString");
        m_MessageString += (field.GetValue (null) as string) + "\n";

        object instance = loadedAssembly.Assembly.CreateInstance ("MyClass");
        MethodInfo method = type.GetMethod ("LogMyString");
        m_MessageString += "Return value: " + method.Invoke (instance, null).ToString ();
    }

    void OnAssemblyLoadFailed (string url)
    {
        m_MessageString = "Failed to load assembly at " + url;
    }

    void OnGUI ()
    {
        GUILayout.BeginArea (new Rect (0.0f, 0.0f, Screen.width, Screen.height));
            GUILayout.FlexibleSpace ();
            GUILayout.BeginHorizontal ();
                GUILayout.FlexibleSpace ();
                GUILayout.Box (m_MessageString);
                GUILayout.FlexibleSpace ();
            GUILayout.EndHorizontal ();
            GUILayout.FlexibleSpace ();
        GUILayout.EndArea ();
    }
}

/MyAssembly.cs

using UnityEngine;

public class MyClass
{
    public static string myString = "This is my string from my class in my assembly";

    public int LogMyString ()
    {
        Debug.Log (myString);
        return 2 + 2;
    }
}

The assembly compile terminal command
mcs -target:library -out:MyAssembly.dll -r:/Applications/Unity/Unity.app/Contents/Frameworks/UnityEngine.dll MyAssembly.cs

Nov 15 09

New license of Path: GPL

by AngryAnt

Since I started at Unity, I haven’t really had the time to do much updating (read: none) of my private projects. In sheer size, Path is the largest of those, so a while ago I realised that maintaining that project is simply not feasible.

By adding the GPL license option to the existing two license options (the show-my-logo license displayed on download from angryant.com and the option for a custom negotiated one), users are able to test and modify the source of Path directly – before deciding on one of the other licenses.

Also, although very messy (yes – very), the source could also serve as a good learning resource for Unity editor scripting.

The project repository is available on github.

Nov 4 09

CopyInspector

by AngryAnt

Yea I know. It’s been a while. I do have two very good excuses though: 2.6 and Unite ‘09. If you enjoyed those then zip it and read on. If not, I’m really out of ammo and sorry for the delay.

At Unite, I attended the talk by John Grden of infrared5 on the special (free) Unity for flash users day. During this session he complained about wanting to be able to copy his runtime transform changes inside the IDE (he was copying by way of paper notes it seemed). I decided to take up the challenge when he continued with “… but someone is probably going to show me how to do that after this” and after his talk I handed him a custom inspector editor script for solving that specific problem.

Later at the conference I had some time to kill and did a rewrite to expand the script to be general for any component type. I didn’t have the time to hand John that version, but I suppose I’ll just email him a link to this post.

    So anyway – lets talk implementation:

  • Add CopyInspector.cs to Assets/Editor.
  • Add CopyTransformInspector.cs to Assets/Editor.
  • For each other component to be made copy/paste-able:
    • Add CopyYourComponentTypeInspector.cs to Assets/Editor.
    • Rename it appropriately.
    • Rename the class to match the file name.
    • Change YourComponentType in “[CustomEditor (typeof (YourComponentType))]” to the type of component you wish to affect.

You’ll notice that CopyTransformInspector has some additional code to it. This is due to the fact that this inspector is rendered non-standard and the code provided just replicates that.

The copy transform inspector

The copy transform inspector

Not really sure why syntax highlighting is not kicking in… Will have a look at that later.

Codes!

CopyInspector.cs

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

public class CopyInspector : Editor
{
    static System.Type m_OriginalType;
    static Dictionary <PropertyInfo, object> m_Values;

    private List <PropertyInfo> GetProperties (Component component)
    {
        List <string> ignoredProperties;
        List <PropertyInfo> properties;

        properties = new List <PropertyInfo> ();
        ignoredProperties = new List <string> ();
        foreach (PropertyInfo propertyInfo in typeof (Component).GetProperties ())
        {
            ignoredProperties.Add (propertyInfo.Name);
        }

        foreach (PropertyInfo propertyInfo in component.GetType ().GetProperties ())
        {
            if (ignoredProperties.Contains (propertyInfo.Name))
            {
                continue;
            }
            properties.Add (propertyInfo);
        }

        return properties;
    }

    public override void OnInspectorGUI ()
    {
        DrawDefaultInspector ();
        OnCopyInspectorGUI ();
    }

    public void OnCopyInspectorGUI ()
    {
        bool enabled;
        List <PropertyInfo> properties;
        Component component;

        component = target as Component;

        if (component == null)
        {
            return;
        }

        GUILayout.Space (10.0f);

        Color backgroundColor = GUI.backgroundColor;

        GUI.backgroundColor = new Color (0.8f, 0.8f, 0.8f);

        GUILayout.BeginVertical ("Toolbar");

            GUI.backgroundColor = backgroundColor;

            GUILayout.BeginHorizontal ();

                GUILayout.Space (10.0f);

                GUILayout.Label ("Copied: " + (m_OriginalType != null ? m_OriginalType.Name : "Nothing"), "MiniLabel");

                GUILayout.FlexibleSpace ();

                if (GUILayout.Button (new GUIContent ("Copy", "Copy component values"), "MiniLabel"))
                {
                    m_OriginalType = target.GetType ();

                    properties = GetProperties (component);

                    m_Values = new Dictionary <PropertyInfo, object> ();
                    foreach (PropertyInfo property in properties)
                    {
                        m_Values [property] = property.GetValue (component, null);
                    }
                }

                enabled = GUI.enabled;
                GUI.enabled = target.GetType () == m_OriginalType;

                GUILayout.Space (10.0f);

                if (GUILayout.Button (new GUIContent ("Paste", "Paste component values"), "MiniLabel"))
                {
                    properties = GetProperties (component);
                    foreach (PropertyInfo property in properties)
                    {
                        if (!property.CanWrite)
                        {
                            continue;
                        }

                        property.SetValue (component, m_Values [property], null);
                    }
                }

                GUILayout.Space (10.0f);

                GUI.enabled = enabled;

            GUILayout.EndHorizontal ();

        GUILayout.EndVertical ();

        GUILayout.Space (-2.0f);
    }
}

CopyTransformInspector.cs

using UnityEngine;
using UnityEditor;
using System.Collections;

[CustomEditor (typeof (Transform))]
public class CopyTransformInspector : CopyInspector
{
    public override void OnInspectorGUI ()
    {
        Transform transform;
        Vector3 localPosition, localScale;
        Quaternion localRotation;

        transform = target as Transform;

        localPosition = EditorGUILayout.Vector3Field ("Position", transform.localPosition);
        localRotation = Quaternion.Euler (EditorGUILayout.Vector3Field ("Rotation", transform.localRotation.eulerAngles));
        localScale = EditorGUILayout.Vector3Field ("Scale", transform.localScale);

        if (GUI.changed)
        {
            transform.localPosition = localPosition;
            transform.localRotation = localRotation;
            transform.localScale = localScale;
        }

        OnCopyInspectorGUI ();
    }
}

CopyYourComponentTypeInspector.cs

using UnityEngine;
using UnityEditor;
using System.Collections;

[CustomEditor (typeof (YourComponentType))]
public class CopyYourComponentTypeInspector : CopyInspector{}
Oct 4 09

Magnetic

by AngryAnt

You know how Little Big Planet, Kingdom Hearts and similar have various pickups move towards your character when you get up close? Yea I also find that’s a nice bit of polish.

One way of getting that up and running in Unity is via the code below. Setup:

  1. Add all objects you want to get attracted to the player to a special layer.
  2. Attach the script below to your player.
  3. Set the layer mask on the component to include the layer from step one.
  4. Tweak, play, repeat.

 

Magnetic.cs:

using UnityEngine;
using System.Collections;

public class Magnetic : MonoBehaviour
{
    public LayerMask m_MagneticLayers;
    public Vector3 m_Position;
    public float m_Radius;
    public float m_Force;

    void FixedUpdate ()
    {
        Collider[] colliders;
        Rigidbody rigidbody;

        colliders = Physics.OverlapSphere (transform.position + m_Position, m_Radius, m_MagneticLayers);
        foreach (Collider collider in colliders)
        {
            rigidbody = (Rigidbody) collider.gameObject.GetComponent (typeof (Rigidbody));
            if (rigidbody == null)
            {
                continue;
            }
            rigidbody.AddExplosionForce (m_Force * -1, transform.position + m_Position, m_Radius);
        }
    }

    void OnDrawGizmosSelected ()
    {
        Gizmos.color = Color.red;
        Gizmos.DrawWireSphere (transform.position + m_Position, m_Radius);
    }
}
Sep 18 09

GUI drag-drop

by AngryAnt

A lot of people have been asking for tips on how to implement drag-drop functionality in Unity GUI scripting, so I decided to put together a reusable script for the purpose.

Basically the solution requires that your data class derives from GUIDraggableObject and at some point in its OnGUI method call Drag( Rect ) – just like GUI.Window handles dragging.

Right. Codez. First off the GUIDraggableObject.cs file:
 

using UnityEngine;
using System.Collections;

public class GUIDraggableObject
{
    protected Vector2 m_Position;
    private Vector2 m_DragStart;
    private bool m_Dragging;

    public GUIDraggableObject (Vector2 position)
    {
        m_Position = position;
    }

    public bool Dragging
    {
        get
        {
            return m_Dragging;
        }
    }

    public Vector2 Position
    {
        get
        {
            return m_Position;
        }

        set
        {
            m_Position = value;
        }
    }

    public void Drag (Rect draggingRect)
    {
        if (Event.current.type == EventType.MouseUp)
        {
            m_Dragging = false;
        }
        else if (Event.current.type == EventType.MouseDown && draggingRect.Contains (Event.current.mousePosition))
        {
            m_Dragging = true;
            m_DragStart = Event.current.mousePosition - m_Position;
            Event.current.Use();
        }

        if (m_Dragging)
        {
            m_Position = Event.current.mousePosition - m_DragStart;
        }
    }
}

 
An example data class inheriting from GUIDraggableObject – DataObject.cs:
 

using UnityEngine;
using System.Collections;

public class DataObject : GUIDraggableObject
// This class just has the capability of being dragged in GUI - it could be any type of generic data class
{
    private string m_Name;
    private int m_Value;

    public DataObject (string name, int value, Vector2 position) : base (position)
    {
        m_Name = name;
        m_Value = value;
    }

    public void OnGUI ()
    {
        Rect drawRect = new Rect (m_Position.x, m_Position.y, 100.0f, 100.0f), dragRect;

        GUILayout.BeginArea (drawRect, GUI.skin.GetStyle ("Box"));
            GUILayout.Label (m_Name, GUI.skin.GetStyle ("Box"), GUILayout.ExpandWidth (true));

            dragRect = GUILayoutUtility.GetLastRect ();
            dragRect = new Rect (dragRect.x + m_Position.x, dragRect.y + m_Position.y, dragRect.width, dragRect.height);

            if (Dragging)
            {
                GUILayout.Label ("Wooo...");
            }
            else if (GUILayout.Button ("Yes!"))
            {
                Debug.Log ("Yes. It is " + m_Value + "!");
            }
        GUILayout.EndArea ();

        Drag (dragRect);
    }
}

 
And finally, this script demonstrates how you could have your data manager class use Unity GUI for data visualisation with drag-drop enabled – MyMonoBehaviour.cs:
 

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

public class MyMonoBehaviour : MonoBehaviour
{
    private List< DataObject > m_Data = new List< DataObject > ();
    private Rect dropTargetRect = new Rect (10.0f, 10.0f, 30.0f, 30.0f);

    void Awake ()
    {
        m_Data.Add (new DataObject ("One", 1, new Vector2 (20.0f * Random.Range (1.0f, 10.0f), 20.0f * Random.Range (1.0f, 10.0f))));
        m_Data.Add (new DataObject ("Two", 2, new Vector2 (20.0f * Random.Range (1.0f, 10.0f), 20.0f * Random.Range (1.0f, 10.0f))));
        m_Data.Add (new DataObject ("Three", 3, new Vector2 (20.0f * Random.Range (1.0f, 10.0f), 20.0f * Random.Range (1.0f, 10.0f))));
        m_Data.Add (new DataObject ("Four", 4, new Vector2 (20.0f * Random.Range (1.0f, 10.0f), 20.0f * Random.Range (1.0f, 10.0f))));
        m_Data.Add (new DataObject ("Five", 5, new Vector2 (20.0f * Random.Range (1.0f, 10.0f), 20.0f * Random.Range (1.0f, 10.0f))));
    }

    public void OnGUI ()
    {
        DataObject toFront, dropDead;
        Color color;

        GUI.Box(dropTargetRect, "Die");

        toFront = dropDead = null;
        foreach (DataObject data in m_Data)
        {
            color = GUI.color;

            if (data.Dragging)
            {
                GUI.color = dropTargetRect.Contains (Event.current.mousePosition) ? Color.red : color;
            }

            data.OnGUI ();

            GUI.color = color;

            if (data.Dragging)
            {
                if (m_Data.IndexOf (data) != m_Data.Count - 1)
                {
                    toFront = data;
                }
            }
        }

        if (toFront != null)
        // Move an object to front if needed
        {
            m_Data.Remove (toFront);
            m_Data.Add (toFront);
        }
    }
}

 
Ah yea and an example of how you could do the same in an editor window – MyEditorWindow.cs:
 

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

public class MyEditorWindow : EditorWindow
{
    private List< DataObject > m_Data = new List< DataObject > ();
    private bool doRepaint = false;
    private Rect dropTargetRect = new Rect (10.0f, 10.0f, 30.0f, 30.0f);

    public MyEditorWindow ()
    {
        m_Data.Add (new DataObject ("One", 1, new Vector2 (20.0f * Random.Range (1.0f, 10.0f), 20.0f * Random.Range (1.0f, 10.0f))));
        m_Data.Add (new DataObject ("Two", 2, new Vector2 (20.0f * Random.Range (1.0f, 10.0f), 20.0f * Random.Range (1.0f, 10.0f))));
        m_Data.Add (new DataObject ("Three", 3, new Vector2 (20.0f * Random.Range (1.0f, 10.0f), 20.0f * Random.Range (1.0f, 10.0f))));
        m_Data.Add (new DataObject ("Four", 4, new Vector2 (20.0f * Random.Range (1.0f, 10.0f), 20.0f * Random.Range (1.0f, 10.0f))));
        m_Data.Add (new DataObject ("Five", 5, new Vector2 (20.0f * Random.Range (1.0f, 10.0f), 20.0f * Random.Range (1.0f, 10.0f))));
    }

    [MenuItem ("Window/MyEditorWindow")]
    public static void Launch ()
    {
        GetWindow (typeof (MyEditorWindow)).Show ();
    }

    public void Update ()
    {
        if (doRepaint)
        {
            Repaint ();
        }
    }

    public void OnGUI ()
    {
        DataObject toFront, dropDead;
        bool previousState, flipRepaint;
        Color color;

        GUI.Box(dropTargetRect, "Die");

        toFront = dropDead = null;
        doRepaint = false;
        flipRepaint = false;
        foreach (DataObject data in m_Data)
        {
            previousState = data.Dragging;

            color = GUI.color;

            if (previousState)
            {
                GUI.color = dropTargetRect.Contains (Event.current.mousePosition) ? Color.red : color;
            }

            data.OnGUI ();

            GUI.color = color;

            if (data.Dragging)
            {
                doRepaint = true;

                if (m_Data.IndexOf (data) != m_Data.Count - 1)
                {
                    toFront = data;
                }
            }
            else if (previousState)
            {
                flipRepaint = true;

                if (dropTargetRect.Contains (Event.current.mousePosition))
                {
                    dropDead = data;
                }
            }
        }

        if (toFront != null)
        // Move an object to front if needed
        {
            m_Data.Remove (toFront);
            m_Data.Add (toFront);
        }

        if (dropDead != null)
        // Destroy an object if needed
        {
            m_Data.Remove (dropDead);
        }

        if (flipRepaint)
        // If some object just stopped being dragged, we should repaing for the state change
        {
            Repaint ();
        }
    }
}
Sep 17 09

Logging an entire GameObject

by AngryAnt

More stuff from the shadowy corners of my hard-drive. Don’t remember the context, but someone needed to log every single piece of information available on a particular GameObject. I suppose this could be useful for end-user “This GameObject Just Went Completely FUBAR” ™ scenarios.

Anyway – it has reflection in it which by definition makes it cool.

Le codez:

using UnityEngine;
using System.Reflection;

public class Utilities
{
    /* ... */

    static void LogGameObject( GameObject gameObject, bool children )
    {
        Component[] components = gameObject.GetComponents( typeof( Component ) );
        FieldInfo[] fields;
        PropertyInfo[] properties;

        Debug.Log( gameObject.name + ":" );

        foreach( Component component in components )
        {
            Debug.Log( " - " + component.GetType().Name + ":" );
            fields = component.GetType().GetFields();
            foreach( FieldInfo field in fields )
            {
                Debug.Log( " ." + field.Name + " = " + field.GetValue( component ) );
            }

            properties = component.GetType().GetProperties();
            foreach( PropertyInfo property in properties )
            {
                Debug.Log( " ." + property.Name + " = " + property.GetGetMethod().Invoke( component, null ) );
            }
        }

        if( children )
        {
            foreach( Transform transform in gameObject.transform )
            {
                Debug.Log( "->" );
                LogGameObject( transform.gameObject, children );
            }
        }
    }

    static void LogGameObject( GameObject gameObject )
    {
        LogGameObject( gameObject, false );
    }

    /* ... */
}