using System; #if UNITY_EDITOR using UnityEditor; using UnityEditorInternal; using System.Collections.Generic; using System.Linq; #endif namespace UnityEngine.Tilemaps { /// /// Animated Tiles are tiles which run through and display a list of sprites in sequence. /// [Serializable] [HelpURL("https://docs.unity3d.com/Packages/com.unity.2d.tilemap.extras@latest/index.html?subfolder=/manual/AnimatedTile.html")] public class AnimatedTile : TileBase { /// /// The List of Sprites set for the Animated Tile. /// This will be played in sequence. /// public Sprite[] m_AnimatedSprites; /// /// The minimum possible speed at which the Animation of the Tile will be played. /// A speed value will be randomly chosen between the minimum and maximum speed. /// public float m_MinSpeed = 1f; /// /// The maximum possible speed at which the Animation of the Tile will be played. /// A speed value will be randomly chosen between the minimum and maximum speed. /// public float m_MaxSpeed = 1f; /// /// The starting time of this Animated Tile. /// This allows you to start the Animation from time in the list of Animated Sprites depending on the /// Tilemap's Animation Frame Rate. /// public float m_AnimationStartTime; /// /// The starting frame of this Animated Tile. /// This allows you to start the Animation from a particular Sprite in the list of Animated Sprites. /// If this is set, this overrides m_AnimationStartTime. /// public int m_AnimationStartFrame = 0; /// /// The Collider Shape generated by the Tile. /// public Tile.ColliderType m_TileColliderType; /// /// Retrieves any tile rendering data from the scripted tile. /// /// Position of the Tile on the Tilemap. /// The Tilemap the tile is present on. /// Data to render the tile. public override void GetTileData(Vector3Int position, ITilemap tilemap, ref TileData tileData) { tileData.transform = Matrix4x4.identity; tileData.color = Color.white; if (m_AnimatedSprites != null && m_AnimatedSprites.Length > 0) { tileData.sprite = m_AnimatedSprites[m_AnimatedSprites.Length - 1]; tileData.colliderType = m_TileColliderType; } } /// /// Retrieves any tile animation data from the scripted tile. /// /// Position of the Tile on the Tilemap. /// The Tilemap the tile is present on. /// Data to run an animation on the tile. /// Whether the call was successful. public override bool GetTileAnimationData(Vector3Int position, ITilemap tilemap, ref TileAnimationData tileAnimationData) { if (m_AnimatedSprites.Length > 0) { tileAnimationData.animatedSprites = m_AnimatedSprites; tileAnimationData.animationSpeed = Random.Range(m_MinSpeed, m_MaxSpeed); tileAnimationData.animationStartTime = m_AnimationStartTime; if (0 < m_AnimationStartFrame && m_AnimationStartFrame <= m_AnimatedSprites.Length) { var tilemapComponent = tilemap.GetComponent(); if (tilemapComponent != null && tilemapComponent.animationFrameRate > 0) tileAnimationData.animationStartTime = (m_AnimationStartFrame - 1) / tilemapComponent.animationFrameRate; } return true; } return false; } } #if UNITY_EDITOR [CustomEditor(typeof(AnimatedTile))] public class AnimatedTileEditor : Editor { private static class Styles { public static readonly GUIContent orderAnimatedTileSpritesInfo = EditorGUIUtility.TrTextContent("Place sprites shown based on the order of animation."); public static readonly GUIContent emptyAnimatedTileInfo = EditorGUIUtility.TrTextContent( "Drag Sprite or Sprite Texture assets \n" + " to start creating an Animated Tile."); public static readonly GUIContent minimumSpeedLabel = EditorGUIUtility.TrTextContent("Minimum Speed", "The minimum possible speed at which the Animation of the Tile will be played. A speed value will be randomly chosen between the minimum and maximum speed."); public static readonly GUIContent maximumSpeedLabel = EditorGUIUtility.TrTextContent("Maximum Speed", "The maximum possible speed at which the Animation of the Tile will be played. A speed value will be randomly chosen between the minimum and maximum speed."); public static readonly GUIContent startTimeLabel = EditorGUIUtility.TrTextContent("Start Time", "The starting time of this Animated Tile. This allows you to start the Animation from a particular time."); public static readonly GUIContent startFrameLabel = EditorGUIUtility.TrTextContent("Start Frame", "The starting frame of this Animated Tile. This allows you to start the Animation from a particular Sprite in the list of Animated Sprites."); public static readonly GUIContent colliderTypeLabel = EditorGUIUtility.TrTextContent("Collider Type", "The Collider Shape generated by the Tile."); } private static readonly string k_UndoName = L10n.Tr("Change AnimatedTile"); private SerializedProperty m_AnimatedSprites; private AnimatedTile tile { get { return (target as AnimatedTile); } } private List dragAndDropSprites; private ReorderableList reorderableList; private void OnEnable() { reorderableList = new ReorderableList(tile.m_AnimatedSprites, typeof(Sprite), true, true, true, true); reorderableList.drawHeaderCallback = OnDrawHeader; reorderableList.drawElementCallback = OnDrawElement; reorderableList.elementHeightCallback = GetElementHeight; reorderableList.onAddCallback = OnAddElement; reorderableList.onRemoveCallback = OnRemoveElement; reorderableList.onReorderCallback = OnReorderElement; m_AnimatedSprites = serializedObject.FindProperty("m_AnimatedSprites"); } private void OnDrawHeader(Rect rect) { GUI.Label(rect, Styles.orderAnimatedTileSpritesInfo); } private void OnDrawElement(Rect rect, int index, bool isActive, bool isFocused) { if (tile.m_AnimatedSprites != null && index < tile.m_AnimatedSprites.Length) { var spriteName = tile.m_AnimatedSprites[index] != null ? tile.m_AnimatedSprites[index].name : "Null"; tile.m_AnimatedSprites[index] = (Sprite) EditorGUI.ObjectField(rect , $"Sprite {index + 1}: {spriteName}" , tile.m_AnimatedSprites[index] , typeof(Sprite) , false); } } private float GetElementHeight(int index) { return 3 * EditorGUI.GetPropertyHeight(SerializedPropertyType.ObjectReference, null); } private void OnAddElement(ReorderableList list) { var count = tile.m_AnimatedSprites != null ? tile.m_AnimatedSprites.Length + 1 : 1; ResizeAnimatedSpriteList(count); if (list.index == 0 || list.index < list.count) { Array.Copy(tile.m_AnimatedSprites, list.index + 1, tile.m_AnimatedSprites, list.index + 2, list.count - list.index - 1); tile.m_AnimatedSprites[list.index + 1] = null; if (list.IsSelected(list.index)) list.index += 1; } else { tile.m_AnimatedSprites[count - 1] = null; } } private void OnRemoveElement(ReorderableList list) { if (tile.m_AnimatedSprites != null && tile.m_AnimatedSprites.Length > 0 && list.index < tile.m_AnimatedSprites.Length) { var sprites = tile.m_AnimatedSprites.ToList(); sprites.RemoveAt(list.index); tile.m_AnimatedSprites = sprites.ToArray(); } } private void OnReorderElement(ReorderableList list) { // Fix for 2020.1, which does not track changes when reordering in the list EditorUtility.SetDirty(tile); } 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 void DragAndDropClear() { dragAndDropSprites = null; DragAndDrop.visualMode = DragAndDropVisualMode.None; Event.current.Use(); } 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) { result.Add(obj as Sprite); } else if (obj is Texture2D) { Texture2D texture = obj as Texture2D; List sprites = GetSpritesFromTexture(texture); 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 Animated Tile"); ResizeAnimatedSpriteList(dragAndDropSprites.Count); Array.Copy(dragAndDropSprites.ToArray(), tile.m_AnimatedSprites, dragAndDropSprites.Count); 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(); } } /// /// Draws an Inspector for the AnimatedTile. /// public override void OnInspectorGUI() { serializedObject.Update(); Undo.RecordObject(tile, k_UndoName); EditorGUI.BeginChangeCheck(); int count = EditorGUILayout.DelayedIntField("Number of Animated Sprites", tile.m_AnimatedSprites != null ? tile.m_AnimatedSprites.Length : 0); if (count < 0) count = 0; if (tile.m_AnimatedSprites == null || tile.m_AnimatedSprites.Length != count) ResizeAnimatedSpriteList(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.emptyAnimatedTileInfo, rect); GUILayout.Space(rect.height); EditorGUILayout.Space(); } if (reorderableList != null) { var tileCount = tile.m_AnimatedSprites != null ? tile.m_AnimatedSprites.Length : 0; if (reorderableList.list == null || reorderableList.count != tileCount) reorderableList.list = tile.m_AnimatedSprites; reorderableList.DoLayoutList(); } using (new EditorGUI.DisabledScope(tile.m_AnimatedSprites == null || tile.m_AnimatedSprites.Length == 0)) { float minSpeed = EditorGUILayout.FloatField(Styles.minimumSpeedLabel, tile.m_MinSpeed); float maxSpeed = EditorGUILayout.FloatField(Styles.maximumSpeedLabel, tile.m_MaxSpeed); if (minSpeed < 0.0f) minSpeed = 0.0f; if (maxSpeed < 0.0f) maxSpeed = 0.0f; if (maxSpeed < minSpeed) maxSpeed = minSpeed; tile.m_MinSpeed = minSpeed; tile.m_MaxSpeed = maxSpeed; using (new EditorGUI.DisabledScope(tile.m_AnimatedSprites == null || (0 < tile.m_AnimationStartFrame && tile.m_AnimationStartFrame <= tile.m_AnimatedSprites.Length))) { tile.m_AnimationStartTime = EditorGUILayout.FloatField(Styles.startTimeLabel, tile.m_AnimationStartTime); } tile.m_AnimationStartFrame = EditorGUILayout.IntField(Styles.startFrameLabel, tile.m_AnimationStartFrame); tile.m_TileColliderType = (Tile.ColliderType) EditorGUILayout.EnumPopup(Styles.colliderTypeLabel, tile.m_TileColliderType); } if (EditorGUI.EndChangeCheck()) { serializedObject.ApplyModifiedProperties(); EditorUtility.SetDirty(tile); } } private void ResizeAnimatedSpriteList(int count) { m_AnimatedSprites.arraySize = count; serializedObject.ApplyModifiedProperties(); } } #endif }