Website may be up and down over next few months. I'm currently doing a complete overhaul of everything. Going back to simple individual .htm pages, new overall site theme, sanitizing and cleaning up html of all pages and blog posts, attempting to implement a new tooling and publishing system etc etc.

Source (converted from C) -> http://rosettacode.org/wiki/Bitmap/Bresenham%27s_line_algorithm#C

/// <summary>
        /// Draws a line on an <see cref="GenericImage{T}"/>.
        /// </summary>
        /// <param name="image">
        /// The destination image.
        /// </param>
        /// <param name="x1">
        /// The x position for the start of the line.
        /// </param>
        /// <param name="y1">
        /// The y position for the start of the line.
        /// </param>
        /// <param name="x2">
        /// The x position for the end of the line.
        /// </param>
        /// <param name="y2">
        /// The y position for the end of the line.
        /// </param>
        /// <param name="color">
        /// The color that the line will be drawn with.
        /// </param>
        public static void DrawLine<T>(this GenericImage<T> image, int x1, int y1, int x2, int y2, T color)
        {
            // source (converted from C) -> http://rosettacode.org/wiki/Bitmap/Bresenham%27s_line_algorithm#C
            int dx = Math.Abs(x2 - x1), sx = x1 < x2 ? 1 : -1;
            int dy = Math.Abs(y2 - y1), sy = y1 < y2 ? 1 : -1;
            int err = (dx > dy ? dx : -dy) / 2;

            for (; ; )
            {
                image[x1, y1] = color;
                if (x1 == x2 && y1 == y2) break;
                int e2 = err;
                if (e2 > -dx) { err -= dy; x1 += sx; }
                if (e2 < dy) { err += dx; y1 += sy; }
            }
        }

Seems like a no brainer but setting up a empty prefab with one or more lights in it can allow you to quickly light up your test scene quick and easy with predictable results. It also helps when you want to keep the same consistent lighting modal from scene to scene. Simply change the lighting in the prefab and hit apply to update all scenes that use it.


Ever wonder how some unity extensions show a notification overlay? Check out the EditorWindow.ShowNotification method. 


You can get access to the build order for scenes in your editor scripts by calling EditorBuildSettings.scenes. This allows you to control what scenes are included with the build through your editor code.

    public class SceneDump
    {
        [MenuItem("Codefarts/Dump Scenes")]
        public static void Dump()
        {
            var parts = from part in EditorBuildSettings.scenes
                        select string.Format("{0} - {1}", part.enabled, part.path);

            Debug.Log(string.Join("\r\n", parts.ToArray()));
        }
    }

DumpScenes


Below is a simple bit of code that will attempt to retrieve the asset path of the prefab stored on disk. The method will also try to walk up the prefab hierarchy if the prefab parameter appears to be a instance of the original. 

/// <summary>
/// Gets the source path to a prefab if any.
/// </summary>
/// <param name="prefab">The prefab reference to get the asset path for.</param>
/// <returns>Returns a asset path for a prefab.</returns>
/// <remarks>This method will attempt to find the source asset of the given <see cref="UnityEngine.Object"/> by 
/// walking up the parent prefab hierarchy.</remarks>
public static string GetSourcePrefab(UnityEngine.Object prefab)
{
    // if no prefab specified then return null
    if (prefab == null)
    {
        return null;
    }

    // attempt to get the path
    var path = AssetDatabase.GetAssetPath(prefab);

    // if no path returned it may be an instantiated prefab so try to get the parent prefab
    while (String.IsNullOrEmpty(path))
    {
        // try parent prefab
        var parent = PrefabUtility.GetPrefabParent(prefab);
                
        // no parent so must be generated through code so just exit loop
        if (parent == null)
        {
            break;
        }

        // attempt to get path for 
        path = AssetDatabase.GetAssetPath(parent);

        // set prefab reference to parent for next loop
        prefab = parent;
    }

    // return the path if any
    return path;
}

If you have some editor scripts and you want to detect weather or not unity has compiled your scripts you can try this simple method.

Created a private class within your editor window and declare a field of that private class.

public class YourEditor : EditorWindow
{
    private RecompileClass recompile;

    private class RecompileClass
    {  
    }
}

In the OnGUI method (or where appropriate) perform a check to see if the field is null. If it’s null you can assume that unity compiled your scripts again so do what you need to do is create a new instance of the private class and assign it to the field.

public class YourEditor : EditorWindow
{
    private RecompileClass recompile;
      
    private class RecompileClass
    {
    }

    public void OnGUI()
    {
        // check if recompile variable is null 
        if (this.recompile == null  )
        {
            // if yes assume recompile then create a reference to a recompile class 
            this.recompile = new RecompileClass();

            // do what you need to do here after detecting a recompile  
        }

        // do regular stuff here
    }
}

The next time unity recompiles your scripts the field will loose it’s reference and will be null again.


    using System;
    using System.Collections.Generic;

    /// <summary>
    /// Implements a generic equality comparer that uses a callback to perform the comparison.
    /// </summary>
    /// <typeparam name="T">The type we want to compare.</typeparam>
    public class EqualityComparerCallback<T> : IEqualityComparer<T>
    {
        /// <summary>
        /// Holds a reference to the callback that handles comparisons.
        /// </summary>
        private readonly Func<T, T, bool> function;

        /// <summary>
        /// Holds a reference to the callback used to calculate hash codes.
        /// </summary>
        private Func<T, int> hashFunction;
      
        /// <summary>
        /// Initializes a new instance of the <see cref="EqualityComparerCallback{T}"/> class.
        /// </summary>
        /// <param name="func">
        /// The callback function that will do the comparing.
        /// </param>
        /// <exception cref="ArgumentNullException">
        /// Throws a <see cref="ArgumentNullException"/> if the <see cref="func"/> parameter is null.
        /// </exception>
        public EqualityComparerCallback(Func<T, T, bool> func)
        {
            if (func == null)
            {
                throw new ArgumentNullException("func");
            }

            this.function = func;
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="EqualityComparerCallback{T}"/> class.
        /// </summary>
        /// <param name="func">
        /// The callback function that will do the comparing.
        /// </param>
        /// <param name="hash">
        /// The callback function that will return the hash code.
        /// </param>  
        public EqualityComparerCallback(Func<T, T, bool> func, Func<T, int> hash)
            : this(func)
        {
            this.hashFunction = hash;
        }

        /// <summary>
        /// Gets or sets the callback function to use to compute the hash.
        /// </summary>
        public Func<T, int> HashFunction
        {
            get
            {
                return this.hashFunction;
            }

            set
            {
                this.hashFunction = value;
            }
        }
                  
        /// <summary>
        /// Provides a static method for returning a <see cref="EqualityComparerCallback{T}"/> type.
        /// </summary>
        /// <param name="func">
        /// The callback function that will do the comparing.
        /// </param>
        /// <returns>Returns a new instance of a <see cref="EqualityComparerCallback{T}"/> type.</returns>
        public static IEqualityComparer<T> Compare(Func<T, T, bool> func)
        {
            return new EqualityComparerCallback<T>(func);
        }

        /// <summary>
        /// Determines whether the specified object instances are considered equal.
        /// </summary>
        /// <param name="x">The first object to compare.</param>
        /// <param name="y">The second object to compare.</param>
        /// <returns>True if the objects are considered equal otherwise, false. If both objA and objB are null, the method returns true.</returns>
        public bool Equals(T x, T y)
        {
            return this.function(x, y);
        }    

        /// <summary>
        /// The get hash code for a object.
        /// </summary>
        /// <param name="obj">
        /// The object whose hash code will be computed.          
        /// </param>
        /// <returns>
        /// Returns a <see cref="int"/> value representing the hash code.
        /// </returns>
        /// <remarks>If no callback function was set for the <see cref="HashFunction"/> the object default <see cref="GetHashCode"/> method will be used.</remarks>
        public int GetHashCode(T obj)
        {
            if (this.hashFunction == null)
            {
                return obj.GetHashCode();
            }

            return this.hashFunction(obj);
        }
    }

A usage scenario

    // build a list of unique categories
    var list = new List<string>();
    foreach (var rule in this.rules)
    {
        if (string.IsNullOrEmpty(rule.Category ?? string.Empty))
        {
            continue;
        }

        // check if already exists
        if (list.Contains(rule.Category, new EqualityComparerCallback<string>((a, b) => a.Trim() == b.Trim())))
        {
            continue;
        }
    }
    return list.ToArray();

Simple solution to get a MIME type from a file extension. Source –> http://stackoverflow.com/a/7161265/341706

public static class MIMEAssistant
{
  private static readonly Dictionary<string, string> MIMETypesDictionary = new Dictionary<string, string>
  {
    {"ai", "application/postscript"},
    {"aif", "audio/x-aiff"},
    {"aifc", "audio/x-aiff"},
    {"aiff", "audio/x-aiff"},
    {"asc", "text/plain"},
    {"atom", "application/atom+xml"},
    {"au", "audio/basic"},
    {"avi", "video/x-msvideo"},
    {"bcpio", "application/x-bcpio"},
    {"bin", "application/octet-stream"},
    {"bmp", "image/bmp"},
    {"cdf", "application/x-netcdf"},
    {"cgm", "image/cgm"},
    {"class", "application/octet-stream"},
    {"cpio", "application/x-cpio"},
    {"cpt", "application/mac-compactpro"},
    {"csh", "application/x-csh"},
    {"css", "text/css"},
    {"dcr", "application/x-director"},
    {"dif", "video/x-dv"},
    {"dir", "application/x-director"},
    {"djv", "image/vnd.djvu"},
    {"djvu", "image/vnd.djvu"},
    {"dll", "application/octet-stream"},
    {"dmg", "application/octet-stream"},
    {"dms", "application/octet-stream"},
    {"doc", "application/msword"},
    {"docx","application/vnd.openxmlformats-officedocument.wordprocessingml.document"},
    {"dotx", "application/vnd.openxmlformats-officedocument.wordprocessingml.template"},
    {"docm","application/vnd.ms-word.document.macroEnabled.12"},
    {"dotm","application/vnd.ms-word.template.macroEnabled.12"},
    {"dtd", "application/xml-dtd"},
    {"dv", "video/x-dv"},
    {"dvi", "application/x-dvi"},
    {"dxr", "application/x-director"},
    {"eps", "application/postscript"},
    {"etx", "text/x-setext"},
    {"exe", "application/octet-stream"},
    {"ez", "application/andrew-inset"},
    {"gif", "image/gif"},
    {"gram", "application/srgs"},
    {"grxml", "application/srgs+xml"},
    {"gtar", "application/x-gtar"},
    {"hdf", "application/x-hdf"},
    {"hqx", "application/mac-binhex40"},
    {"htm", "text/html"},
    {"html", "text/html"},
    {"ice", "x-conference/x-cooltalk"},
    {"ico", "image/x-icon"},
    {"ics", "text/calendar"},
    {"ief", "image/ief"},
    {"ifb", "text/calendar"},
    {"iges", "model/iges"},
    {"igs", "model/iges"},
    {"jnlp", "application/x-java-jnlp-file"},
    {"jp2", "image/jp2"},
    {"jpe", "image/jpeg"},
    {"jpeg", "image/jpeg"},
    {"jpg", "image/jpeg"},
    {"js", "application/x-javascript"},
    {"kar", "audio/midi"},
    {"latex", "application/x-latex"},
    {"lha", "application/octet-stream"},
    {"lzh", "application/octet-stream"},
    {"m3u", "audio/x-mpegurl"},
    {"m4a", "audio/mp4a-latm"},
    {"m4b", "audio/mp4a-latm"},
    {"m4p", "audio/mp4a-latm"},
    {"m4u", "video/vnd.mpegurl"},
    {"m4v", "video/x-m4v"},
    {"mac", "image/x-macpaint"},
    {"man", "application/x-troff-man"},
    {"mathml", "application/mathml+xml"},
    {"me", "application/x-troff-me"},
    {"mesh", "model/mesh"},
    {"mid", "audio/midi"},
    {"midi", "audio/midi"},
    {"mif", "application/vnd.mif"},
    {"mov", "video/quicktime"},
    {"movie", "video/x-sgi-movie"},
    {"mp2", "audio/mpeg"},
    {"mp3", "audio/mpeg"},
    {"mp4", "video/mp4"},
    {"mpe", "video/mpeg"},
    {"mpeg", "video/mpeg"},
    {"mpg", "video/mpeg"},
    {"mpga", "audio/mpeg"},
    {"ms", "application/x-troff-ms"},
    {"msh", "model/mesh"},
    {"mxu", "video/vnd.mpegurl"},
    {"nc", "application/x-netcdf"},
    {"oda", "application/oda"},
    {"ogg", "application/ogg"},
    {"pbm", "image/x-portable-bitmap"},
    {"pct", "image/pict"},
    {"pdb", "chemical/x-pdb"},
    {"pdf", "application/pdf"},
    {"pgm", "image/x-portable-graymap"},
    {"pgn", "application/x-chess-pgn"},
    {"pic", "image/pict"},
    {"pict", "image/pict"},
    {"png", "image/png"}, 
    {"pnm", "image/x-portable-anymap"},
    {"pnt", "image/x-macpaint"},
    {"pntg", "image/x-macpaint"},
    {"ppm", "image/x-portable-pixmap"},
    {"ppt", "application/vnd.ms-powerpoint"},
    {"pptx","application/vnd.openxmlformats-officedocument.presentationml.presentation"},
    {"potx","application/vnd.openxmlformats-officedocument.presentationml.template"},
    {"ppsx","application/vnd.openxmlformats-officedocument.presentationml.slideshow"},
    {"ppam","application/vnd.ms-powerpoint.addin.macroEnabled.12"},
    {"pptm","application/vnd.ms-powerpoint.presentation.macroEnabled.12"},
    {"potm","application/vnd.ms-powerpoint.template.macroEnabled.12"},
    {"ppsm","application/vnd.ms-powerpoint.slideshow.macroEnabled.12"},
    {"ps", "application/postscript"},
    {"qt", "video/quicktime"},
    {"qti", "image/x-quicktime"},
    {"qtif", "image/x-quicktime"},
    {"ra", "audio/x-pn-realaudio"},
    {"ram", "audio/x-pn-realaudio"},
    {"ras", "image/x-cmu-raster"},
    {"rdf", "application/rdf+xml"},
    {"rgb", "image/x-rgb"},
    {"rm", "application/vnd.rn-realmedia"},
    {"roff", "application/x-troff"},
    {"rtf", "text/rtf"},
    {"rtx", "text/richtext"},
    {"sgm", "text/sgml"},
    {"sgml", "text/sgml"},
    {"sh", "application/x-sh"},
    {"shar", "application/x-shar"},
    {"silo", "model/mesh"},
    {"sit", "application/x-stuffit"},
    {"skd", "application/x-koan"},
    {"skm", "application/x-koan"},
    {"skp", "application/x-koan"},
    {"skt", "application/x-koan"},
    {"smi", "application/smil"},
    {"smil", "application/smil"},
    {"snd", "audio/basic"},
    {"so", "application/octet-stream"},
    {"spl", "application/x-futuresplash"},
    {"src", "application/x-wais-source"},
    {"sv4cpio", "application/x-sv4cpio"},
    {"sv4crc", "application/x-sv4crc"},
    {"svg", "image/svg+xml"},
    {"swf", "application/x-shockwave-flash"},
    {"t", "application/x-troff"},
    {"tar", "application/x-tar"},
    {"tcl", "application/x-tcl"},
    {"tex", "application/x-tex"},
    {"texi", "application/x-texinfo"},
    {"texinfo", "application/x-texinfo"},
    {"tif", "image/tiff"},
    {"tiff", "image/tiff"},
    {"tr", "application/x-troff"},
    {"tsv", "text/tab-separated-values"},
    {"txt", "text/plain"},
    {"ustar", "application/x-ustar"},
    {"vcd", "application/x-cdlink"},
    {"vrml", "model/vrml"},
    {"vxml", "application/voicexml+xml"},
    {"wav", "audio/x-wav"},
    {"wbmp", "image/vnd.wap.wbmp"},
    {"wbmxl", "application/vnd.wap.wbxml"},
    {"wml", "text/vnd.wap.wml"},
    {"wmlc", "application/vnd.wap.wmlc"},
    {"wmls", "text/vnd.wap.wmlscript"},
    {"wmlsc", "application/vnd.wap.wmlscriptc"},
    {"wrl", "model/vrml"},
    {"xbm", "image/x-xbitmap"},
    {"xht", "application/xhtml+xml"},
    {"xhtml", "application/xhtml+xml"},
    {"xls", "application/vnd.ms-excel"},                        
    {"xml", "application/xml"},
    {"xpm", "image/x-xpixmap"},
    {"xsl", "application/xml"},
    {"xlsx","application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"},
    {"xltx","application/vnd.openxmlformats-officedocument.spreadsheetml.template"},
    {"xlsm","application/vnd.ms-excel.sheet.macroEnabled.12"},
    {"xltm","application/vnd.ms-excel.template.macroEnabled.12"},
    {"xlam","application/vnd.ms-excel.addin.macroEnabled.12"},
    {"xlsb","application/vnd.ms-excel.sheet.binary.macroEnabled.12"},
    {"xslt", "application/xslt+xml"},
    {"xul", "application/vnd.mozilla.xul+xml"},
    {"xwd", "image/x-xwindowdump"},
    {"xyz", "chemical/x-xyz"},
    {"zip", "application/zip"}
  };

  public static string GetMIMEType(string fileName)
  {
    if (Path.GetExtension(fileName).Length > 1 && MIMETypesDictionary.ContainsKey(Path.GetExtension(fileName).Remove(0, 1)))
    {
      return MIMETypesDictionary[Path.GetExtension(fileName).Remove(0, 1)];
    }
    return "unknown/unknown";
  }
}

Unity comes with a helper method for generating a unique asset paths called AssetDatabase.GenerateUniqueAssetPath. The documentation for this method is rather minimal but the code example below should help you understand how to use it a little better.

GenerateUniqueAssetPath

[MenuItem("CBX/Test")]
public static void Test()
{
    // outputs "Assets/Rules.xml" if "Assets/Rules.xml" file does not already exist.
    // if "Assets/Rules.xml" already exists it outputs "Assets/Rules 1.xml"  
    Debug.Log(AssetDatabase.GenerateUniqueAssetPath("Assets/Rules.xml"));

    // expects path to start with "Assets/" & outputs a console error in the console "pathName.find("assets/") == 0"  
    // then outputs "Rules.xml" 
    Debug.Log(AssetDatabase.GenerateUniqueAssetPath("Rules.xml")); 
}

I needed a quick file caching solution and here is what I came up with …

    using System;
    using System.Collections.Generic;
    using System.IO;
    using System.Text;

    /// <summary>
    /// Provides a file model used by the <see cref="FileCache"/> type.
    /// </summary>
    public class FileModel
    {
        /// <summary>
        /// Gets or sets information about the file.
        /// </summary>
        public FileInfo Info { get; set; }

        /// <summary>
        /// Gets or sets the last time that the file had it's contents cached.
        /// </summary>
        public DateTime LastUpdate { get; set; }

        /// <summary>
        /// Gets or sets the file content for the file.
        /// </summary>
        public StringBuilder  Data { get; set; }
    }

    /// <summary>
    /// Provides a caching for file content based on file extensions.
    /// </summary>
    public class FileCache
    {
        /// <summary>
        /// Holds a singleton instance of a <see cref="FileCache"/> type.
        /// </summary>
        private static FileCache singleton;

        /// <summary>
        /// Used to hold the contents of the files.
        /// </summary>
        private readonly Dictionary<string, FileModel> files;

        /// <summary>
        /// Holds the list of extensions for files that will have there contents cached.
        /// </summary>
        private readonly List<string> extensions;

        /// <summary>
        /// Gets or sets a time value used to determine if a files content should be read again.
        /// </summary>
        public TimeSpan CacheTime { get; set; }

        /// <summary>
        /// Gets a list of file extension that will have there contents cached.
        /// </summary>
        public List<string> Extensions
        {
            get
            {
                return this.extensions;
            }
        }

        /// <summary>
        /// Default constructor.
        /// </summary>
        public FileCache()
        {
            this.files = new Dictionary<string, FileModel>();
            this.extensions = new List<string>();
            this.extensions.AddRange(new[] { ".htm" });
            this.CacheTime = TimeSpan.FromSeconds(2);
        }

        /// <summary>
        /// Provides a helper method for retrieving a file.
        /// </summary>
        /// <param name="path">The path to the file.</param>
        /// <returns>Returns a <see cref="FileModel"/> type containing information about the file.</returns>
        public static FileModel GetFile(string path)
        {
            return Instance.Get(path);
        }

        /// <summary>
        /// Provides a helper method for retrieving a file.
        /// </summary>
        /// <param name="path">The path to the file.</param>
        /// <returns>Returns a <see cref="FileModel"/> type containing information about the file.</returns>
        public FileModel Get(string path)
        {
            // if file does not exit just return null
            if (!File.Exists(path))
            {
                return null;
            }

            // check if file already exists in the cache
            FileModel model;
            if (this.files.ContainsKey(path))
            {
                // get existing entry
                model = this.files[path];
            }
            else
            {
                // add new entry
                model = new FileModel { LastUpdate = DateTime.Now, Info = new FileInfo(path) };
                this.files.Add(path, model);
            }

            // check to cache file data
            if (this.extensions.Contains(model.Info.Extension))
            {
                // not data has been read yet OR (the current time has surpassed the last update time plus the cache time AND 
                // the current time is greater then the last write time  ) we can read the contents of the file
                if (model.Data == null || (DateTime.Now > model.LastUpdate + this.CacheTime && DateTime.Now > model.Info.LastWriteTime))
                {
                    model.Data = new StringBuilder(File.ReadAllText(model.Info.FullName));
                }
            }

            // return the modal data
            return model;
        }

        /// <summary>
        /// Gets a singleton instance of a <see cref="FileCache"/> type.
        /// </summary>
        public static FileCache Instance
        {
            get
            {
                return singleton ?? (singleton = new FileCache());
            }
        }
    }

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