TileDragAndDrop.cs 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412
  1. using System;
  2. using System.Collections.Generic;
  3. using System.IO;
  4. using System.Linq;
  5. using System.Reflection;
  6. using UnityEngine;
  7. using UnityEngine.Tilemaps;
  8. using Object = UnityEngine.Object;
  9. namespace UnityEditor.Tilemaps
  10. {
  11. internal static class TileDragAndDrop
  12. {
  13. private enum UserTileCreationMode
  14. {
  15. Overwrite,
  16. CreateUnique,
  17. Reuse,
  18. }
  19. private static readonly string k_TileExtension = "asset";
  20. private static List<Sprite> GetSpritesFromTexture(Texture2D texture)
  21. {
  22. string path = AssetDatabase.GetAssetPath(texture);
  23. Object[] assets = AssetDatabase.LoadAllAssetsAtPath(path);
  24. List<Sprite> sprites = new List<Sprite>();
  25. foreach (Object asset in assets)
  26. {
  27. if (asset is Sprite)
  28. {
  29. sprites.Add(asset as Sprite);
  30. }
  31. }
  32. return sprites;
  33. }
  34. private static bool AllSpritesAreSameSize(List<Sprite> sprites)
  35. {
  36. if (!sprites.Any())
  37. {
  38. return false;
  39. }
  40. // If sprites are different sizes (not grid sliced). So we abort.
  41. for (int i = 1; i < sprites.Count - 1; i++)
  42. {
  43. if ((int)sprites[i].rect.width != (int)sprites[i + 1].rect.width ||
  44. (int)sprites[i].rect.height != (int)sprites[i + 1].rect.height)
  45. {
  46. return false;
  47. }
  48. }
  49. return true;
  50. }
  51. // Input:
  52. // sheetTextures -> textures containing 2-N equal sized Sprites)
  53. // singleSprites -> All the leftover Sprites that were in same texture but different sizes or just dragged in as Sprite
  54. // tiles -> Just plain tiles
  55. public static Dictionary<Vector2Int, Object> CreateHoverData(List<Texture2D> sheetTextures, List<Sprite> singleSprites, List<TileBase> tiles)
  56. {
  57. Dictionary<Vector2Int, Object> result = new Dictionary<Vector2Int, Object>();
  58. Vector2Int currentPosition = new Vector2Int(0, 0);
  59. int width = 0;
  60. if (sheetTextures != null)
  61. {
  62. foreach (Texture2D sheetTexture in sheetTextures)
  63. {
  64. Dictionary<Vector2Int, Object> sheet = CreateHoverData(sheetTexture);
  65. foreach (KeyValuePair<Vector2Int, Object> item in sheet)
  66. {
  67. result.Add(item.Key + currentPosition, item.Value);
  68. }
  69. Vector2Int min = GetMinMaxRect(sheet.Keys.ToList()).min;
  70. currentPosition += new Vector2Int(0, min.y - 1);
  71. }
  72. }
  73. if (currentPosition.x > 0)
  74. currentPosition = new Vector2Int(0, currentPosition.y - 1);
  75. if (singleSprites != null)
  76. {
  77. width = Mathf.RoundToInt(Mathf.Sqrt(singleSprites.Count));
  78. foreach (Sprite sprite in singleSprites)
  79. {
  80. result.Add(currentPosition, sprite);
  81. currentPosition += new Vector2Int(1, 0);
  82. if (currentPosition.x >= width)
  83. currentPosition = new Vector2Int(0, currentPosition.y - 1);
  84. }
  85. }
  86. if (currentPosition.x > 0)
  87. currentPosition = new Vector2Int(0, currentPosition.y - 1);
  88. if (tiles != null)
  89. {
  90. width = Math.Max(Mathf.RoundToInt(Mathf.Sqrt(tiles.Count)), width);
  91. foreach (TileBase tile in tiles)
  92. {
  93. result.Add(currentPosition, tile);
  94. currentPosition += new Vector2Int(1, 0);
  95. if (currentPosition.x >= width)
  96. currentPosition = new Vector2Int(0, currentPosition.y - 1);
  97. }
  98. }
  99. return result;
  100. }
  101. // Get all textures that are valid spritesheets. More than one Sprites and all equal size.
  102. public static List<Texture2D> GetValidSpritesheets(Object[] objects)
  103. {
  104. List<Texture2D> result = new List<Texture2D>();
  105. foreach (Object obj in objects)
  106. {
  107. if (obj is Texture2D)
  108. {
  109. Texture2D texture = obj as Texture2D;
  110. List<Sprite> sprites = GetSpritesFromTexture(texture);
  111. if (sprites.Count() > 1 && AllSpritesAreSameSize(sprites))
  112. {
  113. result.Add(texture);
  114. }
  115. }
  116. }
  117. return result;
  118. }
  119. // Get all single Sprite(s) and all Sprite(s) that are part of Texture2D that is not valid sheet (it sprites of varying sizes)
  120. public static List<Sprite> GetValidSingleSprites(Object[] objects)
  121. {
  122. List<Sprite> result = new List<Sprite>();
  123. foreach (Object obj in objects)
  124. {
  125. if (obj is Sprite)
  126. {
  127. result.Add(obj as Sprite);
  128. }
  129. else if (obj is Texture2D)
  130. {
  131. Texture2D texture = obj as Texture2D;
  132. List<Sprite> sprites = GetSpritesFromTexture(texture);
  133. if (sprites.Count == 1 || !AllSpritesAreSameSize(sprites))
  134. {
  135. result.AddRange(sprites);
  136. }
  137. }
  138. }
  139. return result;
  140. }
  141. public static List<TileBase> GetValidTiles(Object[] objects)
  142. {
  143. List<TileBase> result = new List<TileBase>();
  144. foreach (Object obj in objects)
  145. {
  146. if (obj is TileBase)
  147. {
  148. result.Add(obj as TileBase);
  149. }
  150. }
  151. return result;
  152. }
  153. private static Vector2Int GetMinimum(List<Sprite> sprites, Func<Sprite, float> minX, Func<Sprite, float> minY)
  154. {
  155. Vector2 minVector = new Vector2(Int32.MaxValue, Int32.MaxValue);
  156. foreach (var sprite in sprites)
  157. {
  158. minVector.x = Mathf.Min(minVector.x, minX(sprite));
  159. minVector.y = Mathf.Min(minVector.y, minY(sprite));
  160. }
  161. return Vector2Int.FloorToInt(minVector);
  162. }
  163. public static Vector2Int EstimateGridPixelSize(List<Sprite> sprites)
  164. {
  165. if (sprites.Count == 0 || sprites.Any(sprite => sprite == null))
  166. {
  167. return Vector2Int.zero;
  168. }
  169. if (sprites.Count == 1)
  170. return Vector2Int.FloorToInt(sprites[0].rect.size);
  171. return GetMinimum(sprites, s => s.rect.width, s => s.rect.height);
  172. }
  173. public static Vector2Int EstimateGridOffsetSize(List<Sprite> sprites)
  174. {
  175. if (sprites.Count == 0 || sprites.Any(sprite => sprite == null))
  176. return Vector2Int.zero;
  177. if (sprites.Count == 1)
  178. return Vector2Int.FloorToInt(sprites[0].rect.position);
  179. return GetMinimum(sprites, s => s.rect.xMin, s => s.rect.yMin);
  180. }
  181. public static Vector2Int EstimateGridPaddingSize(List<Sprite> sprites, Vector2Int cellSize, Vector2Int offsetSize)
  182. {
  183. if (sprites.Count < 2 || sprites.Any(sprite => sprite == null))
  184. return Vector2Int.zero;
  185. var paddingSize = GetMinimum(sprites
  186. , (s =>
  187. {
  188. var xMin = s.rect.xMin - cellSize.x - offsetSize.x;
  189. return xMin >= 0 ? xMin : Int32.MaxValue;
  190. })
  191. , (s =>
  192. {
  193. var yMin = s.rect.yMin - cellSize.y - offsetSize.y;
  194. return yMin >= 0 ? yMin : Int32.MaxValue;
  195. })
  196. );
  197. // Assume there is no padding if the detected padding is greater than the cell size
  198. if (paddingSize.x >= cellSize.x)
  199. paddingSize.x = 0;
  200. if (paddingSize.y >= cellSize.y)
  201. paddingSize.y = 0;
  202. return paddingSize;
  203. }
  204. // Turn texture pixel position into integer grid position based on cell size, offset size and padding
  205. private static Vector2Int GetGridPosition(Sprite sprite, Vector2Int cellPixelSize, Vector2Int offsetSize, Vector2Int paddingSize)
  206. {
  207. return new Vector2Int(
  208. Mathf.FloorToInt((sprite.rect.center.x - offsetSize.x) / (cellPixelSize.x + paddingSize.x)),
  209. Mathf.FloorToInt(-(sprite.texture.height - sprite.rect.center.y - offsetSize.y) / (cellPixelSize.y + paddingSize.y)) + 1
  210. );
  211. }
  212. // Organizes all the sprites in a single texture nicely on a 2D "table" based on their original texture position
  213. // Only call this with spritesheet with all Sprites equal size
  214. public static Dictionary<Vector2Int, Object> CreateHoverData(Texture2D sheet)
  215. {
  216. Dictionary<Vector2Int, Object> result = new Dictionary<Vector2Int, Object>();
  217. List<Sprite> sprites = GetSpritesFromTexture(sheet);
  218. Vector2Int cellPixelSize = EstimateGridPixelSize(sprites);
  219. // Get Offset
  220. Vector2Int offsetSize = EstimateGridOffsetSize(sprites);
  221. // Get Padding
  222. Vector2Int paddingSize = EstimateGridPaddingSize(sprites, cellPixelSize, offsetSize);
  223. foreach (Sprite sprite in sprites)
  224. {
  225. Vector2Int position = GetGridPosition(sprite, cellPixelSize, offsetSize, paddingSize);
  226. result[position] = sprite;
  227. }
  228. return result;
  229. }
  230. public static Dictionary<Vector2Int, TileBase> ConvertToTileSheet(Dictionary<Vector2Int, Object> sheet)
  231. {
  232. Dictionary<Vector2Int, TileBase> result = new Dictionary<Vector2Int, TileBase>();
  233. string defaultPath = ProjectBrowser.s_LastInteractedProjectBrowser
  234. ? ProjectBrowser.s_LastInteractedProjectBrowser.GetActiveFolderPath()
  235. : "Assets";
  236. // Early out if all objects are already tiles
  237. if (sheet.Values.ToList().FindAll(obj => obj is TileBase).Count == sheet.Values.Count)
  238. {
  239. foreach (KeyValuePair<Vector2Int, Object> item in sheet)
  240. {
  241. result.Add(item.Key, item.Value as TileBase);
  242. }
  243. return result;
  244. }
  245. UserTileCreationMode userTileCreationMode = UserTileCreationMode.Overwrite;
  246. string path = "";
  247. bool multipleTiles = sheet.Count > 1;
  248. if (multipleTiles)
  249. {
  250. bool userInterventionRequired = false;
  251. path = EditorUtility.SaveFolderPanel("Generate tiles into folder ", defaultPath, "");
  252. path = FileUtil.GetProjectRelativePath(path);
  253. // Check if this will overwrite any existing assets
  254. foreach (var item in sheet.Values)
  255. {
  256. if (item is Sprite)
  257. {
  258. var tilePath = FileUtil.CombinePaths(path, String.Format("{0}.{1}", item.name, k_TileExtension));
  259. if (File.Exists(tilePath))
  260. {
  261. userInterventionRequired = true;
  262. break;
  263. }
  264. }
  265. }
  266. // There are existing tile assets in the folder with names matching the items to be created
  267. if (userInterventionRequired)
  268. {
  269. var option = EditorUtility.DisplayDialogComplex("Overwrite?", String.Format("Assets exist at {0}. Do you wish to overwrite existing assets?", path), "Overwrite", "Create New Copy", "Reuse");
  270. switch (option)
  271. {
  272. case 0: // Overwrite
  273. {
  274. userTileCreationMode = UserTileCreationMode.Overwrite;
  275. }
  276. break;
  277. case 1: // Create New Copy
  278. {
  279. userTileCreationMode = UserTileCreationMode.CreateUnique;
  280. }
  281. break;
  282. case 2: // Reuse
  283. {
  284. userTileCreationMode = UserTileCreationMode.Reuse;
  285. }
  286. break;
  287. }
  288. }
  289. }
  290. else
  291. {
  292. // Do not check if this will overwrite new tile as user has explicitly selected the file to save to
  293. path = EditorUtility.SaveFilePanelInProject("Generate new tile", sheet.Values.First().name, k_TileExtension, "Generate new tile", defaultPath);
  294. }
  295. if (string.IsNullOrEmpty(path))
  296. return result;
  297. int i = 0;
  298. EditorUtility.DisplayProgressBar("Generating Tile Assets (" + i + "/" + sheet.Count + ")", "Generating tiles", 0f);
  299. try
  300. {
  301. MethodInfo createTileMethod = GridPaintActiveTargetsPreferences.GetCreateTileFromPaletteUsingPreferences();
  302. if (createTileMethod == null)
  303. return null;
  304. foreach (KeyValuePair<Vector2Int, Object> item in sheet)
  305. {
  306. TileBase tile;
  307. string tilePath = "";
  308. if (item.Value is Sprite)
  309. {
  310. tile = createTileMethod.Invoke(null, new object[] { item.Value as Sprite }) as TileBase;
  311. if (tile == null)
  312. continue;
  313. tilePath = multipleTiles
  314. ? FileUtil.CombinePaths(path, String.Format("{0}.{1}", tile.name, k_TileExtension))
  315. : path;
  316. // Case 1216101: Fix path slashes for Windows
  317. tilePath = FileUtil.NiceWinPath(tilePath);
  318. switch (userTileCreationMode)
  319. {
  320. case UserTileCreationMode.CreateUnique:
  321. {
  322. if (File.Exists(tilePath))
  323. tilePath = AssetDatabase.GenerateUniqueAssetPath(tilePath);
  324. AssetDatabase.CreateAsset(tile, tilePath);
  325. }
  326. break;
  327. case UserTileCreationMode.Overwrite:
  328. {
  329. AssetDatabase.CreateAsset(tile, tilePath);
  330. }
  331. break;
  332. case UserTileCreationMode.Reuse:
  333. {
  334. if (File.Exists(tilePath))
  335. tile = AssetDatabase.LoadAssetAtPath<TileBase>(tilePath);
  336. else
  337. AssetDatabase.CreateAsset(tile, tilePath);
  338. }
  339. break;
  340. }
  341. }
  342. else
  343. {
  344. tile = item.Value as TileBase;
  345. }
  346. EditorUtility.DisplayProgressBar("Generating Tile Assets (" + i + "/" + sheet.Count + ")", "Generating " + tilePath, (float)i++ / sheet.Count);
  347. result.Add(item.Key, tile);
  348. }
  349. }
  350. finally
  351. {
  352. EditorUtility.ClearProgressBar();
  353. }
  354. AssetDatabase.Refresh();
  355. return result;
  356. }
  357. internal static RectInt GetMinMaxRect(List<Vector2Int> positions)
  358. {
  359. if (positions == null || positions.Count == 0)
  360. return new RectInt();
  361. return GridEditorUtility.GetMarqueeRect(
  362. new Vector2Int(positions.Min(p1 => p1.x), positions.Min(p1 => p1.y)),
  363. new Vector2Int(positions.Max(p1 => p1.x), positions.Max(p1 => p1.y))
  364. );
  365. }
  366. }
  367. }