I recently came across a strange behavior while loading text resource assets. In particular the Resources.LoadAll method does not accept Path.DirectorySeparatorChar characters in a path. In fact it only accepts Path.AltDirectorySeparatorChar characters. This behavior is different then standard .net file/folder methods that accept either Path.AltDirectorySeparatorChar or Path.DirectorySeparatorChar without distinction. What this means is that you can’t directly use Path.Combine to build a path and pass it to the Resources.LoadAll method you first have to replace any Path.DirectorySeparatorChar characters with Path.AltDirectorySeparatorChar characters.

The documentation for Resources.Load also does not mention this behavior.

I have submitted a bug report here –> https://fogbugz.unity3d.com/default.asp?533268_jgvrk2lbu1qm398e

    using System.IO;

    using UnityEditor;

    using UnityEngine;

    /// <summary>
    /// Handles settings registration.
    /// </summary>
    [InitializeOnLoad]
    public class EditorInitialization
    {
        /// <summary>
        /// Holds a value indicating whether the RunCallbacks method has been called at least once before.
        /// </summary>
        private static bool ranOnce;

        /// <summary>
        /// Initializes static members of the <see cref="EditorInitialization"/> class.
        /// </summary>
        static EditorInitialization()
        {
            EditorApplication.update += RunCallbacks;
        }

        private static void RunCallbacks()
        {
            if (!ranOnce)
            {
                // try to load resource
                var path = Path.Combine("Test/SubFolder", "testfile"); // result is Test/SubFolder\testfile

                // var data = Resources.LoadAll("Test/SubFolder/testfile", typeof(TextAsset)); // this line is successful
                var data = Resources.LoadAll(path, typeof(TextAsset));  // this line fails

                if (data != null && data.Length != 0)
                {
                    Debug.Log("found it");
                }
                else
                {
                    Debug.Log("not found! " + path);
                }

                ranOnce = true;
                return;
            }

            // do stuff 
        }
    }

There is no Unity specific API for getting the project folder, but you can use System.IO.Directory.GetCurrentDirectory. The unity editor expects the current folder to be set to the project folder at all times.

If you are changing the current directory in your editor scripts using System.IO.Directory.SetCurrentDirectory you need to restore the directory back to the project folder when you are done. Otherwise the next time unity compiles your scripts it will prompt you with a dialog box and a message stating you need to restore the current directory back to the project directory and only gives you a “Quit” button to click on that will quit unity.

    // save project folder
    var projectFolder = System.IO.Directory.GetCurrentDirectory();
                   
    // set current directory to a folder of your choosing
    System.IO.Directory.SetCurrentDirectory("c:\\some folder name");
                  
    // do what you need to do
    // ...

    // restore current folder back the the project folder 
    System.IO.Directory.SetCurrentDirectory(projectFolder);

CBX.GridMapping v2.0.1 Released

Published 2/26/2013 by createdbyx in News | Unity
Tags: ,

See the project page at http://www.codefarts.com/CBXGridMapping/

Current Features

  • Can use your own prefabs
  • Includes simple prefab shapes
  • Customizable prefab quick select grid
  • Recently used lists for quick re-selection
  • Support for layering
  • Can name, lock, show/hide, reorder layers
  • Structured prefab naming convention
  • Preset rotations when drawing
  • 3 draw tools Pencil/Rectangle/Fill Rectangle
  • Supports Undo
  • Full off line documentation included
  • Built in feedback tool
  • Includes preliminary API & developer tools
  • Can auto scale and auto center prefabs
  • Lots of settings for customization
  • Fully commented & organized C# source code included
  • 95+% of source code conforms to default StyleCop settings

NOTE: The current version of this tool does not contain a grid or tile mapping/management system. It is a in editor creation tool designed to make it easy to rapidly place prefabs within a grid based layout. A future update will include editor & run time API's.

Version Notes

Items starting with "Completed" are items that have been fully implemented as they were intended
Items starting with "Partial" are items that have only been partially implemented.

v2.0.1

v2.0

  • Completed - Fixed bug if build settings were set to WebPlayer. File.GetLastWriteTime was not available under WebPlayer.
  • Completed - Complete reshuffling of the code base to make it easier to publish and the code is more organized. The code base now lives in it's own folder under "CBX Game\CBX.GridMapping".
  • Completed - All CBX.GridMapping specific code now lives under the CBX.GridMapping name space.
  • Completed - Added additional prefabs to the quick prefab selection drop down.
  • Completed - Fixed issue with the 2D mesh plane prefab. Collider is now properly aligned to the mesh.
  • Completed - Temporarily removed the Quick Tools Beta preview from the code base. It will make an reappearance in a future update.
  • Completed - Removed dead code from numerous code files.
  • Completed - Numerous improvements to the layers system.
  • Partial - Automatic Material Creation window is partially implemented but not recommended for production use just yet. It pretty much works as intended but there is no way to save the materials it creates internally as actual unity *.mat files.
  • Completed - Ability to hide and show individual layers
  • Completed - Have setting to hide show the foldout control for hiding map information
  • Completed - Ability to lock a layer so it can not be drawn or erased
  • Completed - Ability to change the order of layers in the layer list
  • Completed - Ability to delete a specific layer
  • Completed - Added 2 additional draw modes Rectangle & FillRectangle
  • Completed - Added additional settings for more customization
  • Completed - Tile Material Creation, Scene view auto pan, & Hidden Game object tools are no longer part of CBX.GridMapping but instead have migrated over to a free utility library called CBX.GeneralTools available at the Codefarts website.
  • Completed - Fixed bug when changing cell size. The map grid did not automatically update.
  • Completed - Fixed bug where setting the cell size to a non square value prefabs that were drawn were scaled to the minimum dimensions of the cell.

I’ve recently come across what initially seems like a bug with the Unity 4 editor. it involves the use of the Resources.Load method from within a class marked InitializeOnLoad.

Take the fallowing example code …

namespace CBX.CoreProjectCode
{
    using UnityEditor;

    using UnityEngine;

    [InitializeOnLoad]
    public class EditorInitialization
    {
        static EditorInitialization()
        {
            LoadData(); // this will fail to see the test.txt file in the resource folder
        }     

        private static void LoadData()
        {
            // try to load data
            var data = Resources.Load("test", typeof(TextAsset)) as TextAsset;
            if (data == null)
            {
                Debug.LogWarning("No data found");
                return;
            }

            Debug.Log(data.text);
        }
    }
}

After Unity compiles the scripts it will invoke any static constructors of classes marked with InitializeOnLoad. But at this point I suspect that Unity has not yet identified assets you have within any Resources folders. When the class constructor calls the Resources.Load method it will fail to read the test.txt resource file and return a warning message out to the console.

What you could do at this point is right click the “Assets” folder in the project window and select “Reimport”. Unity will reimport the assets and again call classes marked with InitializeOnLoad. But this time Resources.load will successfully load the test.txt file resource and display it’s contents out to the Unity console window.

One work around to this is to hook into the EditorApplication.update callback and make a call to the LoadData method from there.  An example is provided below …

namespace CBX.CoreProjectCode
{
    using UnityEditor;

    using UnityEngine;

    [InitializeOnLoad]
    public class EditorInitialization
    {
        private static bool ranOnce;

        static EditorInitialization()
        {
            EditorApplication.update += RunCallbacks;
        }

        private static void RunCallbacks()
        {
            if (!ranOnce)
            {
                // this will see the test.txt file in the Resource folder and display
                // its contents
                LoadData();
                ranOnce = true;

                // return or don't it’s up to you
                return;
            }

            // do some work here
        }

        private static void LoadData()
        {
            var data = Resources.Load("test", typeof(TextAsset)) as TextAsset;
            if (data == null)
            {
                Debug.LogWarning("No data found");
                return;
            }

            Debug.Log(data.text);
        }
    }
}

Again I am unsure whether this is an actual bug or not but I have submitted a Unity bug report anyway just in case. Here is a link to the my bug report https://fogbugz.unity3d.com/default.asp?525005_eroka98ru2v4netm


The code below allows you to scale a GameObject transform to a specific size along the x & z axis by taking into account the GameObjects renderer bounds if a renderer component is attached.

/// <summary>
/// Scales a transform to specific dimensions along the x & z axis.
/// </summary>
/// <param name="transform">
/// Reference to the transform to scale.
/// </param>
/// <param name="width">The width along the x axis that represents the target size.</param>
/// <param name="height">The height along the z axis that represents the target size.</param>
public static void ScaleTransform(Transform transform, float width, float height)
{
    // get bounds of the prefab
    var bounds = new Bounds();
    var encapsulate = false;
    if (!Utilities.Helpers.GetBoundWithChildren(transform, ref bounds, ref encapsulate))
    {
        return;
    }

    // get minimum size from the size dimensions
    var min = Mathf.Min(width, height);

    // get the maximum x or z size of the transform
    var max = Mathf.Max(bounds.size.x, bounds.size.z);

    // calculate the scale factor 
    var scaleFactor = min / max;

    // apply scaling to the transform
    transform.localScale *= scaleFactor;
}

/// <summary>
/// Gets the rendering bounds of the transform.
/// </summary>
/// <param name="transform">The game object to get the bounding box for.</param>
/// <param name="pBound">The bounding box reference that will </param>
/// <param name="encapsulate">Used to determine if the first bounding box to be 
/// calculated should be encapsulated into the <see cref="pBound"/> argument.</param>
/// <returns>Returns true if at least one bounding box was calculated.</returns>
public static bool GetBoundWithChildren(Transform transform, ref Bounds pBound, ref bool encapsulate)
{
    var didOne = false;

    // get 'this' bound
    if (transform.gameObject.renderer != null)
    {
        var bound = transform.gameObject.renderer.bounds;
        if (encapsulate)
        {
            pBound.Encapsulate(bound.min);
            pBound.Encapsulate(bound.max);
        }
        else
        {
            pBound.min = bound.min;
            pBound.max = bound.max;
            encapsulate = true;
        }

        didOne = true;
    }

    // union with bound(s) of any/all children
    foreach (Transform child in transform)
    {
        if (GetBoundWithChildren(child, ref pBound, ref encapsulate))
        {
            didOne = true;
        }
    }

    return didOne;
}

Using the Undo.IncrementCurrentEventIndex allows your editor scripts to undo a single action rather then have unity undo many actions that may have occurred in rapid succession. Place the code below into a “Editor” folder for it to run.

The “create object” button will create 10 cubes and call Undo.RegisterCreatedObjectUndo for each of them. Click this button then press ctrl+z to undo all of the objects that were created.

The second button “create single object” will also create 10 cubes but will also call the Undo.IncrementCurrentEventIndex method notifying unity that each object that is created should have a separate undo action. Click “create single object” to create 10 cubes then press ctrl+z to undo each object creation individually.

NOTE: At the time of this writing the unity documentation does not have information on the Undo.IncrementCurrentEventIndex method and searching for it also comes back with zero results.

    using UnityEngine;
    using UnityEditor;

    public class UndoTest : EditorWindow
    {

        public void OnGUI()
        {
            if (GUILayout.Button("create object"))
            {
                this.Create(false);
            }
            if (GUILayout.Button("create single object"))
            {
                this.Create(true);
            }

            GUILayout.Label("use ctrl+z to undo each action");
        }

        private void Create(bool single)
        {
            for (int i = 0; i < 10; i++)
            {
                if (single)
                {
                    Undo.IncrementCurrentEventIndex();
                }

                var obj = GameObject.CreatePrimitive(PrimitiveType.Cube);
                obj.transform.position = new Vector3(Random.Range(-10, 10), Random.Range(-10, 10), Random.Range(-10, 10));

                Undo.RegisterCreatedObjectUndo(obj, "create " + obj.name);
            }
        }

        [MenuItem("CBX/UndoTest")]
        public static void ShowWindow()
        {
            GetWindow<UndoTest>("UndoTest").Show();
        }
    }

If you are submitting packages to the asset store and are including version changes that contain html links, remember to specify target="_blank" in your link attributes. Failure to do so can result in the asset store window being replaced by the web page that your link was pointing to. In some occasions it can also cause unity to crash.

Unity101Tip52


I have been thinking that because texture atlases are so common Unity should have some build in support for generating them. Turns out it already does! Check out the Texture2D.PackTextures method.


If your Unity code requires conditional compilation symbols to be present this bit of code may come in handy. After unity compiles your scripts it executes any classes that have the InitializeOnLoad attribute. You can call the SetupConditionalCompilation method provided in the code snippet below to ensure that the conditional compilation symbols persist in your unity project. If a symbol was not present and was added it will write out a notification in the unity console.

ConditionalStartup

[InitializeOnLoad]
public class GridMappingSetup
{
    static GridMappingSetup()
    {
        var types = new[] { BuildTargetGroup.Standalone, BuildTargetGroup.WebPlayer };
        var toInclude = new[] { "CBXControls", "GridMapping", "QuickTools", "ToolService", "TileMaterialCreation" };

        SetupConditionalCompilation(types, toInclude);
    }
}

public static void SetupConditionalCompilation(BuildTargetGroup[] platformTargets, string[] symbolsToInclude)
{
    foreach (var type in platformTargets)
    {
        var hasEntry = new bool[symbolsToInclude.Length];
        var conditionals = PlayerSettings.GetScriptingDefineSymbolsForGroup(type).Trim();
        var parts = conditionals.Split(';');
        var changed = false;

        foreach (var part in parts)
        {
            for (int i = 0; i < symbolsToInclude.Length; i++)
            {
                if (part.Trim() == symbolsToInclude[i].Trim())
                {
                    hasEntry[i] = true;
                    break;
                }
            }
        }

        for (int i = 0; i < hasEntry.Length; i++)
        {
            if (!hasEntry[i])
            {
                conditionals += (String.IsNullOrEmpty(conditionals) ? String.Empty : ";") + symbolsToInclude[i];
                changed = true;
            }
        }

        PlayerSettings.SetScriptingDefineSymbolsForGroup(type, conditionals);

        if (changed)
        {
            Debug.Log(String.Format("Updated player conditional compilation symbols for {0}: {1}", type, conditionals));
        }
    }
}

Here is a C# script for visualizing render bounds. Just attach it to a game object and it will draw the render bounds of the game object and all it’s children. Handy for debugging!

Render Bounds Script Preview

public class RendererBoundsGizmo : MonoBehaviour
{
    public bool ShowCenter;

    public Color Color = Color.white;

    public bool DrawCube = true;

    public bool DrawSphere = false;

    /// <summary>
    /// When the game object is selected this will draw the gizmos
    /// </summary>
    /// <remarks>Only called when in the Unity editor.</remarks>
    private void OnDrawGizmosSelected()
    {
        Gizmos.color = this.Color;

        // get renderer bonding box
        var bounds = new Bounds();
        var initBound = false;
        if (CBX.Utilities.Helpers.GetBoundWithChildren(this.transform, ref bounds, ref initBound))
        {
            if (this.DrawCube)
            {
                Gizmos.DrawWireCube(bounds.center, bounds.size);
            }
            if (this.DrawSphere)
            {
                Gizmos.DrawWireSphere(bounds.center, Mathf.Max(Mathf.Max(bounds.extents.x, bounds.extents.y), bounds.extents.z));
            }
        }

        if (this.ShowCenter)
        {
            Gizmos.DrawLine(new Vector3(bounds.min.x, bounds.center.y, bounds.center.z), new Vector3(bounds.max.x, bounds.center.y, bounds.center.z));
            Gizmos.DrawLine(new Vector3(bounds.center.x, bounds.min.y, bounds.center.z), new Vector3(bounds.center.x, bounds.max.y, bounds.center.z));
            Gizmos.DrawLine(new Vector3(bounds.center.x, bounds.center.y, bounds.min.z), new Vector3(bounds.center.x, bounds.center.y, bounds.max.z));
        }

        Handles.BeginGUI();
        var view = SceneView.currentDrawingSceneView;
        var pos = view.camera.WorldToScreenPoint(bounds.center);
        var size = GUI.skin.label.CalcSize(new GUIContent(bounds.ToString()));
        GUI.Label(new Rect(pos.x - (size.x / 2), -pos.y + view.position.height + 4, size.x, size.y), bounds.ToString());
        Handles.EndGUI();
    }
}

And also the code for the GetBoundsWithChildren method.

/// <summary>
/// Gets the rendering bounds of the transform.
/// </summary>
/// <param name="transform">The game object to get the bounding box for.</param>
/// <param name="pBound">The bounding box reference that will </param>
/// <param name="encapsulate">Used to determine if the first bounding box to be calculated should be encapsulated into the <see cref="pBound"/> argument.</param>
/// <returns>Returns true if at least one bounding box was calculated.</returns>
public static bool GetBoundWithChildren(Transform transform, ref Bounds pBound, ref bool encapsulate)
{
    var bound = new Bounds();
    var didOne = false;

    // get 'this' bound
    if (transform.gameObject.renderer != null)
    {
        bound = transform.gameObject.renderer.bounds;
        if (encapsulate)
        {
            pBound.Encapsulate(bound.min);
            pBound.Encapsulate(bound.max);
        }
        else
        {
            pBound.min = bound.min;
            pBound.max = bound.max;
            encapsulate = true;
        }

        didOne = true;
    }

    // union with bound(s) of any/all children
    foreach (Transform child in transform)
    {
        if (GetBoundWithChildren(child, ref pBound, ref encapsulate))
        {
            didOne = true;
        }
    }

    return didOne;
}

Created by: X

Just another personal website in this crazy online world

Name of author Dean Lunz (aka Created by: X)
Computer programming nerd, and tech geek.
About Me -- Resume