├── Scripts ├── SpriteSequence.cs ├── SpriteSwapper.cs ├── View │ ├── ProgressBar.cs │ ├── FPSCounter.cs │ ├── PageIndicator.cs │ └── ViewStateController.cs ├── SpriteSequenceController.cs ├── Physics2DEventBroadcaster.cs ├── GizmoUtils.cs ├── UnityUtils.cs └── UnityExtensions.cs ├── Editor ├── ReorderableList │ ├── ReorderableListColumn.cs │ └── ReorderableListUtil.cs ├── Tools │ ├── MiscHacks.cs │ ├── CSVReader.cs │ ├── uGUITools.cs │ ├── ViewStateControllerHierachyUtil.cs │ ├── ScriptableObjectUtility.cs │ ├── ResourcesEnumerator.cs │ └── MissingScriptResolver.cs ├── Editors │ └── ViewStateControllerEditor.cs └── Tests │ ├── UnityUtilsTests.cs │ └── UnityExtensionsTests.cs ├── LICENSE ├── .gitignore └── README.md /Scripts/SpriteSequence.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using UnityEngine; 6 | 7 | namespace UnityHelpers 8 | { 9 | [Serializable] 10 | public class SpriteSequence 11 | { 12 | public string name; 13 | public List sprites; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Editor/ReorderableList/ReorderableListColumn.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using UnityEngine; 6 | 7 | namespace UnityHelpers 8 | { 9 | public class ReorderableListColumn 10 | { 11 | public int Width { get; set; } 12 | public float WidthRatio { get; set; } 13 | public string Name { get; set; } 14 | public ReorderableListItemRendererDelegate ItemRenderer { get; set; } 15 | 16 | public ReorderableListColumn() 17 | { 18 | Width = -1; 19 | WidthRatio = 1; 20 | Name = ""; 21 | } 22 | } 23 | 24 | public delegate void ReorderableListItemRendererDelegate(T obj, Rect rect, int index, bool isActive, bool isFocused); 25 | } 26 | -------------------------------------------------------------------------------- /Scripts/SpriteSwapper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using UnityEngine; 6 | 7 | namespace UnityHelpers 8 | { 9 | [RequireComponent(typeof(SpriteRenderer))] 10 | public class SpriteSwapper : MonoBehaviour 11 | { 12 | public Dictionary swapLookup; 13 | 14 | private SpriteRenderer spriteRenderer; 15 | 16 | void Awake() 17 | { 18 | spriteRenderer = GetComponent(); 19 | } 20 | 21 | void LateUpdate() 22 | { 23 | if (swapLookup == null) return; 24 | if (swapLookup.ContainsKey(spriteRenderer.sprite)) 25 | spriteRenderer.sprite = swapLookup[spriteRenderer.sprite]; 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Scripts/View/ProgressBar.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using UnityEngine; 6 | using UnityEngine.UI; 7 | 8 | namespace UnityHelpers.View 9 | { 10 | [ExecuteInEditMode] 11 | [RequireComponent(typeof(RectTransform))] 12 | public class ProgressBar : MonoBehaviour 13 | { 14 | public RectTransform barMask; 15 | 16 | [Range(0,1)] 17 | public float value = 1; 18 | 19 | private RectTransform rectTransform; 20 | 21 | void Update() 22 | { 23 | if (rectTransform == null) rectTransform = GetComponent(); 24 | if (rectTransform == null) return; 25 | barMask.SetSizeWithCurrentAnchors(RectTransform.Axis.Horizontal, value * rectTransform.rect.width); 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Mike Cann 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /Scripts/View/FPSCounter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using UnityEngine; 6 | using UnityEngine.UI; 7 | 8 | namespace UnityHelpers.View 9 | { 10 | [RequireComponent(typeof(Text))] 11 | public class FPSCounter : MonoBehaviour 12 | { 13 | public float timeBetweenUpdates = 1f; 14 | 15 | private Text text; 16 | private DateTime timeOfLastCount; 17 | private float leftover; 18 | private float frameCount; 19 | 20 | void Start() 21 | { 22 | text = GetComponent(); 23 | timeOfLastCount = DateTime.Now; 24 | leftover = 0; 25 | } 26 | 27 | void Update() 28 | { 29 | frameCount++; 30 | var ms = (float)(DateTime.Now - timeOfLastCount).TotalSeconds + leftover; 31 | if (ms > timeBetweenUpdates) 32 | { 33 | leftover = ms - timeBetweenUpdates; 34 | timeOfLastCount = DateTime.Now; 35 | var fps = frameCount * (1f / timeBetweenUpdates); 36 | text.text = fps.ToString("00"); 37 | frameCount = 0; 38 | } 39 | } 40 | 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /Editor/Tools/MiscHacks.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using UnityEditor; 6 | 7 | namespace UnityHelpers 8 | { 9 | public class MiscHacks 10 | { 11 | public static void OpenSpriteEditor(string texturePath) 12 | { 13 | // Lookup the Editor assembly 14 | if (typeof(EditorGUIUtility).Assembly == null) 15 | throw new Exception("Cannot find the EditorGUIUtility Assembly therefore cannot search for SpriteEditorWindow"); 16 | 17 | // Look for the internal class 18 | var type = typeof(EditorGUIUtility).Assembly.GetType("UnityEditor.SpriteEditorWindow"); 19 | if (type == null) 20 | throw new Exception("Cannot find the SpriteEditorWindow class!"); 21 | 22 | // Get the call to open the window 23 | var openMethod = type.GetMethod("GetWindow"); 24 | if (openMethod == null) 25 | throw new Exception("Cannot find the GetWindow method"); 26 | 27 | // Select the texture 28 | Selection.activeObject = AssetDatabase.LoadMainAssetAtPath(texturePath); 29 | 30 | // Finally open it 31 | openMethod.Invoke(null, null); 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Editor/Tools/CSVReader.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using UnityEngine; 6 | 7 | namespace UnityHelpers 8 | { 9 | 10 | public class CSVReader 11 | { 12 | /// 13 | /// Parses a given CSV text doc into a list of rows. 14 | /// Use: ParseCSV("")[5][6] // returns row 5 column 7 15 | /// 16 | /// The text to parse 17 | /// 18 | static public List> ParseCSV(string csvText) 19 | { 20 | var lines = new List>(); 21 | 22 | foreach(var line in csvText.Split("\n"[0])) 23 | { 24 | lines.Add(SplitCsvLine(line).ToList()); 25 | } 26 | 27 | return lines; 28 | } 29 | 30 | // splits a CSV row 31 | static public string[] SplitCsvLine(string line) 32 | { 33 | return (from System.Text.RegularExpressions.Match m in System.Text.RegularExpressions.Regex.Matches(line, 34 | @"(((?(?=[,\r\n]+))|""(?([^""]|"""")+)""|(?[^,\r\n]+)),?)", 35 | System.Text.RegularExpressions.RegexOptions.ExplicitCapture) 36 | select m.Groups[1].Value).ToArray(); 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /Editor/Tools/uGUITools.cs: -------------------------------------------------------------------------------- 1 | using UnityEditor; 2 | using UnityEngine; 3 | 4 | namespace Assets.Libraries.UnityHelpers.Editor.Tools 5 | { 6 | public class uGUITools : MonoBehaviour 7 | { 8 | [MenuItem("Unity Helpers/Anchors to Corners %[")] 9 | static void AnchorsToCorners() 10 | { 11 | RectTransform t = Selection.activeTransform as RectTransform; 12 | RectTransform pt = Selection.activeTransform.parent as RectTransform; 13 | 14 | if (t == null || pt == null) return; 15 | 16 | Vector2 newAnchorsMin = new Vector2(t.anchorMin.x + t.offsetMin.x / pt.rect.width, 17 | t.anchorMin.y + t.offsetMin.y / pt.rect.height); 18 | Vector2 newAnchorsMax = new Vector2(t.anchorMax.x + t.offsetMax.x / pt.rect.width, 19 | t.anchorMax.y + t.offsetMax.y / pt.rect.height); 20 | 21 | t.anchorMin = newAnchorsMin; 22 | t.anchorMax = newAnchorsMax; 23 | t.offsetMin = t.offsetMax = new Vector2(0, 0); 24 | } 25 | 26 | [MenuItem("Unity Helpers/Corners to Anchors %]")] 27 | static void CornersToAnchors() 28 | { 29 | RectTransform t = Selection.activeTransform as RectTransform; 30 | 31 | if (t == null) return; 32 | 33 | t.offsetMin = t.offsetMax = new Vector2(0, 0); 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Scripts/SpriteSequenceController.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using UnityEngine; 6 | 7 | namespace UnityHelpers 8 | { 9 | [RequireComponent(typeof(SpriteRenderer))] 10 | public class SpriteSequenceController : MonoBehaviour 11 | { 12 | public List sequences; 13 | public string sequence; 14 | public int frame; 15 | 16 | private SpriteRenderer spriteRenderer; 17 | private SpriteSequence spriteSequence; 18 | private int lastFrame; 19 | private string lastSequence; 20 | 21 | void Start() 22 | { 23 | spriteRenderer = GetComponent(); 24 | } 25 | 26 | void Update() 27 | { 28 | if(lastSequence!=sequence) 29 | { 30 | lastSequence = sequence; 31 | spriteSequence = sequences.FirstOrDefault(s => s.name == sequence); 32 | lastFrame = -1; 33 | } 34 | 35 | if(spriteSequence!=null) 36 | { 37 | if (lastFrame != frame && frame != -1) 38 | { 39 | lastFrame = frame; 40 | spriteRenderer.sprite = spriteSequence.sprites[frame]; 41 | } 42 | } 43 | } 44 | 45 | public void SetSequence(string sequence) 46 | { 47 | this.sequence = sequence; 48 | } 49 | 50 | public void SetFrame(int frame) 51 | { 52 | this.frame = frame; 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /Scripts/Physics2DEventBroadcaster.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using UnityEngine; 6 | using UnityEngine.Events; 7 | 8 | namespace UnityHelpers 9 | { 10 | public class Physics2DEventBroadcaster : MonoBehaviour 11 | { 12 | public Phyics2DTriggerEvent onTriggerEnter = new Phyics2DTriggerEvent(); 13 | public Phyics2DTriggerEvent onTriggerExit = new Phyics2DTriggerEvent(); 14 | public Phyics2DTriggerEvent onTriggerStay = new Phyics2DTriggerEvent(); 15 | public Phyics2DCollisionEvent onCollisionEnter = new Phyics2DCollisionEvent(); 16 | public Phyics2DCollisionEvent onCollisionExit = new Phyics2DCollisionEvent(); 17 | public Phyics2DCollisionEvent onCollisionStay = new Phyics2DCollisionEvent(); 18 | 19 | public void OnTriggerEnter2D(Collider2D other) 20 | { 21 | onTriggerEnter.Invoke(other); 22 | } 23 | 24 | public void OnTriggerExit2D(Collider2D other) 25 | { 26 | onTriggerExit.Invoke(other); 27 | } 28 | 29 | public void OnTriggerStay2D(Collider2D other) 30 | { 31 | onTriggerStay.Invoke(other); 32 | } 33 | 34 | public void OnCollisionEnter2D(Collision2D coll) 35 | { 36 | onCollisionEnter.Invoke(coll); 37 | } 38 | 39 | public void OnCollisionExit2D(Collision2D coll) 40 | { 41 | onCollisionExit.Invoke(coll); 42 | } 43 | 44 | public void OnCollisionStay2D(Collision2D coll) 45 | { 46 | onCollisionStay.Invoke(coll); 47 | } 48 | } 49 | 50 | public class Phyics2DTriggerEvent : UnityEvent { } 51 | public class Phyics2DCollisionEvent : UnityEvent { } 52 | } 53 | -------------------------------------------------------------------------------- /Scripts/GizmoUtils.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using UnityEngine; 6 | 7 | namespace UnityHelpers 8 | { 9 | public class GizmoUtils 10 | { 11 | /// 12 | /// Thanks to: http://forum.unity3d.com/threads/debug-drawarrow.85980/ 13 | /// 14 | /// 15 | /// 16 | /// 17 | /// 18 | public static void DrawArrow(Vector3 pos, Vector3 direction, float arrowHeadLength = 0.25f, float arrowHeadAngle = 20.0f) 19 | { 20 | Gizmos.DrawRay(pos, direction); 21 | DrawArrowEnd(true, pos, direction, Gizmos.color, arrowHeadLength, arrowHeadAngle); 22 | } 23 | 24 | private static void DrawArrowEnd(bool gizmos, Vector3 pos, Vector3 direction, Color color, float arrowHeadLength = 0.25f, float arrowHeadAngle = 20.0f) 25 | { 26 | Vector3 right = Quaternion.LookRotation(direction) * Quaternion.Euler(arrowHeadAngle, 0, 0) * Vector3.back; 27 | Vector3 left = Quaternion.LookRotation(direction) * Quaternion.Euler(-arrowHeadAngle, 0, 0) * Vector3.back; 28 | Vector3 up = Quaternion.LookRotation(direction) * Quaternion.Euler(0, arrowHeadAngle, 0) * Vector3.back; 29 | Vector3 down = Quaternion.LookRotation(direction) * Quaternion.Euler(0, -arrowHeadAngle, 0) * Vector3.back; 30 | Gizmos.color = color; 31 | Gizmos.DrawRay(pos + direction, right * arrowHeadLength); 32 | Gizmos.DrawRay(pos + direction, left * arrowHeadLength); 33 | Gizmos.DrawRay(pos + direction, up * arrowHeadLength); 34 | Gizmos.DrawRay(pos + direction, down * arrowHeadLength); 35 | } 36 | 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Editor/ReorderableList/ReorderableListUtil.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using UnityEditor; 6 | using UnityEditorInternal; 7 | using UnityEngine; 8 | 9 | namespace UnityHelpers 10 | { 11 | public class ReorderableListUtil 12 | { 13 | public static void SetColumns(ReorderableList list, List> columns) 14 | { 15 | var widthRequired = columns.Where(c => c.Width > -1).Sum(c => c.Width); 16 | 17 | list.drawHeaderCallback = (Rect rect) => 18 | { 19 | var x = rect.x + 14; 20 | var y = rect.y; 21 | var ratioWidth = Math.Max(0, rect.width - widthRequired); 22 | 23 | foreach (var col in columns) 24 | { 25 | var width = col.Width; 26 | if (width < 0) width = (int)(ratioWidth * col.WidthRatio); 27 | EditorGUI.LabelField(new Rect(x, y, width, EditorGUIUtility.singleLineHeight), col.Name); 28 | x += width; 29 | } 30 | }; 31 | 32 | list.drawElementCallback = (Rect rect, int index, bool isActive, bool isFocused) => 33 | { 34 | var obj = (T)list.list[index]; 35 | var x = rect.x; 36 | var y = rect.y; 37 | var ratioWidth = Math.Max(0,rect.width - widthRequired); 38 | 39 | foreach (var col in columns) 40 | { 41 | var width = col.Width; 42 | if (width < 0) width = (int)(ratioWidth * col.WidthRatio); 43 | 44 | col.ItemRenderer(obj, new Rect(x, y, width, EditorGUIUtility.singleLineHeight), index, isActive, isFocused); 45 | x += width; 46 | } 47 | }; 48 | } 49 | 50 | 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /Scripts/View/PageIndicator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using UnityEngine; 6 | using UnityEngine.EventSystems; 7 | using UnityEngine.UI; 8 | using UnityHelpers; 9 | 10 | namespace UnityHelpers.View 11 | { 12 | [ExecuteInEditMode] 13 | public class PageIndicator : MonoBehaviour 14 | { 15 | public ViewStateController viewStateController; 16 | public GameObject togglePrefab; 17 | 18 | private int lastStateCount; 19 | private int lastStateIndex; 20 | 21 | void Awake() 22 | { 23 | if (viewStateController==null) return; 24 | lastStateCount = -1; 25 | lastStateIndex = -1; 26 | Update(); 27 | } 28 | 29 | void Update() 30 | { 31 | if(!viewStateController) return; 32 | 33 | if(viewStateController.states.Count!=lastStateCount) 34 | RebuildIndicators(); 35 | 36 | if (viewStateController.CurrentStateIndex != lastStateIndex) 37 | UpdateTogglesStatus(); 38 | } 39 | 40 | private void RebuildIndicators() 41 | { 42 | if (!viewStateController) return; 43 | if (!togglePrefab) return; 44 | 45 | // Destroy old ones 46 | while (transform.childCount != 0) 47 | DestroyImmediate(transform.GetChild(0).gameObject); 48 | 49 | // Add new ones 50 | for (int i = 0; i < viewStateController.states.Count; i++) 51 | { 52 | var toggle = UnityUtils.Instantiate(togglePrefab); 53 | toggle.transform.SetParent(transform, false); 54 | toggle.onValueChanged.AddListener(b => OnToggleValueChanged(b, toggle)); 55 | } 56 | 57 | // Remember this 58 | lastStateCount = viewStateController.states.Count; 59 | } 60 | 61 | private void OnToggleValueChanged(bool value, Toggle toggle) 62 | { 63 | if (value) 64 | viewStateController.SetState(toggle.transform.GetSiblingIndex()); 65 | } 66 | 67 | private void UpdateTogglesStatus() 68 | { 69 | if (viewStateController.CurrentStateIndex == -1) return; 70 | 71 | foreach (var toggle in GetComponentsInChildren()) 72 | toggle.isOn = false; 73 | 74 | var t = transform.GetChild(viewStateController.CurrentStateIndex).GetComponent(); 75 | t.isOn = true; 76 | 77 | lastStateIndex = viewStateController.CurrentStateIndex; 78 | } 79 | } 80 | 81 | 82 | } 83 | -------------------------------------------------------------------------------- /Editor/Tools/ViewStateControllerHierachyUtil.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using UnityEditor; 6 | using UnityEngine; 7 | using UnityHelpers.View; 8 | 9 | namespace UnityHelpers 10 | { 11 | [InitializeOnLoad] 12 | public class ViewStateControllerHierachyUtil 13 | { 14 | static GUIContent visibleOnIcon; 15 | static GUIContent visibleOffIcon; 16 | static Dictionary viewStateControllerStates; 17 | 18 | static ViewStateControllerHierachyUtil() 19 | { 20 | // Init 21 | //texture = AssetDatabase.LoadAssetAtPath ("Assets/Images/Testicon.png", typeof(Texture2D)) as Texture2D; 22 | //EditorApplication.update += OnEditorUpdate; 23 | viewStateControllerStates = new Dictionary(); 24 | visibleOnIcon = EditorGUIUtility.IconContent("animationvisibilitytoggleon"); 25 | visibleOffIcon = EditorGUIUtility.IconContent("animationvisibilitytoggleoff"); 26 | EditorApplication.hierarchyWindowItemOnGUI += OnHierachyUpdate; 27 | EditorApplication.hierarchyWindowChanged += OnHierachyChanged; 28 | EditorApplication.update += OnUpdate; 29 | UpdateStatesList(); 30 | } 31 | 32 | private static void OnUpdate() 33 | { 34 | UpdateStatesList(); 35 | EditorApplication.update -= OnUpdate; 36 | } 37 | 38 | private static void OnHierachyChanged() 39 | { 40 | UpdateStatesList(); 41 | } 42 | 43 | public static void UpdateStatesList() 44 | { 45 | viewStateControllerStates.Clear(); 46 | foreach (var controller in GameObject.FindObjectsOfType()) 47 | { 48 | if (controller.states == null) return; 49 | foreach (var state in controller.states) 50 | { 51 | if (state == null) return; 52 | viewStateControllerStates[state.GetInstanceID()] = controller; 53 | } 54 | } 55 | } 56 | 57 | static void OnHierachyUpdate(int instanceID, Rect selectionRect) 58 | { 59 | Rect r = new Rect(); 60 | r.y = selectionRect.y + 3; 61 | r.height = selectionRect.height; 62 | r.x = selectionRect.xMax - 20; 63 | r.width = 18; 64 | if (viewStateControllerStates.ContainsKey(instanceID)) 65 | { 66 | var obj = EditorUtility.InstanceIDToObject(instanceID) as GameObject; 67 | if (obj == null) 68 | return; 69 | 70 | var content = obj.activeSelf ? visibleOnIcon : visibleOffIcon; 71 | var b = GUI.Toggle(r, obj.activeSelf, content, GUIStyle.none); 72 | if (b != obj.activeSelf) 73 | { 74 | if(b) 75 | viewStateControllerStates[instanceID].SetState(obj); 76 | } 77 | } 78 | } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /Editor/Tools/ScriptableObjectUtility.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Text; 6 | using UnityEditor; 7 | using UnityEngine; 8 | 9 | namespace Assets.Libraries.UnityHelpers.Editor.Tools 10 | { 11 | public class ScriptableObjectUtility : EditorWindow 12 | { 13 | private bool isOpen; 14 | 15 | [MenuItem("Unity Helpers/Create Asset")] 16 | public static void CreateAsset() 17 | { 18 | GetWindow(); 19 | } 20 | 21 | //[MenuItem("UnityHelpers/Create Asset 2")] 22 | //public static void CreateAsset2() 23 | //{ 24 | // CreateAsset(); 25 | //} 26 | 27 | void OnGUI() 28 | { 29 | if (!isOpen) 30 | { 31 | int controlID = EditorGUIUtility.GetControlID(FocusType.Passive); 32 | EditorGUIUtility.ShowObjectPicker(null, false, "", controlID); 33 | isOpen = true; 34 | } 35 | if (Event.current.commandName == "ObjectSelectorClosed") 36 | { 37 | var script = (MonoScript)EditorGUIUtility.GetObjectPickerObject(); 38 | if(script!=null) 39 | { 40 | var path = AssetDatabase.GetAssetOrScenePath(script); 41 | var classname = Path.GetFileNameWithoutExtension(path); 42 | var type = AppDomain.CurrentDomain.GetAssemblies() 43 | .SelectMany(x => x.GetTypes()) 44 | .FirstOrDefault(x => x.Name == classname); 45 | if (type == null) Debug.LogError("Could not find type with class name: "+classname); 46 | else CreateAsset(type); 47 | Close(); 48 | } 49 | } 50 | } 51 | 52 | public static void CreateAsset() where T : ScriptableObject 53 | { 54 | CreateAsset(typeof(T)); 55 | } 56 | 57 | /// 58 | /// This makes it easy to create, name and place unique new ScriptableObject asset files. 59 | /// Borrowed from: http://wiki.unity3d.com/index.php/CreateScriptableObjectAsset 60 | /// 61 | public static void CreateAsset(Type t) 62 | { 63 | if (!t.IsSubclassOf(typeof(ScriptableObject))) throw new Exception("Asset must a be a scriptable object!"); 64 | 65 | var asset = ScriptableObject.CreateInstance(t); 66 | 67 | string path = AssetDatabase.GetAssetPath(Selection.activeObject); 68 | if (path == "") 69 | { 70 | path = "Assets"; 71 | } 72 | else if (Path.GetExtension(path) != "") 73 | { 74 | path = path.Replace(Path.GetFileName(AssetDatabase.GetAssetPath(Selection.activeObject)), ""); 75 | } 76 | 77 | string assetPathAndName = AssetDatabase.GenerateUniqueAssetPath(path + "/"+t.Name + ".asset"); 78 | 79 | AssetDatabase.CreateAsset(asset, assetPathAndName); 80 | 81 | AssetDatabase.SaveAssets(); 82 | EditorUtility.FocusProjectWindow(); 83 | Selection.activeObject = asset; 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.sln.docstates 8 | 9 | # Build results 10 | [Dd]ebug/ 11 | [Dd]ebugPublic/ 12 | [Rr]elease/ 13 | x64/ 14 | build/ 15 | bld/ 16 | [Bb]in/ 17 | [Oo]bj/ 18 | 19 | # MSTest test Results 20 | [Tt]est[Rr]esult*/ 21 | [Bb]uild[Ll]og.* 22 | 23 | #NUNIT 24 | *.VisualState.xml 25 | TestResult.xml 26 | 27 | # Build Results of an ATL Project 28 | [Dd]ebugPS/ 29 | [Rr]eleasePS/ 30 | dlldata.c 31 | 32 | *_i.c 33 | *_p.c 34 | *_i.h 35 | *.ilk 36 | *.meta 37 | *.obj 38 | *.pch 39 | *.pdb 40 | *.pgc 41 | *.pgd 42 | *.rsp 43 | *.sbr 44 | *.tlb 45 | *.tli 46 | *.tlh 47 | *.tmp 48 | *.tmp_proj 49 | *.log 50 | *.vspscc 51 | *.vssscc 52 | .builds 53 | *.pidb 54 | *.svclog 55 | *.scc 56 | 57 | # Chutzpah Test files 58 | _Chutzpah* 59 | 60 | # Visual C++ cache files 61 | ipch/ 62 | *.aps 63 | *.ncb 64 | *.opensdf 65 | *.sdf 66 | *.cachefile 67 | 68 | # Visual Studio profiler 69 | *.psess 70 | *.vsp 71 | *.vspx 72 | 73 | # TFS 2012 Local Workspace 74 | $tf/ 75 | 76 | # Guidance Automation Toolkit 77 | *.gpState 78 | 79 | # ReSharper is a .NET coding add-in 80 | _ReSharper*/ 81 | *.[Rr]e[Ss]harper 82 | *.DotSettings.user 83 | 84 | # JustCode is a .NET coding addin-in 85 | .JustCode 86 | 87 | # TeamCity is a build add-in 88 | _TeamCity* 89 | 90 | # DotCover is a Code Coverage Tool 91 | *.dotCover 92 | 93 | # NCrunch 94 | *.ncrunch* 95 | _NCrunch_* 96 | .*crunch*.local.xml 97 | 98 | # MightyMoose 99 | *.mm.* 100 | AutoTest.Net/ 101 | 102 | # Web workbench (sass) 103 | .sass-cache/ 104 | 105 | # Installshield output folder 106 | [Ee]xpress/ 107 | 108 | # DocProject is a documentation generator add-in 109 | DocProject/buildhelp/ 110 | DocProject/Help/*.HxT 111 | DocProject/Help/*.HxC 112 | DocProject/Help/*.hhc 113 | DocProject/Help/*.hhk 114 | DocProject/Help/*.hhp 115 | DocProject/Help/Html2 116 | DocProject/Help/html 117 | 118 | # Click-Once directory 119 | publish/ 120 | 121 | # Publish Web Output 122 | *.[Pp]ublish.xml 123 | *.azurePubxml 124 | 125 | # NuGet Packages Directory 126 | packages/ 127 | ## TODO: If the tool you use requires repositories.config uncomment the next line 128 | #!packages/repositories.config 129 | 130 | # Enable "build/" folder in the NuGet Packages folder since NuGet packages use it for MSBuild targets 131 | # This line needs to be after the ignore of the build folder (and the packages folder if the line above has been uncommented) 132 | !packages/build/ 133 | 134 | # Windows Azure Build Output 135 | csx/ 136 | *.build.csdef 137 | 138 | # Windows Store app package directory 139 | AppPackages/ 140 | 141 | # Others 142 | sql/ 143 | *.Cache 144 | ClientBin/ 145 | [Ss]tyle[Cc]op.* 146 | ~$* 147 | *~ 148 | *.dbmdl 149 | *.dbproj.schemaview 150 | *.pfx 151 | *.publishsettings 152 | node_modules/ 153 | 154 | # RIA/Silverlight projects 155 | Generated_Code/ 156 | 157 | # Backup & report files from converting an old project file to a newer 158 | # Visual Studio version. Backup files are not needed, because we have git ;-) 159 | _UpgradeReport_Files/ 160 | Backup*/ 161 | UpgradeLog*.XML 162 | UpgradeLog*.htm 163 | 164 | # SQL Server files 165 | *.mdf 166 | *.ldf 167 | 168 | # Business Intelligence projects 169 | *.rdl.data 170 | *.bim.layout 171 | *.bim_*.settings 172 | 173 | # Microsoft Fakes 174 | FakesAssemblies/ 175 | -------------------------------------------------------------------------------- /Editor/Editors/ViewStateControllerEditor.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using UnityEditor; 6 | using UnityEditorInternal; 7 | using UnityEngine; 8 | using UnityHelpers; 9 | using UnityHelpers.View; 10 | 11 | namespace UnityHelpers 12 | { 13 | [CustomEditor(typeof(ViewStateController))] 14 | public class ViewStateControllerEditor : UnityEditor.Editor 15 | { 16 | private ReorderableList list; 17 | private ViewStateController controller; 18 | private GUIContent visibleOnIcon; 19 | private GUIContent visibleOffIcon; 20 | 21 | void Awake() 22 | { 23 | controller = (ViewStateController)target; 24 | 25 | visibleOnIcon = EditorGUIUtility.IconContent("animationvisibilitytoggleon"); 26 | visibleOffIcon = EditorGUIUtility.IconContent("animationvisibilitytoggleoff"); 27 | 28 | list = new ReorderableList(controller.states, typeof(GameObject), true, true, true, true); 29 | 30 | ReorderableListUtil.SetColumns(list, new List> 31 | { 32 | new ReorderableListColumn { 33 | Name = "Id", 34 | Width = 20, 35 | ItemRenderer = (GameObject state, Rect rect, int index, bool isActive, bool isFocused) => { 36 | EditorGUI.LabelField(rect, "" + index); 37 | } 38 | }, 39 | new ReorderableListColumn { 40 | Name = "State", 41 | WidthRatio = 1, 42 | ItemRenderer = (GameObject state, Rect rect, int index, bool isActive, bool isFocused) => { 43 | controller.states[index] = (GameObject)EditorGUI.ObjectField(rect, state, typeof(GameObject), true); 44 | } 45 | }, 46 | new ReorderableListColumn { 47 | Name = "", 48 | Width = 10, 49 | ItemRenderer = (GameObject state, Rect rect, int index, bool isActive, bool isFocused) => { 50 | EditorGUI.LabelField(rect, ""); 51 | } 52 | }, 53 | new ReorderableListColumn { 54 | Name = "", 55 | Width = 15, 56 | ItemRenderer = (GameObject state, Rect rect, int index, bool isActive, bool isFocused) => { 57 | var content = state.activeSelf ? visibleOnIcon : visibleOffIcon; 58 | var r = new Rect(rect); 59 | r.y += 4; 60 | var b = GUI.Toggle(r, state.activeSelf, content, GUIStyle.none); 61 | if (b != state.activeSelf && b) controller.SetState(state); 62 | } 63 | } 64 | }); 65 | } 66 | 67 | 68 | public override void OnInspectorGUI() 69 | { 70 | if (controller != null && list !=null) 71 | { 72 | EditorGUILayout.LabelField("States"); 73 | list.list = controller.states; 74 | list.DoLayoutList(); 75 | } 76 | 77 | //serializedObject.Update(); 78 | //EditorGUILayout.PropertyField(serializedObject.FindProperty("stateChanged")); 79 | //serializedObject.ApplyModifiedProperties(); 80 | 81 | ViewStateControllerHierachyUtil.UpdateStatesList(); 82 | } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## NOTE 2 | I havent updated this project in a long time, so I have archived it. Please dont use it unless you are aware that its probably very much out of date :) 3 | 4 | --- 5 | 6 | Unity-Helpers 7 | ============= 8 | 9 | A set of utils and extensions for making unity development easier. 10 | 11 | Unity is a great tool for making games however there are a few annoyances with its API that I have tried to fix with this library. 12 | 13 | One such annoyance is the innability to use interfaces in GetComponent(), so I wrote some extension methods to help with that: 14 | 15 | ``` 16 | using UnityHelpers; 17 | 18 | var obj = new GameObject(); 19 | obj.AddComponent(); 20 | 21 | obj.Has(); // Returns true or false 22 | obj.HasComponentOrInterface(); // Can also handle interfaces 23 | 24 | obj.Get(); // Returns the first component 25 | obj.GetComponentOrInterface(); // Returns the first component that implements the interface 26 | 27 | obj.GetAll(); // Gets all the components 28 | obj.GetAllComponentsOrInterfaces(); // Gets all the components that implement the interface 29 | ``` 30 | 31 | Another utility is for adding children to GameObjects: 32 | 33 | ``` 34 | using UnityHelpers; 35 | 36 | var obj = new GameObject(); 37 | 38 | obj.AddChild("Mike"); // Creates a new GameObject named "Mike" and adds it as a child 39 | 40 | var player = obj.AddChild("Dave"); // Creates a new GameObject named "Dave" and adds the component "Player" returning it 41 | 42 | obj.AddChild(typeof(Player), typeof(Friendly), typeof(AI)); // Creates a new GameObject and adds a number of components 43 | ``` 44 | 45 | There are a number of useful utils for loading assets into GameObjects too: 46 | 47 | ``` 48 | using UnityHelpers; 49 | 50 | var obj = new GameObject(); 51 | 52 | obj.Load("Prefabs/Spaceship"); // Loads the Spaceship prefab from the Resources folder and adds it as a child 53 | ``` 54 | 55 | Most of the Utils also can be accessed via the UnityUtils.* class too rather than just extension methods: 56 | 57 | ``` 58 | using UnityHelerps; 59 | 60 | var player = UnityUtils.Load("Prefabs/Spaceship"); 61 | ``` 62 | 63 | Enumerate Resources 64 | =================== 65 | 66 | ![enumerate resources](http://i.imgur.com/OlkmYR6.png) 67 | 68 | Enumerate Resources is a handy util for creating type-safe resource references. Traditionally you have to manually create constant strings to load resources at runtime: 69 | 70 | ``` 71 | Resources.Load("Prefabs/Cars/Porsche"); 72 | ``` 73 | 74 | This is fragile. If the asset is moved you wont know about the crash until you run the game, this line of code may not be executed often and hence introduces a bug that may only present itself at a later date. 75 | 76 | Enumerate Resources scans a resources directory and generates a type-safe alternative: 77 | 78 | ``` 79 | Resources.Load(GameResources.Prefabs.Cars.Porsche); 80 | ``` 81 | 82 | Now if you move the resource and run the enumerator you will get a compile error. 83 | 84 | For added sugar there is a method to add the loaded resource as a child of a game object (handy for prefabs): 85 | 86 | ``` 87 | obj.LoadChild(GameResources.Prefabs.Icons.IndicatorArror); 88 | ``` 89 | 90 | ViewStateController 91 | =================== 92 | 93 | ![view state controller screenshot](http://i.imgur.com/kjBzATD.png) 94 | 95 | The ViewStateController is a utility for UnityUI that allows you to manage view states. Simply add the states to a list then use the dropdown in the inspector to toggle between which one is active. 96 | 97 | At runtime use the methods to change the states: 98 | 99 | ``` 100 | var states = GetComponent(); 101 | 102 | states.SetState("Main Menu"); // Sets the main menu state 103 | 104 | states.SetState(someOtherStateGameObject); // Sets another state using the gameobject reference 105 | ``` 106 | 107 | FPSCounter 108 | ========== 109 | 110 | Attach this component to a gameobject with a Text component and output the current FPS to the screen. 111 | 112 | Misc Hacks 113 | ========== 114 | 115 | The UnityHelpers.MiscHacks class contains a few helpful hacks, such as opening the SpriteEditorWindow directly from code (Unity provides no way of doing this). 116 | 117 | Tests 118 | ===== 119 | 120 | I have included a number of Unit Tests with the project. If you would like to run the tests yourself then just make sure you include the "Unity Test Tools" project from the asset store. 121 | 122 | Installation 123 | ============ 124 | 125 | Simply include the source in your project, to use the extension methods dont forget to include the namespace: 126 | 127 | ``` 128 | using UnityHelpers; 129 | ``` 130 | -------------------------------------------------------------------------------- /Scripts/View/ViewStateController.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using UnityEngine; 6 | using UnityEngine.Events; 7 | using UnityEngine.EventSystems; 8 | 9 | namespace UnityHelpers.View 10 | { 11 | [ExecuteInEditMode] 12 | public class ViewStateController : MonoBehaviour 13 | { 14 | public List states = new List(); 15 | public ViewStateControllerStateChangedEvent stateChanged = new ViewStateControllerStateChangedEvent(); 16 | 17 | public Action enableHandler = obj => obj.SetActive(true); 18 | public Action disableHandler = obj => obj.SetActive(false); 19 | 20 | private GameObject currentState; 21 | 22 | void Awake() 23 | { 24 | // Lets find the current state (and allow each state to Awake() ); 25 | foreach (var state in states) 26 | { 27 | var before = state.activeSelf; 28 | if (before) currentState = state; 29 | state.SetActive(true); 30 | state.SetActive(before); 31 | } 32 | } 33 | 34 | public void SetNoState() 35 | { 36 | GameObject obj = null; 37 | SetState(obj); 38 | } 39 | 40 | public GameObject GetState(string stateName) 41 | { 42 | return states.FirstOrDefault(s => s == null ? false : s.name == stateName); 43 | } 44 | 45 | public T GetState() where T : Component 46 | { 47 | foreach (var state in states) 48 | if (state.Has()) 49 | return state.Get(); 50 | return null; 51 | } 52 | 53 | public void SetState(string stateName) 54 | { 55 | if (CurrentStateName == stateName) return; 56 | if (states == null) return; 57 | SetState(GetState(stateName)); 58 | } 59 | 60 | public void SetState(int stateIndex) 61 | { 62 | if (stateIndex == CurrentStateIndex) return; 63 | if (states == null || states.Count - 1 < stateIndex || stateIndex < 0) return; 64 | SetState(states[stateIndex]); 65 | } 66 | 67 | public void SetState(GameObject obj) 68 | { 69 | if (obj == CurrentState) return; 70 | 71 | if (obj !=null && !states.Contains(obj)) 72 | throw new Exception("Cannot set state '" + obj + "' is not part of the possible states!"); 73 | 74 | foreach(var state in states) 75 | { 76 | if (state == obj) continue; 77 | if (state == null) continue; 78 | if(disableHandler!=null) 79 | disableHandler(state); 80 | } 81 | 82 | if (obj != null) 83 | if (enableHandler != null) 84 | enableHandler(obj); 85 | 86 | var lastState = CurrentState; 87 | currentState = obj; 88 | stateChanged.Invoke(lastState, currentState); 89 | } 90 | 91 | public void NextState() 92 | { 93 | if (states == null || states.Count == 0) return; 94 | if (CurrentState == null) SetState(0); 95 | else 96 | { 97 | var indx = CurrentStateIndex + 1; 98 | if (indx > states.Count-1) indx = 0; 99 | SetState(indx); 100 | } 101 | } 102 | 103 | public void PreviousState() 104 | { 105 | if (states == null || states.Count == 0) return; 106 | if (CurrentState == null) SetState(0); 107 | else 108 | { 109 | var indx = CurrentStateIndex - 1; 110 | if (indx < 0) indx = states.Count - 1; 111 | SetState(indx); 112 | } 113 | } 114 | 115 | public GameObject CurrentState 116 | { 117 | get 118 | { 119 | return currentState; 120 | } 121 | } 122 | 123 | public string CurrentStateName 124 | { 125 | get 126 | { 127 | if (CurrentState == null) return null; 128 | return CurrentState.name; 129 | } 130 | } 131 | 132 | public int CurrentStateIndex 133 | { 134 | get 135 | { 136 | if (CurrentState == null) return -1; 137 | return states.IndexOf(CurrentState); 138 | } 139 | } 140 | 141 | } 142 | 143 | 144 | 145 | public class ViewStateControllerStateChangedEvent : UnityEvent { } 146 | } 147 | -------------------------------------------------------------------------------- /Editor/Tools/ResourcesEnumerator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Text; 6 | using UnityEditor; 7 | using UnityEngine; 8 | 9 | namespace UnityHelpers 10 | { 11 | public class ResourcesEnumerator : EditorWindow 12 | { 13 | private const string FileTemplate = @"using System; 14 | using System.Collections.Generic; 15 | using System.Linq; 16 | using System.Text; 17 | 18 | namespace &&NAMESPACE&& 19 | { 20 | &&CLASSES&& 21 | } 22 | "; 23 | 24 | private const string ClassTemplate = @"public class &&CLASS-NAME&& 25 | { 26 | &&CLASS-CONTENTS&& 27 | }"; 28 | 29 | public string outputPath; 30 | public string pathToResourcesFolder; 31 | public string outputNamespace; 32 | 33 | [MenuItem("Unity Helpers/Enumerate Resources")] 34 | public static void Init() 35 | { 36 | // Get existing open window or if none, make a new one: 37 | var window = new ResourcesEnumerator(); 38 | window.title = "Enumerate Resources"; 39 | window.outputPath = EditorPrefs.GetString("UnityHelpers_GenerateResources_pathToSaveTo"); 40 | window.pathToResourcesFolder = EditorPrefs.GetString("UnityHelpers_GenerateResources_pathToResourcesFolder"); 41 | window.outputNamespace = EditorPrefs.GetString("UnityHelpers_GenerateResources_outputNamespace"); 42 | if (String.IsNullOrEmpty(window.outputPath)) window.outputPath = "Scripts/GameResources.cs"; 43 | if (String.IsNullOrEmpty(window.pathToResourcesFolder)) window.pathToResourcesFolder = "Resources"; 44 | if (String.IsNullOrEmpty(window.outputNamespace)) window.outputNamespace = "UnityHelpers"; 45 | 46 | window.ShowAuxWindow(); 47 | } 48 | 49 | void OnGUI() 50 | { 51 | pathToResourcesFolder = EditorGUILayout.TextField("Resources Folder", pathToResourcesFolder); 52 | outputPath = EditorGUILayout.TextField("Output Folder", outputPath); 53 | outputNamespace = EditorGUILayout.TextField("Output Namespace", outputNamespace); 54 | 55 | if (GUILayout.Button("Enumerate", GUILayout.Height(50))) 56 | { 57 | var classes = new List(); 58 | var c = GetClass("Assets/" + pathToResourcesFolder, Path.GetFileNameWithoutExtension(outputPath), 0); 59 | c = String.Join("\n", c.Split('\n').ToList().ConvertAll(l => "\t" + l).ToArray()); 60 | File.WriteAllText(Application.dataPath + "/" + outputPath, FileTemplate.Replace("&&CLASSES&&", c).Replace("&&NAMESPACE&&", outputNamespace)); 61 | AssetDatabase.Refresh(); 62 | Close(); 63 | } 64 | 65 | // Save the settings 66 | EditorPrefs.SetString("UnityHelpers_GenerateResources_pathToSaveTo", outputPath); 67 | EditorPrefs.SetString("UnityHelpers_GenerateResources_pathToResourcesFolder", pathToResourcesFolder); 68 | EditorPrefs.SetString("UnityHelpers_GenerateResources_outputNamespace", outputNamespace); 69 | } 70 | 71 | private string GetClass(string path, string className, int depth) 72 | { 73 | var assetPaths = new List(AssetDatabase.FindAssets("*.*", new[] { "Assets/" + pathToResourcesFolder })).ConvertAll(guid => AssetDatabase.GUIDToAssetPath(guid)); 74 | var classes = new List(); 75 | var files = new List(); 76 | 77 | foreach (var assetPath in assetPaths) 78 | { 79 | // Is it not the immediate directory then continue 80 | if (Path.GetDirectoryName(assetPath).Replace(path,"") != "") continue; 81 | 82 | // If its a directory then recurse 83 | if (!Path.HasExtension(assetPath)) 84 | { 85 | var dname = assetPath.Replace(path, "").Replace("/", "").Replace(" ", ""); 86 | classes.Add(GetClass(assetPath, dname, depth+1)); 87 | } 88 | else files.Add(assetPath); 89 | } 90 | 91 | var lines = new List(); 92 | 93 | lines.AddRange(classes); 94 | lines.AddRange(files.Distinct().ToList().ConvertAll(f => 95 | { 96 | var fileName = Path.GetFileName(f); 97 | var fileExtension = Path.GetExtension(f); 98 | var varName = Path.GetFileNameWithoutExtension(f).Replace(" ", ""); 99 | if (Char.IsNumber(varName[0])) varName = "_" + varName; 100 | varName = varName.Replace("-", "Minus"); 101 | var strPath = Path.GetDirectoryName(f).Replace("Assets/" + pathToResourcesFolder, "") + "/" + Path.GetFileNameWithoutExtension(f); 102 | if (strPath[0] == '/') strPath = strPath.Substring(1); 103 | if (fileExtension == ".unity") strPath = Path.GetFileNameWithoutExtension(f); 104 | return "\tpublic const string " + varName + " = \"" + strPath + "\";"; 105 | })); 106 | 107 | // Add this path 108 | lines.Add("\tpublic const string _Path = \"" + path.Replace("Assets/Resources/","") + "\";"); 109 | 110 | var contents = String.Join("\n", lines.ToArray()); 111 | 112 | var tabs = ""; 113 | for(var i=0; i tabs + l).ToArray()); 116 | } 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /Scripts/UnityUtils.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using UnityEngine; 6 | 7 | namespace UnityHelpers 8 | { 9 | public class UnityUtils 10 | { 11 | public static List GetSpriteRenderersUnderScreenPoint(Vector2 point) 12 | { 13 | var ray = Camera.main.ScreenPointToRay(point); 14 | var hits = new List(Physics2D.GetRayIntersectionAll(ray, Mathf.Infinity)); 15 | var allRenderers = hits.ConvertAll(hit => hit.transform.gameObject.GetComponent()); 16 | allRenderers.RemoveAll(r => r == null); 17 | 18 | var layers = new Dictionary>(); 19 | allRenderers.ForEach(r => 20 | { 21 | if (!layers.ContainsKey(r.sortingLayerID)) layers[r.sortingLayerID] = new List(); 22 | layers[r.sortingLayerID].Add(r); 23 | }); 24 | 25 | var sortedRenderers = new List(); 26 | var sortedLayers = new List(layers.Keys); 27 | sortedLayers.Sort((a, b) => b - a); 28 | sortedLayers.ForEach(key => layers[key].Sort((a, b) => b.sortingOrder - a.sortingOrder)); 29 | sortedLayers.ForEach(key => sortedRenderers.AddRange(layers[key])); 30 | 31 | return sortedRenderers; 32 | } 33 | 34 | public static GameObject GetObjectUnderScreenPoint(Vector2 screenPos) 35 | { 36 | var r = GetSpriteRenderersUnderScreenPoint(screenPos); 37 | if (r.Count == 0) return null; 38 | return r[0].gameObject; 39 | } 40 | 41 | public static Texture2D MakeTexture2D(int width, int height, Color col) 42 | { 43 | Color[] pix = new Color[width * height]; 44 | for (int i = 0; i < pix.Length; i++) pix[i] = col; 45 | Texture2D result = new Texture2D(width, height); 46 | result.SetPixels(pix); 47 | result.Apply(); 48 | return result; 49 | } 50 | 51 | 52 | /// 53 | /// Copies a component using reflection into a destination. 54 | /// Borrowed from: http://answers.unity3d.com/questions/458207/copy-a-component-at-runtime.html 55 | /// 56 | /// The original component to copy from 57 | /// The destination object to copy the componet to 58 | /// A List of declaring types from which the property or field should not be copied from 59 | /// If attribute is defined on property then ignore property 60 | /// 61 | public static Component CopyComponent(Component original, GameObject destination, Type ignoreAttribute, List illegalDeclaringTypes) 62 | { 63 | var type = original.GetType(); 64 | var copy = destination.AddComponent(type); 65 | 66 | foreach (var field in type.GetFields()) 67 | { 68 | if (illegalDeclaringTypes.Contains(field.DeclaringType)) continue; 69 | field.SetValue(copy, field.GetValue(original)); 70 | } 71 | 72 | foreach (var property in type.GetProperties()) 73 | { 74 | if (property == null) continue; 75 | if (!property.CanWrite) continue; 76 | if (Attribute.IsDefined(property, ignoreAttribute)) continue; 77 | if(illegalDeclaringTypes.Contains(property.DeclaringType)) continue; 78 | property.SetValue(copy, property.GetValue(original, null), null); 79 | } 80 | 81 | return copy; 82 | } 83 | 84 | public static T Instantiate() where T : Component 85 | { 86 | var obj = new GameObject(typeof(T).Name); 87 | return obj.AddComponent(); 88 | } 89 | 90 | public static T Instantiate(GameObject prefab) where T : Component 91 | { 92 | return Instantiate(prefab).GetComponent(); 93 | } 94 | 95 | public static T Load(string resourcePath) where T : Component 96 | { 97 | var obj = Instantiate((GameObject)Resources.Load(resourcePath)); 98 | return obj.GetComponent(); 99 | } 100 | 101 | public static GameObject Load(string resourcePath) 102 | { 103 | return Instantiate((GameObject)Resources.Load(resourcePath)); 104 | } 105 | 106 | public static void Log(string message, params object[] args) 107 | { 108 | Debug.Log(string.Format(message, args)); 109 | } 110 | 111 | public static void LogError(string message, params object[] args) 112 | { 113 | Debug.LogError(string.Format(message, args)); 114 | } 115 | 116 | public static GameObject Instantiate(GameObject prefab) 117 | { 118 | #if UNITY_EDITOR 119 | return (GameObject)UnityEditor.PrefabUtility.InstantiatePrefab(prefab); 120 | #else 121 | return (GameObject)GameObject.Instantiate(prefab); 122 | #endif 123 | } 124 | 125 | /// 126 | /// NOT TESTED! 127 | /// Wraps a given float around between a lower and upper bound 128 | /// 129 | /// the value to wrap 130 | /// the lower bound 131 | /// the upper bound 132 | /// 133 | public static float Wrap(float value, int lower, int upper) 134 | { 135 | float distance = upper - lower; 136 | float times = (float)System.Math.Floor((value - lower) / distance); 137 | return value - (times * distance); 138 | } 139 | 140 | /// 141 | /// NOT TESTED! 142 | /// Finds all objects in the game that match the given type be it an object or interface 143 | /// 144 | /// The type to search for 145 | /// 146 | public static List FindAllComponentsOrInterfaces() 147 | { 148 | return GameObject.FindObjectsOfType().OfType().ToList(); 149 | } 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /Editor/Tests/UnityUtilsTests.cs: -------------------------------------------------------------------------------- 1 | using NUnit.Framework; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Reflection; 6 | using System.Text; 7 | using UnityEditorInternal; 8 | using UnityEngine; 9 | 10 | namespace UnityHelpers.Tests 11 | { 12 | public class UnityUtilsTests 13 | { 14 | [SetUp] 15 | public void Init() 16 | { 17 | 18 | } 19 | 20 | [Test] 21 | public void NothingUnderPointReturnsEmptyList() 22 | { 23 | Assert.AreEqual(0, UnityUtils.GetSpriteRenderersUnderScreenPoint(new Vector2(0, 0)).Count); 24 | } 25 | 26 | [Test] 27 | public void DoesNotReturnAnyObjectsWithoutASpriteRenderer() 28 | { 29 | var obj = new GameObject(); 30 | obj.transform.localPosition = new Vector3(-10, -10, 0); 31 | obj.AddComponent().size = new Vector2(100, 100); 32 | 33 | Camera.main.transform.position = new Vector3(0, 0, -10); 34 | var renderers = UnityUtils.GetSpriteRenderersUnderScreenPoint(new Vector2(0, 0)); 35 | renderers.RemoveAll(r => r==null); 36 | 37 | Assert.AreEqual(0, renderers.Count); 38 | } 39 | 40 | [Test] 41 | public void DoesNotReturnARendererNotUnderThePoint() 42 | { 43 | var obj = new GameObject(); 44 | obj.transform.localPosition = new Vector3(-1000, -1000, 0); 45 | obj.AddComponent().size = new Vector2(100, 100); 46 | obj.AddComponent().sortingOrder = 10; 47 | 48 | Camera.main.transform.position = new Vector3(0, 0, -10); 49 | var renderers = UnityUtils.GetSpriteRenderersUnderScreenPoint(new Vector2(0, 0)); 50 | Assert.AreEqual(0, renderers.Count); 51 | } 52 | 53 | [Test] 54 | public void ReturnsRenderersCorrectlySortedBySortingOrder() 55 | { 56 | var obj1 = new GameObject(); 57 | obj1.transform.localPosition = new Vector3(-10, -10, 0); 58 | obj1.AddComponent().size = new Vector2(100, 100); 59 | obj1.AddComponent().sortingOrder = 10; 60 | 61 | var obj2 = new GameObject(); 62 | obj2.transform.localPosition = new Vector3(-10, -10, 0); 63 | obj2.AddComponent().size = new Vector2(100, 100); 64 | obj2.AddComponent().sortingOrder = 3; 65 | 66 | Camera.main.transform.position = new Vector3(0, 0, -10); 67 | var renderers = UnityUtils.GetSpriteRenderersUnderScreenPoint(new Vector2(0, 0)); 68 | 69 | Assert.AreEqual(2, renderers.Count); 70 | Assert.AreEqual(obj1, renderers[0].gameObject); 71 | Assert.AreEqual(obj2, renderers[1].gameObject); 72 | } 73 | 74 | //[Test] 75 | //public void ReturnsRenderersCorrectlySortedBySortingLayer() 76 | //{ 77 | // var sortingLayerNames = GetSortingLayerNames(); 78 | // if (sortingLayerNames.Length < 2) throw new Exception("Cannot run test, not enough sorting layer names!"); 79 | 80 | // var obj1 = CreateGameObject("obj1"); 81 | // obj1.transform.localPosition = new Vector3(-10, -10, 0); 82 | // obj1.AddComponent().size = new Vector2(100, 100); 83 | // obj1.AddComponent().sortingLayerName = sortingLayerNames[0]; 84 | 85 | // var obj2 = CreateGameObject("obj2"); 86 | // obj2.transform.localPosition = new Vector3(-10, -10, 0); 87 | // obj2.AddComponent().size = new Vector2(100, 100); 88 | // obj2.AddComponent().sortingLayerName = sortingLayerNames[1]; 89 | 90 | // Camera.main.transform.position = new Vector3(0, 0, -10); 91 | // var renderers = UnityUtils.GetSpriteRenderersUnderScreenPoint(new Vector2(0, 0)); 92 | 93 | // Assert.AreEqual(2, renderers.Count); 94 | // Assert.AreEqual(obj2, renderers[0].gameObject); 95 | // Assert.AreEqual(obj1, renderers[1].gameObject); 96 | //} 97 | 98 | //[Test] 99 | //public void ReturnsRenderersCorrectlySortedBySortingLayerAndSortingOrder() 100 | //{ 101 | // var sortingLayerNames = GetSortingLayerNames(); 102 | // if (sortingLayerNames.Length < 2) throw new Exception("Cannot run test, not enough sorting layer names!"); 103 | 104 | // var obj1a = CreateGameObject("obj1a"); 105 | // obj1a.transform.localPosition = new Vector3(-10, -10, 0); 106 | // obj1a.AddComponent().size = new Vector2(100, 100); 107 | // obj1a.AddComponent().sortingLayerName = sortingLayerNames[0]; 108 | // obj1a.GetComponent().sortingOrder = 0; 109 | 110 | // var obj1b = CreateGameObject("obj1b"); 111 | // obj1b.transform.localPosition = new Vector3(-10, -10, 0); 112 | // obj1b.AddComponent().size = new Vector2(100, 100); 113 | // obj1b.AddComponent().sortingLayerName = sortingLayerNames[0]; 114 | // obj1b.GetComponent().sortingOrder = 2; 115 | 116 | // var obj2a = CreateGameObject("obj2a"); 117 | // obj2a.transform.localPosition = new Vector3(-10, -10, 0); 118 | // obj2a.AddComponent().size = new Vector2(100, 100); 119 | // obj2a.AddComponent().sortingLayerName = sortingLayerNames[1]; 120 | // obj2a.GetComponent().sortingOrder = 2; 121 | 122 | // var obj2b = CreateGameObject("obj2a"); 123 | // obj2b.transform.localPosition = new Vector3(-10, -10, 0); 124 | // obj2b.AddComponent().size = new Vector2(100, 100); 125 | // obj2b.AddComponent().sortingLayerName = sortingLayerNames[1]; 126 | // obj2b.GetComponent().sortingOrder = 0; 127 | 128 | // Camera.main.transform.position = new Vector3(0, 0, -10); 129 | // var renderers = UnityUtils.GetSpriteRenderersUnderScreenPoint(new Vector2(0, 0)); 130 | 131 | // Assert.AreEqual(4, renderers.Count); 132 | // Assert.AreEqual(obj2a, renderers[0].gameObject); 133 | // Assert.AreEqual(obj2b, renderers[1].gameObject); 134 | // Assert.AreEqual(obj1b, renderers[2].gameObject); 135 | // Assert.AreEqual(obj1a, renderers[3].gameObject); 136 | //} 137 | 138 | // Get the sorting layer names 139 | public string[] GetSortingLayerNames() 140 | { 141 | Type internalEditorUtilityType = typeof(InternalEditorUtility); 142 | PropertyInfo sortingLayersProperty = internalEditorUtilityType.GetProperty("sortingLayerNames", BindingFlags.Static | BindingFlags.NonPublic); 143 | return (string[])sortingLayersProperty.GetValue(null, new object[0]); 144 | } 145 | 146 | // Get the unique sorting layer IDs -- tossed this in for good measure 147 | public int[] GetSortingLayerUniqueIDs() 148 | { 149 | Type internalEditorUtilityType = typeof(InternalEditorUtility); 150 | PropertyInfo sortingLayerUniqueIDsProperty = internalEditorUtilityType.GetProperty("sortingLayerUniqueIDs", BindingFlags.Static | BindingFlags.NonPublic); 151 | return (int[])sortingLayerUniqueIDsProperty.GetValue(null, new object[0]); 152 | 153 | } 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /Editor/Tests/UnityExtensionsTests.cs: -------------------------------------------------------------------------------- 1 | using NUnit.Framework; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using UnityEngine; 7 | using UnityEngine.Events; 8 | using UnityHelpers; 9 | 10 | namespace UnityHelpers.Tests 11 | { 12 | [TestFixture] 13 | public class UnityExtensionsTests 14 | { 15 | interface IMockComponent { } 16 | class MockComponent : MonoBehaviour, IMockComponent { } 17 | class AnotherMockComponent : MonoBehaviour, IMockComponent { } 18 | 19 | [Test] 20 | public void HasComponentFalseWhenNoComponentExists() 21 | { 22 | var obj = new GameObject(); 23 | Assert.IsFalse(obj.HasComponentOrInterface()); 24 | } 25 | 26 | [Test] 27 | public void HasComponentFalseWhenNoComponentInterfaceExists() 28 | { 29 | var obj = new GameObject(); 30 | Assert.IsFalse(obj.HasComponentOrInterface()); 31 | } 32 | 33 | [Test] 34 | public void HasComponentTrueWhenComponentExists() 35 | { 36 | var obj = new GameObject(); 37 | obj.AddComponent(); 38 | Assert.IsTrue(obj.HasComponentOrInterface()); 39 | } 40 | 41 | [Test] 42 | public void HasComponentTrueWhenComponentInterfaceExists() 43 | { 44 | var obj = new GameObject(); 45 | obj.AddComponent(); 46 | Assert.IsTrue(obj.HasComponentOrInterface()); 47 | } 48 | 49 | [Test] 50 | public void GetsNullComponentWhenNoComponentExists() 51 | { 52 | var obj = new GameObject(); 53 | Assert.IsNull(obj.GetComponentOrInterface()); 54 | } 55 | 56 | [Test] 57 | public void GetsNullComponentWhenNoComponentInterfaceExists() 58 | { 59 | var obj = new GameObject(); 60 | Assert.IsNull(obj.GetComponentOrInterface()); 61 | } 62 | 63 | [Test] 64 | public void GetsComponent() 65 | { 66 | var obj = new GameObject(); 67 | var comp = obj.AddComponent(); 68 | Assert.AreEqual(comp, obj.GetComponentOrInterface()); 69 | } 70 | 71 | [Test] 72 | public void GetsComponentInterface() 73 | { 74 | var obj = new GameObject(); 75 | var comp = obj.AddComponent(); 76 | Assert.AreEqual(comp, obj.GetComponentOrInterface()); 77 | } 78 | 79 | [Test] 80 | public void GetsNoComponentsWhenNoneExists() 81 | { 82 | var obj = new GameObject(); 83 | Assert.AreEqual(0, obj.GetAllComponentsOrInterfaces().Count()); 84 | } 85 | 86 | [Test] 87 | public void GetsNoComponentInterfacesWhenNoneExists() 88 | { 89 | var obj = new GameObject(); 90 | Assert.AreEqual(0, obj.GetAllComponentsOrInterfaces().Count()); 91 | } 92 | 93 | [Test] 94 | public void GetsAllComponents() 95 | { 96 | var obj = new GameObject(); 97 | var comp1 = obj.AddComponent(); 98 | var comp2 = obj.AddComponent(); 99 | 100 | var all = obj.GetAllComponentsOrInterfaces(); 101 | 102 | Assert.AreEqual(2, all.Count()); 103 | Assert.IsTrue(all.Contains(comp1)); 104 | Assert.IsTrue(all.Contains(comp2)); 105 | } 106 | 107 | [Test] 108 | public void GetsAllComponentInterfaces() 109 | { 110 | var obj = new GameObject(); 111 | var comp1 = obj.AddComponent(); 112 | var comp2 = obj.AddComponent(); 113 | 114 | var all = obj.GetAllComponentsOrInterfaces(); 115 | 116 | Assert.AreEqual(2, all.Count()); 117 | Assert.IsTrue(all.Contains(comp1)); 118 | Assert.IsTrue(all.Contains(comp2)); 119 | } 120 | 121 | [Test] 122 | public void AddsChildObject() 123 | { 124 | var parent = new GameObject(); 125 | var child = parent.AddChild(); 126 | Assert.AreEqual(parent.transform, child.transform.parent); 127 | } 128 | 129 | [Test] 130 | public void AddsChildObjectWithComponent() 131 | { 132 | var parent = new GameObject(); 133 | var childComponent = parent.AddChild(); 134 | Assert.AreEqual(parent.transform, childComponent.transform.parent); 135 | } 136 | 137 | [Test] 138 | public void AddsNamedChildObject() 139 | { 140 | var parent = new GameObject(); 141 | var child = parent.AddChild("Test Child Game Object"); 142 | Assert.AreEqual("Test Child Game Object", child.name); 143 | } 144 | 145 | [Test] 146 | public void AddsNamedChildObjectWithComponent() 147 | { 148 | var parent = new GameObject(); 149 | var child = parent.AddChild("Test Child Game Object"); 150 | Assert.AreEqual("Test Child Game Object", child.name); 151 | } 152 | 153 | [Test] 154 | public void AddsChildObjectWithComponents() 155 | { 156 | var parent = new GameObject(); 157 | var child = parent.AddChild(typeof(MockComponent), typeof(AnotherMockComponent)); 158 | 159 | var all = child.GetAllComponentsOrInterfaces(); 160 | 161 | Assert.AreEqual(2, all.Count()); 162 | Assert.AreEqual(parent.transform, child.transform.parent); 163 | } 164 | 165 | [Test] 166 | public void ConvertsColor() 167 | { 168 | var original = new Color(0.1f, 0.2f, 0.3f, 0.3f); 169 | var newColor = original.ToUInt().ToColor(); 170 | Assert.IsTrue(newColor.r > original.r - 0.01 && newColor.r < original.r + 0.01); 171 | Assert.IsTrue(newColor.g > original.g - 0.01 && newColor.g < original.g + 0.01); 172 | Assert.IsTrue(newColor.b > original.b - 0.01 && newColor.b < original.b + 0.01); 173 | Assert.IsTrue(newColor.a > original.a - 0.01 && newColor.a < original.a + 0.01); 174 | } 175 | 176 | [Test] 177 | [ExpectedException(typeof(ArgumentException), ExpectedMessage = "Cannot randomly pick an item from the list, the list is null!")] 178 | public void ThrowsExceptionIfTryingToRandomlyPickFromANullEnumarable() 179 | { 180 | List colors = null; 181 | colors.RandomOne(); 182 | } 183 | 184 | [Test] 185 | [ExpectedException(typeof(ArgumentException), ExpectedMessage = "Cannot randomly pick an item from the list, there are no items in the list!")] 186 | public void ThrowsExceptionIfTryingToRandomlyPickFromAnEmptyList() 187 | { 188 | var colors = new List(); 189 | colors.RandomOne(); 190 | } 191 | 192 | [Test] 193 | public void ReturnsTheFirstElementWhenRandomlyPickingFromAListWithOneElment() 194 | { 195 | var colors = new List() { Color.blue }; 196 | var color = colors.RandomOne(); 197 | Assert.AreEqual(Color.blue, color); 198 | } 199 | 200 | [Test] 201 | public void RoughlyRandomlyPicksElementsFromAList() 202 | { 203 | var colors = new List() { Color.blue, Color.red }; 204 | var redCount = 0; 205 | var blueCount = 0; 206 | // Random it 1000 times 207 | for (int i = 0; i < 1000; i++) 208 | { 209 | if (colors.RandomOne() == Color.red) redCount++; 210 | else blueCount++; 211 | } 212 | 213 | // Should expect roughly 500 reds and 500 blues 214 | Assert.IsTrue(redCount + blueCount == 1000); 215 | Assert.IsTrue(redCount > 400 && redCount < 600); 216 | Assert.IsTrue(blueCount > 400 && blueCount < 600); 217 | } 218 | 219 | [Test] 220 | public void AddOnceRemovesTheCallbackAfterInvokation() 221 | { 222 | var evnt = new UnityEvent(); 223 | var callCount = 0; 224 | evnt.AddOnce(() => 225 | { 226 | callCount++; 227 | }); 228 | evnt.Invoke(); 229 | evnt.Invoke(); // second invoke shouldnt call the handler 230 | Assert.AreEqual(1, callCount); 231 | } 232 | 233 | } 234 | } 235 | -------------------------------------------------------------------------------- /Editor/Tools/MissingScriptResolver.cs: -------------------------------------------------------------------------------- 1 | // *********************************************************** 2 | // Copyright 2013 Daikon Forge, http://www.daikonforge.com 3 | // All rights reserved. 4 | // 5 | // This source code is free for all non-commercial uses. 6 | // 7 | // THIS SOFTWARE IS PROVIDED 'AS IS' AND WITHOUT ANY EXPRESS OR 8 | // IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED 9 | // WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. 10 | // 11 | // *********************************************************** 12 | 13 | using UnityEngine; 14 | using UnityEditor; 15 | using System.Collections; 16 | using System.Collections.Generic; 17 | using System; 18 | using System.Linq; 19 | using System.Reflection; 20 | 21 | using UnityObject = UnityEngine.Object; 22 | 23 | [CustomEditor( typeof( MonoBehaviour ) )] 24 | public class MissingScriptResolver : Editor 25 | { 26 | 27 | #region Static variables 28 | 29 | private const string HELP_INFO = @"This component has a missing script. 30 | 31 | Possible candidates for the missing script are shown below. Click the button that represents the missing script to assign that script. 32 | 33 | This component's properties are shown below to help you determine which script is correct."; 34 | 35 | // Will hold a ScriptMatcher reference for each discovered type 36 | private static List types = null; 37 | 38 | // Cached list of candidate scripts for each component 39 | private static Dictionary> candidates = new Dictionary>(); 40 | 41 | #endregion 42 | 43 | #region Menu add-ins 44 | 45 | [MenuItem( "Unity Helpers/Find Missing Scripts In Prefabs", priority = 1 )] 46 | public static void FindMissingScriptsInPrefabs() 47 | { 48 | 49 | var progressTime = Environment.TickCount; 50 | 51 | #region Load all assets in project before searching 52 | 53 | var allAssetPaths = AssetDatabase.GetAllAssetPaths(); 54 | for( int i = 0; i < allAssetPaths.Length; i++ ) 55 | { 56 | 57 | if( Environment.TickCount - progressTime > 250 ) 58 | { 59 | progressTime = Environment.TickCount; 60 | EditorUtility.DisplayProgressBar( "Find Missing Scripts", "Searching prefabs", (float)i / (float)allAssetPaths.Length ); 61 | } 62 | 63 | AssetDatabase.LoadMainAssetAtPath( allAssetPaths[ i ] ); 64 | 65 | } 66 | 67 | EditorUtility.ClearProgressBar(); 68 | 69 | #endregion 70 | 71 | var prefabs = Resources 72 | .FindObjectsOfTypeAll( typeof( GameObject ) ) 73 | .Cast() 74 | .Where( x => x.transform.parent == null && isPrefab( x ) ) 75 | .OrderBy( x => x.name ) 76 | .ToList(); 77 | 78 | var brokenPrefabs = prefabs 79 | .Where( x => x.GetComponentsInChildren( true ).Any( c => c == null ) ) 80 | .ToList(); 81 | 82 | var message = ""; 83 | if( brokenPrefabs.Count > 0 ) 84 | { 85 | 86 | for( int i = 0; i < brokenPrefabs.Count; i++ ) 87 | { 88 | var prefab = brokenPrefabs[ i ]; 89 | var path = AssetDatabase.GetAssetPath( prefab ); 90 | Debug.LogWarning( "Prefab has missing scripts: " + path, prefab ); 91 | } 92 | 93 | message = string.Format( "Found {0} prefabs with missing scripts. The full list can be viewed in the console pane", brokenPrefabs.Count ); 94 | 95 | } 96 | else 97 | { 98 | message = string.Format( "Searched {0} prefabs. No missing scripts detected.", prefabs.Count ); 99 | } 100 | 101 | EditorUtility.DisplayDialog( "Find Missing Scripts", message, "OK" ); 102 | 103 | } 104 | 105 | [MenuItem( "Unity Helpers/Find Missing Scripts In Scene", priority = 0 )] 106 | public static void FindMissingScriptsInScene() 107 | { 108 | 109 | var broken = findBrokenObjectsInScene(); 110 | if( broken.Count == 0 ) 111 | { 112 | 113 | EditorUtility.DisplayDialog( "No missing scripts", "There are no objects with missing scripts in this scene.", "YAY!" ); 114 | 115 | // Make sure static lists are cleaned up 116 | types = null; 117 | candidates = null; 118 | 119 | return; 120 | 121 | } 122 | 123 | // Grab the object highest up in the scene hierarchy 124 | var sorted = broken 125 | .Select( x => new { target = x, path = getObjectPath( x ) } ) 126 | .OrderBy( x => x.path ) 127 | .First(); 128 | 129 | Debug.LogWarning( string.Format( "{0} objects in this scene have missing scripts, selecting '{1}' first", broken.Count, sorted.target.name ) ); 130 | Selection.activeGameObject = sorted.target; 131 | 132 | } 133 | 134 | #endregion 135 | 136 | #region Unity events 137 | 138 | public override void OnInspectorGUI() 139 | { 140 | 141 | try 142 | { 143 | 144 | // If the inspected component does not have a missing script, 145 | // revert to base Inspector functionality 146 | var scriptProperty = this.serializedObject.FindProperty( "m_Script" ); 147 | if( scriptProperty == null || scriptProperty.objectReferenceValue != null ) 148 | { 149 | base.OnInspectorGUI(); 150 | return; 151 | } 152 | 153 | // Make sure that the inspector is not viewing a prefab directly. 154 | // Not sure what's up, but doing so with this custom inspector 155 | // crashes Unity with 100% reproducability. 156 | var behavior = target as MonoBehaviour; 157 | if( behavior != null && isPrefab( behavior.gameObject ) ) 158 | { 159 | 160 | var prefabMessage = @"The Missing Script Resolver cannot edit prefabs directly. Please move this prefab into a scene before editing"; 161 | EditorGUILayout.HelpBox( prefabMessage, MessageType.Error ); 162 | 163 | types = null; 164 | candidates = null; 165 | 166 | base.OnInspectorGUI(); 167 | return; 168 | 169 | } 170 | 171 | // Ensure candidate list contains an entry for the inspected component 172 | if( candidates == null ) candidates = new Dictionary>(); 173 | if( !candidates.ContainsKey( target ) ) 174 | { 175 | 176 | // Find all MonoScript instances that are a possible match 177 | // for the component currently in the inspector, and sort 178 | // them from mostly likely match to least likely match. 179 | var candidateLookup = 180 | getDefinedScriptTypes() 181 | .Select( c => new ScriptLookup() { Matcher = c, Score = c.ScoreMatch( serializedObject ) } ) 182 | .Where( c => c.Score > 0 ) 183 | .OrderByDescending( c => c.Score ) 184 | .ToList(); 185 | 186 | candidates[ target ] = candidateLookup; 187 | 188 | } 189 | 190 | // Retrieve the list of possible matching scripts 191 | var possibleMatches = candidates[ target ]; 192 | 193 | GUILayout.Label( "Missing Script", "HeaderLabel" ); 194 | 195 | // Show help information 196 | EditorGUILayout.HelpBox( HELP_INFO, MessageType.Warning ); 197 | 198 | // If there are no possible matches found, let the user know that they 199 | // must manually assign the missing script. 200 | if( possibleMatches.Count == 0 ) 201 | { 202 | EditorGUILayout.HelpBox( "No matching scripts found.", MessageType.Error ); 203 | base.OnInspectorGUI(); 204 | return; 205 | } 206 | 207 | // Let the developer decide how many possible matches to display. 208 | // This was done because sometimes there are a number of scripts 209 | // whose fields appear to be a match based on name and type, such 210 | // as when one script inherits from another without adding any 211 | // additional serialized properties. 212 | var candidateCountConfig = EditorPrefs.GetInt( "DaikonForge.MissingScriptCount", 3 ); 213 | var candidateCount = Mathf.Max( EditorGUILayout.IntField( "Num Scripts to Show", candidateCountConfig ), 1 ); 214 | if( candidateCount != candidateCountConfig ) 215 | { 216 | EditorPrefs.SetInt( "DaikonForge.MissingScriptCount", candidateCount ); 217 | } 218 | 219 | EditorGUILayout.BeginHorizontal(); 220 | GUILayout.Space( 25 ); 221 | EditorGUILayout.BeginVertical(); 222 | 223 | for( int i = 0; i < Mathf.Min( candidateCount, possibleMatches.Count ); i++ ) 224 | { 225 | 226 | // Display a button to select the candidate script 227 | var candidate = possibleMatches[ i ]; 228 | if( !GUILayout.Button( candidate.Matcher.Name, "minibutton" ) ) 229 | continue; 230 | 231 | // Make this operation undo-able 232 | #if UNITY_4_3 233 | Undo.RegisterCompleteObjectUndo( target, "Assign missing script" ); 234 | #else 235 | Undo.RegisterSceneUndo( "Assign missing script" ); 236 | #endif 237 | 238 | // Assign the selected MonoScript 239 | scriptProperty.objectReferenceValue = candidate.Matcher.Script; 240 | scriptProperty.serializedObject.ApplyModifiedProperties(); 241 | scriptProperty.serializedObject.Update(); 242 | 243 | // Save the scene in case Unity crashes 244 | EditorUtility.SetDirty( this.target ); 245 | EditorApplication.SaveScene(); 246 | EditorApplication.SaveAssets(); 247 | 248 | // Check for more objects with missing scripts 249 | if( Selection.activeGameObject.activeInHierarchy ) 250 | { 251 | 252 | // If there are no more missing scripts in this scene, 253 | // let the developer know and clean up static lists 254 | var broken = findBrokenObjectsInScene(); 255 | if( broken.Count == 0 ) 256 | { 257 | 258 | EditorUtility.DisplayDialog( "No missing scripts", "There are no objects with missing scripts in this scene", "YAY!" ); 259 | 260 | // Make sure static lists are cleaned up 261 | types = null; 262 | candidates = null; 263 | 264 | } 265 | else 266 | { 267 | 268 | // Select the next object with missing scripts. This may be 269 | // the current object if there are more components with 270 | // missing scripts on this object. Yay for sorting! 271 | Selection.activeGameObject = broken.First(); 272 | 273 | } 274 | 275 | } 276 | 277 | } 278 | 279 | EditorGUILayout.EndVertical(); 280 | EditorGUILayout.EndHorizontal(); 281 | 282 | } 283 | catch( Exception err ) 284 | { 285 | Debug.LogError( err ); 286 | } 287 | 288 | GUILayout.Label( "Component Properties", "HeaderLabel" ); 289 | EditorGUI.indentLevel += 1; 290 | 291 | base.OnInspectorGUI(); 292 | 293 | EditorGUI.indentLevel -= 1; 294 | 295 | } 296 | 297 | #endregion 298 | 299 | #region Private utility functions 300 | 301 | /// 302 | /// Returns a value indicating whether an object is a prefab 303 | /// 304 | private static bool isPrefab( GameObject item ) 305 | { 306 | 307 | if( item == null ) 308 | return false; 309 | 310 | return 311 | item != null && 312 | PrefabUtility.GetPrefabParent( item ) == null && 313 | PrefabUtility.GetPrefabObject( item ) != null; 314 | 315 | } 316 | 317 | /// 318 | /// Returns the nesting level of a GameObject 319 | /// 320 | /// 321 | /// 322 | private static string getObjectPath( GameObject x ) 323 | { 324 | 325 | var path = new System.Text.StringBuilder( 1024 ); 326 | 327 | var depth = 0; 328 | 329 | while( x != null && x.transform.parent != null ) 330 | { 331 | path.Append( x.name ); 332 | path.Append( "/" ); 333 | x = x.transform.parent.gameObject; 334 | depth += 1; 335 | } 336 | 337 | return depth.ToString( "D12" ) + "/" + path.ToString(); 338 | 339 | } 340 | 341 | /// 342 | /// Returns a list of GameObject instances in the current scene that 343 | /// have at least one Missing Script, sorted by their path in the 344 | /// scene hierarchy 345 | /// 346 | /// 347 | private static List findBrokenObjectsInScene() 348 | { 349 | 350 | // Find all of the GameObjects in the scene and sort them 351 | // by the "path" in the scene hierarchy 352 | var brokenObjects = Resources 353 | .FindObjectsOfTypeAll( typeof( GameObject ) ) 354 | .Cast() 355 | .Where( x => x.activeInHierarchy && x.GetComponents().Any( c => c == null ) ) 356 | .OrderBy( x => getObjectPath( x ) ) 357 | .ToList(); 358 | 359 | return brokenObjects; 360 | 361 | } 362 | 363 | /// 364 | /// Return a list containing a ScriptMatcher instance for 365 | /// each MonoScript defined in the current project. 366 | /// Note that Unity defines a MonoScript even for types 367 | /// defined in referenced assemblies, not just user scripts. 368 | /// 369 | /// 370 | private static List getDefinedScriptTypes() 371 | { 372 | 373 | if( types != null ) 374 | return types; 375 | 376 | // Get the list of all MonoScript instances in the project that 377 | // are not abstract or unclosed generic types 378 | types = Resources 379 | .FindObjectsOfTypeAll( typeof( MonoScript ) ) 380 | .Where( x => x.GetType() == typeof( MonoScript ) ) // Fix for Unity crash 381 | .Cast() 382 | .Select( x => new ScriptMatcher( x ) ) 383 | .Where( x => x.Type != null && !x.Type.IsAbstract && !x.Type.IsGenericType ) 384 | .ToList(); 385 | 386 | // Ignore any MonoScript types defined by Unity, as it's extremely 387 | // unlikely that they could ever be missing 388 | var editorAssembly = typeof( Editor ).Assembly; 389 | var engineAssembly = typeof( MonoBehaviour ).Assembly; 390 | types.RemoveAll( x => x.Type.Assembly == editorAssembly || x.Type.Assembly == engineAssembly ); 391 | 392 | return types; 393 | 394 | } 395 | 396 | #endregion 397 | 398 | #region Nested utility classes 399 | 400 | private class ScriptLookup 401 | { 402 | public ScriptMatcher Matcher; 403 | public float Score; 404 | } 405 | 406 | /// 407 | /// Used to determine the likelihood that a particular MonoScript 408 | /// is a match for a component with the "Missing Script" issue 409 | /// 410 | private class ScriptMatcher 411 | { 412 | 413 | #region Private data members 414 | 415 | private MonoScript script; 416 | private Type type; 417 | private List fields; 418 | 419 | #endregion 420 | 421 | #region Constructor 422 | 423 | public ScriptMatcher( MonoScript script ) 424 | { 425 | this.script = script; 426 | this.type = script.GetClass(); 427 | this.fields = GetAllFields( type ).ToList(); 428 | } 429 | 430 | #endregion 431 | 432 | #region Public properties 433 | 434 | public MonoScript Script { get { return this.script; } } 435 | 436 | public Type Type { get { return this.type; } } 437 | 438 | public string Name { get { return type.Name; } } 439 | 440 | #endregion 441 | 442 | #region Public methods 443 | 444 | /// 445 | /// Generates a score indicating how likely the script is to 446 | /// be a match for the serialized object, with values closer 447 | /// to 1 being most likely and values closer to 0 being the 448 | /// least likely. 449 | /// 450 | /// The component with the Missing Script issue 451 | public float ScoreMatch( SerializedObject target ) 452 | { 453 | 454 | int count = 0; 455 | 456 | var iter = target.GetIterator(); 457 | iter.Next( true ); 458 | while( iter.Next( false ) ) 459 | { 460 | var field = fields.Find( f => f.Name == iter.name ); 461 | if( field != null ) 462 | { 463 | switch( iter.propertyType ) 464 | { 465 | case SerializedPropertyType.ArraySize: 466 | if( field.FieldType.HasElementType ) count += 1; 467 | else if( typeof( IEnumerable ).IsAssignableFrom( field.FieldType ) ) count += 1; 468 | break; 469 | case SerializedPropertyType.AnimationCurve: 470 | if( field.FieldType == typeof( AnimationCurve ) ) count += 1; 471 | break; 472 | case SerializedPropertyType.Boolean: 473 | if( field.FieldType == typeof( bool ) ) count += 1; 474 | break; 475 | case SerializedPropertyType.Bounds: 476 | if( field.FieldType == typeof( Bounds ) ) count += 1; 477 | break; 478 | case SerializedPropertyType.Color: 479 | if( field.FieldType == typeof( Color32 ) ) count += 1; 480 | else if( field.FieldType == typeof( Color ) ) count += 1; 481 | break; 482 | case SerializedPropertyType.Enum: 483 | if( field.FieldType.IsEnum ) count += 1; 484 | break; 485 | case SerializedPropertyType.Float: 486 | if( typeof( float ).IsAssignableFrom( field.FieldType ) ) count += 1; 487 | break; 488 | case SerializedPropertyType.Integer: 489 | if( typeof( int ).IsAssignableFrom( field.FieldType ) ) count += 1; 490 | else if( typeof( uint ).IsAssignableFrom( field.FieldType ) ) count += 1; 491 | else if( field.FieldType.IsEnum ) count += 1; 492 | break; 493 | case SerializedPropertyType.ObjectReference: 494 | if( !field.FieldType.IsValueType ) count += 1; 495 | break; 496 | case SerializedPropertyType.Rect: 497 | if( field.FieldType == typeof( Rect ) ) count += 1; 498 | break; 499 | case SerializedPropertyType.String: 500 | if( field.FieldType == typeof( string ) ) count += 1; 501 | break; 502 | case SerializedPropertyType.Vector2: 503 | if( field.FieldType == typeof( Vector2 ) ) count += 1; 504 | break; 505 | case SerializedPropertyType.Vector3: 506 | if( field.FieldType == typeof( Vector3 ) ) count += 1; 507 | break; 508 | default: 509 | count += 1; 510 | break; 511 | } 512 | 513 | } 514 | 515 | } 516 | 517 | if( count == 0 ) 518 | return 0f; 519 | 520 | return (float)count / fields.Count; 521 | 522 | } 523 | 524 | #endregion 525 | 526 | #region Private methods 527 | 528 | /// 529 | /// Returns all instance fields on an object, including inherited fields 530 | /// 531 | private static FieldInfo[] GetAllFields( Type type ) 532 | { 533 | 534 | // http://stackoverflow.com/a/1155549/154165 535 | 536 | if( type == null ) 537 | return new FieldInfo[ 0 ]; 538 | 539 | BindingFlags flags = 540 | BindingFlags.Public | 541 | BindingFlags.NonPublic | 542 | BindingFlags.Instance | 543 | BindingFlags.DeclaredOnly; 544 | 545 | return 546 | type.GetFields( flags ) 547 | .Concat( GetAllFields( type.BaseType ) ) 548 | .Where( f => !f.IsDefined( typeof( HideInInspector ), true ) ) 549 | .ToArray(); 550 | 551 | } 552 | 553 | #endregion 554 | 555 | } 556 | 557 | #endregion 558 | 559 | } -------------------------------------------------------------------------------- /Scripts/UnityExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using UnityEngine; 6 | using UnityEngine.Events; 7 | using UnityHelpers; 8 | 9 | /// 10 | /// Putting these in the global namespace so that they can be accessed from anywhere in the game without importing 11 | /// 12 | public static class UnityExtensions 13 | { 14 | private static List componentCache = new List(); 15 | 16 | /// 17 | /// A shorter way of testing if a game object has a component 18 | /// 19 | /// Component type 20 | /// The object to check on 21 | /// 22 | public static bool Has(this GameObject obj) where T : Component 23 | { 24 | return obj.GetComponent() != null; 25 | } 26 | 27 | /// 28 | /// A slightly shorter way to get a component from an object 29 | /// 30 | /// Component type 31 | /// The object to get from 32 | /// 33 | public static T Get(this GameObject obj) where T : Component 34 | { 35 | return obj.GetComponent(); 36 | } 37 | 38 | /// 39 | /// A slightly shorter way to get a component from an object 40 | /// 41 | /// Component type 42 | /// The object to get from 43 | /// 44 | public static T[] GetAll(this GameObject obj) where T : Component 45 | { 46 | return obj.GetComponents(); 47 | } 48 | 49 | // 50 | /// A shorter way of testing if a game object has a component 51 | /// 52 | /// Component type 53 | /// The component to check on 54 | /// 55 | public static bool Has(this Component obj) where T : Component 56 | { 57 | return obj.GetComponent() != null; 58 | } 59 | 60 | /// 61 | /// A slightly shorter way to get a component from an object 62 | /// 63 | /// Component type 64 | /// The component to get from 65 | /// 66 | public static T Get(this Component obj) where T : Component 67 | { 68 | return obj.GetComponent(); 69 | } 70 | 71 | /// 72 | /// A slightly shorter way to get a component from an object 73 | /// 74 | /// Component type 75 | /// The component to get from 76 | /// 77 | public static T[] GetAll(this Component obj) where T : Component 78 | { 79 | return obj.GetComponents(); 80 | } 81 | 82 | /// 83 | /// Checks to see if the game object has a component, accepts interfaces 84 | /// 85 | /// Type of component to check for (can be an interface) 86 | /// Whether the object has the component 87 | public static bool HasComponentOrInterface(this GameObject obj) where T : class 88 | { 89 | obj.GetComponents(componentCache); 90 | return componentCache.OfType().Count() > 0; 91 | } 92 | 93 | /// 94 | /// Gets a component on a game object, accepts interfaces 95 | /// 96 | /// Type of component to check for (can be an interface) 97 | /// 98 | /// The component, null if it doesnt exist 99 | public static T GetComponentOrInterface(this GameObject obj) where T : class 100 | { 101 | obj.GetComponents(componentCache); 102 | return componentCache.OfType().FirstOrDefault(); 103 | } 104 | 105 | /// 106 | /// Gets all components of a given type, accepts interfaces 107 | /// 108 | /// Type of components to get (accepts interfaces) 109 | /// Enumerable of components 110 | public static IEnumerable GetAllComponentsOrInterfaces(this GameObject obj) where T : class 111 | { 112 | return obj.GetComponents().OfType(); 113 | } 114 | 115 | /// 116 | /// Gets all components of a given type including children, accepts interfaces 117 | /// 118 | /// Type of components to get (accepts interfaces) 119 | /// Enumerable of components 120 | public static IEnumerable GetAllComponentsOrInterfacesInChildren(this GameObject obj) where T : class 121 | { 122 | return obj.GetComponentsInChildren().OfType(); 123 | } 124 | 125 | /// 126 | /// Checks to see if the game object has a component, accepts interfaces 127 | /// 128 | /// Type of component to check for (can be an interface) 129 | /// Whether the object has the component 130 | public static bool HasComponentOrInterface(this Component component) where T : class 131 | { 132 | return HasComponentOrInterface(component.gameObject); 133 | } 134 | 135 | /// 136 | /// Checks to see if the game object this component belongs to has a component, accepts interfaces 137 | /// 138 | /// Type of component to check for (can be an interface) 139 | /// Whether the object has the component 140 | public static T GetComponentOrInterface(this Component component) where T : class 141 | { 142 | return GetComponentOrInterface(component.gameObject); 143 | } 144 | 145 | /// 146 | /// Gets all components of a given type, accepts interfaces 147 | /// 148 | /// Type of components to get (accepts interfaces) 149 | /// Enumerable of components 150 | public static IEnumerable GetAllComponentsOrInterfaces(this Component component) where T : class 151 | { 152 | return GetAllComponentsOrInterfaces(component.gameObject); 153 | } 154 | 155 | /// 156 | /// Gets all components of a given type in itself and the children, accepts interfaces 157 | /// 158 | /// Type of components to get (accepts interfaces) 159 | /// Enumerable of components 160 | public static IEnumerable GetAllComponentsOrInterfacesInChildren(this Component component) where T : class 161 | { 162 | return GetAllComponentsOrInterfacesInChildren(component.gameObject); 163 | } 164 | 165 | /// 166 | /// A shortcut for creating a new game object then adding a component then adding it to a parent object 167 | /// 168 | /// Type of component 169 | /// The new component 170 | public static T AddChild(this GameObject parent) where T : Component 171 | { 172 | return AddChild(parent, typeof(T).Name); 173 | } 174 | 175 | /// 176 | /// A shortcut for adding a given game object as a child 177 | /// 178 | /// This gameobject 179 | public static GameObject AddChild(this GameObject parent, GameObject child, bool worldPositionStays = false) 180 | { 181 | child.transform.SetParent(parent.transform, worldPositionStays); 182 | return parent; 183 | } 184 | 185 | /// 186 | /// A shortcut for creating a new game object then adding a component then adding it to a parent object 187 | /// 188 | /// Type of component 189 | /// Name of the child game object 190 | /// The new component 191 | public static T AddChild(this GameObject parent, string name) where T : Component 192 | { 193 | var obj = AddChild(parent, name, typeof(T)); 194 | return obj.GetComponent(); 195 | } 196 | 197 | /// 198 | /// A shortcut for creating a new game object with a number of components and adding it as a child 199 | /// 200 | /// A list of component types 201 | /// The new game object 202 | public static GameObject AddChild(this GameObject parent, params Type[] components) 203 | { 204 | return AddChild(parent, "Game Object", components); 205 | } 206 | 207 | /// 208 | /// A shortcut for creating a new game object with a number of components and adding it as a child 209 | /// 210 | /// The name of the new game object 211 | /// A list of component types 212 | /// The new game object 213 | public static GameObject AddChild(this GameObject parent, string name, params Type[] components) 214 | { 215 | var obj = new GameObject(name, components); 216 | if (parent != null) 217 | { 218 | if (obj.transform is RectTransform) obj.transform.SetParent(parent.transform, true); 219 | else obj.transform.parent = parent.transform; 220 | } 221 | return obj; 222 | } 223 | 224 | /// 225 | /// Loads a resource and adds it as a child 226 | /// 227 | /// The path to the resource to load 228 | /// 229 | public static GameObject LoadChild(this GameObject parent, string resourcePath) 230 | { 231 | var obj = (GameObject)GameObject.Instantiate(Resources.Load(resourcePath)); 232 | if (obj != null && parent != null) 233 | { 234 | if (obj.transform is RectTransform) obj.transform.SetParent(parent.transform, true); 235 | else obj.transform.parent = parent.transform; 236 | } 237 | return obj; 238 | } 239 | 240 | /// 241 | /// Loads a resource and adds it as a child 242 | /// 243 | /// The path to the resource to load 244 | /// 245 | public static GameObject LoadChild(this Transform parent, string resourcePath) 246 | { 247 | var obj = (GameObject)GameObject.Instantiate(Resources.Load(resourcePath)); 248 | if (obj != null && parent != null) 249 | { 250 | if (obj.transform is RectTransform) obj.transform.SetParent(parent, true); 251 | else obj.transform.parent = parent; 252 | } 253 | return obj; 254 | } 255 | 256 | /// 257 | /// Destroys all the children of a given gameobject 258 | /// 259 | /// The parent game object 260 | public static void DestroyAllChildrenImmediately(this GameObject obj) 261 | { 262 | DestroyAllChildrenImmediately(obj.transform); 263 | } 264 | 265 | /// 266 | /// Destroys all the children of a given transform 267 | /// 268 | /// The parent transform 269 | public static void DestroyAllChildrenImmediately(this Transform trans) 270 | { 271 | while (trans.childCount != 0) 272 | GameObject.DestroyImmediate(trans.GetChild(0).gameObject); 273 | } 274 | 275 | /// 276 | /// Focuses the camera on a point in 2D space (just transforms the x and y to match the target) 277 | /// 278 | /// 279 | /// 280 | public static void FocusOn2D(this Camera camera, GameObject target) 281 | { 282 | camera.transform.position = new Vector3(target.transform.localPosition.x, target.transform.localPosition.y, Camera.main.transform.position.z); 283 | } 284 | 285 | /// 286 | /// Converts a uint to a color 287 | /// 288 | /// The uint to convert 289 | /// 290 | public static uint ToUInt(this Color color) 291 | { 292 | Color32 c32 = color; 293 | return (uint)((c32.a << 24) | (c32.r << 16) | 294 | (c32.g << 8) | (c32.b << 0)); 295 | } 296 | 297 | /// 298 | /// Converts a uint to a color32 299 | /// 300 | /// The uint to convert 301 | /// 302 | public static Color32 ToColor32(this uint color) 303 | { 304 | return new Color32() 305 | { 306 | a = (byte)(color >> 24), 307 | r = (byte)(color >> 16), 308 | g = (byte)(color >> 8), 309 | b = (byte)(color >> 0) 310 | }; 311 | } 312 | 313 | /// 314 | /// Converts a uint to a color 315 | /// 316 | /// The uint to convert 317 | /// 318 | public static Color ToColor(this uint color) 319 | { 320 | return ToColor32(color); 321 | } 322 | 323 | /// 324 | /// Converts a color to its hex counterpart 325 | /// 326 | /// The color to convert 327 | /// 328 | public static string ToHex(this Color32 color) 329 | { 330 | return "#" + color.r.ToString("X2") + color.g.ToString("X2") + color.b.ToString("X2"); 331 | } 332 | 333 | /// 334 | /// Converts a timespan to a readable format 335 | /// 336 | /// 337 | /// 338 | public static string ToReadableString(this TimeSpan span) 339 | { 340 | string formatted = string.Format("{0}{1}{2}{3}", 341 | span.Duration().Days > 0 ? string.Format("{0:0} day{1}, ", span.Days, span.Days == 1 ? String.Empty : "s") : string.Empty, 342 | span.Duration().Hours > 0 ? string.Format("{0:0} hour{1}, ", span.Hours, span.Hours == 1 ? String.Empty : "s") : string.Empty, 343 | span.Duration().Minutes > 0 ? string.Format("{0:0} minute{1}, ", span.Minutes, span.Minutes == 1 ? String.Empty : "s") : string.Empty, 344 | span.Duration().Seconds > 0 ? string.Format("{0:0} second{1}", span.Seconds, span.Seconds == 1 ? String.Empty : "s") : string.Empty); 345 | 346 | if (formatted.EndsWith(", ")) formatted = formatted.Substring(0, formatted.Length - 2); 347 | 348 | if (string.IsNullOrEmpty(formatted)) formatted = "0 seconds"; 349 | 350 | return formatted; 351 | } 352 | 353 | /// 354 | /// Converts a timespan to a readable format but in a shorter form 355 | /// 356 | /// 357 | /// 358 | public static string ToReadableStringShortForm(this TimeSpan span) 359 | { 360 | string formatted = string.Format("{0}{1}{2}{3}", 361 | span.Duration().Days > 0 ? string.Format("{0:0}d, ", span.Days) : string.Empty, 362 | span.Duration().Hours > 0 ? string.Format("{0:0}h, ", span.Hours) : string.Empty, 363 | span.Duration().Minutes > 0 ? string.Format("{0:0}m, ", span.Minutes) : string.Empty, 364 | span.Duration().Seconds > 0 ? string.Format("{0:0}s", span.Seconds) : string.Empty); 365 | 366 | if (formatted.EndsWith(", ")) formatted = formatted.Substring(0, formatted.Length - 2); 367 | 368 | if (string.IsNullOrEmpty(formatted)) formatted = "0s"; 369 | 370 | return formatted; 371 | } 372 | 373 | /// 374 | /// Randomly picks one elements from the enumerable 375 | /// 376 | /// The type of the item 377 | /// The items to random from 378 | /// 379 | public static T RandomOne(this IEnumerable items) 380 | { 381 | if (items == null) throw new ArgumentException("Cannot randomly pick an item from the list, the list is null!"); 382 | if (items.Count() == 0) throw new ArgumentException("Cannot randomly pick an item from the list, there are no items in the list!"); 383 | var r = UnityEngine.Random.Range(0, items.Count()); 384 | return items.ElementAt(r); 385 | } 386 | 387 | /// 388 | /// Borrowed from http://stackoverflow.com/questions/56692/random-weighted-choice 389 | /// Randomly picks one element from the enumerable, taking into account a weight 390 | /// 391 | /// 392 | /// 393 | /// 394 | /// 395 | public static T WeightedRandomOne(this IEnumerable sequence, Func weightSelector) 396 | { 397 | float totalWeight = sequence.Sum(weightSelector); 398 | // The weight we are after... 399 | float itemWeightIndex = UnityEngine.Random.value * totalWeight; 400 | float currentWeightIndex = 0; 401 | 402 | foreach (var item in from weightedItem in sequence select new { Value = weightedItem, Weight = weightSelector(weightedItem) }) 403 | { 404 | currentWeightIndex += item.Weight; 405 | 406 | // If we've hit or passed the weight we are after for this item then it's the one we want.... 407 | if (currentWeightIndex >= itemWeightIndex) 408 | return item.Value; 409 | 410 | } 411 | 412 | return default(T); 413 | } 414 | 415 | /// 416 | /// Loops over each item in the list and logs out a particular value, handy for debugging! 417 | /// 418 | /// The type of the item 419 | /// The list of items 420 | /// A callback in which you will return the item to log 421 | /// The original input list 422 | public static IEnumerable DebugLog(this IEnumerable items, Func objectToLog) 423 | { 424 | var count = 0; 425 | foreach (var item in items) 426 | { 427 | var obj = objectToLog(item); 428 | Debug.Log(String.Format("Item[{0}]: {1}", count, obj)); 429 | } 430 | return items; 431 | } 432 | 433 | /// 434 | /// Adds a listner to a UnityEvent which is removed as soon as the event is invoked 435 | /// 436 | /// the event to listen for 437 | /// the callback to call 438 | /// 439 | public static UnityEvent AddOnce(this UnityEvent evnt, Action callback) 440 | { 441 | UnityAction a = null; 442 | a = () => 443 | { 444 | evnt.RemoveListener(a); 445 | callback(); 446 | }; 447 | 448 | evnt.AddListener(a); 449 | return evnt; 450 | } 451 | 452 | /// 453 | /// Adds a listner to a UnityEvent which is removed as soon as the event is invoked 454 | /// 455 | /// the event to listen for 456 | /// the callback to call 457 | /// 458 | public static UnityEvent AddOnce(this UnityEvent evnt, Action callback) 459 | { 460 | UnityAction a = null; 461 | a = obj => 462 | { 463 | evnt.RemoveListener(a); 464 | callback(obj); 465 | }; 466 | 467 | evnt.AddListener(a); 468 | return evnt; 469 | } 470 | 471 | /// 472 | /// Returns the orthographic bounds for a camera 473 | /// !! NOT UNIT TESTED !! 474 | /// Borrowed from: http://answers.unity3d.com/questions/501893/calculating-2d-camera-bounds.html 475 | /// 476 | /// the camera to get the bounds on 477 | /// the orthographic bounds on a camera 478 | public static Bounds OrthographicBounds(this Camera camera) 479 | { 480 | float screenAspect = (float)Screen.width / (float)Screen.height; 481 | float cameraHeight = camera.orthographicSize * 2; 482 | Bounds bounds = new Bounds( 483 | camera.transform.position, 484 | new Vector3(cameraHeight * screenAspect, cameraHeight, 0)); 485 | return bounds; 486 | } 487 | 488 | /// 489 | /// Tests if two bounds intersect. This is neccessary as there appears to be a bug with normal Bounds Intersection. 490 | /// !! NOT UNIT TESTED !! 491 | /// 492 | /// First bound 493 | /// Second bound 494 | /// 495 | public static bool Intersects2(this Bounds a, Bounds b) 496 | { 497 | return !(b.min.x > a.max.x || 498 | b.max.x < a.min.x || 499 | b.min.y > a.max.y || 500 | b.max.y < a.min.y); 501 | } 502 | 503 | /// 504 | /// A simple clone method for a Vector3 505 | /// 506 | /// The vector to clone 507 | /// 508 | public static Vector3 Clone(this Vector3 v) 509 | { 510 | return new Vector3(v.x, v.y, v.z); 511 | } 512 | 513 | /// 514 | /// A simple clone method for a Vector2 515 | /// 516 | /// The vector to clone 517 | /// 518 | public static Vector2 Clone(this Vector2 v) 519 | { 520 | return new Vector2(v.x, v.y); 521 | } 522 | 523 | /// 524 | /// Simple method to turn a v2 into a v3 525 | /// 526 | /// The vector to convert 527 | /// 528 | public static Vector3 ToVector3(this Vector2 v) 529 | { 530 | return new Vector3(v.x, v.y, 0); 531 | } 532 | 533 | /// 534 | /// Simple method to turn a v3 into a v2 535 | /// 536 | /// The vector to convert 537 | /// 538 | public static Vector2 ToVector2(this Vector3 v) 539 | { 540 | return new Vector2(v.x, v.y); 541 | } 542 | 543 | /// 544 | /// Quick shuffle of a list 545 | /// Borrowed from: http://stackoverflow.com/questions/273313/randomize-a-listt-in-c-sharp 546 | /// 547 | /// the type of the list 548 | /// the list to shuffle 549 | public static void Shuffle(this IList list) 550 | { 551 | int n = list.Count; 552 | while (n > 1) 553 | { 554 | n--; 555 | int k = UnityEngine.Random.Range(0, n + 1); 556 | T value = list[k]; 557 | list[k] = list[n]; 558 | list[n] = value; 559 | } 560 | } 561 | 562 | /// 563 | /// Takes a list, creates a copy and returns the newly shuffled list 564 | /// 565 | /// 566 | /// 567 | public static List Randomise(this List inList) 568 | { 569 | var list = new List(inList); 570 | list.Shuffle(); 571 | return list; 572 | } 573 | 574 | /// 575 | /// Simple method to set the x value on the Vector3, returns a new vector3 576 | /// 577 | /// the vector 578 | /// the x value to set 579 | public static Vector3 SetX(this Vector3 v, float x) 580 | { 581 | return new Vector3(x, v.y, v.z); 582 | } 583 | 584 | /// 585 | /// Simple method to set the y value on the Vector3, returns a new vector3 586 | /// 587 | /// the vector 588 | /// the y value to set 589 | public static Vector3 SetY(this Vector3 v, float y) 590 | { 591 | return new Vector3(v.x, y, v.z); 592 | } 593 | 594 | /// 595 | /// Simple method to set the z value on the Vector3, returns a new vector3 596 | /// 597 | /// the vector 598 | /// the z value to set 599 | public static Vector3 SetZ(this Vector3 v, float z) 600 | { 601 | return new Vector3(v.x, v.y, z); 602 | } 603 | 604 | /// 605 | /// NOT TESTED !! 606 | /// Converts a 3D bounds to a 2D rect 607 | /// 608 | /// The bounds to convert 609 | /// the rect 610 | public static Rect ToRect(this Bounds b) 611 | { 612 | return new Rect(b.min.x, b.min.y, b.size.x, b.size.y); 613 | } 614 | 615 | /// 616 | /// Instantiates an instance of this prefab 617 | /// 618 | /// 619 | /// 620 | public static GameObject Instantiate(this GameObject prefab) 621 | { 622 | return UnityUtils.Instantiate(prefab); 623 | } 624 | 625 | /// 626 | /// Instantiates an instance of this prefab and retuns a component on it 627 | /// 628 | /// 629 | /// 630 | public static T Instantiate(this GameObject prefab) where T : Component 631 | { 632 | return UnityUtils.Instantiate(prefab); 633 | } 634 | 635 | /// 636 | /// Deactivates the gameobject this component is attached to 637 | /// 638 | /// 639 | public static void Deactivate(this Component component) 640 | { 641 | component.gameObject.SetActive(false); 642 | } 643 | 644 | /// 645 | /// Activate the gameobject this component is attached to 646 | /// 647 | /// 648 | public static void Activate(this Component component) 649 | { 650 | component.gameObject.SetActive(true); 651 | } 652 | 653 | /// 654 | /// Loads all the objects in a given path, instantiates them then adds them as children 655 | /// 656 | /// The type of the child to return 657 | /// The parent object to add the children to 658 | /// The path from which to load the prefabs 659 | /// 660 | public static List LoadAllAsChildren(this GameObject parentObject, string prefabsPath) where T : Component 661 | { 662 | var prefabs = Resources.LoadAll(prefabsPath); 663 | var children = new List(); 664 | foreach(var prefab in prefabs) 665 | { 666 | var instance = UnityUtils.Instantiate(prefab.gameObject); 667 | instance.transform.parent = parentObject.transform; 668 | children.Add(instance); 669 | } 670 | return children; 671 | } 672 | 673 | /// 674 | /// Recursively looks up the tree to see if this object is parents by the supplied 675 | /// 676 | /// 677 | /// 678 | public static bool IsParentedBy(this GameObject obj, GameObject parent) 679 | { 680 | if (obj.transform.parent == null) 681 | return false; 682 | if (obj.transform.parent.gameObject == parent) 683 | return true; 684 | 685 | return obj.transform.parent.gameObject.IsParentedBy(parent); 686 | } 687 | } --------------------------------------------------------------------------------