using System; using System.Collections.Generic; using System.Reflection; using System.Linq; using UnityEditorInternal; using UnityEngine; using UnityEngine.Tilemaps; using Object = UnityEngine.Object; namespace UnityEditor { /// /// The Editor for a RuleTile. /// [CustomEditor(typeof(RuleTile), true)] [CanEditMultipleObjects] public class RuleTileEditor : Editor { private const string s_XIconString = "iVBORw0KGgoAAAANSUhEUgAAAA8AAAAPCAYAAAA71pVKAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAAYdEVYdFNvZnR3YXJlAHBhaW50Lm5ldCA0LjAuNWWFMmUAAABoSURBVDhPnY3BDcAgDAOZhS14dP1O0x2C/LBEgiNSHvfwyZabmV0jZRUpq2zi6f0DJwdcQOEdwwDLypF0zHLMa9+NQRxkQ+ACOT2STVw/q8eY1346ZlE54sYAhVhSDrjwFymrSFnD2gTZpls2OvFUHAAAAABJRU5ErkJggg=="; private const string s_Arrow0 = "iVBORw0KGgoAAAANSUhEUgAAAA8AAAAPCAYAAAA71pVKAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAAYdEVYdFNvZnR3YXJlAHBhaW50Lm5ldCA0LjAuNWWFMmUAAACYSURBVDhPzZExDoQwDATzE4oU4QXXcgUFj+YxtETwgpMwXuFcwMFSRMVKKwzZcWzhiMg91jtg34XIntkre5EaT7yjjhI9pOD5Mw5k2X/DdUwFr3cQ7Pu23E/BiwXyWSOxrNqx+ewnsayam5OLBtbOGPUM/r93YZL4/dhpR/amwByGFBz170gNChA6w5bQQMqramBTgJ+Z3A58WuWejPCaHQAAAABJRU5ErkJggg=="; private const string s_Arrow1 = "iVBORw0KGgoAAAANSUhEUgAAAA8AAAAPCAYAAAA71pVKAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAAYdEVYdFNvZnR3YXJlAHBhaW50Lm5ldCA0LjAuNWWFMmUAAABqSURBVDhPxYzBDYAgEATpxYcd+PVr0fZ2siZrjmMhFz6STIiDs8XMlpEyi5RkO/d66TcgJUB43JfNBqRkSEYDnYjhbKD5GIUkDqRDwoH3+NgTAw+bL/aoOP4DOgH+iwECEt+IlFmkzGHlAYKAWF9R8zUnAAAAAElFTkSuQmCC"; private const string s_Arrow2 = "iVBORw0KGgoAAAANSUhEUgAAAA8AAAAPCAYAAAA71pVKAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAAYdEVYdFNvZnR3YXJlAHBhaW50Lm5ldCA0LjAuNWWFMmUAAAC0SURBVDhPjVE5EsIwDMxPKFKYF9CagoJH8xhaMskLmEGsjOSRkBzYmU2s9a58TUQUmCH1BWEHweuKP+D8tphrWcAHuIGrjPnPNY8X2+DzEWE+FzrdrkNyg2YGNNfRGlyOaZDJOxBrDhgOowaYW8UW0Vau5ZkFmXbbDr+CzOHKmLinAXMEePyZ9dZkZR+s5QX2O8DY3zZ/sgYcdDqeEVp8516o0QQV1qeMwg6C91toYoLoo+kNt/tpKQEVvFQAAAAASUVORK5CYII="; private const string s_Arrow3 = "iVBORw0KGgoAAAANSUhEUgAAAA8AAAAPCAYAAAA71pVKAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAAYdEVYdFNvZnR3YXJlAHBhaW50Lm5ldCA0LjAuNWWFMmUAAAB2SURBVDhPzY1LCoAwEEPnLi48gW5d6p31bH5SMhp0Cq0g+CCLxrzRPqMZ2pRqKG4IqzJc7JepTlbRZXYpWTg4RZE1XAso8VHFKNhQuTjKtZvHUNCEMogO4K3BhvMn9wP4EzoPZ3n0AGTW5fiBVzLAAYTP32C2Ay3agtu9V/9PAAAAAElFTkSuQmCC"; private const string s_Arrow5 = "iVBORw0KGgoAAAANSUhEUgAAAA8AAAAPCAYAAAA71pVKAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAAYdEVYdFNvZnR3YXJlAHBhaW50Lm5ldCA0LjAuNWWFMmUAAABqSURBVDhPnY3BCYBADASvFx924NevRdvbyoLBmNuDJQMDGjNxAFhK1DyUQ9fvobCdO+j7+sOKj/uSB+xYHZAxl7IR1wNTXJeVcaAVU+614uWfCT9mVUhknMlxDokd15BYsQrJFHeUQ0+MB5ErsPi/6hO1AAAAAElFTkSuQmCC"; private const string s_Arrow6 = "iVBORw0KGgoAAAANSUhEUgAAAA8AAAAPCAYAAAA71pVKAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAAYdEVYdFNvZnR3YXJlAHBhaW50Lm5ldCA0LjAuNWWFMmUAAACaSURBVDhPxZExEkAwEEVzE4UiTqClUDi0w2hlOIEZsV82xCZmQuPPfFn8t1mirLWf7S5flQOXjd64vCuEKWTKVt+6AayH3tIa7yLg6Qh2FcKFB72jBgJeziA1CMHzeaNHjkfwnAK86f3KUafU2ClHIJSzs/8HHLv09M3SaMCxS7ljw/IYJWzQABOQZ66x4h614ahTCL/WT7BSO51b5Z5hSx88AAAAAElFTkSuQmCC"; private const string s_Arrow7 = "iVBORw0KGgoAAAANSUhEUgAAAA8AAAAPCAYAAAA71pVKAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAAYdEVYdFNvZnR3YXJlAHBhaW50Lm5ldCA0LjAuNWWFMmUAAABQSURBVDhPYxh8QNle/T8U/4MKEQdAmsz2eICx6W530gygr2aQBmSMphkZYxqErAEXxusKfAYQ7XyyNMIAsgEkaYQBkAFkaYQBsjXSGDAwAAD193z4luKPrAAAAABJRU5ErkJggg=="; private const string s_Arrow8 = "iVBORw0KGgoAAAANSUhEUgAAAA8AAAAPCAYAAAA71pVKAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAAYdEVYdFNvZnR3YXJlAHBhaW50Lm5ldCA0LjAuNWWFMmUAAACYSURBVDhPxZE9DoAwCIW9iUOHegJXHRw8tIdx1egJTMSHAeMPaHSR5KVQ+KCkCRF91mdz4VDEWVzXTBgg5U1N5wahjHzXS3iFFVRxAygNVaZxJ6VHGIl2D6oUXP0ijlJuTp724FnID1Lq7uw2QM5+thoKth0N+GGyA7IA3+yM77Ag1e2zkey5gCdAg/h8csy+/89v7E+YkgUntOWeVt2SfAAAAABJRU5ErkJggg=="; private const string s_MirrorX = "iVBORw0KGgoAAAANSUhEUgAAAA8AAAAPCAYAAAA71pVKAAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAOwQAADsEBuJFr7QAAABh0RVh0U29mdHdhcmUAcGFpbnQubmV0IDQuMC41ZYUyZQAAAG1JREFUOE+lj9ENwCAIRB2IFdyRfRiuDSaXAF4MrR9P5eRhHGb2Gxp2oaEjIovTXSrAnPNx6hlgyCZ7o6omOdYOldGIZhAziEmOTSfigLV0RYAB9y9f/7kO8L3WUaQyhCgz0dmCL9CwCw172HgBeyG6oloC8fAAAAAASUVORK5CYII="; private const string s_MirrorY = "iVBORw0KGgoAAAANSUhEUgAAAA8AAAAPCAYAAAA71pVKAAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAOwgAADsIBFShKgAAAABh0RVh0U29mdHdhcmUAcGFpbnQubmV0IDQuMC41ZYUyZQAAAG9JREFUOE+djckNACEMAykoLdAjHbPyw1IOJ0L7mAejjFlm9hspyd77Kk+kBAjPOXcakJIh6QaKyOE0EB5dSPJAiUmOiL8PMVGxugsP/0OOib8vsY8yYwy6gRyC8CB5QIWgCMKBLgRSkikEUr5h6wOPWfMoCYILdgAAAABJRU5ErkJggg=="; private const string s_MirrorXY = "iVBORw0KGgoAAAANSUhEUgAAAA8AAAAPCAYAAAA71pVKAAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAOwgAADsIBFShKgAAAABl0RVh0U29mdHdhcmUAcGFpbnQubmV0IDQuMC4yMfEgaZUAAAHkSURBVDhPrVJLSwJRFJ4cdXwjPlrVJly1kB62cpEguElXKgYKIpaC+EIEEfGxLqI/UES1KaJlEdGmRY9ltCsIWrUJatGm0eZO3xkHIsJdH3zce+ec75z5zr3cf2MMmLdYLA/BYFA2mUyPOPvwnR+GR4PXaDQLLpfrKpVKSb1eT6bV6XTeocAS4sIw7S804BzEZ4IgsGq1ykhcr9dlj8czwPdbxJdBMyX/As/zLiz74Ar2J9lsVulcKpUYut5DnEbsHFwEx8AhtFqtGViD6BOc1ul0B5lMRhGXy2Wm1+ufkBOE/2fsL1FsQpXCiCAcQiAlk0kJRZjf7+9TRxI3Gg0WCoW+IpGISHHERBS5UKUch8n2K5WK3O125VqtpqydTkdZie12W261WjIVo73b7RZVKccZDIZ1q9XaT6fTLB6PD9BFKhQKjITFYpGFw+FBNBpVOgcCARH516pUGZYZXk5R4B3efLBxDM9f1CkWi/WR3ICtGVh6Rd4NPE+p0iEgmkSRLRoMEjYhHpA4kUiIOO8iZRU8AmnadK2/QOOfhnjPZrO95fN5Zdq5XE5yOBwvuKoNxGfBkQ8FzXkPprnj9Xrfm82mDI8fsLON3x5H/Od+RwHdLfDds9vtn0aj8QoF6QH9JzjuG3acpxmu1RgPAAAAAElFTkSuQmCC"; private const string s_Rotated = "iVBORw0KGgoAAAANSUhEUgAAAA8AAAAPCAYAAAA71pVKAAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAOwQAADsEBuJFr7QAAABh0RVh0U29mdHdhcmUAcGFpbnQubmV0IDQuMC41ZYUyZQAAAHdJREFUOE+djssNwCAMQxmIFdgx+2S4Vj4YxWlQgcOT8nuG5u5C732Sd3lfLlmPMR4QhXgrTQaimUlA3EtD+CJlBuQ7aUAUMjEAv9gWCQNEPhHJUkYfZ1kEpcxDzioRzGIlr0Qwi0r+Q5rTgM+AAVcygHgt7+HtBZs/2QVWP8ahAAAAAElFTkSuQmCC"; private const string s_Fixed = "iVBORw0KGgoAAAANSUhEUgAAAA8AAAAPCAYAAAA71pVKAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAAZdEVYdFNvZnR3YXJlAHBhaW50Lm5ldCA0LjAuMjHxIGmVAAAA50lEQVQ4T51Ruw6CQBCkwBYKWkIgQAs9gfgCvgb4BML/qWBM9Bdo9QPIuVOQ3JIzosVkc7Mzty9NCPE3lORaKMm1YA/LsnTXdbdhGJ6iKHoVRTEi+r4/OI6zN01Tl/XM7HneLsuyW13XU9u2ous6gYh3kiR327YPsp6ZgyDom6aZYFqiqqqJ8mdZz8xoca64BHjkZT0zY0aVcQbysp6Z4zj+Vvkp65mZttxjOSozdkEzD7KemekcxzRNHxDOHSDiQ/DIy3pmpjtuSJBThStGKMtyRKSOLnSm3DCMz3f+FUpyLZTkOgjtDSWORSDbpbmNAAAAAElFTkSuQmCC"; private static readonly string k_UndoName = L10n.Tr("Change RuleTile"); private static Texture2D[] s_Arrows; /// /// Array of arrow textures used for marking positions for Rule matches /// public static Texture2D[] arrows { get { if (s_Arrows == null) { s_Arrows = new Texture2D[10]; s_Arrows[0] = Base64ToTexture(s_Arrow0); s_Arrows[1] = Base64ToTexture(s_Arrow1); s_Arrows[2] = Base64ToTexture(s_Arrow2); s_Arrows[3] = Base64ToTexture(s_Arrow3); s_Arrows[5] = Base64ToTexture(s_Arrow5); s_Arrows[6] = Base64ToTexture(s_Arrow6); s_Arrows[7] = Base64ToTexture(s_Arrow7); s_Arrows[8] = Base64ToTexture(s_Arrow8); s_Arrows[9] = Base64ToTexture(s_XIconString); } return s_Arrows; } } private static Texture2D[] s_AutoTransforms; /// /// Arrays of textures used for marking transform Rule matches /// public static Texture2D[] autoTransforms { get { if (s_AutoTransforms == null) { s_AutoTransforms = new Texture2D[5]; s_AutoTransforms[0] = Base64ToTexture(s_Rotated); s_AutoTransforms[1] = Base64ToTexture(s_MirrorX); s_AutoTransforms[2] = Base64ToTexture(s_MirrorY); s_AutoTransforms[3] = Base64ToTexture(s_Fixed); s_AutoTransforms[4] = Base64ToTexture(s_MirrorXY); } return s_AutoTransforms; } } private static class Styles { public static readonly GUIContent defaultSprite = EditorGUIUtility.TrTextContent("Default Sprite" , "The default Sprite set when creating a new Rule."); public static readonly GUIContent defaultGameObject = EditorGUIUtility.TrTextContent("Default GameObject" , "The default GameObject set when creating a new Rule."); public static readonly GUIContent defaultCollider = EditorGUIUtility.TrTextContent("Default Collider" , "The default Collider Type set when creating a new Rule."); public static readonly GUIContent emptyRuleTileInfo = EditorGUIUtility.TrTextContent( "Drag Sprite or Sprite Texture assets \n" + " to start creating a Rule Tile."); public static readonly GUIContent extendNeighbor = EditorGUIUtility.TrTextContent("Extend Neighbor" , "Enabling this allows you to increase the range of neighbors beyond the 3x3 box."); public static readonly GUIContent numberOfTilingRules = EditorGUIUtility.TrTextContent( "Number of Tiling Rules" , "Change this to adjust of the number of tiling rules."); public static readonly GUIContent tilingRules = EditorGUIUtility.TrTextContent("Tiling Rules"); public static readonly GUIContent tilingRulesGameObject = EditorGUIUtility.TrTextContent("GameObject" , "The GameObject for the Tile which fits this Rule."); public static readonly GUIContent tilingRulesCollider = EditorGUIUtility.TrTextContent("Collider" , "The Collider Type for the Tile which fits this Rule"); public static readonly GUIContent tilingRulesOutput = EditorGUIUtility.TrTextContent("Output" , "The Output for the Tile which fits this Rule. Each Output type has its own properties."); public static readonly GUIContent tilingRulesNoise = EditorGUIUtility.TrTextContent("Noise" , "The Perlin noise factor when placing the Tile."); public static readonly GUIContent tilingRulesShuffle = EditorGUIUtility.TrTextContent("Shuffle" , "The randomized transform given to the Tile when placing it."); public static readonly GUIContent tilingRulesRandomSize = EditorGUIUtility.TrTextContent("Size" , "The number of Sprites to randomize from."); public static readonly GUIContent tilingRulesMinSpeed = EditorGUIUtility.TrTextContent("Min Speed" , "The minimum speed at which the animation is played."); public static readonly GUIContent tilingRulesMaxSpeed = EditorGUIUtility.TrTextContent("Max Speed" , "The maximum speed at which the animation is played."); public static readonly GUIContent tilingRulesAnimationSize = EditorGUIUtility.TrTextContent("Size" , "The number of Sprites in the animation."); public static readonly GUIStyle extendNeighborsLightStyle = new GUIStyle() { alignment = TextAnchor.MiddleLeft, fontStyle = FontStyle.Bold, fontSize = 10, normal = new GUIStyleState() { textColor = Color.black } }; public static readonly GUIStyle extendNeighborsDarkStyle = new GUIStyle() { alignment = TextAnchor.MiddleLeft, fontStyle = FontStyle.Bold, fontSize = 10, normal = new GUIStyleState() { textColor = Color.white } }; } /// /// The RuleTile being edited /// public RuleTile tile => target as RuleTile; /// /// List of Sprites for Drag and Drop /// private List dragAndDropSprites; /// /// Reorderable list for Rules /// private ReorderableList m_ReorderableList; /// /// Whether the RuleTile can extend its neighbors beyond directly adjacent ones /// public bool extendNeighbor; /// /// Preview Utility for rendering previews /// public PreviewRenderUtility m_PreviewUtility; /// /// Grid for rendering previews /// public Grid m_PreviewGrid; /// /// List of Tilemaps for rendering previews /// public List m_PreviewTilemaps; /// /// List of TilemapRenderers for rendering previews /// public List m_PreviewTilemapRenderers; /// /// Default height for a Rule Element /// public const float k_DefaultElementHeight = 48f; /// /// Padding between Rule Elements /// public const float k_PaddingBetweenRules = 8f; /// /// Single line height /// public const float k_SingleLineHeight = 18f; /// /// Width for labels /// public const float k_LabelWidth = 80f; private SerializedProperty m_TilingRules; private MethodInfo m_ClearCacheMethod; /// /// OnEnable for the RuleTileEditor /// public virtual void OnEnable() { m_ReorderableList = new ReorderableList(tile != null ? tile.m_TilingRules : null, typeof(RuleTile.TilingRule), true, true, true, true); m_ReorderableList.drawHeaderCallback = OnDrawHeader; m_ReorderableList.drawElementCallback = OnDrawElement; m_ReorderableList.elementHeightCallback = GetElementHeight; m_ReorderableList.onChangedCallback = ListUpdated; m_ReorderableList.onAddDropdownCallback = OnAddDropdownElement; // Required to adjust element height changes var rolType = GetType("UnityEditorInternal.ReorderableList"); if (rolType != null) { // ClearCache was changed to InvalidateCache in newer versions of Unity. // To maintain backwards compatibility, we will attempt to retrieve each method in order m_ClearCacheMethod = rolType.GetMethod("InvalidateCache", BindingFlags.Instance | BindingFlags.NonPublic); if (m_ClearCacheMethod == null) m_ClearCacheMethod = rolType.GetMethod("ClearCache", BindingFlags.Instance | BindingFlags.NonPublic); } m_TilingRules = serializedObject.FindProperty("m_TilingRules"); } /// /// OnDisable for the RuleTileEditor /// public virtual void OnDisable() { DestroyPreview(); } private void UpdateTilingRuleIds() { var existingIdSet = new HashSet(); var usedIdSet = new HashSet(); foreach (var rule in tile.m_TilingRules) { existingIdSet.Add(rule.m_Id); } foreach (var rule in tile.m_TilingRules) { if (usedIdSet.Contains(rule.m_Id)) { while (existingIdSet.Contains(rule.m_Id)) rule.m_Id++; existingIdSet.Add(rule.m_Id); } usedIdSet.Add(rule.m_Id); } } /// /// Get the GUI bounds for a Rule. /// /// Cell bounds of the Rule. /// Rule to get GUI bounds for. /// The GUI bounds for a rule. public virtual BoundsInt GetRuleGUIBounds(BoundsInt bounds, RuleTile.TilingRule rule) { if (extendNeighbor) { bounds.xMin--; bounds.yMin--; bounds.xMax++; bounds.yMax++; } bounds.xMin = Mathf.Min(bounds.xMin, -1); bounds.yMin = Mathf.Min(bounds.yMin, -1); bounds.xMax = Mathf.Max(bounds.xMax, 2); bounds.yMax = Mathf.Max(bounds.yMax, 2); return bounds; } /// /// Callback when the Rule list is updated /// /// Reorderable list for Rules public void ListUpdated(ReorderableList list) { UpdateTilingRuleIds(); } private float GetElementHeight(int index) { RuleTile.TilingRule rule = tile.m_TilingRules[index]; return GetElementHeight(rule); } /// /// Gets the GUI element height for a TilingRule /// /// Rule to get height for /// GUI element height for a TilingRule public float GetElementHeight(RuleTile.TilingRule rule) { BoundsInt bounds = GetRuleGUIBounds(rule.GetBounds(), rule); float inspectorHeight = GetElementHeight(rule as RuleTile.TilingRuleOutput); float matrixHeight = GetMatrixSize(bounds).y + 10f; return Mathf.Max(inspectorHeight, matrixHeight); } /// /// Gets the GUI element height for a TilingRuleOutput /// /// Rule to get height for /// GUI element height for a TilingRuleOutput public float GetElementHeight(RuleTile.TilingRuleOutput rule) { float inspectorHeight = k_DefaultElementHeight + k_PaddingBetweenRules; switch (rule.m_Output) { case RuleTile.TilingRuleOutput.OutputSprite.Random: case RuleTile.TilingRuleOutput.OutputSprite.Animation: inspectorHeight = k_DefaultElementHeight + k_SingleLineHeight * (rule.m_Sprites.Length + 3) + k_PaddingBetweenRules; break; } return inspectorHeight; } /// /// Gets the GUI matrix size for a Rule of a RuleTile /// /// Cell bounds of the Rule. /// Returns the GUI matrix size for a Rule of a RuleTile. public virtual Vector2 GetMatrixSize(BoundsInt bounds) { return new Vector2(bounds.size.x * k_SingleLineHeight, bounds.size.y * k_SingleLineHeight); } /// /// Draws the Rule element for the Rule list /// /// Rect to draw the Rule Element in /// Index of the Rule Element to draw /// Whether the Rule Element is active /// Whether the Rule Element is focused protected virtual void OnDrawElement(Rect rect, int index, bool isactive, bool isfocused) { RuleTile.TilingRule rule = tile.m_TilingRules[index]; BoundsInt bounds = GetRuleGUIBounds(rule.GetBounds(), rule); float yPos = rect.yMin + 2f; float height = rect.height - k_PaddingBetweenRules; Vector2 matrixSize = GetMatrixSize(bounds); Rect spriteRect = new Rect(rect.xMax - k_DefaultElementHeight - 5f, yPos, k_DefaultElementHeight, k_DefaultElementHeight); Rect matrixRect = new Rect(rect.xMax - matrixSize.x - spriteRect.width - 10f, yPos, matrixSize.x, matrixSize.y); Rect inspectorRect = new Rect(rect.xMin, yPos, rect.width - matrixSize.x - spriteRect.width - 20f, height); RuleInspectorOnGUI(inspectorRect, rule); RuleMatrixOnGUI(tile, matrixRect, bounds, rule); SpriteOnGUI(spriteRect, rule); } private void OnAddElement(object obj) { var list = obj as ReorderableList; RuleTile.TilingRule rule = new RuleTile.TilingRule(); rule.m_Output = RuleTile.TilingRuleOutput.OutputSprite.Single; rule.m_Sprites[0] = tile.m_DefaultSprite; rule.m_GameObject = tile.m_DefaultGameObject; rule.m_ColliderType = tile.m_DefaultColliderType; var count = m_TilingRules.arraySize; ResizeRuleTileList(count + 1); if (list.index == -1 || list.index >= list.count) tile.m_TilingRules[count] = rule; else { tile.m_TilingRules.Insert(list.index + 1, rule); tile.m_TilingRules.RemoveAt(count + 1); if (list.IsSelected(list.index)) list.index += 1; } UpdateTilingRuleIds(); } private void OnDuplicateElement(object obj) { var list = obj as ReorderableList; if (list.index < 0 || list.index >= tile.m_TilingRules.Count) return; var copyRule = tile.m_TilingRules[list.index]; var rule = copyRule.Clone(); var count = m_TilingRules.arraySize; ResizeRuleTileList(count + 1); tile.m_TilingRules.Insert(list.index + 1, rule); tile.m_TilingRules.RemoveAt(count + 1); if (list.IsSelected(list.index)) list.index += 1; UpdateTilingRuleIds(); } private void OnAddDropdownElement(Rect rect, ReorderableList list) { if (0 <= list.index && list.index < tile.m_TilingRules.Count && list.IsSelected(list.index)) { GenericMenu menu = new GenericMenu(); menu.AddItem(EditorGUIUtility.TrTextContent("Add"), false, OnAddElement, list); menu.AddItem(EditorGUIUtility.TrTextContent("Duplicate"), false, OnDuplicateElement, list); menu.DropDown(rect); } else { OnAddElement(list); } } /// /// Saves any changes to the RuleTile /// public void SaveTile() { serializedObject.ApplyModifiedProperties(); EditorUtility.SetDirty(target); SceneView.RepaintAll(); UpdateAffectedOverrideTiles(tile); } /// /// Updates all RuleOverrideTiles which override the given RUleTile /// /// RuleTile which has been updated public static void UpdateAffectedOverrideTiles(RuleTile target) { List overrideTiles = FindAffectedOverrideTiles(target); if (overrideTiles != null) { foreach (var overrideTile in overrideTiles) { Undo.RegisterCompleteObjectUndo(overrideTile, k_UndoName); Undo.RecordObject(overrideTile.m_InstanceTile, k_UndoName); overrideTile.Override(); UpdateAffectedOverrideTiles(overrideTile.m_InstanceTile); EditorUtility.SetDirty(overrideTile); } } } /// /// Gets all RuleOverrideTiles which override the given RuleTile /// /// RuleTile which has been updated /// A list of RuleOverrideTiles which override the given RuleTile public static List FindAffectedOverrideTiles(RuleTile target) { List overrideTiles = new List(); string[] overrideTileGuids = AssetDatabase.FindAssets("t:" + typeof(RuleOverrideTile).Name); foreach (string overrideTileGuid in overrideTileGuids) { string overrideTilePath = AssetDatabase.GUIDToAssetPath(overrideTileGuid); RuleOverrideTile overrideTile = AssetDatabase.LoadAssetAtPath(overrideTilePath); if (overrideTile.m_Tile == target) { overrideTiles.Add(overrideTile); } } return overrideTiles; } /// /// Draws the header for the Rule list /// /// GUI Rect to draw the header at public void OnDrawHeader(Rect rect) { GUI.Label(rect, Styles.tilingRules); var toggleRect = new Rect(rect.xMax - rect.height, rect.y, rect.height, rect.height); var style = EditorGUIUtility.isProSkin ? Styles.extendNeighborsDarkStyle : Styles.extendNeighborsLightStyle; var extendSize = style.CalcSize(Styles.extendNeighbor); var toggleWidth = toggleRect.width + extendSize.x + 5f; var toggleLabelRect = new Rect(rect.x + rect.width - toggleWidth, rect.y, toggleWidth, rect.height); EditorGUI.BeginChangeCheck(); extendNeighbor = EditorGUI.Toggle(toggleRect, extendNeighbor); EditorGUI.LabelField(toggleLabelRect, Styles.extendNeighbor, style); if (EditorGUI.EndChangeCheck()) { if (m_ClearCacheMethod != null) m_ClearCacheMethod.Invoke(m_ReorderableList, null); } } /// /// Draws the Inspector GUI for the RuleTileEditor /// public override void OnInspectorGUI() { serializedObject.Update(); Undo.RecordObject(target, k_UndoName); EditorGUI.BeginChangeCheck(); tile.m_DefaultSprite = EditorGUILayout.ObjectField(Styles.defaultSprite, tile.m_DefaultSprite, typeof(Sprite), false) as Sprite; tile.m_DefaultGameObject = EditorGUILayout.ObjectField(Styles.defaultGameObject, tile.m_DefaultGameObject, typeof(GameObject), false) as GameObject; tile.m_DefaultColliderType = (Tile.ColliderType)EditorGUILayout.EnumPopup(Styles.defaultCollider, tile.m_DefaultColliderType); DrawCustomFields(false); EditorGUILayout.Space(); EditorGUI.BeginChangeCheck(); int count = EditorGUILayout.DelayedIntField(Styles.numberOfTilingRules, tile.m_TilingRules?.Count ?? 0); if (count < 0) count = 0; if (EditorGUI.EndChangeCheck()) ResizeRuleTileList(count); if (count == 0) { Rect rect = EditorGUILayout.GetControlRect(false, EditorGUIUtility.singleLineHeight * 5); HandleDragAndDrop(rect); EditorGUI.DrawRect(rect, dragAndDropActive && rect.Contains(Event.current.mousePosition) ? Color.white : Color.black); var innerRect = new Rect(rect.x + 1, rect.y + 1, rect.width - 2, rect.height - 2); EditorGUI.DrawRect(innerRect, EditorGUIUtility.isProSkin ? (Color) new Color32 (56, 56, 56, 255) : (Color) new Color32 (194, 194, 194, 255)); DisplayClipboardText(Styles.emptyRuleTileInfo, rect); GUILayout.Space(rect.height); EditorGUILayout.Space(); } if (m_ReorderableList != null) m_ReorderableList.DoLayoutList(); if (EditorGUI.EndChangeCheck()) SaveTile(); GUILayout.Space(k_DefaultElementHeight); } private void ResizeRuleTileList(int count) { if (m_TilingRules.arraySize == count) return; var isEmpty = m_TilingRules.arraySize == 0; m_TilingRules.arraySize = count; serializedObject.ApplyModifiedProperties(); if (isEmpty) { for (int i = 0; i < count; ++i) tile.m_TilingRules[i] = new RuleTile.TilingRule(); } UpdateTilingRuleIds(); } /// /// Draw editor fields for custom properties for the RuleTile /// /// Whether override fields are drawn public void DrawCustomFields(bool isOverrideInstance) { var customFields = tile.GetCustomFields(isOverrideInstance); serializedObject.Update(); EditorGUI.BeginChangeCheck(); foreach (var field in customFields) { var property = serializedObject.FindProperty(field.Name); if (property != null) EditorGUILayout.PropertyField(property, true); } if (EditorGUI.EndChangeCheck()) { serializedObject.ApplyModifiedProperties(); DestroyPreview(); CreatePreview(); } } /// /// Gets the index for a Rule with the RuleTile to display an arrow. /// /// The relative position of the arrow from the center. /// Returns the index for a Rule with the RuleTile to display an arrow. public virtual int GetArrowIndex(Vector3Int position) { if (Mathf.Abs(position.x) == Mathf.Abs(position.y)) { if (position.x < 0 && position.y > 0) return 0; else if (position.x > 0 && position.y > 0) return 2; else if (position.x < 0 && position.y < 0) return 6; else if (position.x > 0 && position.y < 0) return 8; } else if (Mathf.Abs(position.x) > Mathf.Abs(position.y)) { if (position.x > 0) return 5; else return 3; } else { if (position.y > 0) return 1; else return 7; } return -1; } /// /// Draws a neighbor matching rule /// /// Rect to draw on /// The relative position of the arrow from the center /// The index to the neighbor matching criteria public virtual void RuleOnGUI(Rect rect, Vector3Int position, int neighbor) { switch (neighbor) { case RuleTile.TilingRuleOutput.Neighbor.This: GUI.DrawTexture(rect, arrows[GetArrowIndex(position)]); break; case RuleTile.TilingRuleOutput.Neighbor.NotThis: GUI.DrawTexture(rect, arrows[9]); break; default: var style = new GUIStyle(); style.alignment = TextAnchor.MiddleCenter; style.fontSize = 10; GUI.Label(rect, neighbor.ToString(), style); break; } } /// /// Draws a tooltip for the neighbor matching rule /// /// Rect to draw on /// The index to the neighbor matching criteria public void RuleTooltipOnGUI(Rect rect, int neighbor) { var allConsts = tile.m_NeighborType.GetFields(BindingFlags.Public | BindingFlags.Static | BindingFlags.FlattenHierarchy); foreach (var c in allConsts) { if ((int)c.GetValue(null) == neighbor) { GUI.Label(rect, new GUIContent("", c.Name)); break; } } } /// /// Draws a transform matching rule /// /// Rect to draw on /// The transform matching criteria public virtual void RuleTransformOnGUI(Rect rect, RuleTile.TilingRuleOutput.Transform ruleTransform) { switch (ruleTransform) { case RuleTile.TilingRuleOutput.Transform.Rotated: GUI.DrawTexture(rect, autoTransforms[0]); break; case RuleTile.TilingRuleOutput.Transform.MirrorX: GUI.DrawTexture(rect, autoTransforms[1]); break; case RuleTile.TilingRuleOutput.Transform.MirrorY: GUI.DrawTexture(rect, autoTransforms[2]); break; case RuleTile.TilingRuleOutput.Transform.Fixed: GUI.DrawTexture(rect, autoTransforms[3]); break; case RuleTile.TilingRuleOutput.Transform.MirrorXY: GUI.DrawTexture(rect, autoTransforms[4]); break; } GUI.Label(rect, new GUIContent("", ruleTransform.ToString())); } /// /// Handles a neighbor matching Rule update from user mouse input /// /// Rect containing neighbor matching Rule GUI /// Tiling Rule to update neighbor matching rule /// A dictionary of neighbors /// The relative position of the neighbor matching Rule public void RuleNeighborUpdate(Rect rect, RuleTile.TilingRule tilingRule, Dictionary neighbors, Vector3Int position) { if (Event.current.type == EventType.MouseDown && ContainsMousePosition(rect)) { var allConsts = tile.m_NeighborType.GetFields(BindingFlags.Public | BindingFlags.Static | BindingFlags.FlattenHierarchy); var neighborConsts = allConsts.Select(c => (int)c.GetValue(null)).ToList(); neighborConsts.Sort(); if (neighbors.ContainsKey(position)) { int oldIndex = neighborConsts.IndexOf(neighbors[position]); int newIndex = oldIndex + GetMouseChange(); if (newIndex >= 0 && newIndex < neighborConsts.Count) { newIndex = (int)Mathf.Repeat(newIndex, neighborConsts.Count); neighbors[position] = neighborConsts[newIndex]; } else { neighbors.Remove(position); } } else { neighbors.Add(position, neighborConsts[GetMouseChange() == 1 ? 0 : (neighborConsts.Count - 1)]); } tilingRule.ApplyNeighbors(neighbors); GUI.changed = true; Event.current.Use(); } } /// /// Handles a transform matching Rule update from user mouse input /// /// Rect containing transform matching Rule GUI /// Tiling Rule to update transform matching rule public void RuleTransformUpdate(Rect rect, RuleTile.TilingRule tilingRule) { if (Event.current.type == EventType.MouseDown && ContainsMousePosition(rect)) { tilingRule.m_RuleTransform = (RuleTile.TilingRuleOutput.Transform)(int)Mathf.Repeat((int)tilingRule.m_RuleTransform + GetMouseChange(), Enum.GetValues(typeof(RuleTile.TilingRule.Transform)).Length); GUI.changed = true; Event.current.Use(); } } /// /// Determines the current mouse position is within the given Rect. /// /// Rect to test mouse position for. /// True if the current mouse position is within the given Rect. False otherwise. public virtual bool ContainsMousePosition(Rect rect) { return rect.Contains(Event.current.mousePosition); } /// /// Gets the offset change for a mouse click input /// /// The offset change for a mouse click input public static int GetMouseChange() { return Event.current.button == 1 ? -1 : 1; } /// /// Draws a Rule Matrix for the given Rule for a RuleTile. /// /// Tile to draw rule for. /// GUI Rect to draw rule at. /// Cell bounds of the Rule. /// Rule to draw Rule Matrix for. public virtual void RuleMatrixOnGUI(RuleTile tile, Rect rect, BoundsInt bounds, RuleTile.TilingRule tilingRule) { Handles.color = EditorGUIUtility.isProSkin ? new Color(1f, 1f, 1f, 0.2f) : new Color(0f, 0f, 0f, 0.2f); float w = rect.width / bounds.size.x; float h = rect.height / bounds.size.y; for (int y = 0; y <= bounds.size.y; y++) { float top = rect.yMin + y * h; Handles.DrawLine(new Vector3(rect.xMin, top), new Vector3(rect.xMax, top)); } for (int x = 0; x <= bounds.size.x; x++) { float left = rect.xMin + x * w; Handles.DrawLine(new Vector3(left, rect.yMin), new Vector3(left, rect.yMax)); } Handles.color = Color.white; var neighbors = tilingRule.GetNeighbors(); for (int y = bounds.yMin; y < bounds.yMax; y++) { for (int x = bounds.xMin; x < bounds.xMax; x++) { Vector3Int pos = new Vector3Int(x, y, 0); Rect r = new Rect(rect.xMin + (x - bounds.xMin) * w, rect.yMin + (-y + bounds.yMax - 1) * h, w - 1, h - 1); RuleMatrixIconOnGUI(tilingRule, neighbors, pos, r); } } } /// /// Draws a Rule Matrix Icon for the given matching Rule for a RuleTile with the given position /// /// Tile to draw rule for. /// A dictionary of neighbors /// The relative position of the neighbor matching Rule /// GUI Rect to draw icon at public void RuleMatrixIconOnGUI(RuleTile.TilingRule tilingRule, Dictionary neighbors, Vector3Int position, Rect rect) { using (var check = new EditorGUI.ChangeCheckScope()) { if (position.x != 0 || position.y != 0) { if (neighbors.ContainsKey(position)) { RuleOnGUI(rect, position, neighbors[position]); RuleTooltipOnGUI(rect, neighbors[position]); } RuleNeighborUpdate(rect, tilingRule, neighbors, position); } else { RuleTransformOnGUI(rect, tilingRule.m_RuleTransform); RuleTransformUpdate(rect, tilingRule); } if (check.changed) { tile.UpdateNeighborPositions(); } } } /// /// Draws a Sprite field for the Rule /// /// Rect to draw Sprite Inspector in /// Rule to draw Sprite Inspector for public virtual void SpriteOnGUI(Rect rect, RuleTile.TilingRuleOutput tilingRule) { tilingRule.m_Sprites[0] = EditorGUI.ObjectField(rect, tilingRule.m_Sprites[0], typeof(Sprite), false) as Sprite; } /// /// Draws an Inspector for the Rule /// /// Rect to draw Inspector in /// Rule to draw Inspector for public void RuleInspectorOnGUI(Rect rect, RuleTile.TilingRuleOutput tilingRule) { float y = rect.yMin; GUI.Label(new Rect(rect.xMin, y, k_LabelWidth, k_SingleLineHeight), Styles.tilingRulesGameObject); tilingRule.m_GameObject = (GameObject)EditorGUI.ObjectField(new Rect(rect.xMin + k_LabelWidth, y, rect.width - k_LabelWidth, k_SingleLineHeight), "", tilingRule.m_GameObject, typeof(GameObject), false); y += k_SingleLineHeight; GUI.Label(new Rect(rect.xMin, y, k_LabelWidth, k_SingleLineHeight), Styles.tilingRulesCollider); tilingRule.m_ColliderType = (Tile.ColliderType)EditorGUI.EnumPopup(new Rect(rect.xMin + k_LabelWidth, y, rect.width - k_LabelWidth, k_SingleLineHeight), tilingRule.m_ColliderType); y += k_SingleLineHeight; GUI.Label(new Rect(rect.xMin, y, k_LabelWidth, k_SingleLineHeight), Styles.tilingRulesOutput); tilingRule.m_Output = (RuleTile.TilingRuleOutput.OutputSprite)EditorGUI.EnumPopup(new Rect(rect.xMin + k_LabelWidth, y, rect.width - k_LabelWidth, k_SingleLineHeight), tilingRule.m_Output); y += k_SingleLineHeight; if (tilingRule.m_Output == RuleTile.TilingRuleOutput.OutputSprite.Animation) { GUI.Label(new Rect(rect.xMin, y, k_LabelWidth, k_SingleLineHeight), Styles.tilingRulesMinSpeed); tilingRule.m_MinAnimationSpeed = EditorGUI.FloatField(new Rect(rect.xMin + k_LabelWidth, y, rect.width - k_LabelWidth, k_SingleLineHeight), tilingRule.m_MinAnimationSpeed); y += k_SingleLineHeight; GUI.Label(new Rect(rect.xMin, y, k_LabelWidth, k_SingleLineHeight), Styles.tilingRulesMaxSpeed); tilingRule.m_MaxAnimationSpeed = EditorGUI.FloatField(new Rect(rect.xMin + k_LabelWidth, y, rect.width - k_LabelWidth, k_SingleLineHeight), tilingRule.m_MaxAnimationSpeed); y += k_SingleLineHeight; } if (tilingRule.m_Output == RuleTile.TilingRuleOutput.OutputSprite.Random) { GUI.Label(new Rect(rect.xMin, y, k_LabelWidth, k_SingleLineHeight), Styles.tilingRulesNoise); tilingRule.m_PerlinScale = EditorGUI.Slider(new Rect(rect.xMin + k_LabelWidth, y, rect.width - k_LabelWidth, k_SingleLineHeight), tilingRule.m_PerlinScale, 0.001f, 0.999f); y += k_SingleLineHeight; GUI.Label(new Rect(rect.xMin, y, k_LabelWidth, k_SingleLineHeight), Styles.tilingRulesShuffle); tilingRule.m_RandomTransform = (RuleTile.TilingRuleOutput.Transform)EditorGUI.EnumPopup(new Rect(rect.xMin + k_LabelWidth, y, rect.width - k_LabelWidth, k_SingleLineHeight), tilingRule.m_RandomTransform); y += k_SingleLineHeight; } if (tilingRule.m_Output != RuleTile.TilingRuleOutput.OutputSprite.Single) { GUI.Label(new Rect(rect.xMin, y, k_LabelWidth, k_SingleLineHeight) , tilingRule.m_Output == RuleTile.TilingRuleOutput.OutputSprite.Animation ? Styles.tilingRulesAnimationSize : Styles.tilingRulesRandomSize); EditorGUI.BeginChangeCheck(); int newLength = EditorGUI.DelayedIntField(new Rect(rect.xMin + k_LabelWidth, y, rect.width - k_LabelWidth, k_SingleLineHeight), tilingRule.m_Sprites.Length); if (EditorGUI.EndChangeCheck()) Array.Resize(ref tilingRule.m_Sprites, Math.Max(newLength, 1)); y += k_SingleLineHeight; for (int i = 0; i < tilingRule.m_Sprites.Length; i++) { tilingRule.m_Sprites[i] = EditorGUI.ObjectField(new Rect(rect.xMin + k_LabelWidth, y, rect.width - k_LabelWidth, k_SingleLineHeight), tilingRule.m_Sprites[i], typeof(Sprite), false) as Sprite; y += k_SingleLineHeight; } } } private void DisplayClipboardText(GUIContent clipboardText, Rect position) { Color old = GUI.color; GUI.color = Color.gray; var infoSize = GUI.skin.label.CalcSize(clipboardText); Rect rect = new Rect(position.center.x - infoSize.x * .5f , position.center.y - infoSize.y * .5f , infoSize.x , infoSize.y); GUI.Label(rect, clipboardText); GUI.color = old; } private bool dragAndDropActive { get { return dragAndDropSprites != null && dragAndDropSprites.Count > 0; } } private static List GetSpritesFromTexture(Texture2D texture) { string path = AssetDatabase.GetAssetPath(texture); Object[] assets = AssetDatabase.LoadAllAssetsAtPath(path); List sprites = new List(); foreach (Object asset in assets) { if (asset is Sprite) { sprites.Add(asset as Sprite); } } return sprites; } private static List GetValidSingleSprites(Object[] objects) { List result = new List(); foreach (Object obj in objects) { if (obj is Sprite sprite) { result.Add(sprite); } else if (obj is Texture2D texture2D) { List sprites = GetSpritesFromTexture(texture2D); if (sprites.Count > 0) { result.AddRange(sprites); } } } return result; } private void HandleDragAndDrop(Rect guiRect) { if (DragAndDrop.objectReferences.Length == 0 || !guiRect.Contains(Event.current.mousePosition)) return; switch (Event.current.type) { case EventType.DragUpdated: { dragAndDropSprites = GetValidSingleSprites(DragAndDrop.objectReferences); if (dragAndDropActive) { DragAndDrop.visualMode = DragAndDropVisualMode.Copy; Event.current.Use(); GUI.changed = true; } } break; case EventType.DragPerform: { if (!dragAndDropActive) return; Undo.RegisterCompleteObjectUndo(tile, "Drag and Drop to Rule Tile"); ResizeRuleTileList(dragAndDropSprites.Count); for (int i = 0; i < dragAndDropSprites.Count; ++i) { tile.m_TilingRules[i].m_Sprites[0] = dragAndDropSprites[i]; } DragAndDropClear(); GUI.changed = true; EditorUtility.SetDirty(tile); GUIUtility.ExitGUI(); } break; case EventType.Repaint: // Handled in Render() break; } if (Event.current.type == EventType.DragExited || Event.current.type == EventType.KeyDown && Event.current.keyCode == KeyCode.Escape) { DragAndDropClear(); } } private void DragAndDropClear() { dragAndDropSprites = null; DragAndDrop.visualMode = DragAndDropVisualMode.None; Event.current.Use(); } /// /// Whether the RuleTile has a preview GUI /// /// True public override bool HasPreviewGUI() { return true; } /// /// Draws the preview GUI for the RuleTile /// /// Rect to draw the preview GUI /// The GUIStyle of the background for the preview public override void OnPreviewGUI(Rect rect, GUIStyle background) { if (m_PreviewUtility == null) CreatePreview(); if (Event.current.type != EventType.Repaint) return; m_PreviewUtility.BeginPreview(rect, background); m_PreviewUtility.camera.orthographicSize = 2; if (rect.height > rect.width) m_PreviewUtility.camera.orthographicSize *= rect.height / rect.width; m_PreviewUtility.camera.Render(); m_PreviewUtility.EndAndDrawPreview(rect); } /// /// Creates a Preview for the RuleTile. /// protected virtual void CreatePreview() { m_PreviewUtility = new PreviewRenderUtility(true); m_PreviewUtility.camera.orthographic = true; m_PreviewUtility.camera.orthographicSize = 2; m_PreviewUtility.camera.transform.position = new Vector3(0, 0, -10); var previewInstance = new GameObject(); m_PreviewGrid = previewInstance.AddComponent(); m_PreviewUtility.AddSingleGO(previewInstance); m_PreviewTilemaps = new List(); m_PreviewTilemapRenderers = new List(); for (int i = 0; i < 4; i++) { var previewTilemapGo = new GameObject(); m_PreviewTilemaps.Add(previewTilemapGo.AddComponent()); m_PreviewTilemapRenderers.Add(previewTilemapGo.AddComponent()); previewTilemapGo.transform.SetParent(previewInstance.transform, false); } for (int x = -2; x <= 0; x++) for (int y = -1; y <= 1; y++) m_PreviewTilemaps[0].SetTile(new Vector3Int(x, y, 0), tile); for (int y = -1; y <= 1; y++) m_PreviewTilemaps[1].SetTile(new Vector3Int(1, y, 0), tile); for (int x = -2; x <= 0; x++) m_PreviewTilemaps[2].SetTile(new Vector3Int(x, -2, 0), tile); m_PreviewTilemaps[3].SetTile(new Vector3Int(1, -2, 0), tile); } /// /// Handles cleanup for the Preview GUI /// protected virtual void DestroyPreview() { if (m_PreviewUtility != null) { m_PreviewUtility.Cleanup(); m_PreviewUtility = null; m_PreviewGrid = null; m_PreviewTilemaps = null; m_PreviewTilemapRenderers = null; } } /// /// Renders a static preview Texture2D for a RuleTile asset /// /// Asset path of the RuleTile /// Arrays of assets from the given Asset path /// Width of the static preview /// Height of the static preview /// Texture2D containing static preview for the RuleTile asset public override Texture2D RenderStaticPreview(string assetPath, Object[] subAssets, int width, int height) { if (tile.m_DefaultSprite != null) { Type t = GetType("UnityEditor.SpriteUtility"); if (t != null) { MethodInfo method = t.GetMethod("RenderStaticPreview", new[] { typeof(Sprite), typeof(Color), typeof(int), typeof(int) }); if (method != null) { object ret = method.Invoke("RenderStaticPreview", new object[] { tile.m_DefaultSprite, Color.white, width, height }); if (ret is Texture2D) return ret as Texture2D; } } } return base.RenderStaticPreview(assetPath, subAssets, width, height); } private static Type GetType(string typeName) { var type = Type.GetType(typeName); if (type != null) return type; var currentAssembly = Assembly.GetExecutingAssembly(); var referencedAssemblies = currentAssembly.GetReferencedAssemblies(); foreach (var assemblyName in referencedAssemblies) { var assembly = Assembly.Load(assemblyName); if (assembly != null) { type = assembly.GetType(typeName); if (type != null) return type; } } return null; } /// /// Converts a Base64 string to a Texture2D /// /// Base64 string containing image data /// Texture2D containing an image from the given Base64 string public static Texture2D Base64ToTexture(string base64) { Texture2D t = new Texture2D(1, 1); t.hideFlags = HideFlags.HideAndDontSave; t.LoadImage(Convert.FromBase64String(base64)); return t; } /// /// Wrapper for serializing a list of Rules /// [Serializable] class RuleTileRuleWrapper { /// /// List of Rules to serialize /// [SerializeField] public List rules = new List(); } /// /// Copies all Rules from a RuleTile to the clipboard /// /// MenuCommand storing the RuleTile to copy from [MenuItem("CONTEXT/RuleTile/Copy All Rules")] public static void CopyAllRules(MenuCommand item) { RuleTile tile = item.context as RuleTile; if (tile == null) return; RuleTileRuleWrapper rulesWrapper = new RuleTileRuleWrapper(); rulesWrapper.rules = tile.m_TilingRules; var rulesJson = EditorJsonUtility.ToJson(rulesWrapper); EditorGUIUtility.systemCopyBuffer = rulesJson; } /// /// Pastes all Rules from the clipboard to a RuleTile /// /// MenuCommand storing the RuleTile to paste to [MenuItem("CONTEXT/RuleTile/Paste Rules")] public static void PasteRules(MenuCommand item) { RuleTile tile = item.context as RuleTile; if (tile == null) return; try { RuleTileRuleWrapper rulesWrapper = new RuleTileRuleWrapper(); EditorJsonUtility.FromJsonOverwrite(EditorGUIUtility.systemCopyBuffer, rulesWrapper); tile.m_TilingRules.AddRange(rulesWrapper.rules); } catch (Exception) { Debug.LogError("Unable to paste rules from system copy buffer"); } } } }