├── Editor.meta ├── Editor ├── AssetFileInfo.cs ├── AssetFileInfo.cs.meta ├── Example.meta ├── Example │ ├── AssetsWatcherExample.cs │ └── AssetsWatcherExample.cs.meta ├── Watcher.cs ├── Watcher.cs.meta ├── WatcherPostprocessor.cs └── WatcherPostprocessor.cs.meta ├── License.txt ├── License.txt.meta ├── README.md └── README.md.meta /Editor.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: fb7da7e2cb7494a7cbeac82ea287580e 3 | folderAsset: yes 4 | DefaultImporter: 5 | userData: 6 | -------------------------------------------------------------------------------- /Editor/AssetFileInfo.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2012-2015 Michael Stevenson 2 | // This code is distributed under the MIT license 3 | 4 | using UnityEngine; 5 | using UnityEditor; 6 | using System.Collections.Generic; 7 | using System.IO; 8 | 9 | namespace AssetsWatcher 10 | { 11 | [System.Flags] 12 | public enum UnityAssetType 13 | { 14 | None = 0, 15 | Folder = 1, 16 | Asset = 1 << 1, 17 | Scene = 1 << 2, 18 | Material = 1 << 3, 19 | Shader = 1 << 4, 20 | Script = 1 << 5, 21 | Model = 1 << 6, 22 | Text = 1 << 7, 23 | Texture = 1 << 8, 24 | Audio = 1 << 9, 25 | Video = 1 << 10, 26 | Cubemap = 1 << 11, 27 | Animation = 1 << 12, 28 | LensFlare = 1 << 13, 29 | GUISkin = 1 << 14, 30 | PhysicMaterial = 1 << 15, 31 | Font = 1 << 16, 32 | Prefab = 1 << 17, 33 | RenderTexture = 1 << 18, 34 | ComputeShader = 1 << 19, 35 | AnimatorController = 1 << 20, 36 | AnimatorOverrideController = 1 << 21, 37 | AvatarMask = 1 << 22, 38 | Physics2DMaterial = 1 << 23, 39 | } 40 | 41 | public class AssetFileInfo 42 | { 43 | public string Name { get; private set; } 44 | 45 | public string FullName { get; private set; } 46 | 47 | public string DirectoryName { get; private set; } 48 | 49 | public UnityAssetType Type { get; private set; } 50 | 51 | public string Guid { get; private set; } 52 | 53 | static Dictionary assetExtensions = new Dictionary () { 54 | { UnityAssetType.Folder, new string[] {""} }, 55 | { UnityAssetType.Asset, new string[] {".asset"} }, 56 | { UnityAssetType.Scene, new string[] {".unity"} }, 57 | { UnityAssetType.Material, new string[] {".mat"} }, 58 | { UnityAssetType.Shader, new string[] {".shader"} }, 59 | { UnityAssetType.Script, new string[] {".cs", ".js", ".boo"} }, 60 | { UnityAssetType.Model, new string[] {".ma", ".mb", ".fbx", ".dae", ".lxo", ".max", ".jas", ".c4d", ".blend", ".lwo", ".skp", ".3ds", ".obj", ".dxf"} }, 61 | { UnityAssetType.Texture, new string[] {".psd", ".jpg", ".jpeg", ".png", ".exr", ".tif", ".tiff", ".gif", ".bmp", ".tga", ".iff", ".pict"} }, 62 | { UnityAssetType.Audio, new string[] {".wav", ".aif", ".aiff", ".mp3", ".ogg", ".oga", ".mod", ".it", ".s3m", ".xm"} }, 63 | { UnityAssetType.Video, new string[] {".mov", ".avi", ".asf", ".mpg", ".mpeg", ".mp4", ".ogv"} }, 64 | { UnityAssetType.Text, new string[] {".txt", ".xml"} }, 65 | { UnityAssetType.Cubemap, new string[] {".cubemap"} }, 66 | { UnityAssetType.Animation, new string[] {".anim"} }, 67 | { UnityAssetType.GUISkin, new string[] {".guiskin"} }, 68 | { UnityAssetType.PhysicMaterial, new string[] {".physicMaterial"} }, 69 | { UnityAssetType.LensFlare, new string[] {".flare"} }, 70 | { UnityAssetType.Font, new string[] {".fontsettings"} }, 71 | { UnityAssetType.Prefab, new string[] {".prefab"} }, 72 | { UnityAssetType.RenderTexture, new string[] {".renderTexture"} }, 73 | { UnityAssetType.ComputeShader, new string[] {".compute"} }, 74 | { UnityAssetType.AnimatorController, new string[] {".controller"} }, 75 | { UnityAssetType.AnimatorOverrideController, new string[] {".overrideController"} }, 76 | { UnityAssetType.AvatarMask, new string[] {".mask"} }, 77 | { UnityAssetType.Physics2DMaterial, new string[] {".physicsMaterial2D"} }, 78 | }; 79 | 80 | public AssetFileInfo (string path) 81 | { 82 | this.Name = Path.GetFileName (path); 83 | this.FullName = path; 84 | this.DirectoryName = Path.GetDirectoryName (path); 85 | this.Guid = AssetDatabase.AssetPathToGUID (path); 86 | this.Type = GetTypeForExtension (Path.GetExtension (path)); 87 | } 88 | 89 | 90 | /// 91 | /// Return the path for this asset relative to the current project's Assets folder. 92 | /// 93 | public string AssetsRelativePath { 94 | get { 95 | string path = FullName; 96 | int length = Application.dataPath.Length - 6; 97 | if (path.Length <= length) 98 | return null; 99 | return path.Remove (0, length); 100 | } 101 | } 102 | 103 | /// 104 | /// Extension (with leading dot) for the given Unity asset type 105 | /// 106 | public static string[] GetExtensionsForType (UnityAssetType type) 107 | { 108 | string[] ext; 109 | try { 110 | ext = assetExtensions [type]; 111 | } catch { 112 | return new string[0]; 113 | } 114 | return ext; 115 | } 116 | 117 | public static UnityAssetType GetTypeForExtension (string extension) 118 | { 119 | if (extension == "") 120 | return UnityAssetType.Folder; 121 | foreach (var kvp in assetExtensions) { 122 | foreach (string s in kvp.Value) { 123 | if (s == extension) { 124 | return kvp.Key; 125 | } 126 | } 127 | } 128 | return UnityAssetType.None; 129 | } 130 | 131 | public override int GetHashCode () 132 | { 133 | return Guid.GetHashCode (); 134 | } 135 | 136 | public override bool Equals (object obj) 137 | { 138 | if (obj is AssetFileInfo) 139 | return Guid == ((AssetFileInfo)obj).Guid; 140 | else 141 | return false; 142 | } 143 | 144 | public static bool operator == (AssetFileInfo x, AssetFileInfo y) 145 | { 146 | return System.Object.Equals (x, y); 147 | } 148 | 149 | public static bool operator != (AssetFileInfo x, AssetFileInfo y) 150 | { 151 | return !System.Object.Equals (x, y); 152 | } 153 | 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /Editor/AssetFileInfo.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: f07ba9b386a204f0484bfb4dc33ea2ff 3 | MonoImporter: 4 | serializedVersion: 2 5 | defaultReferences: [] 6 | executionOrder: 0 7 | icon: {instanceID: 0} 8 | userData: 9 | -------------------------------------------------------------------------------- /Editor/Example.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: ca9b7efadb69542d0aaaf072f657f5c2 3 | folderAsset: yes 4 | DefaultImporter: 5 | userData: 6 | -------------------------------------------------------------------------------- /Editor/Example/AssetsWatcherExample.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015 Michael Stevenson 2 | // This code is distributed under the MIT license 3 | 4 | using UnityEngine; 5 | using UnityEditor; 6 | using System.Collections; 7 | using AssetsWatcher; 8 | 9 | [InitializeOnLoad] 10 | public static class AssetsWatcherExample 11 | { 12 | static AssetsWatcherExample () 13 | { 14 | // Observe the entire assets folder for changes 15 | var watcher = Watcher.Observe (); 16 | 17 | watcher.onAssetCreated.AddListener (asset => { 18 | Debug.Log ("[AssetsWatcherExample] Created asset '" + asset.Name + "' of type " + asset.Type); 19 | }); 20 | 21 | watcher.onAssetDeleted.AddListener (asset => { 22 | Debug.Log ("[AssetsWatcherExample] Deleted asset '" + asset.Name + "' of type " + asset.Type); 23 | }); 24 | 25 | watcher.onAssetModified.AddListener (asset => { 26 | Debug.Log ("[AssetsWatcherExample] Modified asset '" + asset.Name + "' of type " + asset.Type); 27 | }); 28 | 29 | watcher.onAssetMoved.AddListener ((before, after) => { 30 | Debug.Log ("[AssetsWatcherExample] Moved asset '" + before.Name + "' from '" + before.DirectoryName + "' to '" + after.DirectoryName + "'"); 31 | }); 32 | 33 | watcher.onAssetRenamed.AddListener ((before, after) => { 34 | Debug.Log ("[AssetsWatcherExample] Renamed asset from '" + before.Name + "' to '" + after.Name + "'"); 35 | }); 36 | } 37 | } -------------------------------------------------------------------------------- /Editor/Example/AssetsWatcherExample.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: ec908a28204bd4e81b8a9df34957d9b4 3 | MonoImporter: 4 | serializedVersion: 2 5 | defaultReferences: [] 6 | executionOrder: 0 7 | icon: {instanceID: 0} 8 | userData: 9 | -------------------------------------------------------------------------------- /Editor/Watcher.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2012-2015 Michael Stevenson 2 | // This code is distributed under the MIT license 3 | 4 | using UnityEngine; 5 | using UnityEditor; 6 | using System.Collections; 7 | using System.Collections.Generic; 8 | using System.IO; 9 | using UnityEngine.Events; 10 | 11 | namespace AssetsWatcher 12 | { 13 | public class Watcher 14 | { 15 | public class FileEvent : UnityEvent {} 16 | public class FileMoveEvent : UnityEvent {} // AssetFileInfo before and after the move 17 | 18 | internal static string[] allAssets; 19 | internal static List allWatchers; 20 | 21 | /// 22 | /// Occurs when an asset is first created. 23 | /// 24 | public readonly FileEvent onAssetCreated = new FileEvent (); 25 | /// 26 | /// Occurs when an asset is deleted or is moved out of scope. 27 | /// 28 | public readonly FileEvent onAssetDeleted = new FileEvent (); 29 | /// 30 | /// Occurs when the content of an asset is modified. 31 | /// 32 | public readonly FileEvent onAssetModified = new FileEvent (); 33 | /// 34 | /// Occurs when an asset is renamed in-place. 35 | /// 36 | public readonly FileMoveEvent onAssetRenamed = new FileMoveEvent (); 37 | /// 38 | /// Occurs when an asset is moved to a new location within scope. 39 | /// 40 | public readonly FileMoveEvent onAssetMoved = new FileMoveEvent (); 41 | 42 | public readonly string basePath; 43 | public readonly UnityAssetType observedAssetTypes; 44 | public readonly bool recurseSubdirectories; 45 | 46 | /// 47 | /// Initialize the AssetsWatcher when a project is loaded. 48 | /// 49 | static Watcher () 50 | { 51 | // Cache asset paths 52 | allAssets = AssetDatabase.GetAllAssetPaths (); 53 | allWatchers = new List (); 54 | } 55 | 56 | Watcher (string path, UnityAssetType assetType, bool recurseSubdirectories) 57 | { 58 | this.basePath = Path.Combine ("Assets", path); 59 | this.observedAssetTypes = assetType; 60 | this.recurseSubdirectories = recurseSubdirectories; 61 | } 62 | 63 | ~Watcher () 64 | { 65 | Watcher.RemoveWatcher (this); 66 | } 67 | 68 | internal void InvokeEventForPaths (string[] paths, FileEvent e) 69 | { 70 | if (e == null) 71 | return; 72 | foreach (var p in paths) { 73 | if (IsValidPath (p)) { 74 | AssetFileInfo asset = new AssetFileInfo (p); 75 | if (observedAssetTypes == UnityAssetType.None || (observedAssetTypes & asset.Type) == asset.Type) { 76 | e.Invoke (asset); 77 | } 78 | } 79 | } 80 | } 81 | 82 | internal void InvokeMovedEventForPaths (Dictionary paths, FileMoveEvent e) 83 | { 84 | if (e == null) 85 | return; 86 | foreach (var p in paths) { 87 | bool beforePathValid = IsValidPath (p.Value); 88 | bool afterPathValid = IsValidPath (p.Key); 89 | if (beforePathValid || afterPathValid) { 90 | var before = beforePathValid ? new AssetFileInfo (p.Value) : null; 91 | var after = afterPathValid ? new AssetFileInfo (p.Key) : null; 92 | e.Invoke (before, after); 93 | } 94 | } 95 | } 96 | 97 | /// 98 | /// Determines whether the specified assetPath is valid given the current path constraints. 99 | /// 100 | bool IsValidPath (string assetPath) 101 | { 102 | if (recurseSubdirectories) 103 | return assetPath.StartsWith (this.basePath); 104 | else 105 | return Path.GetDirectoryName (assetPath) == this.basePath; 106 | } 107 | 108 | 109 | #region API 110 | 111 | /// 112 | /// Watch for changes to the given asset type flags in the given path, and optionally recursing subdirectories. 113 | /// If no path is specified, the entire Assets folder will be used. 114 | /// If no asset type is specified, all asset types will be observed. 115 | /// 116 | public static Watcher Observe (string path = "", UnityAssetType assetType = UnityAssetType.None, bool recurseSubdirectories = true) 117 | { 118 | Watcher w = new Watcher (path, assetType, recurseSubdirectories); 119 | allWatchers.Add (w); 120 | return w; 121 | } 122 | 123 | public void Disable () 124 | { 125 | onAssetCreated.RemoveAllListeners (); 126 | onAssetDeleted.RemoveAllListeners (); 127 | onAssetModified.RemoveAllListeners (); 128 | onAssetMoved.RemoveAllListeners (); 129 | onAssetRenamed.RemoveAllListeners (); 130 | allWatchers.Remove (this); 131 | } 132 | 133 | /// 134 | /// Disable the specified watcher. 135 | /// 136 | static void RemoveWatcher (Watcher watcher) 137 | { 138 | allWatchers.Remove (watcher); 139 | } 140 | 141 | #endregion 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /Editor/Watcher.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: ce0731f154308409ba8e5f48306019fe 3 | MonoImporter: 4 | serializedVersion: 2 5 | defaultReferences: [] 6 | executionOrder: 0 7 | icon: {instanceID: 0} 8 | userData: 9 | -------------------------------------------------------------------------------- /Editor/WatcherPostprocessor.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2012-2015 Michael Stevenson 2 | // This code is distributed under the MIT license 3 | 4 | using UnityEngine; 5 | using UnityEditor; 6 | using System.Collections.Generic; 7 | using System.IO; 8 | using System.Linq; 9 | 10 | namespace AssetsWatcher 11 | { 12 | public sealed class WatcherPostprocessor : AssetPostprocessor 13 | { 14 | static void OnPostprocessAllAssets (string[] importedAssets, string[] deletedAssets, string[] movedAssets, string[] movedFromPaths) 15 | { 16 | string[] created = importedAssets.Except (Watcher.allAssets).ToArray (); 17 | string[] modified = importedAssets.Except (created).ToArray (); 18 | 19 | Dictionary allMoved = new Dictionary (); 20 | for (int i = 0; i < movedAssets.Length; i++) { 21 | allMoved.Add (movedAssets [i], movedFromPaths [i]); 22 | } 23 | 24 | // Renamed to, renamed from 25 | Dictionary renamed = 26 | (from m in allMoved 27 | where (Path.GetDirectoryName (m.Key)) == (Path.GetDirectoryName (m.Value)) 28 | select m).ToDictionary (p => p.Key, p => p.Value); 29 | 30 | Dictionary moved = allMoved.Except (renamed).ToDictionary (p => p.Key, p => p.Value); 31 | 32 | // Dispatch asset events to available watchers 33 | foreach (Watcher w in Watcher.allWatchers) { 34 | w.InvokeEventForPaths (created, w.onAssetCreated); 35 | w.InvokeEventForPaths (deletedAssets, w.onAssetDeleted); 36 | w.InvokeEventForPaths (modified, w.onAssetModified); 37 | w.InvokeMovedEventForPaths (renamed, w.onAssetRenamed); 38 | w.InvokeMovedEventForPaths (moved, w.onAssetMoved); 39 | } 40 | 41 | // Update asset paths cache 42 | Watcher.allAssets = AssetDatabase.GetAllAssetPaths (); 43 | } 44 | } 45 | } 46 | 47 | -------------------------------------------------------------------------------- /Editor/WatcherPostprocessor.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 72eed76f4b8f547d4a7f49f9016f1f7b 3 | MonoImporter: 4 | serializedVersion: 2 5 | defaultReferences: [] 6 | executionOrder: 0 7 | icon: {instanceID: 0} 8 | userData: 9 | -------------------------------------------------------------------------------- /License.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012 Michael Stevenson 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /License.txt.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 3fc38094202bd4560b3576aed2e3b613 3 | TextScriptImporter: 4 | userData: 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | AssetsWatcher 2 | ============= 3 | 4 | AssetsWatcher is a Unity Editor extension that augments AssetPostprocessor by providing fine-grained event handling for changes to specific asset types in specific locations. Editor scripts may instantiate new Watchers to invoke events when a desired asset type is created, deleted, modified, renamed, or moved. 5 | 6 | To create a new watcher: 7 | 8 | 1. Add a static constructor to any class inside an Editor folder. 9 | 2. Add the InitializeOnLoad attribute to the class. This will enable the Unity Editor to call the static constructor when the project is loaded. 10 | 3. Call Watcher.Observe from the static constructor, passing in a desired base path, asset type flags, and directory recursion flag. Keep a reference to the returned Watcher instance. 11 | 4. Add listeners to the Watcher's UnityEvents: 12 | - onAssetCreated 13 | - onAssetDeleted 14 | - onAssetModified 15 | - onAssetMoved 16 | - onAssetRenamed 17 | 18 | Example implementation: 19 | 20 | [InitializeOnLoad] 21 | public static class AssetsWatcherExample 22 | { 23 | static AssetsWatcherExample () 24 | { 25 | Watcher watcher = Watcher.Observe (); 26 | 27 | watcher.onAssetCreated.AddListener (asset => { 28 | Debug.Log ("Created asset '" + asset.Name + "' of type " + asset.Type); 29 | }); 30 | } 31 | } 32 | 33 | You may specify a path to watch and asset type flags to match. You may also specify whether or not to recursively search subdirectories below the given path. The following Watcher will respond to all texture and GUI Skin changes in Assets/Graphics/GUI, but will ignore assets in its subdirectories: 34 | 35 | Watcher watcher = Watcher.Observe ("Graphics/GUI", UnityAssetType.Texture | UnityAssetType.GUISkin, false); 36 | 37 | Each Watcher will return details about an asset event via an AssetFileInfo object. The onAssetMoved and onAssetRenamed events provide AssetFileInfo for both the original asset state and the new asset state. 38 | 39 | If you would like to disable a watcher, call watcher.Disable (). -------------------------------------------------------------------------------- /README.md.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 242d767bbf887493780de5d20dff4af4 3 | DefaultImporter: 4 | userData: 5 | --------------------------------------------------------------------------------